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