MediaWiki master
ContribsPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use DateTime;
36use Wikimedia\IPUtils;
40
51
65 public function __construct(
66 IContextSource $context,
67 array $options,
68 LinkRenderer $linkRenderer = null,
69 LinkBatchFactory $linkBatchFactory = null,
70 HookContainer $hookContainer = null,
71 IConnectionProvider $dbProvider = null,
73 NamespaceInfo $namespaceInfo = null,
75 CommentFormatter $commentFormatter = null
76 ) {
77 // Class is used directly in extensions - T266484
79 $dbProvider ??= $services->getConnectionProvider();
80
81 parent::__construct(
82 $linkRenderer ?? $services->getLinkRenderer(),
83 $linkBatchFactory ?? $services->getLinkBatchFactory(),
84 $hookContainer ?? $services->getHookContainer(),
85 $revisionStore ?? $services->getRevisionStore(),
86 $namespaceInfo ?? $services->getNamespaceInfo(),
87 $commentFormatter ?? $services->getCommentFormatter(),
88 $services->getUserFactory(),
89 $context,
90 $options,
92 );
93 }
94
104 private function getTargetTable() {
105 $dbr = $this->getDatabase();
106 $ipRangeConds = $this->targetUser->isRegistered()
107 ? null : $this->getIpRangeConds( $dbr, $this->target );
108 if ( $ipRangeConds ) {
109 return 'ip_changes';
110 }
111
112 return 'revision';
113 }
114
115 protected function getRevisionQuery() {
116 $revQuery = $this->revisionStore->getQueryInfo( [ 'page', 'user' ] );
117 $queryInfo = [
118 'tables' => $revQuery['tables'],
119 'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
120 'conds' => [],
121 'options' => [],
122 'join_conds' => $revQuery['joins'],
123 ];
124
125 // WARNING: Keep this in sync with getTargetTable()!
126 $ipRangeConds = !$this->targetUser->isRegistered() ?
127 $this->getIpRangeConds( $this->getDatabase(), $this->target ) :
128 null;
129 if ( $ipRangeConds ) {
130 // Put ip_changes first (T284419)
131 array_unshift( $queryInfo['tables'], 'ip_changes' );
132 $queryInfo['join_conds']['revision'] = [
133 'JOIN', [ 'rev_id = ipc_rev_id' ]
134 ];
135 $queryInfo['conds'][] = $ipRangeConds;
136 } else {
137 $queryInfo['conds']['actor_name'] = $this->targetUser->getName();
138 // Force the appropriate index to avoid bad query plans (T307295)
139 $queryInfo['options']['USE INDEX']['revision'] = 'rev_actor_timestamp';
140 }
141
142 return $queryInfo;
143 }
144
151 private function getIpRangeConds( $db, $ip ) {
152 // First make sure it is a valid range and they are not outside the CIDR limit
153 if ( !self::isQueryableRange( $ip, $this->getConfig() ) ) {
154 return false;
155 }
156
157 [ $start, $end ] = IPUtils::parseRange( $ip );
158
159 return $db->expr( 'ipc_hex', '>=', $start )->and( 'ipc_hex', '<=', $end );
160 }
161
171 public static function isQueryableRange( $ipRange, $config ) {
172 $limits = $config->get( MainConfigNames::RangeContributionsCIDRLimit );
173
174 $bits = IPUtils::parseCIDR( $ipRange )[1];
175 if (
176 ( $bits === false ) ||
177 ( IPUtils::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
178 ( IPUtils::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
179 ) {
180 return false;
181 }
182
183 return true;
184 }
185
189 public function getIndexField() {
190 // The returned column is used for sorting and continuation, so we need to
191 // make sure to use the right denormalized column depending on which table is
192 // being targeted by the query to avoid bad query plans.
193 // See T200259, T204669, T220991, and T221380.
194 $target = $this->getTargetTable();
195 switch ( $target ) {
196 case 'revision':
197 return 'rev_timestamp';
198 case 'ip_changes':
199 return 'ipc_rev_timestamp';
200 default:
201 wfWarn(
202 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
203 );
204 return 'rev_timestamp';
205 }
206 }
207
211 protected function getExtraSortFields() {
212 // The returned columns are used for sorting, so we need to make sure
213 // to use the right denormalized column depending on which table is
214 // being targeted by the query to avoid bad query plans.
215 // See T200259, T204669, T220991, and T221380.
216 $target = $this->getTargetTable();
217 switch ( $target ) {
218 case 'revision':
219 return [ 'rev_id' ];
220 case 'ip_changes':
221 return [ 'ipc_rev_id' ];
222 default:
223 wfWarn(
224 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
225 );
226 return [ 'rev_id' ];
227 }
228 }
229
236 public static function processDateFilter( array $opts ) {
237 $start = $opts['start'] ?? '';
238 $end = $opts['end'] ?? '';
239 $year = $opts['year'] ?? '';
240 $month = $opts['month'] ?? '';
241
242 if ( $start !== '' && $end !== '' && $start > $end ) {
243 $temp = $start;
244 $start = $end;
245 $end = $temp;
246 }
247
248 // If year/month legacy filtering options are set, convert them to display the new stamp
249 if ( $year !== '' || $month !== '' ) {
250 // Reuse getDateCond logic, but subtract a day because
251 // the endpoints of our date range appear inclusive
252 // but the internal end offsets are always exclusive
253 $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
254 $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
255 $legacyDateTime = $legacyDateTime->modify( '-1 day' );
256
257 // Clear the new timestamp range options if used and
258 // replace with the converted legacy timestamp
259 $start = '';
260 $end = $legacyDateTime->format( 'Y-m-d' );
261 }
262
263 $opts['start'] = $start;
264 $opts['end'] = $end;
265
266 return $opts;
267 }
268}
269
274class_alias( ContribsPager::class, 'ContribsPager' );
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
This is the main service interface for converting single-line comments from various DB comment fields...
Class that generates HTML for internal links.
A class containing constants representing the names of configuration variables.
const RangeContributionsCIDRLimit
Name constant for the RangeContributionsCIDRLimit setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Pager for Special:Contributions.
static isQueryableRange( $ipRange, $config)
Is the given IP a range and within the CIDR limit?
getRevisionQuery()
Get queryInfo for the main query selecting revisions, not including filtering on namespace,...
static processDateFilter(array $opts)
Set up date filter options, given request data.
__construct(IContextSource $context, array $options, LinkRenderer $linkRenderer=null, LinkBatchFactory $linkBatchFactory=null, HookContainer $hookContainer=null, IConnectionProvider $dbProvider=null, RevisionStore $revisionStore=null, NamespaceInfo $namespaceInfo=null, UserIdentity $targetUser=null, CommentFormatter $commentFormatter=null)
FIXME List services first T266484 / T290405.
Pager for Special:Contributions.
string $target
User name, or a string describing an IP address range.
getDatabase()
Get the Database object in use.
static getOffsetDate( $year, $month, $day=-1)
Core logic of determining the offset timestamp such that we can get all items with a timestamp up to ...
Service for looking up page revisions.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Interface for configuration instances.
Definition Config.php:32
Interface for objects which can provide a MediaWiki context on request.
Interface for objects representing user identity.
Provide primary and replica IDatabase connections.
A database connection without write operations.