Issues (165)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Includes/Page.php (12 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of Peachy MediaWiki Bot API
4
 *
5
 * Peachy is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 *
18
 * @file
19
 */
20
21
/**
22
 * Page class, defines methods that all get/modify page info
23
 */
24
class Page {
25
26
	/**
27
	 * Wiki class
28
	 * @var Wiki
29
	 */
30
	protected $wiki;
31
32
	/**
33
	 * Title of the page
34
	 * @var string
35
	 */
36
	protected $title;
37
38
	/**
39
	 * The ID of the page
40
	 * @var int
41
	 */
42
	protected $pageid;
43
44
	/**
45
	 * If the page exists or not
46
	 * (default value: true)
47
	 * @var bool
48
	 */
49
	protected $exists = true;
50
51
	/**
52
	 * Whether or not the page is a special page
53
	 * (default value: false)
54
	 * @var bool
55
	 */
56
	protected $special = false;
57
58
	/**
59
	 * When retriving the page information was a redirect followed
60
	 * (default value: false)
61
	 * @var bool
62
	 */
63
	protected $redirectFollowed = false;
64
65
	/**
66
	 * The page title without the namespace bit
67
	 * @var string
68
	 */
69
	protected $title_wo_namespace;
70
71
	/**
72
	 * The ID of the namespace
73
	 * @var int
74
	 */
75
	protected $namespace_id;
76
77
	/**
78
	 * Page text
79
	 * @var string
80
	 */
81
	protected $content;
82
83
	/**
84
	 * Templates used in the page
85
	 * (default value: null)
86
	 * @var array
87
	 */
88
	protected $templates;
89
90
	/**
91
	 * Protection information for the page
92
	 * (default value: null)
93
	 * @var array
94
	 */
95
	protected $protection;
96
97
	/**
98
	 * Categories that the page is in
99
	 * (default value: null)
100
	 * @var array
101
	 */
102
	protected $categories;
103
104
	/**
105
	 * Images used in the page
106
	 * (default value: null)
107
	 * @var array
108
	 */
109
	protected $images;
110
111
	/**
112
	 * Internal links in the page
113
	 * (default value: null)
114
	 * @var array
115
	 */
116
	protected $links;
117
118
	/**
119
	 * Timestamp of the last edit
120
     * @var int
121
	 */
122
	protected $lastedit;
123
124
	/**
125
	 * Length of the page in bytes
126
	 * @var int
127
	 */
128
	protected $length;
129
130
	/**
131
	 * Amount of hits (views) the page has
132
	 * @var int
133
	 */
134
	protected $hits;
135
136
	/**
137
	 * Language links on the page
138
	 * (default value: null)
139
	 * @var array
140
	 */
141
	protected $langlinks;
142
143
	/**
144
	 * External links on the page
145
	 * (default value: null)
146
	 * @var array
147
	 */
148
	protected $extlinks;
149
150
	/**
151
	 * Interwiki links on the page
152
	 * (default value: null)
153
	 * @var array
154
	 */
155
	protected $iwlinks;
156
157
	/**
158
	 * Time of script start.  Must be set manually.
159
	 * (default null)
160
	 * @var mixed
161
	 */
162
	protected $starttimestamp;
163
164
	/**
165
	 * Page ID of the talk page.
166
	 * (default null)
167
	 * @var int
168
	 */
169
	protected $talkid;
170
171
	/**
172
	 * Whether the page is watched by the user
173
	 * (default null)
174
	 * @var bool
175
	 */
176
	protected $watched;
177
178
	/**
179
	 * Number of watchers
180
	 * (default null)
181
	 * @var int
182
	 */
183
	protected $watchers;
184
185
	/**
186
	 * Watchlist notification timestamp
187
	 * (default null)
188
	 * @var string
189
	 */
190
	protected $watchlisttimestamp;
191
192
	/**
193
	 * Page ID of parent page
194
	 * (default null)
195
	 * @var int
196
	 */
197
	protected $subjectid;
198
199
	/**
200
	 * Full urls of the page
201
	 * (default null)
202
	 * @var array
203
	 */
204
	protected $urls;
205
206
	/**
207
	 * Whether the page can be read by a user
208
	 * (default null)
209
	 * @var bool
210
	 */
211
	protected $readable;
212
213
	/**
214
	 * EditFormPreloadText
215
	 * (default null)
216
	 * @var string
217
	 */
218
	protected $preload;
219
220
	/**
221
	 * Page's title formatting
222
	 * (default null)
223
	 * @var string
224
	 */
225
	protected $displaytitle;
226
227
	/**
228
	 * Page properties
229
	 * (default null)
230
	 * @var array
231
	 */
232
	protected $properties;
233
234
	/**
235
	 * Construction method for the Page class
236
	 *
237
     * @param Wiki $wikiClass The Wiki class object
238
     * @param mixed $title Title of the page (default: null)
239
     * @param mixed $pageid ID of the page (default: null)
240
     * @param bool $followRedir Should it follow a redirect when retrieving the page (default: true)
241
     * @param bool $normalize Should the class automatically normalize the title (default: true)
242
     * @param string|double|int $timestamp Set the start of a program or start reference to avoid edit conflicts.
243
	 *
244
	 * @throws InvalidArgumentException
245
	 * @throws NoTitle
246
	 * @return Page
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
247
	 */
248 5
	public function __construct( Wiki $wikiClass, $title = null, $pageid = null, $followRedir = true, $normalize = true, $timestamp = null ) {
249 5
		if( !is_string( $title ) && !is_null( $title ) ) {
250 1
			throw new InvalidArgumentException( '$title must be a string or null' );
251
		}
252 4
		if( !is_int( $pageid ) && !is_null( $pageid ) && !is_string( $pageid ) ) {
253 1
			throw new InvalidArgumentException( '$pageid must be a int, string, or null' );
254
		}
255 3
		if( !is_bool( $followRedir ) ) {
256 1
			throw new InvalidArgumentException( '$followRedir must be a bool' );
257
		}
258 2
		if( !is_bool( $normalize ) ) {
259 1
			throw new InvalidArgumentException( '$normalize must be a bool' );
260
		}
261 1
		if( !is_string( $timestamp ) && !is_double( $timestamp ) && !is_int( $timestamp ) && !is_null( $timestamp ) ) {
262 1
			throw new InvalidArgumentException( '$timestamp must be a string, int, double or null' );
263
		}
264
265
		$this->wiki = $wikiClass;
266
267
		if( is_null( $title ) && is_null( $pageid ) ) {
268
			throw new NoTitle();
269
		}
270
271
		if( !is_null( $title ) && $normalize ) {
272
			$title = str_replace( '_', ' ', $title );
273
			$title = str_replace( '%20', ' ', $title );
274
			if( $title[0] == ":" ) {
275
				$title = substr( $title, 1 );
276
			}
277
			$chunks = explode( ':', $title, 2 );
278
			if( count( $chunks ) != 1 ) {
279
				$namespace = strtolower( trim( $chunks[0] ) );
280
				$namespaces = $this->wiki->get_namespaces();
281
				if( $namespace == $namespaces[-2] || $namespace == "media" ) {
282
					// Media or local variant, translate to File:
283
					$title = $namespaces[6] . ":" . $chunks[1];
284
				}
285
			}
286
		}
287
288
		$this->title = $title;
289
290
		$pageInfoArray = array();
291
292
		if( !is_null( $pageid ) ) {
293
			$pageInfoArray['pageids'] = $pageid;
294
			$peachout = "page ID $pageid";
295
		} else {
296
			$pageInfoArray['titles'] = $title;
297
			$peachout = $title;
298
		}
299
300
		if( $followRedir ) $pageInfoArray['redirects'] = '';
301
302
		pecho( "Getting page info for {$peachout}..\n\n", PECHO_NORMAL );
303
304
		$this->get_metadata( $pageInfoArray );
305
		if( !is_null( $timestamp ) ) $this->starttimestamp = $timestamp;
306
	}
307
308
	/**
309
	 * Returns page history. Can be specified to return content as well
310
	 *
311
	 * @param int $count Revisions to return (default: 1)
312
	 * @param string $dir Direction to return revisions (default: "older")
313
	 * @param bool $content Should content of that revision be returned as well (default: false)
314
	 * @param int $revid Revision ID to start from (default: null)
315
	 * @param bool $rollback_token Should a rollback token be returned (default: false)
316
	 * @param bool $recurse Used internally to provide more results than can be returned with a single API query
317
	 * @return array Revision data
318
	 */
319
	public function history( $count = 1, $dir = "older", $content = false, $revid = null, $rollback_token = false, $recurse = false ) {
320
		if( !$this->exists ) return array();
321
322
		$historyArray = array(
323
			'action' => 'query',
324
			'prop'   => 'revisions',
325
			'titles' => $this->title,
326
			'rvprop' => 'timestamp|ids|user|comment',
327
            'rawcontinue' => 1,
328
			'rvdir'  => $dir,
329
330
		);
331
332
		if( $content ) $historyArray['rvprop'] .= "|content";
333
334
		if( !is_null( $revid ) ) $historyArray['rvstartid'] = $revid;
335
		if( !is_null( $count ) ) $historyArray['rvlimit'] = $count;
336
337
		if( $rollback_token ) $historyArray['rvtoken'] = 'rollback';
338
339
		if( !$recurse ) pecho( "Getting page history for {$this->title}..\n\n", PECHO_NORMAL );
340
341
		if( is_null( $count ) ) {
342
			$history = $ei = $this->history( $this->wiki->get_api_limit() + 1, $dir, $content, $revid, $rollback_token, true );
343
			while( !is_null( $ei[1] ) ){
344
				$ei = $this->history( $this->wiki->get_api_limit() + 1, $dir, $content, $ei[1], $rollback_token, true );
345
				foreach( $ei[0] as $eg ){
346
					$history[0][] = $eg;
347
				}
348
			}
349
350
			return $history[0];
351
		}
352
353
		$historyResult = $this->wiki->apiQuery( $historyArray );
354
355
		if( $recurse ) {
356
			if( isset( $historyResult['query-continue'] ) ) {
357
				return array(
358
					$historyResult['query']['pages'][$this->pageid]['revisions'],
359
					$historyResult['query-continue']['revisions']['rvcontinue']
360
				);
361
			}
362
			return array( $historyResult['query']['pages'][$this->pageid]['revisions'], null );
363
		}
364
365
		return $historyResult['query']['pages'][$this->pageid]['revisions'];
366
	}
367
368
	/**
369
	 * Retrieves text from a page, or a cached copy unless $force is true
370
	 *
371
	 * @param bool $force Grab text from the API, don't use the cached copy (default: false)
372
	 * @param string|int $section Section title or ID to retrieve
373
	 * @return string|bool Page content
374
	 */
375
	public function get_text( $force = false, $section = null ) {
376
		pecho( "Getting page content for {$this->title}..\n\n", PECHO_NOTICE );
377
378
		if( !$this->exists ) return null;
379
380
		if( !is_null( $section ) ) {
381
			if( empty( $this->content ) ) {
382
				$this->content = $this->history( 1, "older", true );
383
				$this->content = $this->content[0]['*'];
384
			}
385
386
			$sections = $this->wiki->apiQuery(
387
				array(
388
					'action' => 'parse',
389
					'page'   => $this->title,
390
					'prop'   => 'sections'
391
				)
392
			);
393
394
			if( !is_numeric( $section ) ) {
395
				foreach( $sections['parse']['sections'] as $section3 ){
396
					if( $section3['line'] == $section ) {
397
						$section = $section3['number'];
398
					}
399
				}
400
			}
401
402
			if( !is_numeric( $section ) ) {
403
				pecho( "Warning: Section not found.\n\n", PECHO_WARN );
404
				return false;
405
			}
406
407
			$offsets = array( '0' => '0' );
408
409
			if( $this->wiki->get_mw_version() < '1.16' ) {
410
411
				//FIXME: Implement proper notice suppression
412
413
				$ids = array();
414
415
				foreach( $sections['parse']['sections'] as $section3 ){
416
					$ids[$section3['line']] = $section3['number'];
417
				}
418
419
				$regex = '/^(=+)\s*(.*?)\s*(\1)\s*/m';
420
421
				preg_match_all( $regex, $this->content, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
422
423
				foreach( $m as $id => $match ){
0 ignored issues
show
The expression $m of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
424
					$offsets[$id + 1] = $match[0][1];
425
				}
426
427
			} else {
428
				foreach( $sections['parse']['sections'] as $section2 ){
429
					$offsets[$section2['number']] = $section2['byteoffset'];
430
				}
431
			}
432
433
			if( intval( $section ) != count( $offsets ) - 1 ) {
434
				$length = $offsets[$section + 1] - $offsets[$section];
435
			}
436
437
			if( isset( $length ) ) {
438
				$substr = mb_substr( $this->content, $offsets[$section], $length );
439
			} else {
440
				$substr = mb_substr( $this->content, $offsets[$section] );
441
			}
442
443
			return $substr;
444
		} else {
445
			if( !$force && $this->content !== null ) {
446
				return $this->content;
447
			}
448
449
			$this->content = $this->history( 1, "older", true );
450
			$this->content = $this->content[0]['*'];
451
452
			return $this->content;
453
		}
454
	}
455
456
	/**
457
	 * Returns the pageid of the page.
458
	 *
459
	 * @return int Pageid
460
	 */
461
	public function get_id() {
462
		return $this->pageid;
463
	}
464
465
	/**
466
	 * Returns if the page exists
467
	 *
468
	 * @return bool Exists
469
	 * @deprecated since 18 June 2013
470
	 */
471
	public function exists() {
472
		Peachy::deprecatedWarn( 'Page::exists()', 'Page::get_exists()' );
473
		return $this->exists;
474
	}
475
476
	/**
477
	 * Returns if the page exists
478
	 *
479
	 * @return bool Exists
480
	 */
481
	public function get_exists() {
482
		return $this->exists;
483
	}
484
485
	/**
486
	 * Returns links on the page.
487
	 *
488
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#links_.2F_pl
489
	 * @param bool $force Force use of API, won't use cached copy (default: false)
490
	 * @param array $namespace Show links in this namespace(s) only.  Default array()
491
	 * @param array $titles Only list links to these titles.  Default array()
492
	 * @return bool|array False on error, array of link titles
493
	 */
494
	public function get_links( $force = false, $namespace = array(), $titles = array() ) {
495
496
		if( !$force && $this->links !== null ) {
497
			return $this->links;
498
		}
499
500
		$this->links = array();
501
		if( !$this->exists ) return array();
502
503
		$tArray = array(
504
			'prop'     => 'links',
505
			'titles'   => $this->title,
506
			'_code'    => 'pl',
507
			'_lhtitle' => 'links'
508
		);
509
510
		if( !empty( $namespace ) ) $tArray['plnamespace'] = implode( '|', $namespace );
511
		if( !empty( $titles ) ) $tArray['pltitles'] = implode( '|', $titles );
512
513
		pecho( "Getting internal links on {$this->title}..\n\n", PECHO_NORMAL );
514
515
		$result = $this->wiki->listHandler( $tArray );
516
517
		if( count( $result ) > 0 ) {
518
			foreach( $result[0] as $link ){
519
				$this->links[] = $link['title'];
520
			}
521
		}
522
523
		return $this->links;
524
	}
525
526
	/**
527
	 * Returns templates on the page
528
	 * 
529
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#templates_.2F_tl
530
	 * @param bool $force Force use of API, won't use cached copy (default: false)
531
	 * @param array $namespace Show templates in this namespace(s) only. Default array().
532
	 * @param array $template Only list these templates. Default array()
533
	 * @return bool|array False on error, array of template titles
534
	 */
535
	public function get_templates( $force = false, $namespace = array(), $template = array() ) {
536
537
		if( !$force && $this->templates !== null && empty( $namespace ) && empty( $template ) ) {
538
			return $this->templates;
539
		}
540
541
		$this->templates = array();
542
		if( !$this->exists ) return array();
543
544
		$tArray = array(
545
			'prop'     => 'templates',
546
			'titles'   => $this->title,
547
			'_code'    => 'tl',
548
			'_lhtitle' => 'templates'
549
		);
550
		if( !empty( $namespace ) ) $tArray['tlnamespace'] = implode( '|', $namespace );
551
		if( !empty( $template ) ) $tArray['tltemplates'] = implode( '|', $template );
552
553
		pecho( "Getting templates transcluded on {$this->title}..\n\n", PECHO_NORMAL );
554
555
		$result = $this->wiki->listHandler( $tArray );
556
557
		if( count( $result ) > 0 ) {
558
			foreach( $result as $template ){
559
				$this->templates[] = $template['title'];
560
			}
561
		}
562
563
		return $this->templates;
564
	}
565
566
	/**
567
	 * Get various properties defined in the page content
568
	 * 
569
	 * @link https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp
570
	 * @param bool $force Force use of API, won't use cached copy (default: false)
571
	 * @return bool|array False on error, array of template titles
572
	 */
573
	public function get_properties( $force = false ) {
574
575
		if( !$force && $this->properties !== null ) {
576
			return $this->properties;
577
		}
578
579
		$this->properties = array();
580
		if( !$this->exists ) return array();
581
582
		$tArray = array(
583
			'prop'   => 'pageprops',
584
			'titles' => $this->title,
585
			'_code'  => 'pp'
586
		);
587
588
		pecho( "Getting page properties on {$this->title}..\n\n", PECHO_NORMAL );
589
590
		$this->properties = $this->wiki->listHandler( $tArray );
591
592
		return $this->properties;
593
	}
594
595
	/**
596
	 * Returns categories of page
597
	 * 
598
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#categories_.2F_cl
599
	 * @param bool $force Force use of API, won't use cached copy (default: false)
600
	 * @param array|string $prop Which additional properties to get for each category. Default all
601
	 * @param bool $hidden Show hidden categories. Default false
602
	 * @return bool|array False on error, returns array of categories
603
	 */
604
	public function get_categories( $force = false, $prop = array(
605
		'sortkey', 'timestamp', 'hidden'
606
	), $hidden = false ) {
607
608
		if( !$force && $this->categories !== null ) {
609
			return $this->categories;
610
		}
611
612
		$this->categories = array();
613
		if( !$this->exists ) return array();
614
615
		$tArray = array(
616
			'prop'     => 'categories',
617
			'titles'   => $this->title,
618
			'_code'    => 'cl',
619
			'_lhtitle' => 'categories',
620
			'clprop'   => implode( '|', $prop )
621
		);
622
623
		if( $hidden ) $tArray['clshow'] = '';
624
625
		pecho( "Getting categories {$this->title} is part of..\n\n", PECHO_NORMAL );
626
627
		$result = $this->wiki->listHandler( $tArray );
628
629
		if( count( $result ) > 0 ) {
630
			foreach( $result as $category ){
631
				$this->categories[] = $category['title'];
632
			}
633
		}
634
635
		return $this->categories;
636
637
	}
638
639
	/**
640
	 * Returns images used in the page
641
	 * 
642
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#images_.2F_im
643
	 * @param bool $force Force use of API, won't use cached copy (default: false)
644
	 * @param string|array $images Only list these images. Default null.
645
	 * @return bool|array False on error, returns array of image titles
646
	 */
647
	public function get_images( $force = false, $images = null ) {
648
649
		if( !$force && $this->images !== null ) {
650
			return $this->images;
651
		}
652
653
		$this->images = array();
654
		if( !$this->exists ) return array();
655
656
		$tArray = array(
657
			'prop'     => 'images',
658
			'titles'   => $this->title,
659
			'_code'    => 'im',
660
			'_lhtitle' => 'images'
661
		);
662
663
		if( !is_null( $images ) ) {
664
			if( is_array( $images ) ) {
665
				$tArray['imimages'] = implode( '|', $images );
666
			} else $tArray['imimage'] = $images;
667
		}
668
669
		pecho( "Getting images used on {$this->title}..\n\n", PECHO_NORMAL );
670
671
		$result = $this->wiki->listHandler( $tArray );
672
673
		if( count( $result ) > 0 ) {
674
			foreach( $result[0] as $image ){
675
				$this->images[] = $image['title'];
676
			}
677
		}
678
679
		return $this->images;
680
681
682
	}
683
684
	/**
685
	 * Returns external links used in the page
686
	 * 
687
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#extlinks_.2F_el
688
	 * @param bool $force Force use of API, won't use cached copy (default: false)
689
	 * @return bool|array False on error, returns array of URLs
690
	 */
691
	public function get_extlinks( $force = false ) {
692
693
		if( !$force && $this->extlinks !== null ) {
694
			return $this->extlinks;
695
		}
696
697
		$this->extlinks = array();
698
		if( !$this->exists ) return array();
699
700
		$tArray = array(
701
			'prop'     => 'extlinks',
702
			'titles'   => $this->title,
703
			'_code'    => 'el',
704
			'_lhtitle' => 'extlinks'
705
		);
706
707
		pecho( "Getting external links used on {$this->title}..\n\n", PECHO_NORMAL );
708
709
		$result = $this->wiki->listHandler( $tArray );
710
711
		if( count( $result ) > 0 ) {
712
			foreach( $result as $extlink ){
713
				$this->extlinks[] = $extlink['*'];
714
			}
715
		}
716
717
		return $this->extlinks;
718
	}
719
720
	/**
721
	 * Returns interlanguage links on the page
722
	 *
723
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#langlinks_.2F_ll
724
	 * @param bool $force Force use of API, won't use cached copy (default: false)
725
	 * @param bool $fullurl Include a list of full of URLs.  Output formatting changes.  Requires force parameter to be true to return a different result.
726
	 * @param string $title Link to search for. Must be used with $lang.  Default null
727
	 * @param string $lang Language code.  Default null
728
	 * @return bool|array False on error, returns array of links in the form of lang:title
729
	 */
730
	public function get_langlinks( $force = false, $fullurl = false, $title = null, $lang = null ) {
731
		if( !$force && $this->langlinks !== null ) {
732
			return $this->langlinks;
733
		}
734
735
		$this->langlinks = array();
736
		if( !$this->exists ) return array();
737
738
		$tArray = array(
739
			'prop'     => 'langlinks',
740
			'titles'   => $this->title,
741
			'_code'    => 'll',
742
			'_lhtitle' => 'langlinks'
743
		);
744
745
		if( !is_null( $lang ) ) $tArray['lllang'] = $lang;
746
		if( !is_null( $title ) ) $tArray['lltitle'] = $title;
747
		if( $fullurl ) $tArray['llurl'] = '';
748
749
		pecho( "Getting all interlanguage links for {$this->title}..\n\n", PECHO_NORMAL );
750
751
		$result = $this->wiki->listHandler( $tArray );
752
753
		if( count( $result ) > 0 ) {
754
			foreach( $result[0] as $langlink ){
755
				if( $fullurl ) {
756
					$this->langlinks[] = array(
757
						'link' => $langlink['lang'] . ":" . $langlink['*'], 'url' => $langlink['url']
758
					);
759
				} else $this->langlinks[] = $langlink['lang'] . ":" . $langlink['*'];
760
			}
761
		}
762
763
		return $this->langlinks;
764
	}
765
766
	/**
767
	 * Returns interwiki links on the page
768
	 *
769
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#langlinks_.2F_ll
770
	 * @param bool $force Force use of API, won't use cached copy (default: false)
771
	 * @param bool $fullurl Include a list of full of URLs.  Output formatting changes.  Requires force parameter to be true to return a different result.
772
	 * @param string $title Interwiki link to search for. Must be used with $prefix.  Default null
773
	 * @param string $prefix Prefix for the interwiki.  Default null
774
	 * @return bool|array False on error, returns array of links in the form of lang:title
775
	 */
776
	public function get_interwikilinks( $force = false, $fullurl = false, $title = null, $prefix = null ) {
777
		if( !$force && $this->iwlinks !== null ) {
778
			return $this->iwlinks;
779
		}
780
781
		$this->iwlinks = array();
782
		if( !$this->exists ) return array();
783
784
		$tArray = array(
785
			'prop'     => 'iwlinks',
786
			'titles'   => $this->title,
787
			'_code'    => 'iw',
788
			'_lhtitle' => 'iwlinks'
789
		);
790
791
		if( !is_null( $prefix ) ) $tArray['iwprefix'] = $prefix;
792
		if( !is_null( $title ) ) $tArray['iwtitle'] = $title;
793
		if( $fullurl ) $tArray['iwurl'] = '';
794
795
796
		pecho( "Getting all interwiki links for {$this->title}..\n\n", PECHO_NORMAL );
797
798
		$result = $this->wiki->listHandler( $tArray );
799
800
		if( count( $result ) > 0 ) {
801
			foreach( $result[0] as $iwlinks ){
802
				if( $fullurl ) {
803
					$this->iwlinks[] = array(
804
						'link' => $iwlinks['prefix'] . ":" . $iwlinks['*'], 'url' => $iwlinks['url']
805
					);
806
				} else $this->iwlinks[] = $iwlinks['prefix'] . ":" . $iwlinks['*'];
807
			}
808
		}
809
810
		return $this->iwlinks;
811
	}
812
813
	/**
814
	 * Returns the protection level of the page
815
	 *
816
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
817
	 * @param bool $force Force use of API, won't use cached copy (default: false)
818
	 * @return bool|array False on error, returns array with protection levels
819
	 */
820
	public function get_protection( $force = false ) {
821
822
		if( !$force && $this->protection !== null ) {
823
			return $this->protection;
824
		}
825
826
		$this->protection = array();
827
828
		$tArray = array(
829
			'action' => 'query',
830
			'prop'   => 'info',
831
			'inprop' => 'protection',
832
			'titles' => $this->title,
833
		);
834
835
		pecho( "Getting protection levels for {$this->title}..\n\n", PECHO_NORMAL );
836
837
		$tRes = $this->wiki->apiQuery( $tArray );
838
839
		$this->protection = $tRes['query']['pages'][$this->pageid]['protection'];
840
841
		return $this->protection;
842
843
	}
844
845
	/**
846
	 * Returns the page ID of the talk page for each non-talk page
847
	 *
848
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
849
	 * @param bool $force Force use of API, won't use cached copy (default: false)
850
	 * @return int Null or empty if no id exists.
851
	 */
852
	public function get_talkID( $force = false ) {
853
854
		if( !$force ) {
855
			return $this->talkid;
856
		}
857
858
		$tArray = array(
859
			'action' => 'query',
860
			'prop'   => 'info',
861
			'inprop' => 'talkid',
862
			'titles' => $this->title,
863
		);
864
865
		pecho( "Getting talk page ID for {$this->title}..\n\n", PECHO_NORMAL );
866
867
		$tRes = $this->wiki->apiQuery( $tArray );
868
869
		if( isset( $tRes['query']['pages'][$this->pageid]['talkid'] ) ) {
870
			$this->talkid = $tRes['query']['pages'][$this->pageid]['talkid'];
871
		} else $this->talkid = null;
872
873
		return $this->talkid;
874
	}
875
876
	/**
877
	 * Returns the watch status of the page
878
	 *
879
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
880
	 * @param bool $force Force use of API, won't use cached copy (default: false)
881
	 * @return bool
882
	 */
883
	public function is_watched( $force = false ) {
884
885
		if( !$force && $this->watched !== null ) {
886
			return $this->watched;
887
		}
888
889
		$tArray = array(
890
			'action' => 'query',
891
			'prop'   => 'info',
892
			'inprop' => 'watched',
893
			'titles' => $this->title,
894
		);
895
896
		pecho( "Getting watch status for {$this->title}..\n\n", PECHO_NORMAL );
897
898
		$tRes = $this->wiki->apiQuery( $tArray );
899
900
		if( isset( $tRes['query']['pages'][$this->pageid]['watched'] ) ) {
901
			$this->watched = true;
902
		} else $this->watched = false;
903
904
		return $this->watched;
905
	}
906
907
	/**
908
	 * Returns the count for the number of watchers of a page.
909
	 *
910
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
911
	 * @param bool $force Force use of API, won't use cached copy (default: false)
912
	 * @return int
913
	 */
914
	public function get_watchcount( $force = false ) {
915
916
		if( !$force && $this->watchers !== null ) {
917
			return $this->watchers;
918
		}
919
920
		$tArray = array(
921
			'action' => 'query',
922
			'prop'   => 'info',
923
			'inprop' => 'watchers',
924
			'titles' => $this->title,
925
		);
926
927
		pecho( "Getting watch count for {$this->title}..\n\n", PECHO_NORMAL );
928
929
		$tRes = $this->wiki->apiQuery( $tArray );
930
931
		if( isset( $tRes['query']['pages'][$this->pageid]['watchers'] ) ) {
932
			$this->watchers = $tRes['query']['pages'][$this->pageid]['watchers'];
933
		} else $this->watchers = 0;
934
935
		return $this->watchers;
936
	}
937
938
	/**
939
	 * Returns the watchlist notification timestamp of each page.
940
	 *
941
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
942
	 * @param bool $force Force use of API, won't use cached copy (default: false)
943
	 * @return string
944
	 */
945
	public function get_notificationtimestamp( $force = false ) {
946
947
		if( !$force ) {
948
			return $this->watchlisttimestamp;
949
		}
950
951
		$tArray = array(
952
			'action' => 'query',
953
			'prop'   => 'info',
954
			'inprop' => 'notificationtimestamp',
955
			'titles' => $this->title,
956
		);
957
958
		pecho( "Getting the notification timestamp for {$this->title}..\n\n", PECHO_NORMAL );
959
960
		$tRes = $this->wiki->apiQuery( $tArray );
961
962
		if( isset( $tRes['query']['pages'][$this->pageid]['notificationtimestamp'] ) ) {
963
            $this->watchlisttimestamp = $tRes['query']['pages'][$this->pageid]['notificationtimestamp'];
964
		} else $this->watchlisttimestamp = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $watchlisttimestamp was declared of type string, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
965
966
		return $this->watchlisttimestamp;
967
	}
968
969
	/**
970
	 * Returns the page ID of the parent page for each talk page.
971
	 *
972
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
973
	 * @param bool $force Force use of API, won't use cached copy (default: false)
974
	 * @return int Null if it doesn't exist.
975
	 */
976
	public function get_subjectid( $force = false ) {
977
978
		if( !$force ) {
979
			return $this->subjectid;
980
		}
981
982
		$tArray = array(
983
			'action' => 'query',
984
			'prop'   => 'info',
985
			'inprop' => 'subjectid',
986
			'titles' => $this->title,
987
		);
988
989
		pecho( "Getting the parent page ID for {$this->title}..\n\n", PECHO_NORMAL );
990
991
		$tRes = $this->wiki->apiQuery( $tArray );
992
993
		if( isset( $tRes['query']['pages'][$this->pageid]['subjectid'] ) ) {
994
			$this->subjectid = $tRes['query']['pages'][$this->pageid]['subjectid'];
995
		} else $this->subjectid = null;
996
997
		return $this->subjectid;
998
	}
999
1000
	/**
1001
	 * Gives a full URL to the page, and also an edit URL.
1002
	 *
1003
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1004
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1005
	 * @return array
1006
	 */
1007
	public function get_urls( $force = false ) {
1008
1009
		if( !$force && $this->urls !== null ) {
1010
			return $this->urls;
1011
		}
1012
1013
		$tArray = array(
1014
			'action' => 'query',
1015
			'prop'   => 'info',
1016
			'inprop' => 'url',
1017
			'titles' => $this->title,
1018
		);
1019
1020
		pecho( "Getting the URLs for {$this->title}..\n\n", PECHO_NORMAL );
1021
1022
		$tRes = $this->wiki->apiQuery( $tArray );
1023
		$info = $tRes['query']['pages'][$this->pageid];
1024
1025
		$this->urls = array();
1026
1027
		if( isset( $info['fullurl'] ) ) $this->urls['full'] = $info['fullurl'];
1028
		if( isset( $info['editurl'] ) ) $this->urls['edit'] = $info['editurl'];
1029
1030
		return $this->urls;
1031
	}
1032
1033
	/**
1034
	 * Returns whether the user can read this page.
1035
	 *
1036
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1037
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1038
	 * @return boolean Null if it doesn't exist.
1039
	 */
1040
	public function get_readability( $force = false ) {
1041
1042
		if( !$force && $this->readable !== null ) {
1043
			return $this->readable;
1044
		}
1045
1046
		$tArray = array(
1047
			'action' => 'query',
1048
			'prop'   => 'info',
1049
			'inprop' => 'readable',
1050
			'titles' => $this->title,
1051
		);
1052
1053
		pecho( "Getting the readability status for {$this->title}..\n\n", PECHO_NORMAL );
1054
1055
		$tRes = $this->wiki->apiQuery( $tArray );
1056
1057
		if( isset( $tRes['query']['pages'][$this->pageid]['readable'] ) ) {
1058
			$this->readable = true;
1059
		} else $this->readable = false;
1060
1061
		return $this->readable;
1062
	}
1063
1064
	/**
1065
	 * Gives the text returned by EditFormPreloadText.
1066
	 *
1067
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1068
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1069
	 * @return string
1070
	 */
1071
	public function get_preload( $force = false ) {
1072
1073
		if( !$force ) {
1074
			return $this->preload;
1075
		}
1076
1077
		$tArray = array(
1078
			'action' => 'query',
1079
			'prop'   => 'info',
1080
			'inprop' => 'preload',
1081
			'titles' => $this->title,
1082
		);
1083
1084
		pecho( "Getting the preload text for {$this->title}..\n\n", PECHO_NORMAL );
1085
1086
		$tRes = $this->wiki->apiQuery( $tArray );
1087
1088
		if( isset( $tRes['query']['pages'][$this->pageid]['preload'] ) ) {
1089
			$this->preload = $tRes['query']['pages'][$this->pageid]['preload'];
1090
		} else $this->preload = null;
1091
1092
		return $this->preload;
1093
	}
1094
1095
	/**
1096
	 * Gives the way the page title is actually displayed.
1097
	 *
1098
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1099
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1100
	 * @return string
1101
	 */
1102
	public function get_displaytitle( $force = false ) {
1103
1104
		if( !$force ) {
1105
			return $this->displaytitle;
1106
		}
1107
1108
		$tArray = array(
1109
			'action' => 'query',
1110
			'prop'   => 'info',
1111
			'inprop' => 'displaytitle',
1112
			'titles' => $this->title,
1113
		);
1114
1115
		pecho( "Getting the title formatting for {$this->title}..\n\n", PECHO_NORMAL );
1116
1117
		$tRes = $this->wiki->apiQuery( $tArray );
1118
1119
		if( isset( $tRes['query']['pages'][$this->pageid]['displaytitle'] ) ) {
1120
			$this->displaytitle = $tRes['query']['pages'][$this->pageid]['displaytitle'];
1121
		} else $this->displaytitle = null;
1122
1123
		return $this->displaytitle;
1124
	}
1125
1126
	/**
1127
	 * Edits the page
1128
	 *
1129
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1130
	 * @param string $text Text of the page that will be saved
1131
	 * @param string $summary Summary of the edit (default: "")
1132
	 * @param bool $minor Minor edit (default: false)
1133
	 * @param bool $bot Mark as bot edit (default: true)
1134
	 * @param bool $force Override nobots check (default: false)
1135
	 * @param string $pend Set to 'pre' or 'ap' to prepend or append, respectively (default: "")
1136
	 * @param bool $create Set to 'never', 'only', or 'recreate' to never create a new page, only create a new page, or override errors about the page having been deleted, respectively (default: false)
1137
	 * @param string $section Section number. 0 for the top section, 'new' for a new section.  Default null.
1138
	 * @param string $sectiontitle The title for a new section. Default null.
1139
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1140
	 * @return string|int|bool The revision id of the successful edit, false on failure.
1141
	 */
1142
	public function edit(
1143
		$text,
1144
		$summary = "",
1145
		$minor = false,
1146
		$bot = true,
1147
		$force = false,
1148
		$pend = "",
1149
		$create = false,
1150
		$section = null,
1151
		$sectiontitle = null,
1152
		$watch = null
1153
	) {
1154
		global $pgNotag, $pgTag;
1155
1156
		$tokens = $this->wiki->get_tokens();
1157
1158
		if( !$pgNotag ) {
1159
			$summary = substr( $summary . $pgTag, 0, 255 );
1160
		}
1161
1162
		if( $tokens['edit'] == '' ) {
1163
			pecho( "User is not allowed to edit {$this->title}\n\n", PECHO_FATAL );
1164
			return false;
1165
		}
1166
1167
		if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
The call to mb_strlen() has too many arguments starting with '8bit'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1168
			pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
1169
			return false;
1170
		}
1171
1172
		$editarray = array(
1173
			'title'         => $this->title,
1174
			'action'        => 'edit',
1175
			'token'         => $tokens['edit'],
1176
			'basetimestamp' => $this->lastedit,
1177
			'md5'           => md5( $text ),
1178
			'text'          => $text
1179
		);
1180
		if( !is_null( $this->starttimestamp ) ) $editarray['starttimestamp'] = $this->starttimestamp;
1181
		if( !is_null( $section ) ) {
1182
			if( $section == 'new' ) {
1183
				if( is_null( $sectiontitle ) ) {
1184
                    pecho("Error: sectionTitle parameter must be specified.  Aborting...\n\n", PECHO_FATAL);
1185
					return false;
1186
				} else {
1187
					$editarray['section'] = 'new';
1188
                    $editarray['sectionTitle'] = $sectiontitle;
1189
				}
1190
			} else $editarray['section'] = $section;
1191
		}
1192
1193
		if( $pend == "pre" ) {
1194
			$editarray['prependtext'] = $text;
1195
		} elseif( $pend == "ap" ) {
1196
			$editarray['appendtext'] = $text;
1197
		}
1198
1199
		if( !is_null( $watch ) ) {
1200
			if( $watch ) {
1201
				$editarray['watchlist'] = 'watch';
1202
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1203
			elseif( in_array(
1204
				$watch, array(
1205
					'watch', 'unwatch', 'preferences', 'nochange'
1206
				)
1207
			)
1208
			) {
1209
				$editarray['watchlist'] = $watch;
1210
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1211
		}
1212
1213
		if( $create == "never" ) {
1214
			$editarray['nocreate'] = '';
1215
		} elseif( $create == "only" ) $editarray['createonly'] = '';
1216
		elseif( $create == "recreate" ) $editarray['recreate'] = '';
1217
1218
		if( $this->wiki->get_maxlag() ) $editarray['maxlag'] = $this->wiki->get_maxlag();
1219
1220
		if( !empty( $summary ) ) $editarray['summary'] = $summary;
1221
1222
		if( $minor ) {
1223
			$editarray['minor'] = '';
1224
		} else $editarray['notminor'] = '';
1225
1226
		if( $bot ) $editarray['bot'] = '';
1227
1228
		if( !$force ) {
1229
			try{
1230
				$this->preEditChecks( "Edit" );
1231
			} catch( EditError $e ){
1232
				pecho( "Error: $e\n\n", PECHO_FATAL );
1233
				return false;
1234
			}
1235
		}
1236
1237
		Hooks::runHook( 'StartEdit', array( &$editarray ) );
1238
1239
		pecho( "Making edit to {$this->title}..\n\n", PECHO_NOTICE );
1240
1241
		$result = $this->wiki->apiQuery( $editarray, true );
1242
1243
		if( isset( $result['edit'] ) ) {
1244
			if( $result['edit']['result'] == "Success" ) {
1245
                if (array_key_exists('nochange', $result['edit'])) {
1246
                    return $this->lastedit;
1247
                }
1248
1249
				$this->__construct( $this->wiki, null, $this->pageid );
1250
1251
				if( !is_null( $this->wiki->get_edit_rate() ) && $this->wiki->get_edit_rate() != 0 ) {
1252
					sleep( intval( 60 / $this->wiki->get_edit_rate() ) - 1 );
1253
				}
1254
1255
				return $result['edit']['newrevid'];
1256
			} else {
1257
				pecho( "Edit error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1258
				return false;
1259
			}
1260
		} else {
1261
			pecho( "Edit error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1262
			return false;
1263
		}
1264
1265
	}
1266
1267
	/**
1268
	 * Add text to the beginning of the page. Shortcut for Page::edit()
1269
	 *
1270
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1271
	 * @param string $text Text of the page that will be saved
1272
	 * @param string $summary Summary of the edit (default: "")
1273
	 * @param bool $minor Minor edit (default: false)
1274
	 * @param bool $bot Mark as bot edit (default: true)
1275
	 * @param bool $force Override nobots check (default: false)
1276
	 * @param bool $create Set to 'never', 'only', or 'recreate' to never create a new page, only create a new page, or override errors about the page having been deleted, respectively (default: false)
1277
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch.  Default: go by user preference.
1278
	 * @return int|bool The revision id of the successful edit, false on failure.
1279
	 */
1280
	public function prepend( $text, $summary = "", $minor = false, $bot = true, $force = false, $create = false, $watch = null ) {
1281
		return $this->edit( $text, $summary, $minor, $bot, $force, 'pre', $create, null, null, $watch );
1282
	}
1283
1284
	/**
1285
	 * Add text to the end of the page. Shortcut for Page::edit()
1286
	 *
1287
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1288
	 * @param string $text Text of the page that will be saved
1289
	 * @param string $summary Summary of the edit (default: "")
1290
	 * @param bool $minor Minor edit (default: false)
1291
	 * @param bool $bot Mark as bot edit (default: true)
1292
	 * @param bool $force Override nobots check (default: false)
1293
	 * @param bool $create Set to 'never', 'only', or 'recreate' to never create a new page, only create a new page, or override errors about the page having been deleted, respectively (default: false)
1294
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1295
	 * @return int|bool The revision id of the successful edit, false on failure.
1296
	 */
1297
	public function append( $text, $summary = "", $minor = false, $bot = true, $force = false, $create = false, $watch = null ) {
1298
		return $this->edit( $text, $summary, $minor, $bot, $force, 'ap', $create, null, null, $watch );
1299
	}
1300
1301
	/**
1302
	 * Create a new section.  Shortcut for Page::edit()
1303
	 *
1304
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1305
     * @param    string $text Text of the page that will be saved
1306
     * @param    string $sectionTitle The title for a new section. Default null.
1307
     * @param    string $summary Summary of the edit (default: "")
1308
     * @param    bool $minor Minor edit (default: false)
1309
     * @param    bool $bot Mark as bot edit (default: true)
1310
     * @param    bool $force Override nobots check (default: false)
1311
     * @param   bool $create Set to 'never', 'only', or 'recreate' to never create a new page,
1312
     *                                          only create a new page, or override errors about the page having
1313
     *                                          been deleted, respectively (default: false)
1314
     * @param    string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences
1315
     *                                          or do not change watch. Default: go by user preference.
1316
     * @return    int|bool                    The revision ID of the successful edit, false on failure.
1317
	 */
1318
    public function newsection(
1319
        $text,
1320
        $sectionTitle,
1321
        $summary = null,
1322
        $minor = false,
1323
        $bot = true,
1324
        $force = false,
1325
        $create = false,
1326
        $watch = null
1327
    ) {
1328
        if (is_null($summary)) {
1329
            $summary = "/* " . $sectionTitle . " */ new section";
1330
        }
1331
1332
        return $this->edit($text, $summary, $minor, $bot, $force, false, $create, 'new', $sectionTitle, $watch);
0 ignored issues
show
false is of type boolean, but the function expects a string.

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...
1333
    }
1334
1335
    /**
1336
     * Undoes one or more edits. (Subject to standard editing restrictions.)
1337
     *
1338
     * @param   string $summary Override the default edit summary (default null).
1339
     * @param   int $revisions The number of revisions to undo (default 1).
1340
     * @param   bool $force Force an undo, despite e.g. new messages (default false).
1341
     * @param   bool|string $watch Unconditionally add or remove the page from your watchlist, use preferences
1342
     *                                      or do not change watch. Default: goes by user preference.
1343
     * @return  bool|int                The new revision id of the page edited.
1344
     * @throws AssertFailure
1345
     * @throws LoggedOut
1346
     * @throws MWAPIError
1347
     * @throws NoTitle
1348
     */
1349
    public function undo($summary = null, $revisions = 1, $force = false, $watch = null ) {
1350
		global $pgNotag, $pgTag;
1351
		$info = $this->history( $revisions );
1352
		$oldrev = $info[( count( $info ) - 1 )]['revid'];
1353
		$newrev = $info[0]['revid'];
1354
1355
		$tokens = $this->wiki->get_tokens();
1356
1357
		if( $tokens['edit'] == '+\\' ) {
1358
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1359
			return false;
1360
		} elseif( $tokens['edit'] == '' ) {
1361
			pecho( "User is not allowed to edit {$this->title}\n\n", PECHO_FATAL );
1362
			return false;
1363
		}
1364
1365
		$params = array(
1366
			'title'         => $this->title,
1367
			'action'        => 'edit',
1368
			'token'         => $tokens['edit'],
1369
			'basetimestamp' => $this->lastedit,
1370
			'undo'          => $oldrev,
1371
			'undoafter'     => $newrev
1372
		);
1373
		if( !is_null( $this->starttimestamp ) ) $params['starttimestamp'] = $this->starttimestamp;
1374
		if( !is_null( $summary ) ) {
1375
			if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
The call to mb_strlen() has too many arguments starting with '8bit'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1376
				pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
1377
				return false;
1378
			}
1379
			if( !$pgNotag ) $summary .= $pgTag;
1380
1381
			$params['summary'] = $summary;
1382
		}
1383
1384
		if( !is_null( $watch ) ) {
1385
			if( $watch ) {
1386
				$editarray['watchlist'] = 'watch';
1387
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1388
			elseif( in_array(
1389
				$watch, array(
1390
					'watch', 'unwatch', 'preferences', 'nochange'
1391
				)
1392
			) ) {
1393
				$editarray['watchlist'] = $watch;
1394
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1395
		}
1396
1397
		if( !$force ) {
1398
			try{
1399
				$this->preEditChecks( "Undo" );
1400
			} catch( EditError $e ){
1401
				pecho( "Error: $e\n\n", PECHO_FATAL );
1402
				return false;
1403
			}
1404
		}
1405
1406
		pecho( "Undoing revision(s) on {$this->title}...\n\n", PECHO_NORMAL );
1407
		$result = $this->wiki->apiQuery( $params, true );
1408
1409
		if( $result['edit']['result'] == "Success") {
1410
            if (array_key_exists('nochange', $result['edit'])) return $this->lastedit;
1411
1412
			$this->__construct( $this->wiki, null, $this->pageid );
1413
1414
			if( !is_null( $this->wiki->get_edit_rate() ) && $this->wiki->get_edit_rate() != 0 ) {
1415
				sleep( intval( 60 / $this->wiki->get_edit_rate() ) - 1 );
1416
			}
1417
1418
			return $result['edit']['newrevid'];
1419
		} else {
1420
			pecho( "Undo error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1421
			return false;
1422
		}
1423
	}
1424
1425
	/**
1426
	 * Returns a boolean depending on whether the page can have subpages or not.
1427
	 *
1428
	 * @return bool True if subpages allowed
1429
	 */
1430
	public function allow_subpages() {
1431
		$allows = $this->wiki->get_allow_subpages();
1432
		return (bool)$allows[$this->namespace_id];
1433
	}
1434
1435
	/**
1436
	 * Returns a boolean depending on whether the page is a discussion (talk) page or not.
1437
	 *
1438
	 * @return bool True if discussion page, false if not
1439
	 */
1440
	public function is_discussion() {
1441
		return ( $this->namespace_id >= 0 && $this->namespace_id % 2 == 1 );
1442
	}
1443
1444
	/**
1445
	 * Returns the title of the discussion (talk) page associated with a page, if it exists.
1446
	 *
1447
	 * @return string Title of discussion page
1448
	 * @throws BadEntryError
1449
	 */
1450
	public function get_discussion() {
1451
		if( $this->namespace_id < 0 ) {
1452
			// No discussion page exists
1453
			// Guessing we want to error
1454
			throw new BadEntryError( "get_discussion", "Tried to find the discussion page of a page which could never have one" );
1455
		} else {
1456
			$namespaces = $this->wiki->get_namespaces();
1457
			if( $this->is_discussion() ) {
1458
				return $namespaces[( $this->namespace_id - 1 )] . ":" . $this->title_wo_namespace;
1459
			} else {
1460
				return $namespaces[( $this->namespace_id + 1 )] . ":" . $this->title_wo_namespace;
1461
			}
1462
		}
1463
	}
1464
1465
	/**
1466
	 * Moves a page to a new location.
1467
	 *
1468
	 * @param string $newTitle The new title to which to move the page.
1469
	 * @param string $reason A descriptive reason for the move.
1470
	 * @param bool $movetalk Whether or not to move any associated talk (discussion) page.
1471
	 * @param bool $movesubpages Whether or not to move any subpages.
1472
	 * @param bool $noredirect Whether or not to suppress the leaving of a redirect to the new title at the old title.
1473
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1474
	 * @param bool $nowarnings Ignore any warnings. Default false.
1475
	 * @return bool True on success
1476
	 */
1477
	public function move( $newTitle, $reason = '', $movetalk = true, $movesubpages = true, $noredirect = false, $watch = null, $nowarnings = false ) {
1478
		global $pgNotag, $pgTag;
1479
		$tokens = $this->wiki->get_tokens();
1480
1481
		if( $tokens['move'] == '+\\' ) {
1482
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1483
			return false;
1484
		} elseif( $tokens['move'] == '' ) {
1485
			pecho( "User is not allowed to move {$this->title}\n\n", PECHO_FATAL );
1486
			return false;
1487
		}
1488
1489
		if( mb_strlen( $reason, '8bit' ) > 255 ) {
0 ignored issues
show
The call to mb_strlen() has too many arguments starting with '8bit'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1490
			pecho( "Reason is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
1491
			return false;
1492
		}
1493
1494
		try{
1495
			$this->preEditChecks( "Move" );
1496
		} catch( EditError $e ){
1497
			pecho( "Error: $e\n\n", PECHO_FATAL );
1498
			return false;
1499
		}
1500
1501
		pecho( "Moving {$this->title} to $newTitle...\n\n", PECHO_NOTICE );
1502
1503
		$editarray = array(
1504
			'from'   => $this->title,
1505
			'to'     => $newTitle,
1506
			'action' => 'move',
1507
			'token'  => $tokens['move'],
1508
		);
1509
1510
		if( !is_null( $watch ) ) {
1511
			if( $watch ) {
1512
				$editarray['watchlist'] = 'watch';
1513
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1514
			elseif( in_array(
1515
				$watch, array(
1516
					'watch', 'unwatch', 'preferences', 'nochange'
1517
				)
1518
			) ) {
1519
				$editarray['watchlist'] = $watch;
1520
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1521
		}
1522
1523
		if( $nowarnings ) $editarray['ignorewarnings'] = '';
1524
		if( !$pgNotag ) $reason .= $pgTag;
1525
		if( !empty( $reason ) ) $editarray['reason'] = $reason;
1526
1527
		if( $movetalk ) $editarray['movetalk'] = '';
1528
		if( $movesubpages ) $editarray['movesubpages'] = '';
1529
		if( $noredirect ) $editarray['noredirect'] = '';
1530
1531
		if( $this->wiki->get_maxlag() ) {
1532
			$editarray['maxlag'] = $this->wiki->get_maxlag();
1533
1534
		}
1535
1536
		Hooks::runHook( 'StartMove', array( &$editarray ) );
1537
1538
		$result = $this->wiki->apiQuery( $editarray, true );
1539
1540
		if( isset( $result['move'] ) ) {
1541
			if( isset( $result['move']['to'] ) ) {
1542
				$this->__construct( $this->wiki, null, $this->pageid );
1543
				return true;
1544
			} else {
1545
				pecho( "Move error...\n\n" . print_r( $result['move'], true ) . "\n\n", PECHO_FATAL );
1546
				return false;
1547
			}
1548
		} else {
1549
			pecho( "Move error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1550
			return false;
1551
		}
1552
	}
1553
1554
	/**
1555
	 * Protects the page.
1556
	 *
1557
	 * @param array $levels Array of protections levels. The key is the type, the value is the level. Default: array( 'edit' => 'sysop', 'move' => 'sysop' )
1558
	 * @param string $reason Reason for protection. Default null
1559
	 * @param string $expiry Expiry time. Default 'indefinite'
1560
	 * @param bool $cascade Whether or not to enable cascade protection. Default false
1561
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1562
	 * @return bool True on success
1563
	 */
1564
	public function protect( $levels = null, $reason = null, $expiry = 'indefinite', $cascade = false, $watch = null ) {
1565
		global $pgNotag, $pgTag;
1566
		if( !in_array( 'protect', $this->wiki->get_userrights() ) ) {
1567
			pecho( "User is not allowed to protect pages", PECHO_FATAL );
1568
			return false;
1569
		}
1570
1571
		if( $levels === null ){
1572
			$levels = array( 'edit' => 'sysop', 'move' => 'sysop' );
1573
		}
1574
1575
		$tokens = $this->wiki->get_tokens();
1576
		if( !$pgNotag ) $reason .= $pgTag;
1577
		$editarray = array(
1578
			'action'      => 'protect',
1579
			'title'       => $this->title,
1580
			'token'       => $tokens['protect'],
1581
			'reason'      => $reason,
1582
			'protections' => array(),
1583
			'expiry'      => $expiry
1584
		);
1585
1586
		foreach( $levels as $type => $level ){
1587
			$editarray['protections'][] = "$type=$level";
1588
		}
1589
1590
		$editarray['protections'] = implode( "|", $editarray['protections'] );
1591
1592
		if( $cascade ) $editarray['cascade'] = '';
1593
1594
		if( !is_null( $watch ) ) {
1595
			if( $watch ) {
1596
				$editarray['watchlist'] = 'watch';
1597
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1598
			elseif( in_array(
1599
				$watch, array(
1600
					'watch', 'unwatch', 'preferences', 'nochange'
1601
				)
1602
			) ) {
1603
				$editarray['watchlist'] = $watch;
1604
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1605
		}
1606
		try{
1607
			$this->preEditChecks( "Protect" );
1608
		} catch( EditError $e ){
1609
			pecho( "Error: $e\n\n", PECHO_FATAL );
1610
			return false;
1611
		}
1612
		if( !$editarray['protections'] == array() ) {
1613
			pecho( "Protecting {$this->title}...\n\n", PECHO_NOTICE );
1614
		} else pecho( "Unprotecting {$this->title}...\n\n", PECHO_NOTICE );
1615
1616
		Hooks::runHook( 'StartProtect', array( &$editarray ) );
1617
1618
		$result = $this->wiki->apiQuery( $editarray, true );
1619
1620
		if( isset( $result['protect'] ) ) {
1621
			if( isset( $result['protect']['title'] ) ) {
1622
				$this->__construct( $this->wiki, $this->title );
1623
				return true;
1624
			} else {
1625
				pecho( "Protect error...\n\n" . print_r( $result['protect'], true ) . "\n\n", PECHO_FATAL );
1626
				return false;
1627
			}
1628
		} else {
1629
			pecho( "Protect error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1630
			return false;
1631
		}
1632
	}
1633
1634
	/**
1635
	 * Unprotects the page.
1636
	 *
1637
	 * @param string $reason A reason for the unprotection. Defaults to null (blank).
1638
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1639
	 * @return bool True on success
1640
	 */
1641
	public function unprotect( $reason = null, $watch = null ) {
1642
		return $this->protect( array(), $reason, 'indefinite', false, $watch );
1643
	}
1644
1645
	/**
1646
	 * Deletes the page.
1647
	 *
1648
	 * @param string $reason A reason for the deletion. Defaults to null (blank).
1649
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1650
     * @param string $oldimage The name of the old image to delete as provided by iiprop=archivename
1651
	 * @return bool True on success
1652
	 */
1653
	public function delete( $reason = null, $watch = null, $oldimage = null ) {
1654
		global $pgNotag, $pgTag;
1655
1656
		if( !in_array( 'delete', $this->wiki->get_userrights() ) ) {
1657
			pecho( "User is not allowed to delete pages", PECHO_FATAL );
1658
			return false;
1659
		}
1660
1661
		$tokens = $this->wiki->get_tokens();
1662
		if( !$pgNotag ) $reason .= $pgTag;
1663
		$editarray = array(
1664
			'action' => 'delete',
1665
			'title'  => $this->title,
1666
			'token'  => $tokens['delete'],
1667
			'reason' => $reason
1668
		);
1669
1670
		if( !is_null( $watch ) ) {
1671
			if( $watch ) {
1672
				$editarray['watchlist'] = 'watch';
1673
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1674
			elseif( in_array(
1675
				$watch, array(
1676
					'watch', 'unwatch', 'preferences', 'nochange'
1677
				)
1678
			) ) {
1679
				$editarray['watchlist'] = $watch;
1680
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1681
		}
1682
        if( !is_null( $oldimage ) ) $editarray['oldimage'] = $oldimage;
1683
1684
		Hooks::runHook( 'StartDelete', array( &$editarray ) );
1685
1686
		try{
1687
			$this->preEditChecks( "Delete" );
1688
		} catch( EditError $e ){
1689
			pecho( "Error: $e\n\n", PECHO_FATAL );
1690
			return false;
1691
		}
1692
		pecho( "Deleting {$this->title}...\n\n", PECHO_NOTICE );
1693
1694
		$result = $this->wiki->apiQuery( $editarray, true );
1695
1696
		if( isset( $result['delete'] ) ) {
1697
			if( isset( $result['delete']['title'] ) ) {
1698
				$this->__construct( $this->wiki, $this->title );
1699
				return true;
1700
			} else {
1701
				pecho( "Delete error...\n\n" . print_r( $result['delete'], true ) . "\n\n", PECHO_FATAL );
1702
				return false;
1703
			}
1704
		} else {
1705
			pecho( "Delete error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1706
			return false;
1707
		}
1708
1709
	}
1710
1711
	/**
1712
	 * Undeletes the page
1713
	 *
1714
	 * @param string $reason Reason for undeletion
1715
	 * @param array $timestamps Array of timestamps to selectively restore
1716
	 * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default: go by user preference.
1717
	 * @return bool
1718
	 */
1719
	public function undelete( $reason = null, $timestamps = null, $watch = null ) {
1720
		global $pgNotag, $pgTag;
1721
		if( !in_array( 'undelete', $this->wiki->get_userrights() ) ) {
1722
			pecho( "User is not allowed to undelete pages", PECHO_FATAL );
1723
			return false;
1724
		}
1725
1726
		$tokens = $this->wiki->get_tokens();
1727
		if( !$pgNotag ) $reason .= $pgTag;
1728
		$undelArray = array(
1729
			'action' => 'undelete',
1730
			'title'  => $this->title,
1731
			'token'  => $tokens['delete'],
1732
			//Using the delete token, it's the exact same, and we don't have to do another API call
1733
			'reason' => $reason
1734
		);
1735
1736
		if( !is_null( $timestamps ) ) {
1737
			$undelArray['timestamps'] = $timestamps;
1738
			if( is_array( $timestamps ) ) {
1739
				$undelArray['timestamps'] = implode( '|', $timestamps );
1740
			}
1741
		}
1742
1743
		if( !is_null( $watch ) ) {
1744
			if( $watch ) {
1745
				$undelArray['watchlist'] = 'watch';
1746
			} elseif( !$watch ) $undelArray['watchlist'] = 'nochange';
1747
			elseif( in_array(
1748
				$watch, array(
1749
					'watch', 'unwatch', 'preferences', 'nochange'
1750
				)
1751
			) ) {
1752
				$undelArray['watchlist'] = $watch;
1753
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1754
		}
1755
1756
		try{
1757
			$this->preEditChecks( "Undelete" );
1758
		} catch( EditError $e ){
1759
			pecho( "Error: $e\n\n", PECHO_FATAL );
1760
			return false;
1761
		}
1762
		pecho( "Undeleting {$this->title}...\n\n", PECHO_NOTICE );
1763
1764
		Hooks::runHook( 'StartUndelete', array( &$undelArray ) );
1765
1766
		$result = $this->wiki->apiQuery( $undelArray, true );
1767
1768
		if( isset( $result['undelete'] ) ) {
1769
			if( isset( $result['undelete']['title'] ) ) {
1770
				$this->__construct( $this->wiki, $this->title );
1771
				return true;
1772
			} else {
1773
				pecho( "Undelete error...\n\n" . print_r( $result['undelete'], true ) . "\n\n", PECHO_FATAL );
1774
				return false;
1775
			}
1776
		} else {
1777
			pecho( "Undelete error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1778
			return false;
1779
		}
1780
	}
1781
1782
	/**
1783
	 * List deleted revisions of the page
1784
	 *
1785
	 * @param bool $content Whether or not to retrieve the content of each revision, Default false
1786
	 * @param string $user Only list revisions by this user. Default null.
1787
	 * @param string $excludeuser Don't list revisions by this user. Default null
1788
	 * @param string $start Timestamp to start at. Default null
1789
	 * @param string $end Timestamp to end at. Default null
1790
	 * @param string $dir Direction to enumerate. Default 'older'
1791
	 * @param array $prop Properties to retrieve. Default array( 'revid', 'user', 'parsedcomment', 'minor', 'len', 'content', 'token' )
1792
	 * @return array List of deleted revisions
1793
	 */
1794
	public function deletedrevs( $content = false, $user = null, $excludeuser = null, $start = null, $end = null, $dir = 'older', $prop = array(
1795
		'revid', 'user', 'parsedcomment', 'minor', 'len', 'content', 'token'
1796
	) ) {
1797
		if( !in_array( 'deletedhistory', $this->wiki->get_userrights() ) ) {
1798
			pecho( "User is not allowed to view deleted revisions", PECHO_FATAL );
1799
			return false;
1800
		}
1801
1802
		if( $content ) $prop[] = 'content';
1803
1804
		$drArray = array(
1805
			'_code'  => 'dr',
1806
			'list'   => 'deletedrevs',
1807
			'titles' => $this->title,
1808
			'drprop' => implode( '|', $prop ),
1809
			'drdir'  => $dir
1810
		);
1811
1812
		if( !is_null( $user ) ) $drArray['druser'] = $user;
1813
		if( !is_null( $excludeuser ) ) $drArray['drexcludeuser'] = $excludeuser;
1814
		if( !is_null( $start ) ) $drArray['drstart'] = $start;
1815
		if( !is_null( $end ) ) $drArray['drend'] = $end;
1816
1817
		pecho( "Getting deleted revisions of {$this->title}...\n\n", PECHO_NORMAL );
1818
1819
		return $this->wiki->listHandler( $drArray );
1820
	}
1821
1822
	/**
1823
	 * Alias of embeddedin
1824
	 *
1825
	 * @see Page::embeddedin()
1826
	 * @deprecated since 18 June 2013
1827
	 * @param null $namespace
1828
	 * @param null $limit
1829
	 * @return array
1830
	 */
1831
	public function get_transclusions( $namespace = null, $limit = null ) {
1832
		Peachy::deprecatedWarn( 'Page::get_transclusions()', 'Page::embeddedin()' );
1833
		return $this->embeddedin( $namespace, $limit );
1834
	}
1835
1836
	/**
1837
	 * Adds the page to the logged in user's watchlist
1838
	 *
1839
	 * @param string $lang The code for the language to show any error message in (default: user preference)
1840
	 * @return bool True on success
1841
	 */
1842
	public function watch( $lang = null ) {
1843
1844
		Hooks::runHook( 'StartWatch' );
1845
1846
		pecho( "Watching {$this->title}...\n\n", PECHO_NOTICE );
1847
1848
		$tokens = $this->wiki->get_tokens();
1849
1850
		if( $tokens['watch'] == '+\\' ) {
1851
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1852
			return false;
1853
		}
1854
1855
		$apiArray = array(
1856
			'action' => 'watch',
1857
			'token'  => $tokens['watch'],
1858
			'title'  => $this->title
1859
		);
1860
1861
		if( !is_null( $lang ) ) $apiArray['uselang'] = $lang;
1862
1863
		$result = $this->wiki->apiQuery( $apiArray, true );
1864
1865
		if( isset( $result['watch'] ) ) {
1866
			if( isset( $result['watch']['watched'] ) ) {
1867
				return true;
1868
			} else {
1869
				pecho( "Watch error...\n\n" . print_r( $result['watch'], true ) . "\n\n", PECHO_FATAL );
1870
				return false;
1871
			}
1872
		} else {
1873
			pecho( "Watch error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1874
			return false;
1875
		}
1876
1877
	}
1878
1879
	/**
1880
	 * Removes the page to the logged in user's watchlist
1881
	 *
1882
	 * @param string $lang The code for the language to show any error message in (default: user preference)
1883
	 * @return bool True on sucecess
1884
	 */
1885
	public function unwatch( $lang = null ) {
1886
		Hooks::runHook( 'StartUnwatch' );
1887
1888
		pecho( "Unwatching {$this->title}...\n\n", PECHO_NOTICE );
1889
1890
		$tokens = $this->wiki->get_tokens();
1891
1892
		if( $tokens['watch'] == '+\\' ) {
1893
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1894
			return false;
1895
		}
1896
1897
		$apiArray = array(
1898
			'action'  => 'watch',
1899
			'token'   => $tokens['watch'],
1900
			'title'   => $this->title,
1901
			'unwatch' => ''
1902
		);
1903
1904
		if( !is_null( $lang ) ) $apiArray['uselang'] = $lang;
1905
1906
		$result = $this->wiki->apiQuery( $apiArray, true );
1907
1908
		if( isset( $result['watch'] ) ) {
1909
			if( isset( $result['watch']['unwatched'] ) ) {
1910
				return true;
1911
			} else {
1912
				pecho( "Unwatch error...\n\n" . print_r( $result['watch'], true ) . "\n\n", PECHO_FATAL );
1913
				return false;
1914
			}
1915
		} else {
1916
			pecho( "Unwatch error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1917
			return false;
1918
		}
1919
1920
	}
1921
1922
	/**
1923
	 * Returns the page title
1924
	 *
1925
	 * @param bool $namespace Set to true to return the title with namespace, false to return it without the namespace. Default true.
1926
	 * @return string Page title
1927
	 */
1928
	public function get_title( $namespace = true ) {
1929
		if( !$namespace ) {
1930
			return $this->title_wo_namespace;
1931
		}
1932
		return $this->title;
1933
	}
1934
1935
	/**
1936
	 * Returns whether or not a redirect was followed to get to the real page title
1937
	 *
1938
	 * @return bool
1939
	 */
1940
	public function redirectFollowed() {
1941
		return $this->redirectFollowed;
1942
	}
1943
1944
	/**
1945
	 * Returns whether or not the page is a special page
1946
	 *
1947
	 * @return bool
1948
	 */
1949
	public function get_special() {
1950
		return $this->special;
1951
	}
1952
1953
	/**
1954
	 * Gets ID or name of the namespace
1955
	 *
1956
	 * @param bool $id Set to true to get namespace ID, set to false to get namespace name. Default true
1957
	 * @return int|string
1958
	 */
1959
	public function get_namespace( $id = true ) {
1960
		if( $id ) {
1961
			return $this->namespace_id;
1962
		} else {
1963
			$namespaces = $this->wiki->get_namespaces();
1964
1965
			return $namespaces[$this->namespace_id];
1966
		}
1967
	}
1968
1969
	/**
1970
	 * Returns the timestamp of the last edit
1971
     *
1972
     * @param   bool $force Regenerate the cached value (default: false)
1973
     * @return  string
1974
	 */
1975
	public function get_lastedit( $force = false ) {
1976
		if( $force ) $this->get_metadata();
1977
1978
        return $this->lastedit;
1979
	}
1980
1981
	/**
1982
	 * Returns length of the page
1983
	 *
1984
	 * @param bool $force Regenerate the cached value (default: false)
1985
	 * @return int
1986
	 */
1987
	public function get_length( $force = false ) {
1988
		if( $force ) $this->get_metadata();
1989
1990
		return $this->length;
1991
	}
1992
1993
	/**
1994
	 * Returns number of hits the page has received
1995
	 *
1996
	 * @param bool $force Regenerate the cached value (default: false)
1997
	 * @return int
1998
	 */
1999
	public function get_hits( $force = false ) {
2000
		if( $force ) $this->get_metadata();
2001
2002
		return $this->hits;
2003
	}
2004
2005
	/**
2006
	 * (Re)generates lastedit, length, and hits
2007
	 *
2008
	 * @param array $pageInfoArray2 Array of values to merge with defaults (default: null)
2009
	 * @throws BadTitle
2010
	 */
2011
	protected function get_metadata( $pageInfoArray2 = null ) {
2012
		$pageInfoArray = array(
2013
			'action' => 'query',
2014
			'prop'   => "info"
2015
		);
2016
		$pageInfoArray['inprop'] = 'protection|talkid|watched|watchers|notificationtimestamp|subjectid|url|readable|preload|displaytitle';
2017
2018
		if( $pageInfoArray2 != null ) {
2019
			$pageInfoArray = array_merge( $pageInfoArray, $pageInfoArray2 );
2020
		} else {
2021
			$pageInfoArray['titles'] = $this->title;
2022
		}
2023
2024
		$pageInfoRes = $this->wiki->apiQuery( $pageInfoArray );
2025
2026
		if( isset( $pageInfoRes['warnings']['query']['*'] ) && in_string( 'special pages', $pageInfoRes['warnings']['query']['*'] ) ) {
2027
			pecho( "Special pages are not currently supported by the API.\n\n", PECHO_ERROR );
2028
			$this->exists = false;
2029
			$this->special = true;
2030
		}
2031
2032
		if( isset( $pageInfoRes['query']['redirects'][0] ) ) {
2033
			$this->redirectFollowed = true;
2034
		}
2035
2036
		foreach( $pageInfoRes['query']['pages'] as $key => $info ){
2037
			$this->pageid = $key;
0 ignored issues
show
Documentation Bug introduced by
It seems like $key can also be of type string. However, the property $pageid is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2038
			if( $this->pageid > 0 ) {
2039
				$this->exists = true;
2040
				$this->lastedit = $info['touched'];
2041
				$this->hits = isset( $info['counter'] ) ? $info['counter'] : '';
2042
				$this->length = $info['length'];
2043
			} else {
2044
				$this->pageid = 0;
2045
				$this->lastedit = '';
0 ignored issues
show
Documentation Bug introduced by
The property $lastedit was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2046
				$this->hits = '';
0 ignored issues
show
Documentation Bug introduced by
The property $hits was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2047
				$this->length = '';
0 ignored issues
show
Documentation Bug introduced by
The property $length was declared of type integer, but '' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
2048
				$this->starttimestamp = '';
2049
			}
2050
2051
			if( isset( $info['missing'] ) ) $this->exists = false;
2052
2053
			if( isset( $info['invalid'] ) ) throw new BadTitle( $this->title );
2054
2055
			$this->title = $info['title'];
2056
			$this->namespace_id = $info['ns'];
2057
2058
			if( $this->namespace_id != 0) {
2059
                $title_wo_namespace = explode(':', $this->title, 2);
2060
                $this->title_wo_namespace = $title_wo_namespace[1];
2061
			} else {
2062
				$this->title_wo_namespace = $this->title;
2063
			}
2064
2065
			if( isset( $info['special'] ) ) $this->special = true;
2066
2067
			if( isset( $info['protection'] ) ) $this->protection = $info['protection'];
2068
			if( isset( $info['talkid'] ) ) $this->talkid = $info['talkid'];
2069
			if( isset( $info['watched'] ) ) $this->watched = true;
2070
			if( isset( $info['watchers'] ) ) $this->watchers = $info['watchers'];
2071
			if( isset( $info['notificationtimestamp'] ) ) $this->watchlisttimestamp = $info['notificationtimestamp'];
2072
			if( isset( $info['subjectid'] ) ) $this->subjectid = $info['subjectid'];
2073
			if( isset( $info['fullurl'] ) ) $this->urls['full'] = $info['fullurl'];
2074
			if( isset( $info['editurl'] ) ) $this->urls['edit'] = $info['editurl'];
2075
			if( isset( $info['readable'] ) ) $this->readable = true;
2076
			if( isset( $info['preload'] ) ) $this->preload = $info['preload'];
2077
			if( isset( $info['displaytitle'] ) ) $this->displaytitle = $info['displaytitle'];
2078
		}
2079
	}
2080
2081
	/**
2082
	 * Returns all links to the page
2083
	 *
2084
	 * @param array $namespaces Namespaces to get. Default array( 0 );
2085
	 * @param string $redirects How to handle redirects. 'all' = List all pages. 'redirects' = Only list redirects. 'nonredirects' = Don't list redirects. Default 'all'
2086
	 * @param bool $followredir List links through redirects to the page
2087
	 * @return array List of backlinks
2088
	 */
2089
	public function get_backlinks( $namespaces = array( 0 ), $redirects = 'all', $followredir = true ) {
2090
		$leArray = array(
2091
			'list'          => 'backlinks',
2092
			'_code'         => 'bl',
2093
			'blnamespace'   => $namespaces,
2094
			'blfilterredir' => $redirects,
2095
			'bltitle'       => $this->title
2096
		);
2097
2098
		if( $followredir ) $leArray['blredirect'] = '';
2099
2100
		Hooks::runHook( 'PreQueryBacklinks', array( &$leArray ) );
2101
2102
		pecho( "Getting all links to {$this->title}...\n\n", PECHO_NORMAL );
2103
2104
		return $this->wiki->listHandler( $leArray );
2105
    }
2106
2107
    /**
2108
	 * Rollbacks the latest edit(s) to a page.
2109
	 * 
2110
	 * @see http://www.mediawiki.org/wiki/API:Edit_-_Rollback
2111
	 * @param bool $force Whether to force an (attempt at an) edit, regardless of news messages, etc.
2112
	 * @param string $summary Override the default edit summary for this rollback. Default null.
2113
	 * @param bool $markbot If set, both the rollback and the revisions being rolled back will be marked as bot edits.
2114
     * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default preferences.
2115
	 * @return array Details of the rollback perform. ['revid']: The revision ID of the rollback. ['old_revid']: The revision ID of the first (most recent) revision that was rolled back. ['last_revid']: The revision ID of the last (oldest) revision that was rolled back.
2116
	 */
2117
	public function rollback( $force = false, $summary = null, $markbot = false, $watch = null ) {
2118
		global $pgNotag, $pgTag;
2119
		if( !in_array( 'rollback', $this->wiki->get_userrights() ) ) {
2120
			pecho( "User is not allowed to rollback edits", PECHO_FATAL );
2121
			return false;
2122
		}
2123
2124
		if( !$force ) {
2125
			try{
2126
				$this->preEditChecks();
2127
			} catch( EditError $e ){
2128
				pecho( "Error: $e\n\n", PECHO_FATAL );
2129
				return false;
2130
			}
2131
		}
2132
2133
		$history = $this->history( 1, 'older', false, null, true );
2134
2135
		$params = array(
2136
			'action' => 'rollback',
2137
			'title'  => $this->title,
2138
			'user'   => $history[0]['user'],
2139
			'token'  => $history[0]['rollbacktoken'],
2140
		);
2141
2142
		if( !is_null( $summary ) ) {
2143
			if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
The call to mb_strlen() has too many arguments starting with '8bit'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2144
				pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
2145
				return false;
2146
			}
2147
			if( !$pgNotag ) $summary .= $pgTag;
2148
2149
			$params['summary'] = $summary;
2150
		}
2151
		if( $markbot ) $params['markbot'] = '';
2152
2153
		if( !is_null( $watch ) ) {
2154
			if( $watch ) {
2155
				$params['watchlist'] = 'watch';
2156
			} elseif( !$watch ) $params['watchlist'] = 'nochange';
2157
			elseif( in_array(
2158
				$watch, array(
2159
					'watch', 'unwatch', 'preferences', 'nochange'
2160
				)
2161
			) ) {
2162
				$params['watchlist'] = $watch;
2163
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
2164
		}
2165
2166
		try{
2167
			$this->preEditChecks( "Rollback" );
2168
		} catch( EditError $e ){
2169
			pecho( "Error: $e\n\n", PECHO_FATAL );
2170
			return false;
2171
		}
2172
		Hooks::runHook( 'PreRollback', array( &$params ) );
2173
2174
		pecho( "Rolling back {$this->title}...\n\n", PECHO_NOTICE );
2175
2176
		$result = $this->wiki->apiQuery( $params, true );
2177
2178
		if( isset( $result['rollback'] ) ) {
2179
			if( isset( $result['rollback']['title'] ) ) {
2180
				$this->__construct( $this->wiki, $this->title );
2181
				return true;
2182
			} else {
2183
				pecho( "Rollback error...\n\n" . print_r( $result['rollback'], true ) . "\n\n", PECHO_FATAL );
2184
				return false;
2185
			}
2186
		} else {
2187
			pecho( "Rollback error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2188
			return false;
2189
		}
2190
    }
2191
2192
    /**
2193
     * Performs nobots checking, new message checking, etc
2194
     *
2195
     * @param   string $action
2196
     * @throws  EditError
2197
     */
2198
	protected function preEditChecks( $action = "Edit" ) {
2199
	    $this->wiki->preEditChecks( $action, $this->title, $this->pageid );
2200
	}
2201
2202
	/**
2203
	 * Returns array of pages that embed (transclude) the page given.
2204
     *
2205
     * @param   array $namespace Which namespaces to search (default: null).
2206
     * @param   int $limit How many results to retrieve (default: null i.e. all).
2207
     * @return  array               A list of pages the title is transcluded in.
2208
	 */
2209
	public function embeddedin( $namespace = null, $limit = null ) {
2210
		$eiArray = array(
2211
			'list'     => 'embeddedin',
2212
			'_code'    => 'ei',
2213
			'eititle'  => $this->title,
2214
			'_lhtitle' => 'title',
2215
			'_limit'   => $limit
2216
		);
2217
2218
		if( !is_null( $namespace ) ) {
2219
			$eiArray['einamespace'] = $namespace;
2220
		}
2221
2222
		Hooks::runHook( 'PreQueryEmbeddedin', array( &$eiArray ) );
2223
2224
		pecho( "Getting list of pages that include {$this->title}...\n\n", PECHO_NORMAL );
2225
2226
		return $this->wiki->listHandler( $eiArray );
2227
	}
2228
2229
	/**
2230
	 * Purges a list of pages. Shortcut for {@link Wiki::purge()}
2231
	 *
2232
	 * @see Wiki::purge()
2233
	 * @return boolean
2234
	 */
2235
	public function purge() {
2236
		return $this->wiki->purge( $this->title );
2237
	}
2238
2239
	/**
2240
	 * Parses wikitext and returns parser output. Shortcut for Wiki::parse
2241
	 *
2242
	 * @param string $summary Summary to parse. Default null.
2243
	 * @param string $id Parse the content of this revision
2244
	 * @param array $prop Properties to retrieve. array( 'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext', 'properties' )
2245
	 * @param string $uselang Code of the language to use in interface messages, etc. (where applicable). Default 'en'.
2246
	 * @param string $section Only retrieve the content of this section number.  Default null.
2247
	 * @return array
2248
	 */
2249
	public function parse( $summary = null, $id = null, $prop = null, $uselang = 'en', $section = null ) {
2250
		if( $prop === null ){
2251
			$prop = array(
2252
				'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid',
2253
				'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext', 'properties'
2254
			);
2255
		}
2256
		return $this->wiki->parse(
2257
			null, null, $summary, false, false, $prop, $uselang, $this->title, $id, null, false, $section
2258
		);
2259
	}
2260
2261
}
2262