MediaWiki  REL1_23
ApiQueryLogEvents.php
Go to the documentation of this file.
00001 <?php
00032 class ApiQueryLogEvents extends ApiQueryBase {
00033 
00034     public function __construct( $query, $moduleName ) {
00035         parent::__construct( $query, $moduleName, 'le' );
00036     }
00037 
00038     private $fld_ids = false, $fld_title = false, $fld_type = false,
00039         $fld_action = false, $fld_user = false, $fld_userid = false,
00040         $fld_timestamp = false, $fld_comment = false, $fld_parsedcomment = false,
00041         $fld_details = false, $fld_tags = false;
00042 
00043     public function execute() {
00044         $params = $this->extractRequestParams();
00045         $db = $this->getDB();
00046 
00047         $prop = array_flip( $params['prop'] );
00048 
00049         $this->fld_ids = isset( $prop['ids'] );
00050         $this->fld_title = isset( $prop['title'] );
00051         $this->fld_type = isset( $prop['type'] );
00052         $this->fld_action = isset( $prop['action'] );
00053         $this->fld_user = isset( $prop['user'] );
00054         $this->fld_userid = isset( $prop['userid'] );
00055         $this->fld_timestamp = isset( $prop['timestamp'] );
00056         $this->fld_comment = isset( $prop['comment'] );
00057         $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
00058         $this->fld_details = isset( $prop['details'] );
00059         $this->fld_tags = isset( $prop['tags'] );
00060 
00061         $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getUser() );
00062         if ( $hideLogs !== false ) {
00063             $this->addWhere( $hideLogs );
00064         }
00065 
00066         // Order is significant here
00067         $this->addTables( array( 'logging', 'user', 'page' ) );
00068         $this->addJoinConds( array(
00069             'user' => array( 'LEFT JOIN',
00070                 'user_id=log_user' ),
00071             'page' => array( 'LEFT JOIN',
00072                 array( 'log_namespace=page_namespace',
00073                     'log_title=page_title' ) ) ) );
00074 
00075         $this->addFields( array(
00076             'log_id',
00077             'log_type',
00078             'log_action',
00079             'log_timestamp',
00080             'log_deleted',
00081         ) );
00082 
00083         $this->addFieldsIf( 'page_id', $this->fld_ids );
00084         $this->addFieldsIf( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user );
00085         $this->addFieldsIf( 'log_user', $this->fld_userid );
00086         $this->addFieldsIf(
00087             array( 'log_namespace', 'log_title' ),
00088             $this->fld_title || $this->fld_parsedcomment
00089         );
00090         $this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
00091         $this->addFieldsIf( 'log_params', $this->fld_details );
00092 
00093         if ( $this->fld_tags ) {
00094             $this->addTables( 'tag_summary' );
00095             $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', 'log_id=ts_log_id' ) ) );
00096             $this->addFields( 'ts_tags' );
00097         }
00098 
00099         if ( !is_null( $params['tag'] ) ) {
00100             $this->addTables( 'change_tag' );
00101             $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN',
00102                 array( 'log_id=ct_log_id' ) ) ) );
00103             $this->addWhereFld( 'ct_tag', $params['tag'] );
00104         }
00105 
00106         if ( !is_null( $params['action'] ) ) {
00107             // Do validation of action param, list of allowed actions can contains wildcards
00108             // Allow the param, when the actions is in the list or a wildcard version is listed.
00109             $logAction = $params['action'];
00110             if ( strpos( $logAction, '/' ) === false ) {
00111                 // all items in the list have a slash
00112                 $valid = false;
00113             } else {
00114                 $logActions = array_flip( $this->getAllowedLogActions() );
00115                 list( $type, $action ) = explode( '/', $logAction, 2 );
00116                 $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] );
00117             }
00118 
00119             if ( !$valid ) {
00120                 $valueName = $this->encodeParamName( 'action' );
00121                 $this->dieUsage(
00122                     "Unrecognized value for parameter '$valueName': {$logAction}",
00123                     "unknown_$valueName"
00124                 );
00125             }
00126 
00127             $this->addWhereFld( 'log_type', $type );
00128             $this->addWhereFld( 'log_action', $action );
00129         } elseif ( !is_null( $params['type'] ) ) {
00130             $this->addWhereFld( 'log_type', $params['type'] );
00131         }
00132 
00133         $this->addTimestampWhereRange(
00134             'log_timestamp',
00135             $params['dir'],
00136             $params['start'],
00137             $params['end']
00138         );
00139         // Include in ORDER BY for uniqueness
00140         $this->addWhereRange( 'log_id', $params['dir'], null, null );
00141 
00142         if ( !is_null( $params['continue'] ) ) {
00143             $cont = explode( '|', $params['continue'] );
00144             $this->dieContinueUsageIf( count( $cont ) != 2 );
00145             $op = ( $params['dir'] === 'newer' ? '>' : '<' );
00146             $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
00147             $continueId = (int)$cont[1];
00148             $this->dieContinueUsageIf( $continueId != $cont[1] );
00149             $this->addWhere( "log_timestamp $op $continueTimestamp OR " .
00150                 "(log_timestamp = $continueTimestamp AND " .
00151                 "log_id $op= $continueId)"
00152             );
00153         }
00154 
00155         $limit = $params['limit'];
00156         $this->addOption( 'LIMIT', $limit + 1 );
00157 
00158         $user = $params['user'];
00159         if ( !is_null( $user ) ) {
00160             $userid = User::idFromName( $user );
00161             if ( $userid ) {
00162                 $this->addWhereFld( 'log_user', $userid );
00163             } else {
00164                 $this->addWhereFld( 'log_user_text', IP::sanitizeIP( $user ) );
00165             }
00166         }
00167 
00168         $title = $params['title'];
00169         if ( !is_null( $title ) ) {
00170             $titleObj = Title::newFromText( $title );
00171             if ( is_null( $titleObj ) ) {
00172                 $this->dieUsage( "Bad title value '$title'", 'param_title' );
00173             }
00174             $this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
00175             $this->addWhereFld( 'log_title', $titleObj->getDBkey() );
00176         }
00177 
00178         $prefix = $params['prefix'];
00179 
00180         if ( !is_null( $prefix ) ) {
00181             global $wgMiserMode;
00182             if ( $wgMiserMode ) {
00183                 $this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' );
00184             }
00185 
00186             $title = Title::newFromText( $prefix );
00187             if ( is_null( $title ) ) {
00188                 $this->dieUsage( "Bad title value '$prefix'", 'param_prefix' );
00189             }
00190             $this->addWhereFld( 'log_namespace', $title->getNamespace() );
00191             $this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
00192         }
00193 
00194         // Paranoia: avoid brute force searches (bug 17342)
00195         if ( !is_null( $title ) || !is_null( $user ) ) {
00196             if ( !$this->getUser()->isAllowed( 'deletedhistory' ) ) {
00197                 $titleBits = LogPage::DELETED_ACTION;
00198                 $userBits = LogPage::DELETED_USER;
00199             } elseif ( !$this->getUser()->isAllowed( 'suppressrevision' ) ) {
00200                 $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
00201                 $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
00202             } else {
00203                 $titleBits = 0;
00204                 $userBits = 0;
00205             }
00206             if ( !is_null( $title ) && $titleBits ) {
00207                 $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" );
00208             }
00209             if ( !is_null( $user ) && $userBits ) {
00210                 $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" );
00211             }
00212         }
00213 
00214         $count = 0;
00215         $res = $this->select( __METHOD__ );
00216         $result = $this->getResult();
00217         foreach ( $res as $row ) {
00218             if ( ++$count > $limit ) {
00219                 // We've reached the one extra which shows that there are
00220                 // additional pages to be had. Stop here...
00221                 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
00222                 break;
00223             }
00224 
00225             $vals = $this->extractRowInfo( $row );
00226             if ( !$vals ) {
00227                 continue;
00228             }
00229             $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
00230             if ( !$fit ) {
00231                 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
00232                 break;
00233             }
00234         }
00235         $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
00236     }
00237 
00248     public static function addLogParams( $result, &$vals, $params, $type,
00249         $action, $ts, $legacy = false
00250     ) {
00251         switch ( $type ) {
00252             case 'move':
00253                 if ( $legacy ) {
00254                     $targetKey = 0;
00255                     $noredirKey = 1;
00256                 } else {
00257                     $targetKey = '4::target';
00258                     $noredirKey = '5::noredir';
00259                 }
00260 
00261                 if ( isset( $params[$targetKey] ) ) {
00262                     $title = Title::newFromText( $params[$targetKey] );
00263                     if ( $title ) {
00264                         $vals2 = array();
00265                         ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
00266                         $vals[$type] = $vals2;
00267                     }
00268                 }
00269                 if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) {
00270                     $vals[$type]['suppressedredirect'] = '';
00271                 }
00272                 $params = null;
00273                 break;
00274             case 'patrol':
00275                 if ( $legacy ) {
00276                     $cur = 0;
00277                     $prev = 1;
00278                     $auto = 2;
00279                 } else {
00280                     $cur = '4::curid';
00281                     $prev = '5::previd';
00282                     $auto = '6::auto';
00283                 }
00284                 $vals2 = array();
00285                 $vals2['cur'] = $params[$cur];
00286                 $vals2['prev'] = $params[$prev];
00287                 $vals2['auto'] = $params[$auto];
00288                 $vals[$type] = $vals2;
00289                 $params = null;
00290                 break;
00291             case 'rights':
00292                 $vals2 = array();
00293                 if ( $legacy ) {
00294                     list( $vals2['old'], $vals2['new'] ) = $params;
00295                 } else {
00296                     $vals2['new'] = implode( ', ', $params['5::newgroups'] );
00297                     $vals2['old'] = implode( ', ', $params['4::oldgroups'] );
00298                 }
00299                 $vals[$type] = $vals2;
00300                 $params = null;
00301                 break;
00302             case 'block':
00303                 if ( $action == 'unblock' ) {
00304                     break;
00305                 }
00306                 $vals2 = array();
00307                 list( $vals2['duration'], $vals2['flags'] ) = $params;
00308 
00309                 // Indefinite blocks have no expiry time
00310                 if ( SpecialBlock::parseExpiryInput( $params[0] ) !== wfGetDB( DB_SLAVE )->getInfinity() ) {
00311                     $vals2['expiry'] = wfTimestamp( TS_ISO_8601,
00312                         strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) );
00313                 }
00314                 $vals[$type] = $vals2;
00315                 $params = null;
00316                 break;
00317             case 'upload':
00318                 if ( isset( $params['img_timestamp'] ) ) {
00319                     $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] );
00320                 }
00321                 break;
00322         }
00323         if ( !is_null( $params ) ) {
00324             $logParams = array();
00325             // Keys like "4::paramname" can't be used for output so we change them to "paramname"
00326             foreach ( $params as $key => $value ) {
00327                 if ( strpos( $key, ':' ) === false ) {
00328                     $logParams[$key] = $value;
00329                     continue;
00330                 }
00331                 $logParam = explode( ':', $key, 3 );
00332                 $logParams[$logParam[2]] = $value;
00333             }
00334             $result->setIndexedTagName( $logParams, 'param' );
00335             $result->setIndexedTagName_recursive( $logParams, 'param' );
00336             $vals = array_merge( $vals, $logParams );
00337         }
00338 
00339         return $vals;
00340     }
00341 
00342     private function extractRowInfo( $row ) {
00343         $logEntry = DatabaseLogEntry::newFromRow( $row );
00344         $vals = array();
00345         $anyHidden = false;
00346         $user = $this->getUser();
00347 
00348         if ( $this->fld_ids ) {
00349             $vals['logid'] = intval( $row->log_id );
00350         }
00351 
00352         if ( $this->fld_title || $this->fld_parsedcomment ) {
00353             $title = Title::makeTitle( $row->log_namespace, $row->log_title );
00354         }
00355 
00356         if ( $this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '' ) {
00357             if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
00358                 $vals['actionhidden'] = '';
00359                 $anyHidden = true;
00360             }
00361             if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $user ) ) {
00362                 if ( $this->fld_title ) {
00363                     ApiQueryBase::addTitleInfo( $vals, $title );
00364                 }
00365                 if ( $this->fld_ids ) {
00366                     $vals['pageid'] = intval( $row->page_id );
00367                 }
00368                 if ( $this->fld_details && $row->log_params !== '' ) {
00369                     self::addLogParams(
00370                         $this->getResult(),
00371                         $vals,
00372                         $logEntry->getParameters(),
00373                         $logEntry->getType(),
00374                         $logEntry->getSubtype(),
00375                         $logEntry->getTimestamp(),
00376                         $logEntry->isLegacy()
00377                     );
00378                 }
00379             }
00380         }
00381 
00382         if ( $this->fld_type || $this->fld_action ) {
00383             $vals['type'] = $row->log_type;
00384             $vals['action'] = $row->log_action;
00385         }
00386 
00387         if ( $this->fld_user || $this->fld_userid ) {
00388             if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
00389                 $vals['userhidden'] = '';
00390                 $anyHidden = true;
00391             }
00392             if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $user ) ) {
00393                 if ( $this->fld_user ) {
00394                     $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name;
00395                 }
00396                 if ( $this->fld_userid ) {
00397                     $vals['userid'] = $row->log_user;
00398                 }
00399 
00400                 if ( !$row->log_user ) {
00401                     $vals['anon'] = '';
00402                 }
00403             }
00404         }
00405         if ( $this->fld_timestamp ) {
00406             $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp );
00407         }
00408 
00409         if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
00410             if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
00411                 $vals['commenthidden'] = '';
00412                 $anyHidden = true;
00413             }
00414             if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) {
00415                 if ( $this->fld_comment ) {
00416                     $vals['comment'] = $row->log_comment;
00417                 }
00418 
00419                 if ( $this->fld_parsedcomment ) {
00420                     $vals['parsedcomment'] = Linker::formatComment( $row->log_comment, $title );
00421                 }
00422             }
00423         }
00424 
00425         if ( $this->fld_tags ) {
00426             if ( $row->ts_tags ) {
00427                 $tags = explode( ',', $row->ts_tags );
00428                 $this->getResult()->setIndexedTagName( $tags, 'tag' );
00429                 $vals['tags'] = $tags;
00430             } else {
00431                 $vals['tags'] = array();
00432             }
00433         }
00434 
00435         if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
00436             $vals['suppressed'] = '';
00437         }
00438 
00439         return $vals;
00440     }
00441 
00442     private function getAllowedLogActions() {
00443         global $wgLogActions, $wgLogActionsHandlers;
00444 
00445         return array_keys( array_merge( $wgLogActions, $wgLogActionsHandlers ) );
00446     }
00447 
00448     public function getCacheMode( $params ) {
00449         if ( $this->userCanSeeRevDel() ) {
00450             return 'private';
00451         }
00452         if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
00453             // formatComment() calls wfMessage() among other things
00454             return 'anon-public-user-private';
00455         } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getUser() )
00456             === LogEventsList::getExcludeClause( $this->getDB(), 'public' )
00457         ) { // Output can only contain public data.
00458             return 'public';
00459         } else {
00460             return 'anon-public-user-private';
00461         }
00462     }
00463 
00464     public function getAllowedParams( $flags = 0 ) {
00465         global $wgLogTypes;
00466 
00467         return array(
00468             'prop' => array(
00469                 ApiBase::PARAM_ISMULTI => true,
00470                 ApiBase::PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details',
00471                 ApiBase::PARAM_TYPE => array(
00472                     'ids',
00473                     'title',
00474                     'type',
00475                     'user',
00476                     'userid',
00477                     'timestamp',
00478                     'comment',
00479                     'parsedcomment',
00480                     'details',
00481                     'tags'
00482                 )
00483             ),
00484             'type' => array(
00485                 ApiBase::PARAM_TYPE => $wgLogTypes
00486             ),
00487             'action' => array(
00488                 // validation on request is done in execute()
00489                 ApiBase::PARAM_TYPE => ( $flags & ApiBase::GET_VALUES_FOR_HELP )
00490                     ? $this->getAllowedLogActions()
00491                     : null
00492             ),
00493             'start' => array(
00494                 ApiBase::PARAM_TYPE => 'timestamp'
00495             ),
00496             'end' => array(
00497                 ApiBase::PARAM_TYPE => 'timestamp'
00498             ),
00499             'dir' => array(
00500                 ApiBase::PARAM_DFLT => 'older',
00501                 ApiBase::PARAM_TYPE => array(
00502                     'newer',
00503                     'older'
00504                 )
00505             ),
00506             'user' => null,
00507             'title' => null,
00508             'prefix' => null,
00509             'tag' => null,
00510             'limit' => array(
00511                 ApiBase::PARAM_DFLT => 10,
00512                 ApiBase::PARAM_TYPE => 'limit',
00513                 ApiBase::PARAM_MIN => 1,
00514                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00515                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00516             ),
00517             'continue' => null,
00518         );
00519     }
00520 
00521     public function getParamDescription() {
00522         $p = $this->getModulePrefix();
00523 
00524         return array(
00525             'prop' => array(
00526                 'Which properties to get',
00527                 ' ids            - Adds the ID of the log event',
00528                 ' title          - Adds the title of the page for the log event',
00529                 ' type           - Adds the type of log event',
00530                 ' user           - Adds the user responsible for the log event',
00531                 ' userid         - Adds the user ID who was responsible for the log event',
00532                 ' timestamp      - Adds the timestamp for the event',
00533                 ' comment        - Adds the comment of the event',
00534                 ' parsedcomment  - Adds the parsed comment of the event',
00535                 ' details        - Lists additional details about the event',
00536                 ' tags           - Lists tags for the event',
00537             ),
00538             'type' => 'Filter log entries to only this type',
00539             'action' => array(
00540                 "Filter log actions to only this action. Overrides {$p}type",
00541                 "Wildcard actions like 'action/*' allows to specify any string for the asterisk"
00542             ),
00543             'start' => 'The timestamp to start enumerating from',
00544             'end' => 'The timestamp to end enumerating',
00545             'dir' => $this->getDirectionDescription( $p ),
00546             'user' => 'Filter entries to those made by the given user',
00547             'title' => 'Filter entries to those related to a page',
00548             'prefix' => 'Filter entries that start with this prefix. Disabled in Miser Mode',
00549             'limit' => 'How many total event entries to return',
00550             'tag' => 'Only list event entries tagged with this tag',
00551             'continue' => 'When more results are available, use this to continue',
00552         );
00553     }
00554 
00555     public function getResultProperties() {
00556         global $wgLogTypes;
00557 
00558         return array(
00559             'ids' => array(
00560                 'logid' => 'integer',
00561                 'pageid' => 'integer'
00562             ),
00563             'title' => array(
00564                 'ns' => 'namespace',
00565                 'title' => 'string'
00566             ),
00567             'type' => array(
00568                 'type' => array(
00569                     ApiBase::PROP_TYPE => $wgLogTypes
00570                 ),
00571                 'action' => 'string'
00572             ),
00573             'details' => array(
00574                 'actionhidden' => 'boolean'
00575             ),
00576             'user' => array(
00577                 'userhidden' => 'boolean',
00578                 'user' => array(
00579                     ApiBase::PROP_TYPE => 'string',
00580                     ApiBase::PROP_NULLABLE => true
00581                 ),
00582                 'anon' => 'boolean'
00583             ),
00584             'userid' => array(
00585                 'userhidden' => 'boolean',
00586                 'userid' => array(
00587                     ApiBase::PROP_TYPE => 'integer',
00588                     ApiBase::PROP_NULLABLE => true
00589                 ),
00590                 'anon' => 'boolean'
00591             ),
00592             'timestamp' => array(
00593                 'timestamp' => 'timestamp'
00594             ),
00595             'comment' => array(
00596                 'commenthidden' => 'boolean',
00597                 'comment' => array(
00598                     ApiBase::PROP_TYPE => 'string',
00599                     ApiBase::PROP_NULLABLE => true
00600                 )
00601             ),
00602             'parsedcomment' => array(
00603                 'commenthidden' => 'boolean',
00604                 'parsedcomment' => array(
00605                     ApiBase::PROP_TYPE => 'string',
00606                     ApiBase::PROP_NULLABLE => true
00607                 )
00608             )
00609         );
00610     }
00611 
00612     public function getDescription() {
00613         return 'Get events from logs.';
00614     }
00615 
00616     public function getPossibleErrors() {
00617         return array_merge( parent::getPossibleErrors(), array(
00618             array( 'code' => 'param_user', 'info' => 'User name $user not found' ),
00619             array( 'code' => 'param_title', 'info' => 'Bad title value \'title\'' ),
00620             array( 'code' => 'param_prefix', 'info' => 'Bad title value \'prefix\'' ),
00621             array( 'code' => 'prefixsearchdisabled', 'info' => 'Prefix search disabled in Miser Mode' ),
00622         ) );
00623     }
00624 
00625     public function getExamples() {
00626         return array(
00627             'api.php?action=query&list=logevents'
00628         );
00629     }
00630 
00631     public function getHelpUrls() {
00632         return 'https://www.mediawiki.org/wiki/API:Logevents';
00633     }
00634 }