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

Page::get_title()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
ccs 0
cts 5
cp 0
rs 8.8571
cc 5
eloc 10
nc 5
nop 2
crap 30
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
	 * When retriving the page information, it was a redirect followed, hence the new Title
67
	 * @var string
68
	 */
69
	protected $redirectsTitle;
70
71
	/**
72
	 * When retriving the page information, the right title is normalized
73
	 * @var string
74
	 */
75
	protected $normalizedTitle;
76
77
	/**
78
	 * The page title without the namespace bit
79
	 * @var string
80
	 */
81
	protected $title_wo_namespace;
82
83
	/**
84
	 * The ID of the namespace
85
	 * @var int
86
	 */
87
	protected $namespace_id;
88
89
	/**
90
	 * Page text
91
	 * @var string
92
	 */
93
	protected $content;
94
95
	/**
96
	 * Templates used in the page
97
	 * (default value: null)
98
	 * @var array
99
	 */
100
	protected $templates;
101
102
	/**
103
	 * Protection information for the page
104
	 * (default value: null)
105
	 * @var array
106
	 */
107
	protected $protection;
108
109
	/**
110
	 * Categories that the page is in
111
	 * (default value: null)
112
	 * @var array
113
	 */
114
	protected $categories;
115
116
	/**
117
	 * Images used in the page
118
	 * (default value: null)
119
	 * @var array
120
	 */
121
	protected $images;
122
123
	/**
124
	 * Internal links in the page
125
	 * (default value: null)
126
	 * @var array
127
	 */
128
	protected $links;
129
130
	/**
131
	 * Timestamp of the last edit
132
        * @var int
133
	 */
134
	protected $lastedit;
135
136
	/**
137
	 * Timestamp of the last page edit, permission changes, creation or deletion of linked pages, and alteration of contained templates.
138
        * @var int
139
	 */
140
	protected $touch;
141
142
	/**
143
	 * Length of the page in bytes
144
	 * @var int
145
	 */
146
	protected $length;
147
148
	/**
149
	 * Amount of hits (views) the page has
150
	 * @var int
151
	 */
152
	protected $hits;
153
154
	/**
155
	 * Language links on the page
156
	 * (default value: null)
157
	 * @var array
158
	 */
159
	protected $langlinks;
160
161
	/**
162
	 * External links on the page
163
	 * (default value: null)
164
	 * @var array
165
	 */
166
	protected $extlinks;
167
168
	/**
169
	 * Interwiki links on the page
170
	 * (default value: null)
171
	 * @var array
172
	 */
173
	protected $iwlinks;
174
175
	/**
176
	 * Time of script start.  Must be set manually.
177
	 * (default null)
178
	 * @var mixed
179
	 */
180
	protected $starttimestamp;
181
182
	/**
183
	 * Page ID of the talk page.
184
	 * (default null)
185
	 * @var int
186
	 */
187
	protected $talkid;
188
189
	/**
190
	 * Whether the page is watched by the user
191
	 * (default null)
192
	 * @var bool
193
	 */
194
	protected $watched;
195
196
	/**
197
	 * Number of watchers
198
	 * (default null)
199
	 * @var int
200
	 */
201
	protected $watchers;
202
203
	/**
204
	 * Watchlist notification timestamp
205
	 * (default null)
206
	 * @var string
207
	 */
208
	protected $watchlisttimestamp;
209
210
	/**
211
	 * Page ID of parent page
212
	 * (default null)
213
	 * @var int
214
	 */
215
	protected $subjectid;
216
217
	/**
218
	 * Full urls of the page
219
	 * (default null)
220
	 * @var array
221
	 */
222
	protected $urls;
223
224
	/**
225
	 * Whether the page can be read by a user
226
	 * (default null)
227
	 * @var bool
228
	 */
229
	protected $readable;
230
231
	/**
232
	 * EditFormPreloadText
233
	 * (default null)
234
	 * @var string
235
	 */
236
	protected $preload;
237
238
	/**
239
	 * Page's title formatting
240
	 * (default null)
241
	 * @var string
242
	 */
243
	protected $displaytitle;
244
245
	/**
246
	 * Page properties
247
	 * (default null)
248 5
	 * @var array
249 5
	 */
250 1
	protected $properties;
251
252 4
	/**
253 1
	 * Construction method for the Page class
254
	 *
255 3
     * @param Wiki $wikiClass The Wiki class object
256 1
     * @param mixed $title Title of the page (default: null)
257
     * @param mixed $pageid ID of the page (default: null)
258 2
     * @param bool $followRedir Should it follow a redirect when retrieving the page (default: true)
259 1
     * @param bool $normalize Should the class automatically normalize the title (default: true)
260
     * @param string|double|int $timestamp Set the start of a program or start reference to avoid edit conflicts.
261 1
	 *
262 1
	 * @throws InvalidArgumentException
263
	 * @throws NoTitle
264
	 * @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...
265
	 */
266
	public function __construct( Wiki $wikiClass, $title = null, $pageid = null, $followRedir = true, $normalize = true, $timestamp = null ) {
267
		if( !is_string( $title ) && !is_null( $title ) ) {
268
			throw new InvalidArgumentException( '$title must be a string or null' );
269
		}
270
		if( !is_int( $pageid ) && !is_null( $pageid ) && !is_string( $pageid ) ) {
271
			throw new InvalidArgumentException( '$pageid must be a int, string, or null' );
272
		}
273
		if( !is_bool( $followRedir ) ) {
274
			throw new InvalidArgumentException( '$followRedir must be a bool' );
275
		}
276
		if( !is_bool( $normalize ) ) {
277
			throw new InvalidArgumentException( '$normalize must be a bool' );
278
		}
279
		if( !is_string( $timestamp ) && !is_double( $timestamp ) && !is_int( $timestamp ) && !is_null( $timestamp ) ) {
280
			throw new InvalidArgumentException( '$timestamp must be a string, int, double or null' );
281
		}
282
283
		$this->wiki = $wikiClass;
284
285
		if( is_null( $title ) && is_null( $pageid ) ) {
286
			throw new NoTitle();
287
		}
288
289
		if( !is_null( $title ) && $normalize ) {
290
			$title = str_replace( '_', ' ', $title );
291
			$title = str_replace( '%20', ' ', $title );
292
			if( $title[0] == ":" ) {
293
				$title = substr( $title, 1 );
294
			}
295
			$chunks = explode( ':', $title, 2 );
296
			if( count( $chunks ) != 1 ) {
297
				$namespace = strtolower( trim( $chunks[0] ) );
298
				$namespaces = $this->wiki->get_namespaces();
299
				if( $namespace == $namespaces[-2] || $namespace == "media" ) {
300
					// Media or local variant, translate to File:
301
					$title = $namespaces[6] . ":" . $chunks[1];
302
				}
303
			}
304
		}
305
306
		$this->title = $title;
307
308
		$pageInfoArray = array();
309
310
		if( !is_null( $pageid ) ) {
311
			$pageInfoArray['pageids'] = $pageid;
312
			$peachout = "page ID $pageid";
313
		} else {
314
			$pageInfoArray['titles'] = $title;
315
			$peachout = $title;
316
		}
317
318
		if( $followRedir ) $pageInfoArray['redirects'] = '';
319
320
		pecho( "Getting page info for {$peachout}..\n\n", PECHO_NORMAL );
321
322
		$this->get_metadata( $pageInfoArray );
323
		if( !is_null( $timestamp ) ) $this->starttimestamp = $timestamp;
324
	}
325
326
	/**
327
	 * Returns page history. Can be specified to return content as well
328
	 *
329
	 * @param int $count Revisions to return (default: 1)
330
	 * @param string $dir Direction to return revisions (default: "older")
331
	 * @param bool $content Should content of that revision be returned as well (default: false)
332
	 * @param int $revid Revision ID to start from (default: null)
333
	 * @param bool $rollback_token Should a rollback token be returned (default: false)
334
	 * @param bool $recurse Used internally to provide more results than can be returned with a single API query
335
	 * @param int $rvstart Timestamp to start from (default: null)
336
	 * @return array Revision data
337
	 */
338
	public function history( $count = 1, $dir = "older", $content = false, $revid = null, $rollback_token = false, $recurse = false, $rvstart = null ) {
339
		if( !$this->exists ) return array();
340
341
		$historyArray = array(
342
			'action' => 'query',
343
			'prop'   => 'revisions',
344
			'titles' => $this->title,
345
			'rvprop' => 'timestamp|ids|user|comment',
346
			'rawcontinue' => 1,
347
			'rvdir'  => $dir,
348
349
		);
350
351
		if( $content ) $historyArray['rvprop'] .= "|content";
352
353
		if( !is_null( $revid ) ) $historyArray['rvstartid'] = $revid;
354
		if( !is_null( $rvstart ) ) $historyArray['rvstart'] = $rvstart;
355
		if( !is_null( $count ) ) $historyArray['rvlimit'] = $count;
356
357
		if( $rollback_token ) $historyArray['rvtoken'] = 'rollback';
358
359
		if( !$recurse ) pecho( "Getting page history for {$this->title}..\n\n", PECHO_NORMAL );
360
361
		if( is_null( $count ) ) {
362
			$history = $ei = $this->history( $this->wiki->get_api_limit() + 1, $dir, $content, $revid, $rollback_token, true );
363
			while( !is_null( $ei[1] ) ){
364
				$ei = $this->history( $this->wiki->get_api_limit() + 1, $dir, $content, $ei[1], $rollback_token, true );
365
				foreach( $ei[0] as $eg ){
366
					$history[0][] = $eg;
367
				}
368
			}
369
370
			return $history[0];
371
		}
372
373
		$historyResult = $this->wiki->apiQuery( $historyArray );
374
375
		if( $recurse ) {
376
			if( isset( $historyResult['query-continue'] ) ) {
377
				return array(
378
					$historyResult['query']['pages'][$this->pageid]['revisions'],
379
					$historyResult['query-continue']['revisions']['rvcontinue']
380
				);
381
			}
382
			return array( $historyResult['query']['pages'][$this->pageid]['revisions'], null );
383
		}
384
385
		return $historyResult['query']['pages'][$this->pageid]['revisions'];
386
	}
387
388
	/**
389
	 * Retrieves text from a page, or a cached copy unless $force is true
390
	 *
391
	 * @param bool $force Grab text from the API, don't use the cached copy (default: false)
392
	 * @param string|int $section Section title or ID to retrieve
393
	 * @return string|bool Page content
394
	 */
395
	public function get_text( $force = false, $section = null ) {
396
		pecho( "Getting page content for {$this->title}..\n\n", PECHO_NOTICE );
397
398
		if( !$this->exists ) return null;
399
400
		if( !is_null( $section ) ) {
401
			if( empty( $this->content ) ) {
402
				$this->content = $this->history( 1, "older", true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->history(1, 'older', true) of type array is incompatible with the declared type string of property $content.

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...
403
				$this->content = $this->content[0]['*'];
404
			}
405
406
			$sections = $this->wiki->apiQuery(
407
				array(
408
					'action' => 'parse',
409
					'page'   => $this->title,
410
					'prop'   => 'sections'
411
				)
412
			);
413
414
			if( !is_numeric( $section ) ) {
415
				foreach( $sections['parse']['sections'] as $section3 ){
416
					if( $section3['line'] == $section ) {
417
						$section = $section3['number'];
418
					}
419
				}
420
			}
421
422
			if( !is_numeric( $section ) ) {
423
				pecho( "Warning: Section not found.\n\n", PECHO_WARN );
424
				return false;
425
			}
426
427
			$offsets = array( '0' => '0' );
428
429
			if( $this->wiki->get_mw_version() < '1.16' ) {
430
431
				//FIXME: Implement proper notice suppression
432
433
				$ids = array();
434
435
				foreach( $sections['parse']['sections'] as $section3 ){
436
					$ids[$section3['line']] = $section3['number'];
437
				}
438
439
				$regex = '/^(=+)\s*(.*?)\s*(\1)\s*/m';
440
441
				preg_match_all( $regex, $this->content, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
442
443
				foreach( $m as $id => $match ){
0 ignored issues
show
Bug introduced by
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...
444
					$offsets[$id + 1] = $match[0][1];
445
				}
446
447
			} else {
448
				foreach( $sections['parse']['sections'] as $section2 ){
449
					$offsets[$section2['number']] = $section2['byteoffset'];
450
				}
451
			}
452
453
			if( intval( $section ) != count( $offsets ) - 1 ) {
454
				$length = $offsets[$section + 1] - $offsets[$section];
455
			}
456
457
			if( isset( $length ) ) {
458
				$substr = mb_substr( $this->content, $offsets[$section], $length );
459
			} else {
460
				$substr = mb_substr( $this->content, $offsets[$section] );
461
			}
462
463
			return $substr;
464
		} else {
465
			if( !$force && $this->content !== null ) {
466
				return $this->content;
467
			}
468
469
			$this->content = $this->history( 1, "older", true );
470
			$this->content = $this->content[0]['*'];
471
472
			return $this->content;
473
		}
474
	}
475
476
	/**
477
	 * Returns the pageid of the page.
478
	 *
479
	 * @return int Pageid
480
	 */
481
	public function get_id() {
482
		return $this->pageid;
483
	}
484
485
	/**
486
	 * Returns if the page exists
487
	 *
488
	 * @return bool Exists
489
	 * @deprecated since 18 June 2013
490
	 */
491
	public function exists() {
492
		Peachy::deprecatedWarn( 'Page::exists()', 'Page::get_exists()' );
493
		return $this->exists;
494
	}
495
496
	/**
497
	 * Returns if the page exists
498
	 *
499
	 * @return bool Exists
500
	 */
501
	public function get_exists() {
502
		return $this->exists;
503
	}
504
505
	/**
506
	 * Returns links on the page.
507
	 *
508
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#links_.2F_pl
509
	 * @param bool $force Force use of API, won't use cached copy (default: false)
510
	 * @param array $namespace Show links in this namespace(s) only.  Default array()
511
	 * @param array $titles Only list links to these titles.  Default array()
512
	 * @return bool|array False on error, array of link titles
513
	 */
514
	public function get_links( $force = false, $namespace = array(), $titles = array() ) {
515
516
		if( !$force && $this->links !== null ) {
517
			return $this->links;
518
		}
519
520
		$this->links = array();
521
		if( !$this->exists ) return array();
522
523
		$tArray = array(
524
			'prop'     => 'links',
525
			'titles'   => $this->title,
526
			'_code'    => 'pl',
527
			'_lhtitle' => 'links'
528
		);
529
530
		if( !empty( $namespace ) ) $tArray['plnamespace'] = implode( '|', $namespace );
531
		if( !empty( $titles ) ) $tArray['pltitles'] = implode( '|', $titles );
532
533
		pecho( "Getting internal links on {$this->title}..\n\n", PECHO_NORMAL );
534
535
		$result = $this->wiki->listHandler( $tArray );
536
537
		if( count( $result ) > 0 ) {
538
			foreach( $result[0] as $link ){
539
				$this->links[] = $link['title'];
540
			}
541
		}
542
543
		return $this->links;
544
	}
545
546
	/**
547
	 * Returns templates on the page
548
	 * 
549
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#templates_.2F_tl
550
	 * @param bool $force Force use of API, won't use cached copy (default: false)
551
	 * @param array $namespace Show templates in this namespace(s) only. Default array().
552
	 * @param array $template Only list these templates. Default array()
553
	 * @return bool|array False on error, array of template titles
554
	 */
555
	public function get_templates( $force = false, $namespace = array(), $template = array() ) {
556
557
		if( !$force && $this->templates !== null && empty( $namespace ) && empty( $template ) ) {
558
			return $this->templates;
559
		}
560
561
		$this->templates = array();
562
		if( !$this->exists ) return array();
563
564
		$tArray = array(
565
			'prop'     => 'templates',
566
			'titles'   => $this->title,
567
			'_code'    => 'tl',
568
			'_lhtitle' => 'templates'
569
		);
570
		if( !empty( $namespace ) ) $tArray['tlnamespace'] = implode( '|', $namespace );
571
		if( !empty( $template ) ) $tArray['tltemplates'] = implode( '|', $template );
572
573
		pecho( "Getting templates transcluded on {$this->title}..\n\n", PECHO_NORMAL );
574
575
		$result = $this->wiki->listHandler( $tArray );
576
577
		if( count( $result ) > 0 ) {
578
			foreach( $result as $template ){
579
				$this->templates[] = $template['title'];
580
			}
581
		}
582
583
		return $this->templates;
584
	}
585
586
	/**
587
	 * Get various properties defined in the page content
588
	 * 
589
	 * @link https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp
590
	 * @param bool $force Force use of API, won't use cached copy (default: false)
591
	 * @return bool|array False on error, array of template titles
592
	 */
593
	public function get_properties( $force = false ) {
594
595
		if( !$force && $this->properties !== null ) {
596
			return $this->properties;
597
		}
598
599
		$this->properties = array();
600
		if( !$this->exists ) return array();
601
602
		$tArray = array(
603
			'prop'   => 'pageprops',
604
			'titles' => $this->title,
605
			'_code'  => 'pp'
606
		);
607
608
		pecho( "Getting page properties on {$this->title}..\n\n", PECHO_NORMAL );
609
610
		$this->properties = $this->wiki->listHandler( $tArray );
611
612
		return $this->properties;
613
	}
614
615
	/**
616
	 * Returns categories of page
617
	 * 
618
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#categories_.2F_cl
619
	 * @param bool $force Force use of API, won't use cached copy (default: false)
620
	 * @param array|string $prop Which additional properties to get for each category. Default all
621
	 * @param bool $hidden Show hidden categories. Default false
622
	 * @return bool|array False on error, returns array of categories
623
	 */
624
	public function get_categories( $force = false, $prop = array(
625
		'sortkey', 'timestamp', 'hidden'
626
	), $hidden = false ) {
627
628
		if( !$force && $this->categories !== null ) {
629
			return $this->categories;
630
		}
631
632
		$this->categories = array();
633
		if( !$this->exists ) return array();
634
635
		$tArray = array(
636
			'prop'     => 'categories',
637
			'titles'   => $this->title,
638
			'_code'    => 'cl',
639
			'_lhtitle' => 'categories',
640
			'clprop'   => implode( '|', $prop )
641
		);
642
643
		if( $hidden ) $tArray['clshow'] = '';
644
645
		pecho( "Getting categories {$this->title} is part of..\n\n", PECHO_NORMAL );
646
647
		$result = $this->wiki->listHandler( $tArray );
648
649
		if( count( $result ) > 0 ) {
650
			foreach( $result as $category ){
651
				$this->categories[] = $category['title'];
652
			}
653
		}
654
655
		return $this->categories;
656
657
	}
658
659
	/**
660
	 * Returns images used in the page
661
	 * 
662
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#images_.2F_im
663
	 * @param bool $force Force use of API, won't use cached copy (default: false)
664
	 * @param string|array $images Only list these images. Default null.
665
	 * @return bool|array False on error, returns array of image titles
666
	 */
667
	public function get_images( $force = false, $images = null ) {
668
669
		if( !$force && $this->images !== null ) {
670
			return $this->images;
671
		}
672
673
		$this->images = array();
674
		if( !$this->exists ) return array();
675
676
		$tArray = array(
677
			'prop'     => 'images',
678
			'titles'   => $this->title,
679
			'_code'    => 'im',
680
			'_lhtitle' => 'images'
681
		);
682
683
		if( !is_null( $images ) ) {
684
			if( is_array( $images ) ) {
685
				$tArray['imimages'] = implode( '|', $images );
686
			} else $tArray['imimage'] = $images;
687
		}
688
689
		pecho( "Getting images used on {$this->title}..\n\n", PECHO_NORMAL );
690
691
		$result = $this->wiki->listHandler( $tArray );
692
693
		if( count( $result ) > 0 ) {
694
			foreach( $result[0] as $image ){
695
				$this->images[] = $image['title'];
696
			}
697
		}
698
699
		return $this->images;
700
701
702
	}
703
704
	/**
705
	 * Returns external links used in the page
706
	 * 
707
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#extlinks_.2F_el
708
	 * @param bool $force Force use of API, won't use cached copy (default: false)
709
	 * @return bool|array False on error, returns array of URLs
710
	 */
711
	public function get_extlinks( $force = false ) {
712
713
		if( !$force && $this->extlinks !== null ) {
714
			return $this->extlinks;
715
		}
716
717
		$this->extlinks = array();
718
		if( !$this->exists ) return array();
719
720
		$tArray = array(
721
			'prop'     => 'extlinks',
722
			'titles'   => $this->title,
723
			'_code'    => 'el',
724
			'_lhtitle' => 'extlinks'
725
		);
726
727
		pecho( "Getting external links used on {$this->title}..\n\n", PECHO_NORMAL );
728
729
		$result = $this->wiki->listHandler( $tArray );
730
731
		if( count( $result ) > 0 ) {
732
			foreach( $result as $extlink ){
733
				$this->extlinks[] = $extlink['*'];
734
			}
735
		}
736
737
		return $this->extlinks;
738
	}
739
740
	/**
741
	 * Returns interlanguage links on the page
742
	 *
743
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#langlinks_.2F_ll
744
	 * @param bool $force Force use of API, won't use cached copy (default: false)
745
	 * @param bool $fullurl Include a list of full of URLs.  Output formatting changes.  Requires force parameter to be true to return a different result.
746
	 * @param string $title Link to search for. Must be used with $lang.  Default null
747
	 * @param string $lang Language code.  Default null
748
	 * @return bool|array False on error, returns array of links in the form of lang:title
749
	 */
750
	public function get_langlinks( $force = false, $fullurl = false, $title = null, $lang = null ) {
751
		if( !$force && $this->langlinks !== null ) {
752
			return $this->langlinks;
753
		}
754
755
		$this->langlinks = array();
756
		if( !$this->exists ) return array();
757
758
		$tArray = array(
759
			'prop'     => 'langlinks',
760
			'titles'   => $this->title,
761
			'_code'    => 'll',
762
			'_lhtitle' => 'langlinks'
763
		);
764
765
		if( !is_null( $lang ) ) $tArray['lllang'] = $lang;
766
		if( !is_null( $title ) ) $tArray['lltitle'] = $title;
767
		if( $fullurl ) $tArray['llurl'] = '';
768
769
		pecho( "Getting all interlanguage links for {$this->title}..\n\n", PECHO_NORMAL );
770
771
		$result = $this->wiki->listHandler( $tArray );
772
773
		if( count( $result ) > 0 ) {
774
			foreach( $result[0] as $langlink ){
775
				if( $fullurl ) {
776
					$this->langlinks[] = array(
777
						'link' => $langlink['lang'] . ":" . $langlink['*'], 'url' => $langlink['url']
778
					);
779
				} else $this->langlinks[] = $langlink['lang'] . ":" . $langlink['*'];
780
			}
781
		}
782
783
		return $this->langlinks;
784
	}
785
786
	/**
787
	 * Returns interwiki links on the page
788
	 *
789
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#langlinks_.2F_ll
790
	 * @param bool $force Force use of API, won't use cached copy (default: false)
791
	 * @param bool $fullurl Include a list of full of URLs.  Output formatting changes.  Requires force parameter to be true to return a different result.
792
	 * @param string $title Interwiki link to search for. Must be used with $prefix.  Default null
793
	 * @param string $prefix Prefix for the interwiki.  Default null
794
	 * @return bool|array False on error, returns array of links in the form of lang:title
795
	 */
796
	public function get_interwikilinks( $force = false, $fullurl = false, $title = null, $prefix = null ) {
797
		if( !$force && $this->iwlinks !== null ) {
798
			return $this->iwlinks;
799
		}
800
801
		$this->iwlinks = array();
802
		if( !$this->exists ) return array();
803
804
		$tArray = array(
805
			'prop'     => 'iwlinks',
806
			'titles'   => $this->title,
807
			'_code'    => 'iw',
808
			'_lhtitle' => 'iwlinks'
809
		);
810
811
		if( !is_null( $prefix ) ) $tArray['iwprefix'] = $prefix;
812
		if( !is_null( $title ) ) $tArray['iwtitle'] = $title;
813
		if( $fullurl ) $tArray['iwurl'] = '';
814
815
816
		pecho( "Getting all interwiki links for {$this->title}..\n\n", PECHO_NORMAL );
817
818
		$result = $this->wiki->listHandler( $tArray );
819
820
		if( count( $result ) > 0 ) {
821
			foreach( $result[0] as $iwlinks ){
822
				if( $fullurl ) {
823
					$this->iwlinks[] = array(
824
						'link' => $iwlinks['prefix'] . ":" . $iwlinks['*'], 'url' => $iwlinks['url']
825
					);
826
				} else $this->iwlinks[] = $iwlinks['prefix'] . ":" . $iwlinks['*'];
827
			}
828
		}
829
830
		return $this->iwlinks;
831
	}
832
833
	/**
834
	 * Returns the protection level of the page
835
	 *
836
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
837
	 * @param bool $force Force use of API, won't use cached copy (default: false)
838
	 * @return bool|array False on error, returns array with protection levels
839
	 */
840
	public function get_protection( $force = false ) {
841
842
		if( !$force && $this->protection !== null ) {
843
			return $this->protection;
844
		}
845
846
		$this->protection = array();
847
848
		$tArray = array(
849
			'action' => 'query',
850
			'prop'   => 'info',
851
			'inprop' => 'protection',
852
			'titles' => $this->title,
853
		);
854
855
		pecho( "Getting protection levels for {$this->title}..\n\n", PECHO_NORMAL );
856
857
		$tRes = $this->wiki->apiQuery( $tArray );
858
859
		$this->protection = $tRes['query']['pages'][$this->pageid]['protection'];
860
861
		return $this->protection;
862
863
	}
864
865
	/**
866
	 * Returns the page ID of the talk page for each non-talk page
867
	 *
868
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
869
	 * @param bool $force Force use of API, won't use cached copy (default: false)
870
	 * @return int Null or empty if no id exists.
871
	 */
872
	public function get_talkID( $force = false ) {
873
874
		if( !$force ) {
875
			return $this->talkid;
876
		}
877
878
		$tArray = array(
879
			'action' => 'query',
880
			'prop'   => 'info',
881
			'inprop' => 'talkid',
882
			'titles' => $this->title,
883
		);
884
885
		pecho( "Getting talk page ID for {$this->title}..\n\n", PECHO_NORMAL );
886
887
		$tRes = $this->wiki->apiQuery( $tArray );
888
889
		if( isset( $tRes['query']['pages'][$this->pageid]['talkid'] ) ) {
890
			$this->talkid = $tRes['query']['pages'][$this->pageid]['talkid'];
891
		} else $this->talkid = null;
892
893
		return $this->talkid;
894
	}
895
896
	/**
897
	 * Returns the watch status of the page
898
	 *
899
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
900
	 * @param bool $force Force use of API, won't use cached copy (default: false)
901
	 * @return bool
902
	 */
903
	public function is_watched( $force = false ) {
904
905
		if( !$force && $this->watched !== null ) {
906
			return $this->watched;
907
		}
908
909
		$tArray = array(
910
			'action' => 'query',
911
			'prop'   => 'info',
912
			'inprop' => 'watched',
913
			'titles' => $this->title,
914
		);
915
916
		pecho( "Getting watch status for {$this->title}..\n\n", PECHO_NORMAL );
917
918
		$tRes = $this->wiki->apiQuery( $tArray );
919
920
		if( isset( $tRes['query']['pages'][$this->pageid]['watched'] ) ) {
921
			$this->watched = true;
922
		} else $this->watched = false;
923
924
		return $this->watched;
925
	}
926
927
	/**
928
	 * Returns the count for the number of watchers of a page.
929
	 *
930
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
931
	 * @param bool $force Force use of API, won't use cached copy (default: false)
932
	 * @return int
933
	 */
934
	public function get_watchcount( $force = false ) {
935
936
		if( !$force && $this->watchers !== null ) {
937
			return $this->watchers;
938
		}
939
940
		$tArray = array(
941
			'action' => 'query',
942
			'prop'   => 'info',
943
			'inprop' => 'watchers',
944
			'titles' => $this->title,
945
		);
946
947
		pecho( "Getting watch count for {$this->title}..\n\n", PECHO_NORMAL );
948
949
		$tRes = $this->wiki->apiQuery( $tArray );
950
951
		if( isset( $tRes['query']['pages'][$this->pageid]['watchers'] ) ) {
952
			$this->watchers = $tRes['query']['pages'][$this->pageid]['watchers'];
953
		} else $this->watchers = 0;
954
955
		return $this->watchers;
956
	}
957
958
	/**
959
	 * Returns the watchlist notification timestamp of each page.
960
	 *
961
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
962
	 * @param bool $force Force use of API, won't use cached copy (default: false)
963
	 * @return string
964
	 */
965
	public function get_notificationtimestamp( $force = false ) {
966
967
		if( !$force ) {
968
			return $this->watchlisttimestamp;
969
		}
970
971
		$tArray = array(
972
			'action' => 'query',
973
			'prop'   => 'info',
974
			'inprop' => 'notificationtimestamp',
975
			'titles' => $this->title,
976
		);
977
978
		pecho( "Getting the notification timestamp for {$this->title}..\n\n", PECHO_NORMAL );
979
980
		$tRes = $this->wiki->apiQuery( $tArray );
981
982
		if( isset( $tRes['query']['pages'][$this->pageid]['notificationtimestamp'] ) ) {
983
            $this->watchlisttimestamp = (int)$tRes['query']['pages'][$this->pageid]['notificationtimestamp'];
0 ignored issues
show
Documentation Bug introduced by
The property $watchlisttimestamp was declared of type string, but (int) $tRes['query']['pa...notificationtimestamp'] 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...
984
		} 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...
985
986
		return $this->watchlisttimestamp;
987
	}
988
989
	/**
990
	 * Returns the page ID of the parent page for each talk page.
991
	 *
992
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
993
	 * @param bool $force Force use of API, won't use cached copy (default: false)
994
	 * @return int Null if it doesn't exist.
995
	 */
996
	public function get_subjectid( $force = false ) {
997
998
		if( !$force ) {
999
			return $this->subjectid;
1000
		}
1001
1002
		$tArray = array(
1003
			'action' => 'query',
1004
			'prop'   => 'info',
1005
			'inprop' => 'subjectid',
1006
			'titles' => $this->title,
1007
		);
1008
1009
		pecho( "Getting the parent page ID for {$this->title}..\n\n", PECHO_NORMAL );
1010
1011
		$tRes = $this->wiki->apiQuery( $tArray );
1012
1013
		if( isset( $tRes['query']['pages'][$this->pageid]['subjectid'] ) ) {
1014
			$this->subjectid = $tRes['query']['pages'][$this->pageid]['subjectid'];
1015
		} else $this->subjectid = null;
1016
1017
		return $this->subjectid;
1018
	}
1019
1020
	/**
1021
	 * Gives a full URL to the page, and also an edit URL.
1022
	 *
1023
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1024
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1025
	 * @return array
1026
	 */
1027
	public function get_urls( $force = false ) {
1028
1029
		if( !$force && $this->urls !== null ) {
1030
			return $this->urls;
1031
		}
1032
1033
		$tArray = array(
1034
			'action' => 'query',
1035
			'prop'   => 'info',
1036
			'inprop' => 'url',
1037
			'titles' => $this->title,
1038
		);
1039
1040
		pecho( "Getting the URLs for {$this->title}..\n\n", PECHO_NORMAL );
1041
1042
		$tRes = $this->wiki->apiQuery( $tArray );
1043
		$info = $tRes['query']['pages'][$this->pageid];
1044
1045
		$this->urls = array();
1046
1047
		if( isset( $info['fullurl'] ) ) $this->urls['full'] = $info['fullurl'];
1048
		if( isset( $info['editurl'] ) ) $this->urls['edit'] = $info['editurl'];
1049
1050
		return $this->urls;
1051
	}
1052
1053
	/**
1054
	 * Returns whether the user can read this page.
1055
	 *
1056
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1057
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1058
	 * @return boolean Null if it doesn't exist.
1059
	 */
1060
	public function get_readability( $force = false ) {
1061
1062
		if( !$force && $this->readable !== null ) {
1063
			return $this->readable;
1064
		}
1065
1066
		$tArray = array(
1067
			'action' => 'query',
1068
			'prop'   => 'info',
1069
			'inprop' => 'readable',
1070
			'titles' => $this->title,
1071
		);
1072
1073
		pecho( "Getting the readability status for {$this->title}..\n\n", PECHO_NORMAL );
1074
1075
		$tRes = $this->wiki->apiQuery( $tArray );
1076
1077
		if( isset( $tRes['query']['pages'][$this->pageid]['readable'] ) ) {
1078
			$this->readable = true;
1079
		} else $this->readable = false;
1080
1081
		return $this->readable;
1082
	}
1083
1084
	/**
1085
	 * Gives the text returned by EditFormPreloadText.
1086
	 *
1087
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1088
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1089
	 * @return string
1090
	 */
1091
	public function get_preload( $force = false ) {
1092
1093
		if( !$force ) {
1094
			return $this->preload;
1095
		}
1096
1097
		$tArray = array(
1098
			'action' => 'query',
1099
			'prop'   => 'info',
1100
			'inprop' => 'preload',
1101
			'titles' => $this->title,
1102
		);
1103
1104
		pecho( "Getting the preload text for {$this->title}..\n\n", PECHO_NORMAL );
1105
1106
		$tRes = $this->wiki->apiQuery( $tArray );
1107
1108
		if( isset( $tRes['query']['pages'][$this->pageid]['preload'] ) ) {
1109
			$this->preload = $tRes['query']['pages'][$this->pageid]['preload'];
1110
		} else $this->preload = null;
1111
1112
		return $this->preload;
1113
	}
1114
1115
	/**
1116
	 * Gives the way the page title is actually displayed.
1117
	 *
1118
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Properties#info_.2F_in
1119
	 * @param bool $force Force use of API, won't use cached copy (default: false)
1120
	 * @return string
1121
	 */
1122
	public function get_displaytitle( $force = false ) {
1123
1124
		if( !$force ) {
1125
			return $this->displaytitle;
1126
		}
1127
1128
		$tArray = array(
1129
			'action' => 'query',
1130
			'prop'   => 'info',
1131
			'inprop' => 'displaytitle',
1132
			'titles' => $this->title,
1133
		);
1134
1135
		pecho( "Getting the title formatting for {$this->title}..\n\n", PECHO_NORMAL );
1136
1137
		$tRes = $this->wiki->apiQuery( $tArray );
1138
1139
		if( isset( $tRes['query']['pages'][$this->pageid]['displaytitle'] ) ) {
1140
			$this->displaytitle = $tRes['query']['pages'][$this->pageid]['displaytitle'];
1141
		} else $this->displaytitle = null;
1142
1143
		return $this->displaytitle;
1144
	}
1145
1146
	/**
1147
	 * Edits the page
1148
	 *
1149
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1150
	 * @param string $text Text of the page that will be saved
1151
	 * @param string $summary Summary of the edit (default: "")
1152
	 * @param bool $minor Minor edit (default: false)
1153
	 * @param bool $bot Mark as bot edit (default: true)
1154
	 * @param bool $force Override nobots check (default: false)
1155
	 * @param string $pend Set to 'pre' or 'ap' to prepend or append, respectively (default: "")
1156
	 * @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)
1157
	 * @param string $section Section number. 0 for the top section, 'new' for a new section.  Default null.
1158
	 * @param string $sectiontitle The title for a new section. Default null.
1159
	 * @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.
1160
	 * @return string|int|bool The revision id of the successful edit, false on failure.
1161
	 *
1162
	 * @fixme: Cast return $this->lastedit; as integer, not string?
1163
	 */
1164
	public function edit(
1165
		$text,
1166
		$summary = "",
1167
		$minor = false,
1168
		$bot = true,
1169
		$force = false,
1170
		$pend = "",
1171
		$create = false,
1172
		$section = null,
1173
		$sectiontitle = null,
1174
		$watch = null
1175
	) {
1176
		global $pgNotag, $pgTag;
1177
1178
		$tokens = $this->wiki->get_tokens();
1179
1180
		if( !$pgNotag ) $summary .= $pgTag;
1181
1182
		if( $tokens['edit'] == '' ) {
1183
			pecho( "User is not allowed to edit {$this->title}\n\n", PECHO_FATAL );
1184
			return false;
1185
		}
1186
1187
		if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
Unused Code introduced by
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...
1188
			pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_WARN );
1189
//			return false; // Not necessary, summary text is truncated if needed
1190
		}
1191
1192
		$editarray = array(
1193
			'title'         => $this->title,
1194
			'action'        => 'edit',
1195
			'token'         => $tokens['edit'],
1196
			'md5'           => md5( $text ),
1197
			'text'          => $text
1198
		);
1199
		if( !is_null( $this->lastedit ) ) $editarray['basetimestamp'] = $this->lastedit;
1200
		if( !is_null( $this->starttimestamp ) ) $editarray['starttimestamp'] = $this->starttimestamp;
1201
		if( !is_null( $section ) ) {
1202
			if( $section == 'new' ) {
1203
				if( is_null( $sectiontitle ) ) {
1204
                    pecho("Error: sectionTitle parameter must be specified.  Aborting...\n\n", PECHO_FATAL);
1205
					return false;
1206
				} else {
1207
					$editarray['section'] = 'new';
1208
                    $editarray['sectionTitle'] = $sectiontitle;
1209
				}
1210
			} else $editarray['section'] = $section;
1211
		}
1212
1213
		if( $pend == "pre" ) {
1214
			$editarray['prependtext'] = $text;
1215
		} elseif( $pend == "ap" ) {
1216
			$editarray['appendtext'] = $text;
1217
		}
1218
1219
		if( !is_null( $watch ) ) {
1220
			if( $watch ) {
1221
				$editarray['watchlist'] = 'watch';
1222
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1223
			elseif( in_array(
1224
				$watch, array(
1225
					'watch', 'unwatch', 'preferences', 'nochange'
1226
				)
1227
			)
1228
			) {
1229
				$editarray['watchlist'] = $watch;
1230
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1231
		}
1232
1233
		if( $create == "never" ) {
1234
			$editarray['nocreate'] = '';
1235
		} elseif( $create == "only" ) $editarray['createonly'] = '';
1236
		elseif( $create == "recreate" ) $editarray['recreate'] = '';
1237
1238
		if( $this->wiki->get_maxlag() ) $editarray['maxlag'] = $this->wiki->get_maxlag();
1239
1240
		if( !empty( $summary ) ) $editarray['summary'] = $summary;
1241
1242
		if( $minor ) {
1243
			$editarray['minor'] = '';
1244
		} else $editarray['notminor'] = '';
1245
1246
		if( $bot ) $editarray['bot'] = '';
1247
1248
		if( !$force ) {
1249
			try{
1250
				$this->preEditChecks( "Edit" );
1251
			} catch( EditError $e ){
1252
				pecho( "Error: $e\n\n", PECHO_FATAL );
1253
				return false;
1254
			}
1255
		}
1256
1257
		Hooks::runHook( 'StartEdit', array( &$editarray ) );
1258
1259
		pecho( "Making edit to {$this->title}..\n\n", PECHO_NOTICE );
1260
1261
		$result = $this->wiki->apiQuery( $editarray, true );
1262
1263
		if( isset( $result['edit'] ) ) {
1264
			if( $result['edit']['result'] == "Success" ) {
1265
                                if (array_key_exists('nochange', $result['edit'])) {
1266
                                    return (int)$this->lastedit;
1267
                                }
1268
1269
				$this->__construct( $this->wiki, null, $this->pageid );
1270
1271
				if( !is_null( $this->wiki->get_edit_rate() ) && $this->wiki->get_edit_rate() != 0 ) {
1272
					sleep( intval( 60 / $this->wiki->get_edit_rate() ) - 1 );
1273
				}
1274
1275
				return $result['edit']['newrevid'];
1276
			} else {
1277
				pecho( "Edit error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1278
				return false;
1279
			}
1280
		} else {
1281
			pecho( "Edit error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1282
			return false;
1283
		}
1284
1285
	}
1286
1287
	/**
1288
	 * Add text to the beginning of the page. Shortcut for Page::edit()
1289
	 *
1290
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1291
	 * @param string $text Text of the page that will be saved
1292
	 * @param string $summary Summary of the edit (default: "")
1293
	 * @param bool $minor Minor edit (default: false)
1294
	 * @param bool $bot Mark as bot edit (default: true)
1295
	 * @param bool $force Override nobots check (default: false)
1296
	 * @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)
1297
	 * @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.
1298
	 * @return int|bool The revision id of the successful edit, false on failure.
1299
	 */
1300
	public function prepend( $text, $summary = "", $minor = false, $bot = true, $force = false, $create = false, $watch = null ) {
1301
		return $this->edit( $text, $summary, $minor, $bot, $force, 'pre', $create, null, null, $watch );
1302
	}
1303
1304
	/**
1305
	 * Add text to the end of the page. Shortcut for Page::edit()
1306
	 *
1307
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1308
	 * @param string $text Text of the page that will be saved
1309
	 * @param string $summary Summary of the edit (default: "")
1310
	 * @param bool $minor Minor edit (default: false)
1311
	 * @param bool $bot Mark as bot edit (default: true)
1312
	 * @param bool $force Override nobots check (default: false)
1313
	 * @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)
1314
	 * @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.
1315
	 * @return int|bool The revision id of the successful edit, false on failure.
1316
	 */
1317
	public function append( $text, $summary = "", $minor = false, $bot = true, $force = false, $create = false, $watch = null ) {
1318
		return $this->edit( $text, $summary, $minor, $bot, $force, 'ap', $create, null, null, $watch );
1319
	}
1320
1321
	/**
1322
	 * Create a new section.  Shortcut for Page::edit()
1323
	 *
1324
	 * @link http://www.mediawiki.org/wiki/API:Edit_-_Create%26Edit_pages
1325
     * @param    string $text Text of the page that will be saved
1326
     * @param    string $sectionTitle The title for a new section. Default null.
1327
     * @param    string $summary Summary of the edit (default: "")
1328
     * @param    bool $minor Minor edit (default: false)
1329
     * @param    bool $bot Mark as bot edit (default: true)
1330
     * @param    bool $force Override nobots check (default: false)
1331
     * @param   bool $create Set to 'never', 'only', or 'recreate' to never create a new page,
1332
     *                                          only create a new page, or override errors about the page having
1333
     *                                          been deleted, respectively (default: false)
1334
     * @param    string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences
1335
     *                                          or do not change watch. Default: go by user preference.
1336
     * @return    int|bool                    The revision ID of the successful edit, false on failure.
1337
	 */
1338
    public function newsection(
1339
        $text,
1340
        $sectionTitle,
1341
        $summary = null,
1342
        $minor = false,
1343
        $bot = true,
1344
        $force = false,
1345
        $create = false,
1346
        $watch = null
1347
    ) {
1348
        if (is_null($summary)) {
1349
            $summary = "/* " . $sectionTitle . " */ new section";
1350
        }
1351
1352
        return $this->edit($text, $summary, $minor, $bot, $force, false, $create, 'new', $sectionTitle, $watch);
0 ignored issues
show
Documentation introduced by
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...
1353
    }
1354
1355
    /**
1356
     * Undoes one or more edits. (Subject to standard editing restrictions.)
1357
     *
1358
     * @param   string $summary Override the default edit summary (default null).
1359
     * @param   int $revisions The number of revisions to undo (default 1).
1360
     * @param   bool $force Force an undo, despite e.g. new messages (default false).
1361
     * @param   bool|string $watch Unconditionally add or remove the page from your watchlist, use preferences
1362
     *                                      or do not change watch. Default: goes by user preference.
1363
     * @return  bool|int                The new revision id of the page edited.
1364
     * @throws AssertFailure
1365
     * @throws LoggedOut
1366
     * @throws MWAPIError
1367
     * @throws NoTitle
1368
     */
1369
    public function undo($summary = null, $revisions = 1, $force = false, $watch = null ) {
1370
		global $pgNotag, $pgTag;
1371
		$info = $this->history( $revisions );
1372
		$oldrev = $info[( count( $info ) - 1 )]['revid'];
1373
		$newrev = $info[0]['revid'];
1374
1375
		$tokens = $this->wiki->get_tokens();
1376
1377
		if( $tokens['edit'] == '+\\' ) {
1378
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1379
			return false;
1380
		} elseif( $tokens['edit'] == '' ) {
1381
			pecho( "User is not allowed to edit {$this->title}\n\n", PECHO_FATAL );
1382
			return false;
1383
		}
1384
1385
		$params = array(
1386
			'title'         => $this->title,
1387
			'action'        => 'edit',
1388
			'token'         => $tokens['edit'],
1389
			'basetimestamp' => $this->lastedit,
1390
			'undo'          => $oldrev,
1391
			'undoafter'     => $newrev
1392
		);
1393
		if( !is_null( $this->starttimestamp ) ) $params['starttimestamp'] = $this->starttimestamp;
1394
		if( !is_null( $summary ) ) {
1395
			if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
Unused Code introduced by
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...
1396
				pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_WARN );
1397
//				return false; // Not necessary, summary text is truncated if needed
1398
			}
1399
			if( !$pgNotag ) $summary .= $pgTag;
1400
1401
			$params['summary'] = $summary;
1402
		}
1403
1404
		if( !is_null( $watch ) ) {
1405
			if( $watch ) {
1406
				$editarray['watchlist'] = 'watch';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$editarray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $editarray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1407
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$editarray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $editarray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1408
			elseif( in_array(
1409
				$watch, array(
1410
					'watch', 'unwatch', 'preferences', 'nochange'
1411
				)
1412
			) ) {
1413
				$editarray['watchlist'] = $watch;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$editarray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $editarray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1414
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1415
		}
1416
1417
		if( !$force ) {
1418
			try{
1419
				$this->preEditChecks( "Undo" );
1420
			} catch( EditError $e ){
1421
				pecho( "Error: $e\n\n", PECHO_FATAL );
1422
				return false;
1423
			}
1424
		}
1425
1426
		pecho( "Undoing revision(s) on {$this->title}...\n\n", PECHO_NORMAL );
1427
		$result = $this->wiki->apiQuery( $params, true );
1428
1429
		if( $result['edit']['result'] == "Success") {
1430
            if (array_key_exists('nochange', $result['edit'])) return (int)$this->lastedit;
1431
1432
			$this->__construct( $this->wiki, null, $this->pageid );
1433
1434
			if( !is_null( $this->wiki->get_edit_rate() ) && $this->wiki->get_edit_rate() != 0 ) {
1435
				sleep( intval( 60 / $this->wiki->get_edit_rate() ) - 1 );
1436
			}
1437
1438
			return $result['edit']['newrevid'];
1439
		} else {
1440
			pecho( "Undo error...\n\n" . print_r( $result['edit'], true ) . "\n\n", PECHO_FATAL );
1441
			return false;
1442
		}
1443
	}
1444
1445
	/**
1446
	 * Returns a boolean depending on whether the page can have subpages or not.
1447
	 *
1448
	 * @return bool True if subpages allowed
1449
	 */
1450
	public function allow_subpages() {
1451
		$allows = $this->wiki->get_allow_subpages();
1452
		return (bool)$allows[$this->namespace_id];
1453
	}
1454
1455
	/**
1456
	 * Returns a boolean depending on whether the page is a discussion (talk) page or not.
1457
	 *
1458
	 * @return bool True if discussion page, false if not
1459
	 */
1460
	public function is_discussion() {
1461
		return ( $this->namespace_id >= 0 && $this->namespace_id % 2 == 1 );
1462
	}
1463
1464
	/**
1465
	 * Returns the title of the discussion (talk) page associated with a page, if it exists.
1466
	 *
1467
	 * @return string Title of discussion page
1468
	 * @throws BadEntryError
1469
	 */
1470
	public function get_discussion() {
1471
		if( $this->namespace_id < 0 ) {
1472
			// No discussion page exists
1473
			// Guessing we want to error
1474
			throw new BadEntryError( "get_discussion", "Tried to find the discussion page of a page which could never have one" );
1475
		} else {
1476
			$namespaces = $this->wiki->get_namespaces();
1477
			if( $this->is_discussion() ) {
1478
				return $namespaces[( $this->namespace_id - 1 )] . ":" . $this->title_wo_namespace;
1479
			} else {
1480
				return $namespaces[( $this->namespace_id + 1 )] . ":" . $this->title_wo_namespace;
1481
			}
1482
		}
1483
	}
1484
1485
	/**
1486
	 * Moves a page to a new location.
1487
	 *
1488
	 * @param string $newTitle The new title to which to move the page.
1489
	 * @param string $reason A descriptive reason for the move.
1490
	 * @param bool $movetalk Whether or not to move any associated talk (discussion) page.
1491
	 * @param bool $movesubpages Whether or not to move any subpages.
1492
	 * @param bool $noredirect Whether or not to suppress the leaving of a redirect to the new title at the old title.
1493
	 * @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.
1494
	 * @param bool $nowarnings Ignore any warnings. Default false.
1495
	 * @return bool True on success
1496
	 */
1497
	public function move( $newTitle, $reason = '', $movetalk = true, $movesubpages = true, $noredirect = false, $watch = null, $nowarnings = false ) {
1498
		global $pgNotag, $pgTag;
1499
		$tokens = $this->wiki->get_tokens();
1500
1501
		if( $tokens['move'] == '+\\' ) {
1502
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1503
			return false;
1504
		} elseif( $tokens['move'] == '' ) {
1505
			pecho( "User is not allowed to move {$this->title}\n\n", PECHO_FATAL );
1506
			return false;
1507
		}
1508
1509
		if( mb_strlen( $reason, '8bit' ) > 255 ) {
0 ignored issues
show
Unused Code introduced by
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...
1510
			pecho( "Reason is over 255 bytes, the maximum allowed.\n\n", PECHO_FATAL );
1511
			return false;
1512
		}
1513
1514
		try{
1515
			$this->preEditChecks( "Move" );
1516
		} catch( EditError $e ){
1517
			pecho( "Error: $e\n\n", PECHO_FATAL );
1518
			return false;
1519
		}
1520
1521
		pecho( "Moving {$this->title} to $newTitle...\n\n", PECHO_NOTICE );
1522
1523
		$editarray = array(
1524
			'from'   => $this->title,
1525
			'to'     => $newTitle,
1526
			'action' => 'move',
1527
			'token'  => $tokens['move'],
1528
		);
1529
1530
		if( !is_null( $watch ) ) {
1531
			if( $watch ) {
1532
				$editarray['watchlist'] = 'watch';
1533
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1534
			elseif( in_array(
1535
				$watch, array(
1536
					'watch', 'unwatch', 'preferences', 'nochange'
1537
				)
1538
			) ) {
1539
				$editarray['watchlist'] = $watch;
1540
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1541
		}
1542
1543
		if( $nowarnings ) $editarray['ignorewarnings'] = '';
1544
		if( !$pgNotag ) $reason .= $pgTag;
1545
		if( !empty( $reason ) ) $editarray['reason'] = $reason;
1546
1547
		if( $movetalk ) $editarray['movetalk'] = '';
1548
		if( $movesubpages ) $editarray['movesubpages'] = '';
1549
		if( $noredirect ) $editarray['noredirect'] = '';
1550
1551
		if( $this->wiki->get_maxlag() ) {
1552
			$editarray['maxlag'] = $this->wiki->get_maxlag();
1553
1554
		}
1555
1556
		Hooks::runHook( 'StartMove', array( &$editarray ) );
1557
1558
		$result = $this->wiki->apiQuery( $editarray, true );
1559
1560
		if( isset( $result['move'] ) ) {
1561
			if( isset( $result['move']['to'] ) ) {
1562
				$this->__construct( $this->wiki, null, $this->pageid );
1563
				return true;
1564
			} else {
1565
				pecho( "Move error...\n\n" . print_r( $result['move'], true ) . "\n\n", PECHO_FATAL );
1566
				return false;
1567
			}
1568
		} else {
1569
			pecho( "Move error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1570
			return false;
1571
		}
1572
	}
1573
1574
	/**
1575
	 * Protects the page.
1576
	 *
1577
	 * @param array $levels Array of protections levels. The key is the type, the value is the level. Default: array( 'edit' => 'sysop', 'move' => 'sysop' )
1578
	 * @param string $reason Reason for protection. Default null
1579
	 * @param string $expiry Expiry time. Default 'indefinite'
1580
	 * @param bool $cascade Whether or not to enable cascade protection. Default false
1581
	 * @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.
1582
	 * @return bool True on success
1583
	 */
1584
	public function protect( $levels = null, $reason = null, $expiry = 'indefinite', $cascade = false, $watch = null ) {
1585
		global $pgNotag, $pgTag;
1586
		if( !in_array( 'protect', $this->wiki->get_userrights() ) ) {
1587
			pecho( "User is not allowed to protect pages", PECHO_FATAL );
1588
			return false;
1589
		}
1590
1591
		if( $levels === null ){
1592
			$levels = array( 'edit' => 'sysop', 'move' => 'sysop' );
1593
		}
1594
1595
		$tokens = $this->wiki->get_tokens();
1596
		if( !$pgNotag ) $reason .= $pgTag;
1597
		$editarray = array(
1598
			'action'      => 'protect',
1599
			'title'       => $this->title,
1600
			'token'       => $tokens['protect'],
1601
			'reason'      => $reason,
1602
			'protections' => array(),
1603
			'expiry'      => $expiry
1604
		);
1605
1606
		foreach( $levels as $type => $level ){
1607
			$editarray['protections'][] = "$type=$level";
1608
		}
1609
1610
		$editarray['protections'] = implode( "|", $editarray['protections'] );
1611
1612
		if( $cascade ) $editarray['cascade'] = '';
1613
1614
		if( !is_null( $watch ) ) {
1615
			if( $watch ) {
1616
				$editarray['watchlist'] = 'watch';
1617
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1618
			elseif( in_array(
1619
				$watch, array(
1620
					'watch', 'unwatch', 'preferences', 'nochange'
1621
				)
1622
			) ) {
1623
				$editarray['watchlist'] = $watch;
1624
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1625
		}
1626
		try{
1627
			$this->preEditChecks( "Protect" );
1628
		} catch( EditError $e ){
1629
			pecho( "Error: $e\n\n", PECHO_FATAL );
1630
			return false;
1631
		}
1632
		if( !$editarray['protections'] == array() ) {
1633
			pecho( "Protecting {$this->title}...\n\n", PECHO_NOTICE );
1634
		} else pecho( "Unprotecting {$this->title}...\n\n", PECHO_NOTICE );
1635
1636
		Hooks::runHook( 'StartProtect', array( &$editarray ) );
1637
1638
		$result = $this->wiki->apiQuery( $editarray, true );
1639
1640
		if( isset( $result['protect'] ) ) {
1641
			if( isset( $result['protect']['title'] ) ) {
1642
				$this->__construct( $this->wiki, $this->title );
1643
				return true;
1644
			} else {
1645
				pecho( "Protect error...\n\n" . print_r( $result['protect'], true ) . "\n\n", PECHO_FATAL );
1646
				return false;
1647
			}
1648
		} else {
1649
			pecho( "Protect error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1650
			return false;
1651
		}
1652
	}
1653
1654
	/**
1655
	 * Unprotects the page.
1656
	 *
1657
	 * @param string $reason A reason for the unprotection. Defaults to null (blank).
1658
	 * @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.
1659
	 * @return bool True on success
1660
	 */
1661
	public function unprotect( $reason = null, $watch = null ) {
1662
		return $this->protect( array(), $reason, 'indefinite', false, $watch );
1663
	}
1664
1665
	/**
1666
	 * Deletes the page.
1667
	 *
1668
	 * @param string $reason A reason for the deletion. Defaults to null (blank).
1669
	 * @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.
1670
     * @param string $oldimage The name of the old image to delete as provided by iiprop=archivename
1671
	 * @return bool True on success
1672
	 */
1673
	public function delete( $reason = null, $watch = null, $oldimage = null ) {
1674
		global $pgNotag, $pgTag;
1675
1676
		if( !in_array( 'delete', $this->wiki->get_userrights() ) ) {
1677
			pecho( "User is not allowed to delete pages", PECHO_FATAL );
1678
			return false;
1679
		}
1680
1681
		$tokens = $this->wiki->get_tokens();
1682
		if( !$pgNotag ) $reason .= $pgTag;
1683
		$editarray = array(
1684
			'action' => 'delete',
1685
			'title'  => $this->title,
1686
			'token'  => $tokens['delete'],
1687
			'reason' => $reason
1688
		);
1689
1690
		if( !is_null( $watch ) ) {
1691
			if( $watch ) {
1692
				$editarray['watchlist'] = 'watch';
1693
			} elseif( !$watch ) $editarray['watchlist'] = 'nochange';
1694
			elseif( in_array(
1695
				$watch, array(
1696
					'watch', 'unwatch', 'preferences', 'nochange'
1697
				)
1698
			) ) {
1699
				$editarray['watchlist'] = $watch;
1700
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1701
		}
1702
        if( !is_null( $oldimage ) ) $editarray['oldimage'] = $oldimage;
1703
1704
		Hooks::runHook( 'StartDelete', array( &$editarray ) );
1705
1706
		try{
1707
			$this->preEditChecks( "Delete" );
1708
		} catch( EditError $e ){
1709
			pecho( "Error: $e\n\n", PECHO_FATAL );
1710
			return false;
1711
		}
1712
		pecho( "Deleting {$this->title}...\n\n", PECHO_NOTICE );
1713
1714
		$result = $this->wiki->apiQuery( $editarray, true );
1715
1716
		if( isset( $result['delete'] ) ) {
1717
			if( isset( $result['delete']['title'] ) ) {
1718
				$this->__construct( $this->wiki, $this->title );
1719
				return true;
1720
			} else {
1721
				pecho( "Delete error...\n\n" . print_r( $result['delete'], true ) . "\n\n", PECHO_FATAL );
1722
				return false;
1723
			}
1724
		} else {
1725
			pecho( "Delete error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1726
			return false;
1727
		}
1728
1729
	}
1730
1731
	/**
1732
	 * Undeletes the page
1733
	 *
1734
	 * @param string $reason Reason for undeletion
1735
	 * @param array $timestamps Array of timestamps to selectively restore
1736
	 * @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.
1737
	 * @return bool
1738
	 */
1739
	public function undelete( $reason = null, $timestamps = null, $watch = null ) {
1740
		global $pgNotag, $pgTag;
1741
		if( !in_array( 'undelete', $this->wiki->get_userrights() ) ) {
1742
			pecho( "User is not allowed to undelete pages", PECHO_FATAL );
1743
			return false;
1744
		}
1745
1746
		$tokens = $this->wiki->get_tokens();
1747
		if( !$pgNotag ) $reason .= $pgTag;
1748
		$undelArray = array(
1749
			'action' => 'undelete',
1750
			'title'  => $this->title,
1751
			'token'  => $tokens['delete'],
1752
			//Using the delete token, it's the exact same, and we don't have to do another API call
1753
			'reason' => $reason
1754
		);
1755
1756
		if( !is_null( $timestamps ) ) {
1757
			$undelArray['timestamps'] = $timestamps;
1758
			if( is_array( $timestamps ) ) {
1759
				$undelArray['timestamps'] = implode( '|', $timestamps );
1760
			}
1761
		}
1762
1763
		if( !is_null( $watch ) ) {
1764
			if( $watch ) {
1765
				$undelArray['watchlist'] = 'watch';
1766
			} elseif( !$watch ) $undelArray['watchlist'] = 'nochange';
1767
			elseif( in_array(
1768
				$watch, array(
1769
					'watch', 'unwatch', 'preferences', 'nochange'
1770
				)
1771
			) ) {
1772
				$undelArray['watchlist'] = $watch;
1773
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
1774
		}
1775
1776
		try{
1777
			$this->preEditChecks( "Undelete" );
1778
		} catch( EditError $e ){
1779
			pecho( "Error: $e\n\n", PECHO_FATAL );
1780
			return false;
1781
		}
1782
		pecho( "Undeleting {$this->title}...\n\n", PECHO_NOTICE );
1783
1784
		Hooks::runHook( 'StartUndelete', array( &$undelArray ) );
1785
1786
		$result = $this->wiki->apiQuery( $undelArray, true );
1787
1788
		if( isset( $result['undelete'] ) ) {
1789
			if( isset( $result['undelete']['title'] ) ) {
1790
				$this->__construct( $this->wiki, $this->title );
1791
				return true;
1792
			} else {
1793
				pecho( "Undelete error...\n\n" . print_r( $result['undelete'], true ) . "\n\n", PECHO_FATAL );
1794
				return false;
1795
			}
1796
		} else {
1797
			pecho( "Undelete error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1798
			return false;
1799
		}
1800
	}
1801
1802
	/**
1803
	 * List deleted revisions of the page
1804
	 *
1805
	 * @param bool $content Whether or not to retrieve the content of each revision, Default false
1806
	 * @param string $user Only list revisions by this user. Default null.
1807
	 * @param string $excludeuser Don't list revisions by this user. Default null
1808
	 * @param string $start Timestamp to start at. Default null
1809
	 * @param string $end Timestamp to end at. Default null
1810
	 * @param string $dir Direction to enumerate. Default 'older'
1811
	 * @param array $prop Properties to retrieve. Default array( 'revid', 'user', 'parsedcomment', 'minor', 'len', 'content', 'token' )
1812
	 * @return array List of deleted revisions
1813
	 */
1814
	public function deletedrevs( $content = false, $user = null, $excludeuser = null, $start = null, $end = null, $dir = 'older', $prop = array(
1815
		'revid', 'user', 'parsedcomment', 'minor', 'len', 'content', 'token'
1816
	) ) {
1817
		if( !in_array( 'deletedhistory', $this->wiki->get_userrights() ) ) {
1818
			pecho( "User is not allowed to view deleted revisions", PECHO_FATAL );
1819
			return false;
1820
		}
1821
1822
		if( $content ) $prop[] = 'content';
1823
1824
		$drArray = array(
1825
			'_code'  => 'dr',
1826
			'list'   => 'deletedrevs',
1827
			'titles' => $this->title,
1828
			'drprop' => implode( '|', $prop ),
1829
			'drdir'  => $dir
1830
		);
1831
1832
		if( !is_null( $user ) ) $drArray['druser'] = $user;
1833
		if( !is_null( $excludeuser ) ) $drArray['drexcludeuser'] = $excludeuser;
1834
		if( !is_null( $start ) ) $drArray['drstart'] = $start;
1835
		if( !is_null( $end ) ) $drArray['drend'] = $end;
1836
1837
		pecho( "Getting deleted revisions of {$this->title}...\n\n", PECHO_NORMAL );
1838
1839
		return $this->wiki->listHandler( $drArray );
1840
	}
1841
1842
	/**
1843
	 * Alias of embeddedin
1844
	 *
1845
	 * @see Page::embeddedin()
1846
	 * @deprecated since 18 June 2013
1847
	 * @param null $namespace
1848
	 * @param null $limit
1849
	 * @return array
1850
	 */
1851
	public function get_transclusions( $namespace = null, $limit = null ) {
1852
		Peachy::deprecatedWarn( 'Page::get_transclusions()', 'Page::embeddedin()' );
1853
		return $this->embeddedin( $namespace, $limit );
1854
	}
1855
1856
	/**
1857
	 * Adds the page to the logged in user's watchlist
1858
	 *
1859
	 * @param string $lang The code for the language to show any error message in (default: user preference)
1860
	 * @return bool True on success
1861
	 */
1862
	public function watch( $lang = null ) {
1863
1864
		Hooks::runHook( 'StartWatch' );
1865
1866
		pecho( "Watching {$this->title}...\n\n", PECHO_NOTICE );
1867
1868
		$tokens = $this->wiki->get_tokens();
1869
1870
		if( $tokens['watch'] == '+\\' ) {
1871
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1872
			return false;
1873
		}
1874
1875
		$apiArray = array(
1876
			'action' => 'watch',
1877
			'token'  => $tokens['watch'],
1878
			'title'  => $this->title
1879
		);
1880
1881
		if( !is_null( $lang ) ) $apiArray['uselang'] = $lang;
1882
1883
		$result = $this->wiki->apiQuery( $apiArray, true );
1884
1885
		if( isset( $result['watch'] ) ) {
1886
			if( isset( $result['watch']['watched'] ) ) {
1887
				return true;
1888
			} else {
1889
				pecho( "Watch error...\n\n" . print_r( $result['watch'], true ) . "\n\n", PECHO_FATAL );
1890
				return false;
1891
			}
1892
		} else {
1893
			pecho( "Watch error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1894
			return false;
1895
		}
1896
1897
	}
1898
1899
	/**
1900
	 * Removes the page to the logged in user's watchlist
1901
	 *
1902
	 * @param string $lang The code for the language to show any error message in (default: user preference)
1903
	 * @return bool True on sucecess
1904
	 */
1905
	public function unwatch( $lang = null ) {
1906
		Hooks::runHook( 'StartUnwatch' );
1907
1908
		pecho( "Unwatching {$this->title}...\n\n", PECHO_NOTICE );
1909
1910
		$tokens = $this->wiki->get_tokens();
1911
1912
		if( $tokens['watch'] == '+\\' ) {
1913
			pecho( "User has logged out.\n\n", PECHO_FATAL );
1914
			return false;
1915
		}
1916
1917
		$apiArray = array(
1918
			'action'  => 'watch',
1919
			'token'   => $tokens['watch'],
1920
			'title'   => $this->title,
1921
			'unwatch' => ''
1922
		);
1923
1924
		if( !is_null( $lang ) ) $apiArray['uselang'] = $lang;
1925
1926
		$result = $this->wiki->apiQuery( $apiArray, true );
1927
1928
		if( isset( $result['watch'] ) ) {
1929
			if( isset( $result['watch']['unwatched'] ) ) {
1930
				return true;
1931
			} else {
1932
				pecho( "Unwatch error...\n\n" . print_r( $result['watch'], true ) . "\n\n", PECHO_FATAL );
1933
				return false;
1934
			}
1935
		} else {
1936
			pecho( "Unwatch error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1937
			return false;
1938
		}
1939
1940
	}
1941
1942
	/**
1943
	 * Returns the page title
1944
	 *
1945
	 * @param bool $namespace Set to true to return the title with namespace, false to return it without the namespace. Default true.
1946
	 * @param bool $normalized Set to true to return the title from a redirect or normalisez. Default false, for backwards compatibility.
1947
	 * @return string Page title
1948
	 */
1949
	public function get_title( $namespace = true, $normalized = false ) {
1950
		if( !$namespace ) {
1951
			return $this->title_wo_namespace;
1952
		}
1953
                $title = $this->title;
1954
                if ($normalized){
1955
                    if ( $this->redirectsTitle != NULL )
1956
                        $title = $this->redirectsTitle;
1957
                    else if ( $this->normalizedTitle != NULL )
1958
                        $title = $this->normalizedTitle;
1959
                }
1960
                                
1961
		return $title;
1962
	}
1963
1964
	/**
1965
	 * Returns whether or not a redirect was followed to get to the real page title
1966
	 *
1967
	 * @return bool
1968
	 */
1969
	public function redirectFollowed() {
1970
		return $this->redirectFollowed;
1971
	}
1972
1973
	/**
1974
	 * Returns whether or not the page is a special page
1975
	 *
1976
	 * @return bool
1977
	 */
1978
	public function get_special() {
1979
		return $this->special;
1980
	}
1981
1982
	/**
1983
	 * Gets ID or name of the namespace
1984
	 *
1985
	 * @param bool $id Set to true to get namespace ID, set to false to get namespace name. Default true
1986
	 * @return int|string
1987
	 */
1988
	public function get_namespace( $id = true ) {
1989
		if( $id ) {
1990
			return $this->namespace_id;
1991
		} else {
1992
			$namespaces = $this->wiki->get_namespaces();
1993
1994
			return $namespaces[$this->namespace_id];
1995
		}
1996
	}
1997
1998
	/**
1999
	 * Returns the timestamp of the last edit
2000
     *
2001
     * @param   bool $force Regenerate the cached value (default: false)
2002
     * @return  int
2003
	 */
2004
	public function get_lastedit( $force = false ) {
2005
		if( $force ) $this->get_metadata();
2006
2007
        return (int)$this->lastedit;
2008
	}
2009
2010
	/**
2011
	 * Returns length of the page
2012
	 *
2013
	 * @param bool $force Regenerate the cached value (default: false)
2014
	 * @return int
2015
	 */
2016
	public function get_length( $force = false ) {
2017
		if( $force ) $this->get_metadata();
2018
2019
		return $this->length;
2020
	}
2021
2022
	/**
2023
	 * Returns number of hits the page has received
2024
	 *
2025
	 * @param bool $force Regenerate the cached value (default: false)
2026
	 * @return int
2027
	 */
2028
	public function get_hits( $force = false ) {
2029
		if( $force ) $this->get_metadata();
2030
2031
		return $this->hits;
2032
	}
2033
2034
	/**
2035
	 * (Re)generates lastedit, length, and hits
2036
	 *
2037
	 * @param array $pageInfoArray2 Array of values to merge with defaults (default: null)
2038
	 * @throws BadTitle
2039
	 */
2040
	protected function get_metadata( $pageInfoArray2 = null ) {
2041
		$pageInfoArray = array(
2042
			'action' => 'query',
2043
			'prop'   => "info|revisions" // Added revisions property in order to get the last timestamp edit of the page 
2044
		);
2045
		$pageInfoArray['inprop'] = 'protection|talkid|watched|watchers|notificationtimestamp|subjectid|url|readable|preload|displaytitle';
2046
2047
		if( $pageInfoArray2 != null ) {
2048
			$pageInfoArray = array_merge( $pageInfoArray, $pageInfoArray2 );
2049
		} else {
2050
			$pageInfoArray['titles'] = $this->title;
2051
		}
2052
2053
		$pageInfoRes = $this->wiki->apiQuery( $pageInfoArray );
2054
2055
		if( isset( $pageInfoRes['warnings']['query']['*'] ) && in_string( 'special pages', $pageInfoRes['warnings']['query']['*'] ) ) {
2056
			pecho( "Special pages are not currently supported by the API.\n\n", PECHO_ERROR );
2057
			$this->exists = false;
2058
			$this->special = true;
2059
		}
2060
2061
		if( isset( $pageInfoRes['query']['redirects'][0] ) ) {
2062
			$this->redirectFollowed = true;
2063
			$this->redirectsTitle = $pageInfoRes['query']['redirects'][0]["to"]; 
2064
		}
2065
                
2066
                if( isset( $pageInfoRes['query']['normalized'][0] ) ) {
2067
			$this->normalizedTitle = $pageInfoRes['query']['normalized'][0]["to"]; 
2068
		}
2069
2070
		foreach( $pageInfoRes['query']['pages'] as $key => $info ){
2071
			$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...
2072
			if( $this->pageid > 0 ) {
2073
				$this->exists = true;
2074
				$this->touched = $info['touched'];
0 ignored issues
show
Bug introduced by
The property touched 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...
2075
				$this->lastedit = $info['revisions'][0]['timestamp'];
2076
				$this->hits = isset( $info['counter'] ) ? $info['counter'] : '';
2077
				$this->length = $info['length'];
2078
			} else {
2079
				$this->pageid = 0;
2080
				$this->lastedit = null;
2081
				$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...
2082
				$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...
2083
				$this->starttimestamp = '';
2084
			}
2085
2086
			if( isset( $info['missing'] ) ) $this->exists = false;
2087
2088
			if( isset( $info['invalid'] ) ) throw new BadTitle( $this->title );
2089
2090
			$this->title = $info['title'];
2091
			$this->namespace_id = $info['ns'];
2092
2093
			if( $this->namespace_id != 0) {
2094
                                $title_wo_namespace = explode(':', $this->title, 2);
2095
                                $this->title_wo_namespace = $title_wo_namespace[1];
2096
			} else {
2097
				$this->title_wo_namespace = $this->title;
2098
			}
2099
2100
			if( isset( $info['special'] ) ) $this->special = true;
2101
2102
			if( isset( $info['protection'] ) ) $this->protection = $info['protection'];
2103
			if( isset( $info['talkid'] ) ) $this->talkid = $info['talkid'];
2104
			if( isset( $info['watched'] ) ) $this->watched = true;
2105
			if( isset( $info['watchers'] ) ) $this->watchers = $info['watchers'];
2106
			if( isset( $info['notificationtimestamp'] ) ) $this->watchlisttimestamp = $info['notificationtimestamp'];
2107
			if( isset( $info['subjectid'] ) ) $this->subjectid = $info['subjectid'];
2108
			if( isset( $info['fullurl'] ) ) $this->urls['full'] = $info['fullurl'];
2109
			if( isset( $info['editurl'] ) ) $this->urls['edit'] = $info['editurl'];
2110
			if( isset( $info['readable'] ) ) $this->readable = true;
2111
			if( isset( $info['preload'] ) ) $this->preload = $info['preload'];
2112
			if( isset( $info['displaytitle'] ) ) $this->displaytitle = $info['displaytitle'];
2113
		}
2114
	}
2115
2116
	/**
2117
	 * Returns all links to the page
2118
	 *
2119
	 * @param array $namespaces Namespaces to get. Default array( 0 );
2120
	 * @param string $redirects How to handle redirects. 'all' = List all pages. 'redirects' = Only list redirects. 'nonredirects' = Don't list redirects. Default 'all'
2121
	 * @param bool $followredir List links through redirects to the page
2122
	 * @return array List of backlinks
2123
	 */
2124
	public function get_backlinks( $namespaces = array( 0 ), $redirects = 'all', $followredir = true ) {
2125
		$leArray = array(
2126
			'list'          => 'backlinks',
2127
			'_code'         => 'bl',
2128
			'blnamespace'   => $namespaces,
2129
			'blfilterredir' => $redirects,
2130
			'bltitle'       => $this->title
2131
		);
2132
2133
		if( $followredir ) $leArray['blredirect'] = '';
2134
2135
		Hooks::runHook( 'PreQueryBacklinks', array( &$leArray ) );
2136
2137
		pecho( "Getting all links to {$this->title}...\n\n", PECHO_NORMAL );
2138
2139
		return $this->wiki->listHandler( $leArray );
2140
    }
2141
2142
    /**
2143
	 * Rollbacks the latest edit(s) to a page.
2144
	 * 
2145
	 * @see http://www.mediawiki.org/wiki/API:Edit_-_Rollback
2146
	 * @param bool $force Whether to force an (attempt at an) edit, regardless of news messages, etc.
2147
	 * @param string $summary Override the default edit summary for this rollback. Default null.
2148
	 * @param bool $markbot If set, both the rollback and the revisions being rolled back will be marked as bot edits.
2149
     * @param string|bool $watch Unconditionally add or remove the page from your watchlist, use preferences or do not change watch. Default preferences.
2150
	 * @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.
2151
	 */
2152
	public function rollback( $force = false, $summary = null, $markbot = false, $watch = null ) {
2153
		global $pgNotag, $pgTag;
2154
		if( !in_array( 'rollback', $this->wiki->get_userrights() ) ) {
2155
			pecho( "User is not allowed to rollback edits", PECHO_FATAL );
2156
			return false;
2157
		}
2158
2159
		if( !$force ) {
2160
			try{
2161
				$this->preEditChecks();
2162
			} catch( EditError $e ){
2163
				pecho( "Error: $e\n\n", PECHO_FATAL );
2164
				return false;
2165
			}
2166
		}
2167
2168
		$history = $this->history( 1, 'older', false, null, true );
2169
2170
		$params = array(
2171
			'action' => 'rollback',
2172
			'title'  => $this->title,
2173
			'user'   => $history[0]['user'],
2174
			'token'  => $history[0]['rollbacktoken'],
2175
		);
2176
2177
		if( !is_null( $summary ) ) {
2178
			if( mb_strlen( $summary, '8bit' ) > 255 ) {
0 ignored issues
show
Unused Code introduced by
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...
2179
				pecho( "Summary is over 255 bytes, the maximum allowed.\n\n", PECHO_WARN );
2180
//				return false; // Not necessary, summary text is truncated if needed
2181
			}
2182
			if( !$pgNotag ) $summary .= $pgTag;
2183
2184
			$params['summary'] = $summary;
2185
		}
2186
		if( $markbot ) $params['markbot'] = '';
2187
2188
		if( !is_null( $watch ) ) {
2189
			if( $watch ) {
2190
				$params['watchlist'] = 'watch';
2191
			} elseif( !$watch ) $params['watchlist'] = 'nochange';
2192
			elseif( in_array(
2193
				$watch, array(
2194
					'watch', 'unwatch', 'preferences', 'nochange'
2195
				)
2196
			) ) {
2197
				$params['watchlist'] = $watch;
2198
			} else pecho( "Watch parameter set incorrectly.  Omitting...\n\n", PECHO_WARN );
2199
		}
2200
2201
		try{
2202
			$this->preEditChecks( "Rollback" );
2203
		} catch( EditError $e ){
2204
			pecho( "Error: $e\n\n", PECHO_FATAL );
2205
			return false;
2206
		}
2207
		Hooks::runHook( 'PreRollback', array( &$params ) );
2208
2209
		pecho( "Rolling back {$this->title}...\n\n", PECHO_NOTICE );
2210
2211
		$result = $this->wiki->apiQuery( $params, true );
2212
2213
		if( isset( $result['rollback'] ) ) {
2214
			if( isset( $result['rollback']['title'] ) ) {
2215
				$this->__construct( $this->wiki, $this->title );
2216
				return true;
2217
			} else {
2218
				pecho( "Rollback error...\n\n" . print_r( $result['rollback'], true ) . "\n\n", PECHO_FATAL );
2219
				return false;
2220
			}
2221
		} else {
2222
			pecho( "Rollback error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2223
			return false;
2224
		}
2225
    }
2226
2227
    /**
2228
     * Performs nobots checking, new message checking, etc
2229
     *
2230
     * @param   string $action
2231
     * @throws  EditError
2232
     */
2233
	protected function preEditChecks( $action = "Edit" ) {
2234
	    $this->wiki->preEditChecks( $action, $this->title, $this->pageid );
2235
	}
2236
2237
	/**
2238
	 * Returns array of pages that embed (transclude) the page given.
2239
     *
2240
     * @param   array $namespace Which namespaces to search (default: null).
2241
     * @param   int $limit How many results to retrieve (default: null i.e. all).
2242
     * @return  array               A list of pages the title is transcluded in.
2243
	 */
2244
	public function embeddedin( $namespace = null, $limit = null ) {
2245
		$eiArray = array(
2246
			'list'     => 'embeddedin',
2247
			'_code'    => 'ei',
2248
			'eititle'  => $this->title,
2249
			'_lhtitle' => 'title',
2250
			'_limit'   => $limit
2251
		);
2252
2253
		if( !is_null( $namespace ) ) {
2254
			$eiArray['einamespace'] = $namespace;
2255
		}
2256
2257
		Hooks::runHook( 'PreQueryEmbeddedin', array( &$eiArray ) );
2258
2259
		pecho( "Getting list of pages that include {$this->title}...\n\n", PECHO_NORMAL );
2260
2261
		return $this->wiki->listHandler( $eiArray );
2262
	}
2263
2264
	/**
2265
	 * Purges a list of pages. Shortcut for {@link Wiki::purge()}
2266
	 *
2267
	 * @see Wiki::purge()
2268
	 * @return boolean
2269
	 */
2270
	public function purge() {
2271
		return $this->wiki->purge( $this->title );
2272
	}
2273
2274
	/**
2275
	 * Parses wikitext and returns parser output. Shortcut for Wiki::parse
2276
	 *
2277
	 * @param string $summary Summary to parse. Default null.
2278
	 * @param string $id Parse the content of this revision
2279
	 * @param array $prop Properties to retrieve. array( 'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext', 'properties' )
2280
	 * @param string $uselang Code of the language to use in interface messages, etc. (where applicable). Default 'en'.
2281
	 * @param string $section Only retrieve the content of this section number.  Default null.
2282
	 * @return array
2283
	 */
2284
	public function parse( $summary = null, $id = null, $prop = null, $uselang = 'en', $section = null ) {
2285
		if( $prop === null ){
2286
			$prop = array(
2287
				'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid',
2288
				'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext', 'properties'
2289
			);
2290
		}
2291
		return $this->wiki->parse(
2292
			null, null, $summary, false, false, $prop, $uselang, $this->title, $id, null, false, $section
2293
		);
2294
	}
2295
2296
}
2297