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