MediaWiki
REL1_21
|
00001 <?php 00029 class LogPager extends ReverseChronologicalPager { 00030 private $types = array(), $performer = '', $title = '', $pattern = ''; 00031 private $typeCGI = ''; 00032 public $mLogEventsList; 00033 00047 public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '', 00048 $conds = array(), $year = false, $month = false, $tagFilter = '' ) { 00049 parent::__construct( $list->getContext() ); 00050 $this->mConds = $conds; 00051 00052 $this->mLogEventsList = $list; 00053 00054 $this->limitType( $types ); // also excludes hidden types 00055 $this->limitPerformer( $performer ); 00056 $this->limitTitle( $title, $pattern ); 00057 $this->getDateCond( $year, $month ); 00058 $this->mTagFilter = $tagFilter; 00059 } 00060 00061 public function getDefaultQuery() { 00062 $query = parent::getDefaultQuery(); 00063 $query['type'] = $this->typeCGI; // arrays won't work here 00064 $query['user'] = $this->performer; 00065 $query['month'] = $this->mMonth; 00066 $query['year'] = $this->mYear; 00067 return $query; 00068 } 00069 00070 // Call ONLY after calling $this->limitType() already! 00071 public function getFilterParams() { 00072 global $wgFilterLogTypes; 00073 $filters = array(); 00074 if( count( $this->types ) ) { 00075 return $filters; 00076 } 00077 foreach( $wgFilterLogTypes as $type => $default ) { 00078 // Avoid silly filtering 00079 if( $type !== 'patrol' || $this->getUser()->useNPPatrol() ) { 00080 $hide = $this->getRequest()->getInt( "hide_{$type}_log", $default ); 00081 $filters[$type] = $hide; 00082 if( $hide ) 00083 $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type ); 00084 } 00085 } 00086 return $filters; 00087 } 00088 00096 private function limitType( $types ) { 00097 global $wgLogRestrictions; 00098 00099 $user = $this->getUser(); 00100 // If $types is not an array, make it an array 00101 $types = ($types === '') ? array() : (array)$types; 00102 // Don't even show header for private logs; don't recognize it... 00103 $needReindex = false; 00104 foreach ( $types as $type ) { 00105 if( isset( $wgLogRestrictions[$type] ) 00106 && !$user->isAllowed( $wgLogRestrictions[$type] ) 00107 ) { 00108 $needReindex = true; 00109 $types = array_diff( $types, array( $type ) ); 00110 } 00111 } 00112 if ( $needReindex ) { 00113 // Lots of this code makes assumptions that 00114 // the first entry in the array is $types[0]. 00115 $types = array_values( $types ); 00116 } 00117 $this->types = $types; 00118 // Don't show private logs to unprivileged users. 00119 // Also, only show them upon specific request to avoid suprises. 00120 $audience = $types ? 'user' : 'public'; 00121 $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience, $user ); 00122 if( $hideLogs !== false ) { 00123 $this->mConds[] = $hideLogs; 00124 } 00125 if( count( $types ) ) { 00126 $this->mConds['log_type'] = $types; 00127 // Set typeCGI; used in url param for paging 00128 if( count( $types ) == 1 ) $this->typeCGI = $types[0]; 00129 } 00130 } 00131 00138 private function limitPerformer( $name ) { 00139 if( $name == '' ) { 00140 return false; 00141 } 00142 $usertitle = Title::makeTitleSafe( NS_USER, $name ); 00143 if( is_null( $usertitle ) ) { 00144 return false; 00145 } 00146 /* Fetch userid at first, if known, provides awesome query plan afterwards */ 00147 $userid = User::idFromName( $name ); 00148 if( !$userid ) { 00149 /* It should be nicer to abort query at all, 00150 but for now it won't pass anywhere behind the optimizer */ 00151 $this->mConds[] = "NULL"; 00152 } else { 00153 $this->mConds['log_user'] = $userid; 00154 // Paranoia: avoid brute force searches (bug 17342) 00155 $user = $this->getUser(); 00156 if( !$user->isAllowed( 'deletedhistory' ) ) { 00157 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::DELETED_USER ) . ' = 0'; 00158 } elseif( !$user->isAllowed( 'suppressrevision' ) ) { 00159 $this->mConds[] = $this->mDb->bitAnd( 'log_deleted', LogPage::SUPPRESSED_USER ) . 00160 ' != ' . LogPage::SUPPRESSED_USER; 00161 } 00162 $this->performer = $usertitle->getText(); 00163 } 00164 } 00165 00174 private function limitTitle( $page, $pattern ) { 00175 global $wgMiserMode; 00176 00177 if ( $page instanceof Title ) { 00178 $title = $page; 00179 } else { 00180 $title = Title::newFromText( $page ); 00181 if( strlen( $page ) == 0 || !$title instanceof Title ) { 00182 return false; 00183 } 00184 } 00185 00186 $this->title = $title->getPrefixedText(); 00187 $ns = $title->getNamespace(); 00188 $db = $this->mDb; 00189 00190 # Using the (log_namespace, log_title, log_timestamp) index with a 00191 # range scan (LIKE) on the first two parts, instead of simple equality, 00192 # makes it unusable for sorting. Sorted retrieval using another index 00193 # would be possible, but then we might have to scan arbitrarily many 00194 # nodes of that index. Therefore, we need to avoid this if $wgMiserMode 00195 # is on. 00196 # 00197 # This is not a problem with simple title matches, because then we can 00198 # use the page_time index. That should have no more than a few hundred 00199 # log entries for even the busiest pages, so it can be safely scanned 00200 # in full to satisfy an impossible condition on user or similar. 00201 if( $pattern && !$wgMiserMode ) { 00202 $this->mConds['log_namespace'] = $ns; 00203 $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ); 00204 $this->pattern = $pattern; 00205 } else { 00206 $this->mConds['log_namespace'] = $ns; 00207 $this->mConds['log_title'] = $title->getDBkey(); 00208 } 00209 // Paranoia: avoid brute force searches (bug 17342) 00210 $user = $this->getUser(); 00211 if( !$user->isAllowed( 'deletedhistory' ) ) { 00212 $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::DELETED_ACTION) . ' = 0'; 00213 } elseif( !$user->isAllowed( 'suppressrevision' ) ) { 00214 $this->mConds[] = $db->bitAnd( 'log_deleted', LogPage::SUPPRESSED_ACTION) . 00215 ' != ' . LogPage::SUPPRESSED_ACTION; 00216 } 00217 } 00218 00224 public function getQueryInfo() { 00225 $basic = DatabaseLogEntry::getSelectQueryData(); 00226 00227 $tables = $basic['tables']; 00228 $fields = $basic['fields']; 00229 $conds = $basic['conds']; 00230 $options = $basic['options']; 00231 $joins = $basic['join_conds']; 00232 00233 $index = array(); 00234 # Add log_search table if there are conditions on it. 00235 # This filters the results to only include log rows that have 00236 # log_search records with the specified ls_field and ls_value values. 00237 if( array_key_exists( 'ls_field', $this->mConds ) ) { 00238 $tables[] = 'log_search'; 00239 $index['log_search'] = 'ls_field_val'; 00240 $index['logging'] = 'PRIMARY'; 00241 if ( !$this->hasEqualsClause( 'ls_field' ) 00242 || !$this->hasEqualsClause( 'ls_value' ) ) 00243 { 00244 # Since (ls_field,ls_value,ls_logid) is unique, if the condition is 00245 # to match a specific (ls_field,ls_value) tuple, then there will be 00246 # no duplicate log rows. Otherwise, we need to remove the duplicates. 00247 $options[] = 'DISTINCT'; 00248 } 00249 # Avoid usage of the wrong index by limiting 00250 # the choices of available indexes. This mainly 00251 # avoids site-breaking filesorts. 00252 } elseif( $this->title || $this->pattern || $this->performer ) { 00253 $index['logging'] = array( 'page_time', 'user_time' ); 00254 if( count( $this->types ) == 1 ) { 00255 $index['logging'][] = 'log_user_type_time'; 00256 } 00257 } elseif( count( $this->types ) == 1 ) { 00258 $index['logging'] = 'type_time'; 00259 } else { 00260 $index['logging'] = 'times'; 00261 } 00262 $options['USE INDEX'] = $index; 00263 # Don't show duplicate rows when using log_search 00264 $joins['log_search'] = array( 'INNER JOIN', 'ls_log_id=log_id' ); 00265 00266 $info = array( 00267 'tables' => $tables, 00268 'fields' => $fields, 00269 'conds' => array_merge( $conds, $this->mConds ), 00270 'options' => $options, 00271 'join_conds' => $joins, 00272 ); 00273 # Add ChangeTags filter query 00274 ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'], 00275 $info['join_conds'], $info['options'], $this->mTagFilter ); 00276 return $info; 00277 } 00278 00284 protected function hasEqualsClause( $field ) { 00285 return ( 00286 array_key_exists( $field, $this->mConds ) && 00287 ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 ) 00288 ); 00289 } 00290 00291 function getIndexField() { 00292 return 'log_timestamp'; 00293 } 00294 00295 public function getStartBody() { 00296 wfProfileIn( __METHOD__ ); 00297 # Do a link batch query 00298 if( $this->getNumRows() > 0 ) { 00299 $lb = new LinkBatch; 00300 foreach ( $this->mResult as $row ) { 00301 $lb->add( $row->log_namespace, $row->log_title ); 00302 $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); 00303 $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); 00304 $formatter = LogFormatter::newFromRow( $row ); 00305 foreach ( $formatter->getPreloadTitles() as $title ) { 00306 $lb->addObj( $title ); 00307 } 00308 } 00309 $lb->execute(); 00310 $this->mResult->seek( 0 ); 00311 } 00312 wfProfileOut( __METHOD__ ); 00313 return ''; 00314 } 00315 00316 public function formatRow( $row ) { 00317 return $this->mLogEventsList->logLine( $row ); 00318 } 00319 00320 public function getType() { 00321 return $this->types; 00322 } 00323 00327 public function getPerformer() { 00328 return $this->performer; 00329 } 00330 00334 public function getPage() { 00335 return $this->title; 00336 } 00337 00338 public function getPattern() { 00339 return $this->pattern; 00340 } 00341 00342 public function getYear() { 00343 return $this->mYear; 00344 } 00345 00346 public function getMonth() { 00347 return $this->mMonth; 00348 } 00349 00350 public function getTagFilter() { 00351 return $this->mTagFilter; 00352 } 00353 00354 public function doQuery() { 00355 // Workaround MySQL optimizer bug 00356 $this->mDb->setBigSelects(); 00357 parent::doQuery(); 00358 $this->mDb->setBigSelects( 'default' ); 00359 } 00360 }