X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Fspecials%2Fpagers%2FContribsPager.php;h=979460cf8aa5d09c0bc870625393f2c9aa50c5bb;hp=e5831089928c202550c4c0a1ea3cba95c2dd5d58;hb=7dbe3faa67045255d092c6a407b7b5ad46c01db1;hpb=0b77984fea493951a4d91c02ce5018a7a24bf82c diff --git a/includes/specials/pagers/ContribsPager.php b/includes/specials/pagers/ContribsPager.php index e583108992..979460cf8a 100644 --- a/includes/specials/pagers/ContribsPager.php +++ b/includes/specials/pagers/ContribsPager.php @@ -28,7 +28,7 @@ use Wikimedia\Rdbms\ResultWrapper; use Wikimedia\Rdbms\FakeResultWrapper; use Wikimedia\Rdbms\IDatabase; -class ContribsPager extends ReverseChronologicalPager { +class ContribsPager extends RangeChronologicalPager { public $mDefaultDirection = IndexPager::DIR_DESCENDING; public $messages; @@ -76,9 +76,20 @@ class ContribsPager extends ReverseChronologicalPager { $this->newOnly = !empty( $options['newOnly'] ); $this->hideMinor = !empty( $options['hideMinor'] ); - $year = isset( $options['year'] ) ? $options['year'] : false; - $month = isset( $options['month'] ) ? $options['month'] : false; - $this->getDateCond( $year, $month ); + // Date filtering: use timestamp if available + $startTimestamp = ''; + $endTimestamp = ''; + if ( $options['start'] ) { + $startTimestamp = $options['start'] . ' 00:00:00'; + } + if ( $options['end'] ) { + $endTimestamp = $options['end'] . ' 23:59:59'; + } + $this->getDateRangeCond( $startTimestamp, $endTimestamp ); + + // This property on IndexPager is set by $this->getIndexField() in parent::__construct(). + // We need to reassign it here so that it is used when the actual query is ran. + $this->mIndexField = $this->getIndexField(); // Most of this code will use the 'contributions' group DB, which can map to replica DBs // with extra user based indexes or partioning by user. The additional metadata @@ -200,6 +211,12 @@ class ContribsPager extends ReverseChronologicalPager { 'join_conds' => $join_cond ]; + // For IPv6, we use ipc_rev_timestamp on ip_changes as the index field, + // which will be referenced when parsing the results of a query. + if ( self::isQueryableRange( $this->target ) ) { + $queryInfo['fields'][] = 'ipc_rev_timestamp'; + } + ChangeTags::modifyDisplayQuery( $queryInfo['tables'], $queryInfo['fields'], @@ -250,8 +267,18 @@ class ContribsPager extends ReverseChronologicalPager { $condition['rev_user'] = $uid; $index = 'user_timestamp'; } else { - $condition['rev_user_text'] = $this->target; - $index = 'usertext_timestamp'; + $ipRangeConds = $this->getIpRangeConds( $this->mDb, $this->target ); + + if ( $ipRangeConds ) { + $tables[] = 'ip_changes'; + $join_conds['ip_changes'] = [ + 'LEFT JOIN', [ 'ipc_rev_id = rev_id' ] + ]; + $condition[] = $ipRangeConds; + } else { + $condition['rev_user_text'] = $this->target; + $index = 'usertext_timestamp'; + } } } @@ -298,8 +325,57 @@ class ContribsPager extends ReverseChronologicalPager { return []; } - function getIndexField() { - return 'rev_timestamp'; + /** + * Get SQL conditions for an IP range, if applicable + * @param IDatabase $db + * @param string $ip The IP address or CIDR + * @return string|false SQL for valid IP ranges, false if invalid + */ + private function getIpRangeConds( $db, $ip ) { + // First make sure it is a valid range and they are not outside the CIDR limit + if ( !$this->isQueryableRange( $ip ) ) { + return false; + } + + list( $start, $end ) = IP::parseRange( $ip ); + + return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ); + } + + /** + * Is the given IP a range and within the CIDR limit? + * + * @param string $ipRange + * @return bool True if it is valid + * @since 1.30 + */ + public function isQueryableRange( $ipRange ) { + $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' ); + + $bits = IP::parseCIDR( $ipRange )[1]; + if ( + ( $bits === false ) || + ( IP::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) || + ( IP::isIPv6( $ipRange ) && $bits < $limits['IPv6'] ) + ) { + return false; + } + + return true; + } + + /** + * Override of getIndexField() in IndexPager. + * For IP ranges, it's faster to use the replicated ipc_rev_timestamp + * on the `ip_changes` table than the rev_timestamp on the `revision` table. + * @return string Name of field + */ + public function getIndexField() { + if ( $this->isQueryableRange( $this->target ) ) { + return 'ipc_rev_timestamp'; + } else { + return 'rev_timestamp'; + } } function doBatchLookups() { @@ -308,6 +384,7 @@ class ContribsPager extends ReverseChronologicalPager { $parentRevIds = []; $this->mParentLens = []; $batch = new LinkBatch(); + $isIpRange = $this->isQueryableRange( $this->target ); # Give some pointers to make (last) links foreach ( $this->mResult as $row ) { if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) { @@ -318,6 +395,9 @@ class ContribsPager extends ReverseChronologicalPager { if ( $this->contribs === 'newbie' ) { // multiple users $batch->add( NS_USER, $row->user_name ); $batch->add( NS_USER_TALK, $row->user_name ); + } elseif ( $isIpRange ) { + // If this is an IP range, batch the IP's talk page + $batch->add( NS_USER_TALK, $row->rev_user_text ); } $batch->add( $row->page_namespace, $row->page_title ); } @@ -358,9 +438,9 @@ class ContribsPager extends ReverseChronologicalPager { * @return string */ function formatRow( $row ) { - $ret = ''; $classes = []; + $attribs = []; $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); @@ -381,7 +461,7 @@ class ContribsPager extends ReverseChronologicalPager { MediaWiki\restoreWarnings(); if ( $validRevision ) { - $classes = []; + $attribs['data-mw-revid'] = $rev->getId(); $page = Title::newFromRow( $row ); $link = $linkRenderer->makeLink( @@ -393,6 +473,7 @@ class ContribsPager extends ReverseChronologicalPager { # Mark current revisions $topmarktext = ''; $user = $this->getUser(); + if ( $row->rev_id === $row->page_latest ) { $topmarktext .= '' . $this->messages['uctop'] . ''; $classes[] = 'mw-contributions-current'; @@ -466,8 +547,10 @@ class ContribsPager extends ReverseChronologicalPager { # Show user names for /newbies as there may be different users. # Note that only unprivileged users have rows with hidden user names excluded. + # When querying for an IP range, we want to always show user and user talk links. $userlink = ''; - if ( $this->contribs == 'newbie' && !$rev->isDeleted( Revision::DELETED_USER ) ) { + if ( ( $this->contribs == 'newbie' && !$rev->isDeleted( Revision::DELETED_USER ) ) + || $this->isQueryableRange( $this->target ) ) { $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() ); $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams( @@ -528,19 +611,21 @@ class ContribsPager extends ReverseChronologicalPager { } // Let extensions add data - Hooks::run( 'ContributionsLineEnding', [ $this, &$ret, $row, &$classes ] ); + Hooks::run( 'ContributionsLineEnding', [ $this, &$ret, $row, &$classes, &$attribs ] ); + $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 'isReservedDataAttribute' ] ); // TODO: Handle exceptions in the catch block above. Do any extensions rely on // receiving empty rows? - if ( $classes === [] && $ret === '' ) { + if ( $classes === [] && $attribs === [] && $ret === '' ) { wfDebug( "Dropping Special:Contribution row that could not be formatted\n" ); return "\n"; } + $attribs['class'] = $classes; // FIXME: The signature of the ContributionsLineEnding hook makes it // very awkward to move this LI wrapper into the template. - return Html::rawElement( 'li', [ 'class' => $classes ], $ret ) . "\n"; + return Html::rawElement( 'li', $attribs, $ret ) . "\n"; } /** @@ -566,4 +651,43 @@ class ContribsPager extends ReverseChronologicalPager { public function getPreventClickjacking() { return $this->preventClickjacking; } + + /** + * Set up date filter options, given request data. + * + * @param array $opts Options array + * @return array Options array with processed start and end date filter options + */ + public static function processDateFilter( $opts ) { + $start = isset( $opts['start'] ) ? $opts['start'] : ''; + $end = isset( $opts['end'] ) ? $opts['end'] : ''; + $year = isset( $opts['year'] ) ? $opts['year'] : ''; + $month = isset( $opts['month'] ) ? $opts['month'] : ''; + + if ( $start !== '' && $end !== '' && $start > $end ) { + $temp = $start; + $start = $end; + $end = $temp; + } + + // If year/month legacy filtering options are set, convert them to display the new stamp + if ( $year !== '' || $month !== '' ) { + // Reuse getDateCond logic, but subtract a day because + // the endpoints of our date range appear inclusive + // but the internal end offsets are always exclusive + $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month ); + $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) ); + $legacyDateTime = $legacyDateTime->modify( '-1 day' ); + + // Clear the new timestamp range options if used and + // replace with the converted legacy timestamp + $start = ''; + $end = $legacyDateTime->format( 'Y-m-d' ); + } + + $opts['start'] = $start; + $opts['end'] = $end; + + return $opts; + } }