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