[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Oct 16, 2006 6 * 7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 */ 26 27 /** 28 * Query action to List the log events, with optional filtering by various parameters. 29 * 30 * @ingroup API 31 */ 32 class ApiQueryLogEvents extends ApiQueryBase { 33 34 public function __construct( ApiQuery $query, $moduleName ) { 35 parent::__construct( $query, $moduleName, 'le' ); 36 } 37 38 private $fld_ids = false, $fld_title = false, $fld_type = false, 39 $fld_action = false, $fld_user = false, $fld_userid = false, 40 $fld_timestamp = false, $fld_comment = false, $fld_parsedcomment = false, 41 $fld_details = false, $fld_tags = false; 42 43 public function execute() { 44 $params = $this->extractRequestParams(); 45 $db = $this->getDB(); 46 $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' ); 47 48 $prop = array_flip( $params['prop'] ); 49 50 $this->fld_ids = isset( $prop['ids'] ); 51 $this->fld_title = isset( $prop['title'] ); 52 $this->fld_type = isset( $prop['type'] ); 53 $this->fld_action = isset( $prop['action'] ); 54 $this->fld_user = isset( $prop['user'] ); 55 $this->fld_userid = isset( $prop['userid'] ); 56 $this->fld_timestamp = isset( $prop['timestamp'] ); 57 $this->fld_comment = isset( $prop['comment'] ); 58 $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); 59 $this->fld_details = isset( $prop['details'] ); 60 $this->fld_tags = isset( $prop['tags'] ); 61 62 $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getUser() ); 63 if ( $hideLogs !== false ) { 64 $this->addWhere( $hideLogs ); 65 } 66 67 // Order is significant here 68 $this->addTables( array( 'logging', 'user', 'page' ) ); 69 $this->addJoinConds( array( 70 'user' => array( 'LEFT JOIN', 71 'user_id=log_user' ), 72 'page' => array( 'LEFT JOIN', 73 array( 'log_namespace=page_namespace', 74 'log_title=page_title' ) ) ) ); 75 76 $this->addFields( array( 77 'log_id', 78 'log_type', 79 'log_action', 80 'log_timestamp', 81 'log_deleted', 82 ) ); 83 84 $this->addFieldsIf( 'page_id', $this->fld_ids ); 85 // log_page is the page_id saved at log time, whereas page_id is from a 86 // join at query time. This leads to different results in various 87 // scenarios, e.g. deletion, recreation. 88 $this->addFieldsIf( 'log_page', $this->fld_ids ); 89 $this->addFieldsIf( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user ); 90 $this->addFieldsIf( 'log_user', $this->fld_userid ); 91 $this->addFieldsIf( 92 array( 'log_namespace', 'log_title' ), 93 $this->fld_title || $this->fld_parsedcomment 94 ); 95 $this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment ); 96 $this->addFieldsIf( 'log_params', $this->fld_details ); 97 98 if ( $this->fld_tags ) { 99 $this->addTables( 'tag_summary' ); 100 $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', 'log_id=ts_log_id' ) ) ); 101 $this->addFields( 'ts_tags' ); 102 } 103 104 if ( !is_null( $params['tag'] ) ) { 105 $this->addTables( 'change_tag' ); 106 $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', 107 array( 'log_id=ct_log_id' ) ) ) ); 108 $this->addWhereFld( 'ct_tag', $params['tag'] ); 109 } 110 111 if ( !is_null( $params['action'] ) ) { 112 // Do validation of action param, list of allowed actions can contains wildcards 113 // Allow the param, when the actions is in the list or a wildcard version is listed. 114 $logAction = $params['action']; 115 if ( strpos( $logAction, '/' ) === false ) { 116 // all items in the list have a slash 117 $valid = false; 118 } else { 119 $logActions = array_flip( $this->getAllowedLogActions() ); 120 list( $type, $action ) = explode( '/', $logAction, 2 ); 121 $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] ); 122 } 123 124 if ( !$valid ) { 125 $valueName = $this->encodeParamName( 'action' ); 126 $this->dieUsage( 127 "Unrecognized value for parameter '$valueName': {$logAction}", 128 "unknown_$valueName" 129 ); 130 } 131 132 $this->addWhereFld( 'log_type', $type ); 133 $this->addWhereFld( 'log_action', $action ); 134 } elseif ( !is_null( $params['type'] ) ) { 135 $this->addWhereFld( 'log_type', $params['type'] ); 136 } 137 138 $this->addTimestampWhereRange( 139 'log_timestamp', 140 $params['dir'], 141 $params['start'], 142 $params['end'] 143 ); 144 // Include in ORDER BY for uniqueness 145 $this->addWhereRange( 'log_id', $params['dir'], null, null ); 146 147 if ( !is_null( $params['continue'] ) ) { 148 $cont = explode( '|', $params['continue'] ); 149 $this->dieContinueUsageIf( count( $cont ) != 2 ); 150 $op = ( $params['dir'] === 'newer' ? '>' : '<' ); 151 $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) ); 152 $continueId = (int)$cont[1]; 153 $this->dieContinueUsageIf( $continueId != $cont[1] ); 154 $this->addWhere( "log_timestamp $op $continueTimestamp OR " . 155 "(log_timestamp = $continueTimestamp AND " . 156 "log_id $op= $continueId)" 157 ); 158 } 159 160 $limit = $params['limit']; 161 $this->addOption( 'LIMIT', $limit + 1 ); 162 163 $user = $params['user']; 164 if ( !is_null( $user ) ) { 165 $userid = User::idFromName( $user ); 166 if ( $userid ) { 167 $this->addWhereFld( 'log_user', $userid ); 168 } else { 169 $this->addWhereFld( 'log_user_text', IP::sanitizeIP( $user ) ); 170 } 171 } 172 173 $title = $params['title']; 174 if ( !is_null( $title ) ) { 175 $titleObj = Title::newFromText( $title ); 176 if ( is_null( $titleObj ) ) { 177 $this->dieUsage( "Bad title value '$title'", 'param_title' ); 178 } 179 $this->addWhereFld( 'log_namespace', $titleObj->getNamespace() ); 180 $this->addWhereFld( 'log_title', $titleObj->getDBkey() ); 181 } 182 183 if ( $params['namespace'] !== null ) { 184 $this->addWhereFld( 'log_namespace', $params['namespace'] ); 185 } 186 187 $prefix = $params['prefix']; 188 189 if ( !is_null( $prefix ) ) { 190 if ( $this->getConfig()->get( 'MiserMode' ) ) { 191 $this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' ); 192 } 193 194 $title = Title::newFromText( $prefix ); 195 if ( is_null( $title ) ) { 196 $this->dieUsage( "Bad title value '$prefix'", 'param_prefix' ); 197 } 198 $this->addWhereFld( 'log_namespace', $title->getNamespace() ); 199 $this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) ); 200 } 201 202 // Paranoia: avoid brute force searches (bug 17342) 203 $hideActions = $params['namespace'] !== null || !is_null( $title ) || !is_null( $params['action'] ); 204 if ( $hideActions || !is_null( $user ) ) { 205 if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) { 206 $titleBits = LogPage::DELETED_ACTION; 207 $userBits = LogPage::DELETED_USER; 208 } elseif ( !$this->getUser()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) { 209 $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED; 210 $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED; 211 } else { 212 $titleBits = 0; 213 $userBits = 0; 214 } 215 if ( $hideActions && $titleBits ) { 216 $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" ); 217 } 218 if ( !is_null( $user ) && $userBits ) { 219 $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" ); 220 } 221 } 222 223 $count = 0; 224 $res = $this->select( __METHOD__ ); 225 $result = $this->getResult(); 226 foreach ( $res as $row ) { 227 if ( ++$count > $limit ) { 228 // We've reached the one extra which shows that there are 229 // additional pages to be had. Stop here... 230 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" ); 231 break; 232 } 233 234 $vals = $this->extractRowInfo( $row ); 235 if ( !$vals ) { 236 continue; 237 } 238 $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); 239 if ( !$fit ) { 240 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" ); 241 break; 242 } 243 } 244 $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' ); 245 } 246 247 /** 248 * @param ApiResult $result 249 * @param array $vals 250 * @param string $params 251 * @param string $type 252 * @param string $action 253 * @param string $ts 254 * @param bool $legacy 255 * @return array 256 */ 257 public static function addLogParams( $result, &$vals, $params, $type, 258 $action, $ts, $legacy = false 259 ) { 260 switch ( $type ) { 261 case 'move': 262 if ( $legacy ) { 263 $targetKey = 0; 264 $noredirKey = 1; 265 } else { 266 $targetKey = '4::target'; 267 $noredirKey = '5::noredir'; 268 } 269 270 if ( isset( $params[$targetKey] ) ) { 271 $title = Title::newFromText( $params[$targetKey] ); 272 if ( $title ) { 273 $vals2 = array(); 274 ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' ); 275 $vals[$type] = $vals2; 276 } 277 } 278 if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) { 279 $vals[$type]['suppressedredirect'] = ''; 280 } 281 $params = null; 282 break; 283 case 'patrol': 284 if ( $legacy ) { 285 $cur = 0; 286 $prev = 1; 287 $auto = 2; 288 } else { 289 $cur = '4::curid'; 290 $prev = '5::previd'; 291 $auto = '6::auto'; 292 } 293 $vals2 = array(); 294 $vals2['cur'] = $params[$cur]; 295 $vals2['prev'] = $params[$prev]; 296 $vals2['auto'] = $params[$auto]; 297 $vals[$type] = $vals2; 298 $params = null; 299 break; 300 case 'rights': 301 $vals2 = array(); 302 if ( $legacy ) { 303 list( $vals2['old'], $vals2['new'] ) = $params; 304 } else { 305 $vals2['new'] = implode( ', ', $params['5::newgroups'] ); 306 $vals2['old'] = implode( ', ', $params['4::oldgroups'] ); 307 } 308 $vals[$type] = $vals2; 309 $params = null; 310 break; 311 case 'block': 312 if ( $action == 'unblock' ) { 313 break; 314 } 315 $vals2 = array(); 316 list( $vals2['duration'], $vals2['flags'] ) = $params; 317 318 // Indefinite blocks have no expiry time 319 if ( SpecialBlock::parseExpiryInput( $params[0] ) !== wfGetDB( DB_SLAVE )->getInfinity() ) { 320 $vals2['expiry'] = wfTimestamp( TS_ISO_8601, 321 strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) ); 322 } 323 $vals[$type] = $vals2; 324 $params = null; 325 break; 326 case 'upload': 327 if ( isset( $params['img_timestamp'] ) ) { 328 $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] ); 329 } 330 break; 331 } 332 if ( !is_null( $params ) ) { 333 $logParams = array(); 334 // Keys like "4::paramname" can't be used for output so we change them to "paramname" 335 foreach ( $params as $key => $value ) { 336 if ( strpos( $key, ':' ) === false ) { 337 $logParams[$key] = $value; 338 continue; 339 } 340 $logParam = explode( ':', $key, 3 ); 341 $logParams[$logParam[2]] = $value; 342 } 343 $result->setIndexedTagName( $logParams, 'param' ); 344 $result->setIndexedTagName_recursive( $logParams, 'param' ); 345 $vals = array_merge( $vals, $logParams ); 346 } 347 348 return $vals; 349 } 350 351 private function extractRowInfo( $row ) { 352 $logEntry = DatabaseLogEntry::newFromRow( $row ); 353 $vals = array(); 354 $anyHidden = false; 355 $user = $this->getUser(); 356 357 if ( $this->fld_ids ) { 358 $vals['logid'] = intval( $row->log_id ); 359 } 360 361 if ( $this->fld_title || $this->fld_parsedcomment ) { 362 $title = Title::makeTitle( $row->log_namespace, $row->log_title ); 363 } 364 365 if ( $this->fld_title || $this->fld_ids || $this->fld_type 366 || $this->fld_details && $row->log_params !== '' 367 ) { 368 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) { 369 $vals['actionhidden'] = ''; 370 $anyHidden = true; 371 } 372 if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $user ) ) { 373 374 if ( $this->fld_type ) { 375 $vals['action'] = $row->log_action; 376 } 377 if ( $this->fld_title ) { 378 ApiQueryBase::addTitleInfo( $vals, $title ); 379 } 380 if ( $this->fld_ids ) { 381 $vals['pageid'] = intval( $row->page_id ); 382 $vals['logpage'] = intval( $row->log_page ); 383 } 384 if ( $this->fld_details && $row->log_params !== '' ) { 385 self::addLogParams( 386 $this->getResult(), 387 $vals, 388 $logEntry->getParameters(), 389 $logEntry->getType(), 390 $logEntry->getSubtype(), 391 $logEntry->getTimestamp(), 392 $logEntry->isLegacy() 393 ); 394 } 395 } 396 } 397 398 if ( $this->fld_type ) { 399 $vals['type'] = $row->log_type; 400 } 401 402 if ( $this->fld_user || $this->fld_userid ) { 403 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) { 404 $vals['userhidden'] = ''; 405 $anyHidden = true; 406 } 407 if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $user ) ) { 408 if ( $this->fld_user ) { 409 $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name; 410 } 411 if ( $this->fld_userid ) { 412 $vals['userid'] = intval( $row->log_user ); 413 } 414 415 if ( !$row->log_user ) { 416 $vals['anon'] = ''; 417 } 418 } 419 } 420 if ( $this->fld_timestamp ) { 421 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp ); 422 } 423 424 if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) { 425 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) { 426 $vals['commenthidden'] = ''; 427 $anyHidden = true; 428 } 429 if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) { 430 if ( $this->fld_comment ) { 431 $vals['comment'] = $row->log_comment; 432 } 433 434 if ( $this->fld_parsedcomment ) { 435 $vals['parsedcomment'] = Linker::formatComment( $row->log_comment, $title ); 436 } 437 } 438 } 439 440 if ( $this->fld_tags ) { 441 if ( $row->ts_tags ) { 442 $tags = explode( ',', $row->ts_tags ); 443 $this->getResult()->setIndexedTagName( $tags, 'tag' ); 444 $vals['tags'] = $tags; 445 } else { 446 $vals['tags'] = array(); 447 } 448 } 449 450 if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) { 451 $vals['suppressed'] = ''; 452 } 453 454 return $vals; 455 } 456 457 /** 458 * @return array 459 */ 460 private function getAllowedLogActions() { 461 $config = $this->getConfig(); 462 return array_keys( array_merge( $config->get( 'LogActions' ), $config->get( 'LogActionsHandlers' ) ) ); 463 } 464 465 public function getCacheMode( $params ) { 466 if ( $this->userCanSeeRevDel() ) { 467 return 'private'; 468 } 469 if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) { 470 // formatComment() calls wfMessage() among other things 471 return 'anon-public-user-private'; 472 } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getUser() ) 473 === LogEventsList::getExcludeClause( $this->getDB(), 'public' ) 474 ) { // Output can only contain public data. 475 return 'public'; 476 } else { 477 return 'anon-public-user-private'; 478 } 479 } 480 481 public function getAllowedParams( $flags = 0 ) { 482 $config = $this->getConfig(); 483 return array( 484 'prop' => array( 485 ApiBase::PARAM_ISMULTI => true, 486 ApiBase::PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details', 487 ApiBase::PARAM_TYPE => array( 488 'ids', 489 'title', 490 'type', 491 'user', 492 'userid', 493 'timestamp', 494 'comment', 495 'parsedcomment', 496 'details', 497 'tags' 498 ) 499 ), 500 'type' => array( 501 ApiBase::PARAM_TYPE => $config->get( 'LogTypes' ) 502 ), 503 'action' => array( 504 // validation on request is done in execute() 505 ApiBase::PARAM_TYPE => ( $flags & ApiBase::GET_VALUES_FOR_HELP ) 506 ? $this->getAllowedLogActions() 507 : null 508 ), 509 'start' => array( 510 ApiBase::PARAM_TYPE => 'timestamp' 511 ), 512 'end' => array( 513 ApiBase::PARAM_TYPE => 'timestamp' 514 ), 515 'dir' => array( 516 ApiBase::PARAM_DFLT => 'older', 517 ApiBase::PARAM_TYPE => array( 518 'newer', 519 'older' 520 ) 521 ), 522 'user' => null, 523 'title' => null, 524 'namespace' => array( 525 ApiBase::PARAM_TYPE => 'namespace' 526 ), 527 'prefix' => null, 528 'tag' => null, 529 'limit' => array( 530 ApiBase::PARAM_DFLT => 10, 531 ApiBase::PARAM_TYPE => 'limit', 532 ApiBase::PARAM_MIN => 1, 533 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 534 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 535 ), 536 'continue' => null, 537 ); 538 } 539 540 public function getParamDescription() { 541 $p = $this->getModulePrefix(); 542 543 return array( 544 'prop' => array( 545 'Which properties to get', 546 ' ids - Adds the ID of the log event', 547 ' title - Adds the title of the page for the log event', 548 ' type - Adds the type of log event', 549 ' user - Adds the user responsible for the log event', 550 ' userid - Adds the user ID who was responsible for the log event', 551 ' timestamp - Adds the timestamp for the event', 552 ' comment - Adds the comment of the event', 553 ' parsedcomment - Adds the parsed comment of the event', 554 ' details - Lists additional details about the event', 555 ' tags - Lists tags for the event', 556 ), 557 'type' => 'Filter log entries to only this type', 558 'action' => array( 559 "Filter log actions to only this action. Overrides {$p}type", 560 "Wildcard actions like 'action/*' allows to specify any string for the asterisk" 561 ), 562 'start' => 'The timestamp to start enumerating from', 563 'end' => 'The timestamp to end enumerating', 564 'dir' => $this->getDirectionDescription( $p ), 565 'user' => 'Filter entries to those made by the given user', 566 'title' => 'Filter entries to those related to a page', 567 'namespace' => 'Filter entries to those in the given namespace', 568 'prefix' => 'Filter entries that start with this prefix. Disabled in Miser Mode', 569 'limit' => 'How many total event entries to return', 570 'tag' => 'Only list event entries tagged with this tag', 571 'continue' => 'When more results are available, use this to continue', 572 ); 573 } 574 575 public function getDescription() { 576 return 'Get events from logs.'; 577 } 578 579 public function getExamples() { 580 return array( 581 'api.php?action=query&list=logevents' 582 ); 583 } 584 585 public function getHelpUrls() { 586 return 'https://www.mediawiki.org/wiki/API:Logevents'; 587 } 588 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |