wikimedia /
mediawiki
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.