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