This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
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 |
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:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.