Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / specials / pagers / ContribsPager.php
blob15c1cb6f9393a328debba0982593ed6d383b3d54
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
19 * @ingroup Pager
22 namespace MediaWiki\Pager;
24 use DateTime;
25 use MediaWiki\Cache\LinkBatchFactory;
26 use MediaWiki\CommentFormatter\CommentFormatter;
27 use MediaWiki\Context\IContextSource;
28 use MediaWiki\HookContainer\HookContainer;
29 use MediaWiki\Linker\LinkRenderer;
30 use MediaWiki\MediaWikiServices;
31 use MediaWiki\Revision\RevisionStore;
32 use MediaWiki\SpecialPage\ContributionsRangeTrait;
33 use MediaWiki\Title\NamespaceInfo;
34 use MediaWiki\User\UserIdentity;
35 use Wikimedia\IPUtils;
36 use Wikimedia\Rdbms\IConnectionProvider;
37 use Wikimedia\Rdbms\IExpression;
38 use Wikimedia\Rdbms\IReadableDatabase;
40 /**
41 * Pager for Special:Contributions
43 * Most of the work is done by the parent class. This class:
44 * - handles using the ip_changes table in case of an IP range target
45 * - provides static utility functions (kept here for backwards compatibility)
47 * @ingroup Pager
49 class ContribsPager extends ContributionsPager {
51 use ContributionsRangeTrait;
53 /**
54 * FIXME List services first T266484 / T290405
55 * @param IContextSource $context
56 * @param array $options
57 * @param LinkRenderer|null $linkRenderer
58 * @param LinkBatchFactory|null $linkBatchFactory
59 * @param HookContainer|null $hookContainer
60 * @param IConnectionProvider|null $dbProvider
61 * @param RevisionStore|null $revisionStore
62 * @param NamespaceInfo|null $namespaceInfo
63 * @param UserIdentity|null $targetUser
64 * @param CommentFormatter|null $commentFormatter
66 public function __construct(
67 IContextSource $context,
68 array $options,
69 ?LinkRenderer $linkRenderer = null,
70 ?LinkBatchFactory $linkBatchFactory = null,
71 ?HookContainer $hookContainer = null,
72 ?IConnectionProvider $dbProvider = null,
73 ?RevisionStore $revisionStore = null,
74 ?NamespaceInfo $namespaceInfo = null,
75 ?UserIdentity $targetUser = null,
76 ?CommentFormatter $commentFormatter = null
77 ) {
78 // Class is used directly in extensions - T266484
79 $services = MediaWikiServices::getInstance();
80 $dbProvider ??= $services->getConnectionProvider();
82 parent::__construct(
83 $linkRenderer ?? $services->getLinkRenderer(),
84 $linkBatchFactory ?? $services->getLinkBatchFactory(),
85 $hookContainer ?? $services->getHookContainer(),
86 $revisionStore ?? $services->getRevisionStore(),
87 $namespaceInfo ?? $services->getNamespaceInfo(),
88 $commentFormatter ?? $services->getCommentFormatter(),
89 $services->getUserFactory(),
90 $context,
91 $options,
92 $targetUser
96 /**
97 * Return the table targeted for ordering and continuation
99 * See T200259 and T221380.
101 * @warning Keep this in sync with self::getQueryInfo()!
103 * @return string
105 private function getTargetTable() {
106 $dbr = $this->getDatabase();
107 $ipRangeConds = $this->targetUser->isRegistered()
108 ? null : $this->getIpRangeConds( $dbr, $this->target );
109 if ( $ipRangeConds ) {
110 return 'ip_changes';
113 return 'revision';
116 protected function getRevisionQuery() {
117 $revQuery = $this->revisionStore->getQueryInfo( [ 'page', 'user' ] );
118 $queryInfo = [
119 'tables' => $revQuery['tables'],
120 'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
121 'conds' => [],
122 'options' => [],
123 'join_conds' => $revQuery['joins'],
126 // WARNING: Keep this in sync with getTargetTable()!
127 $ipRangeConds = !$this->targetUser->isRegistered() ?
128 $this->getIpRangeConds( $this->getDatabase(), $this->target ) :
129 null;
130 if ( $ipRangeConds ) {
131 // Put ip_changes first (T284419)
132 array_unshift( $queryInfo['tables'], 'ip_changes' );
133 $queryInfo['join_conds']['revision'] = [
134 'JOIN', [ 'rev_id = ipc_rev_id' ]
136 $queryInfo['conds'][] = $ipRangeConds;
137 } else {
138 $queryInfo['conds']['actor_name'] = $this->targetUser->getName();
139 // Force the appropriate index to avoid bad query plans (T307295)
140 $queryInfo['options']['USE INDEX']['revision'] = 'rev_actor_timestamp';
143 return $queryInfo;
147 * Get SQL conditions for an IP range, if applicable
148 * @param IReadableDatabase $db
149 * @param string $ip The IP address or CIDR
150 * @return IExpression|false SQL for valid IP ranges, false if invalid
152 private function getIpRangeConds( $db, $ip ) {
153 // First make sure it is a valid range and they are not outside the CIDR limit
154 if ( !$this->isQueryableRange( $ip, $this->getConfig() ) ) {
155 return false;
158 [ $start, $end ] = IPUtils::parseRange( $ip );
160 return $db->expr( 'ipc_hex', '>=', $start )->and( 'ipc_hex', '<=', $end );
164 * @return string
166 public function getIndexField() {
167 // The returned column is used for sorting and continuation, so we need to
168 // make sure to use the right denormalized column depending on which table is
169 // being targeted by the query to avoid bad query plans.
170 // See T200259, T204669, T220991, and T221380.
171 $target = $this->getTargetTable();
172 switch ( $target ) {
173 case 'revision':
174 return 'rev_timestamp';
175 case 'ip_changes':
176 return 'ipc_rev_timestamp';
177 default:
178 wfWarn(
179 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
181 return 'rev_timestamp';
186 * @return string[]
188 protected function getExtraSortFields() {
189 // The returned columns are used for sorting, so we need to make sure
190 // to use the right denormalized column depending on which table is
191 // being targeted by the query to avoid bad query plans.
192 // See T200259, T204669, T220991, and T221380.
193 $target = $this->getTargetTable();
194 switch ( $target ) {
195 case 'revision':
196 return [ 'rev_id' ];
197 case 'ip_changes':
198 return [ 'ipc_rev_id' ];
199 default:
200 wfWarn(
201 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
203 return [ 'rev_id' ];
208 * Set up date filter options, given request data.
210 * @param array $opts Options array
211 * @return array Options array with processed start and end date filter options
213 public static function processDateFilter( array $opts ) {
214 $start = $opts['start'] ?? '';
215 $end = $opts['end'] ?? '';
216 $year = $opts['year'] ?? '';
217 $month = $opts['month'] ?? '';
219 if ( $start !== '' && $end !== '' && $start > $end ) {
220 $temp = $start;
221 $start = $end;
222 $end = $temp;
225 // If year/month legacy filtering options are set, convert them to display the new stamp
226 if ( $year !== '' || $month !== '' ) {
227 // Reuse getDateCond logic, but subtract a day because
228 // the endpoints of our date range appear inclusive
229 // but the internal end offsets are always exclusive
230 $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
231 $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
232 $legacyDateTime = $legacyDateTime->modify( '-1 day' );
234 // Clear the new timestamp range options if used and
235 // replace with the converted legacy timestamp
236 $start = '';
237 $end = $legacyDateTime->format( 'Y-m-d' );
240 $opts['start'] = $start;
241 $opts['end'] = $end;
243 return $opts;
248 * Retain the old class name for backwards compatibility.
249 * @deprecated since 1.41
251 class_alias( ContribsPager::class, 'ContribsPager' );