ApiQueryDeletedRevisions::getExamplesMessages()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created on Oct 3, 2014
4
 *
5
 * Copyright © 2014 Brad Jorsch "[email protected]"
6
 *
7
 * Heavily based on ApiQueryDeletedrevs,
8
 * Copyright © 2007 Roan Kattouw "<Firstname>.<Lastname>@gmail.com"
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License along
21
 * with this program; if not, write to the Free Software Foundation, Inc.,
22
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23
 * http://www.gnu.org/copyleft/gpl.html
24
 *
25
 * @file
26
 */
27
28
/**
29
 * Query module to enumerate deleted revisions for pages.
30
 *
31
 * @ingroup API
32
 */
33
class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
34
35
	public function __construct( ApiQuery $query, $moduleName ) {
36
		parent::__construct( $query, $moduleName, 'drv' );
37
	}
38
39
	protected function run( ApiPageSet $resultPageSet = null ) {
40
		$user = $this->getUser();
41
		// Before doing anything at all, let's check permissions
42
		if ( !$user->isAllowed( 'deletedhistory' ) ) {
43
			$this->dieUsage(
44
				'You don\'t have permission to view deleted revision information',
45
				'permissiondenied'
46
			);
47
		}
48
49
		$pageSet = $this->getPageSet();
50
		$pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
51
		$pageCount = count( $pageSet->getGoodAndMissingTitles() );
52
		$revCount = $pageSet->getRevisionCount();
53
		if ( $revCount === 0 && $pageCount === 0 ) {
54
			// Nothing to do
55
			return;
56
		}
57
		if ( $revCount !== 0 && count( $pageSet->getDeletedRevisionIDs() ) === 0 ) {
58
			// Nothing to do, revisions were supplied but none are deleted
59
			return;
60
		}
61
62
		$params = $this->extractRequestParams( false );
63
64
		$db = $this->getDB();
65
66 View Code Duplication
		if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
67
			$this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
68
		}
69
70
		$this->addTables( 'archive' );
71
		if ( $resultPageSet === null ) {
72
			$this->parseParameters( $params );
73
			$this->addFields( Revision::selectArchiveFields() );
74
			$this->addFields( [ 'ar_title', 'ar_namespace' ] );
75
		} else {
76
			$this->limit = $this->getParameter( 'limit' ) ?: 10;
77
			$this->addFields( [ 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ] );
78
		}
79
80 View Code Duplication
		if ( $this->fld_tags ) {
81
			$this->addTables( 'tag_summary' );
82
			$this->addJoinConds(
83
				[ 'tag_summary' => [ 'LEFT JOIN', [ 'ar_rev_id=ts_rev_id' ] ] ]
84
			);
85
			$this->addFields( 'ts_tags' );
86
		}
87
88 View Code Duplication
		if ( !is_null( $params['tag'] ) ) {
89
			$this->addTables( 'change_tag' );
90
			$this->addJoinConds(
91
				[ 'change_tag' => [ 'INNER JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
92
			);
93
			$this->addWhereFld( 'ct_tag', $params['tag'] );
94
		}
95
96 View Code Duplication
		if ( $this->fetchContent ) {
97
			// Modern MediaWiki has the content for deleted revs in the 'text'
98
			// table using fields old_text and old_flags. But revisions deleted
99
			// pre-1.5 store the content in the 'archive' table directly using
100
			// fields ar_text and ar_flags, and no corresponding 'text' row. So
101
			// we have to LEFT JOIN and fetch all four fields.
102
			$this->addTables( 'text' );
103
			$this->addJoinConds(
104
				[ 'text' => [ 'LEFT JOIN', [ 'ar_text_id=old_id' ] ] ]
105
			);
106
			$this->addFields( [ 'ar_text', 'ar_flags', 'old_text', 'old_flags' ] );
107
108
			// This also means stricter restrictions
109
			if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
110
				$this->dieUsage(
111
					'You don\'t have permission to view deleted revision content',
112
					'permissiondenied'
113
				);
114
			}
115
		}
116
117
		$dir = $params['dir'];
118
119
		if ( $revCount !== 0 ) {
120
			$this->addWhere( [
121
				'ar_rev_id' => array_keys( $pageSet->getDeletedRevisionIDs() )
122
			] );
123
		} else {
124
			// We need a custom WHERE clause that matches all titles.
125
			$lb = new LinkBatch( $pageSet->getGoodAndMissingTitles() );
126
			$where = $lb->constructSet( 'ar', $db );
127
			$this->addWhere( $where );
0 ignored issues
show
Bug introduced by
It seems like $where defined by $lb->constructSet('ar', $db) on line 126 can also be of type boolean; however, ApiQueryBase::addWhere() does only seem to accept string|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
128
		}
129
130 View Code Duplication
		if ( !is_null( $params['user'] ) ) {
131
			$this->addWhereFld( 'ar_user_text', $params['user'] );
132
		} elseif ( !is_null( $params['excludeuser'] ) ) {
133
			$this->addWhere( 'ar_user_text != ' .
134
				$db->addQuotes( $params['excludeuser'] ) );
135
		}
136
137 View Code Duplication
		if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
138
			// Paranoia: avoid brute force searches (bug 17342)
139
			// (shouldn't be able to get here without 'deletedhistory', but
140
			// check it again just in case)
141
			if ( !$user->isAllowed( 'deletedhistory' ) ) {
142
				$bitmask = Revision::DELETED_USER;
143
			} elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
144
				$bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
145
			} else {
146
				$bitmask = 0;
147
			}
148
			if ( $bitmask ) {
149
				$this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
150
			}
151
		}
152
153
		if ( !is_null( $params['continue'] ) ) {
154
			$cont = explode( '|', $params['continue'] );
155
			$op = ( $dir == 'newer' ? '>' : '<' );
156
			if ( $revCount !== 0 ) {
157
				$this->dieContinueUsageIf( count( $cont ) != 2 );
158
				$rev = intval( $cont[0] );
159
				$this->dieContinueUsageIf( strval( $rev ) !== $cont[0] );
160
				$ar_id = (int)$cont[1];
161
				$this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
162
				$this->addWhere( "ar_rev_id $op $rev OR " .
163
					"(ar_rev_id = $rev AND " .
164
					"ar_id $op= $ar_id)" );
165 View Code Duplication
			} else {
166
				$this->dieContinueUsageIf( count( $cont ) != 4 );
167
				$ns = intval( $cont[0] );
168
				$this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
169
				$title = $db->addQuotes( $cont[1] );
170
				$ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
171
				$ar_id = (int)$cont[3];
172
				$this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
173
				$this->addWhere( "ar_namespace $op $ns OR " .
174
					"(ar_namespace = $ns AND " .
175
					"(ar_title $op $title OR " .
176
					"(ar_title = $title AND " .
177
					"(ar_timestamp $op $ts OR " .
178
					"(ar_timestamp = $ts AND " .
179
					"ar_id $op= $ar_id)))))" );
180
			}
181
		}
182
183
		$this->addOption( 'LIMIT', $this->limit + 1 );
184
185
		if ( $revCount !== 0 ) {
186
			// Sort by ar_rev_id when querying by ar_rev_id
187
			$this->addWhereRange( 'ar_rev_id', $dir, null, null );
188
		} else {
189
			// Sort by ns and title in the same order as timestamp for efficiency
190
			// But only when not already unique in the query
191
			if ( count( $pageMap ) > 1 ) {
192
				$this->addWhereRange( 'ar_namespace', $dir, null, null );
193
			}
194
			$oneTitle = key( reset( $pageMap ) );
195
			foreach ( $pageMap as $pages ) {
196
				if ( count( $pages ) > 1 || key( $pages ) !== $oneTitle ) {
197
					$this->addWhereRange( 'ar_title', $dir, null, null );
198
					break;
199
				}
200
			}
201
			$this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
202
		}
203
		// Include in ORDER BY for uniqueness
204
		$this->addWhereRange( 'ar_id', $dir, null, null );
205
206
		$res = $this->select( __METHOD__ );
207
		$count = 0;
208
		$generated = [];
209
		foreach ( $res as $row ) {
210 View Code Duplication
			if ( ++$count > $this->limit ) {
211
				// We've had enough
212
				$this->setContinueEnumParameter( 'continue',
213
					$revCount
214
						? "$row->ar_rev_id|$row->ar_id"
215
						: "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
216
				);
217
				break;
218
			}
219
220
			if ( $resultPageSet !== null ) {
221
				$generated[] = $row->ar_rev_id;
222
			} else {
223
				if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
224
					// Was it converted?
225
					$title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
226
					$converted = $pageSet->getConvertedTitles();
227
					if ( $title && isset( $converted[$title->getPrefixedText()] ) ) {
228
						$title = Title::newFromText( $converted[$title->getPrefixedText()] );
229
						if ( $title && isset( $pageMap[$title->getNamespace()][$title->getDBkey()] ) ) {
230
							$pageMap[$row->ar_namespace][$row->ar_title] =
231
								$pageMap[$title->getNamespace()][$title->getDBkey()];
232
						}
233
					}
234
				}
235
				if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
236
					ApiBase::dieDebug(
237
						__METHOD__,
238
						"Found row in archive (ar_id={$row->ar_id}) that didn't get processed by ApiPageSet"
239
					);
240
				}
241
242
				$fit = $this->addPageSubItem(
243
					$pageMap[$row->ar_namespace][$row->ar_title],
244
					$this->extractRevisionInfo( Revision::newFromArchiveRow( $row ), $row ),
245
					'rev'
246
				);
247 View Code Duplication
				if ( !$fit ) {
248
					$this->setContinueEnumParameter( 'continue',
249
						$revCount
250
							? "$row->ar_rev_id|$row->ar_id"
251
							: "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
252
					);
253
					break;
254
				}
255
			}
256
		}
257
258
		if ( $resultPageSet !== null ) {
259
			$resultPageSet->populateFromRevisionIDs( $generated );
260
		}
261
	}
262
263
	public function getAllowedParams() {
264
		return parent::getAllowedParams() + [
265
			'start' => [
266
				ApiBase::PARAM_TYPE => 'timestamp',
267
			],
268
			'end' => [
269
				ApiBase::PARAM_TYPE => 'timestamp',
270
			],
271
			'dir' => [
272
				ApiBase::PARAM_TYPE => [
273
					'newer',
274
					'older'
275
				],
276
				ApiBase::PARAM_DFLT => 'older',
277
				ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
278
			],
279
			'tag' => null,
280
			'user' => [
281
				ApiBase::PARAM_TYPE => 'user'
282
			],
283
			'excludeuser' => [
284
				ApiBase::PARAM_TYPE => 'user'
285
			],
286
			'continue' => [
287
				ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
288
			],
289
		];
290
	}
291
292
	protected function getExamplesMessages() {
293
		return [
294
			'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' .
295
				'drvprop=user|comment|content'
296
				=> 'apihelp-query+deletedrevisions-example-titles',
297
			'action=query&prop=deletedrevisions&revids=123456'
298
				=> 'apihelp-query+deletedrevisions-example-revids',
299
		];
300
	}
301
302
	public function getHelpUrls() {
303
		return 'https://www.mediawiki.org/wiki/API:Deletedrevisions';
304
	}
305
}
306