MediaWiki REL1_39
ApiQueryContributors.php
Go to the documentation of this file.
1<?php
32
44 private const MAX_PAGES = 100;
45
47 private $revisionStore;
48
50 private $actorMigration;
51
53 private $userGroupManager;
54
56 private $groupPermissionsLookup;
57
66 public function __construct(
67 ApiQuery $query,
68 $moduleName,
69 RevisionStore $revisionStore,
70 ActorMigration $actorMigration,
71 UserGroupManager $userGroupManager,
72 GroupPermissionsLookup $groupPermissionsLookup
73 ) {
74 // "pc" is short for "page contributors", "co" was already taken by the
75 // GeoData extension's prop=coordinates.
76 parent::__construct( $query, $moduleName, 'pc' );
77 $this->revisionStore = $revisionStore;
78 $this->actorMigration = $actorMigration;
79 $this->userGroupManager = $userGroupManager;
80 $this->groupPermissionsLookup = $groupPermissionsLookup;
81 }
82
83 public function execute() {
84 $db = $this->getDB();
85 $params = $this->extractRequestParams();
86 $this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
87
88 // Only operate on existing pages
89 $pages = array_keys( $this->getPageSet()->getGoodPages() );
90
91 // Filter out already-processed pages
92 if ( $params['continue'] !== null ) {
93 $cont = explode( '|', $params['continue'] );
94 $this->dieContinueUsageIf( count( $cont ) != 2 );
95 $cont_page = (int)$cont[0];
96 $pages = array_filter( $pages, static function ( $v ) use ( $cont_page ) {
97 return $v >= $cont_page;
98 } );
99 }
100 if ( $pages === [] ) {
101 // Nothing to do
102 return;
103 }
104
105 // Apply MAX_PAGES, leaving any over the limit for a continue.
106 sort( $pages );
107 $continuePages = null;
108 if ( count( $pages ) > self::MAX_PAGES ) {
109 $continuePages = $pages[self::MAX_PAGES] . '|0';
110 $pages = array_slice( $pages, 0, self::MAX_PAGES );
111 }
112
113 $result = $this->getResult();
114 $revQuery = $this->revisionStore->getQueryInfo();
115 $pageField = 'rev_page';
116 $idField = 'rev_actor';
117 $countField = 'rev_actor';
118
119 // First, count anons
120 $this->addTables( $revQuery['tables'] );
121 $this->addJoinConds( $revQuery['joins'] );
122 $this->addFields( [
123 'page' => $pageField,
124 'anons' => "COUNT(DISTINCT $countField)",
125 ] );
126 $this->addWhereFld( $pageField, $pages );
127 $this->addWhere( $this->actorMigration->isAnon( $revQuery['fields']['rev_user'] ) );
128 $this->addWhere( $db->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0' );
129 $this->addOption( 'GROUP BY', $pageField );
130 $res = $this->select( __METHOD__ );
131 foreach ( $res as $row ) {
132 $fit = $result->addValue( [ 'query', 'pages', $row->page ],
133 'anoncontributors', (int)$row->anons
134 );
135 if ( !$fit ) {
136 // This not fitting isn't reasonable, so it probably means that
137 // some other module used up all the space. Just set a dummy
138 // continue and hope it works next time.
139 $this->setContinueEnumParameter( 'continue',
140 $params['continue'] ?? '0|0'
141 );
142
143 return;
144 }
145 }
146
147 // Next, add logged-in users
148 $this->resetQueryParams();
149 $this->addTables( $revQuery['tables'] );
150 $this->addJoinConds( $revQuery['joins'] );
151 $this->addFields( [
152 'page' => $pageField,
153 'id' => $idField,
154 // Non-MySQL databases don't like partial group-by
155 'userid' => 'MAX(' . $revQuery['fields']['rev_user'] . ')',
156 'username' => 'MAX(' . $revQuery['fields']['rev_user_text'] . ')',
157 ] );
158 $this->addWhereFld( $pageField, $pages );
159 $this->addWhere( $this->actorMigration->isNotAnon( $revQuery['fields']['rev_user'] ) );
160 $this->addWhere( $db->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0' );
161 $this->addOption( 'GROUP BY', [ $pageField, $idField ] );
162 $this->addOption( 'LIMIT', $params['limit'] + 1 );
163
164 // Force a sort order to ensure that properties are grouped by page
165 // But only if rev_page is not constant in the WHERE clause.
166 if ( count( $pages ) > 1 ) {
167 $this->addOption( 'ORDER BY', [ 'page', 'id' ] );
168 } else {
169 $this->addOption( 'ORDER BY', 'id' );
170 }
171
172 $limitGroups = [];
173 if ( $params['group'] ) {
174 $excludeGroups = false;
175 $limitGroups = $params['group'];
176 } elseif ( $params['excludegroup'] ) {
177 $excludeGroups = true;
178 $limitGroups = $params['excludegroup'];
179 } elseif ( $params['rights'] ) {
180 $excludeGroups = false;
181 foreach ( $params['rights'] as $r ) {
182 $limitGroups = array_merge( $limitGroups,
183 $this->groupPermissionsLookup->getGroupsWithPermission( $r ) );
184 }
185
186 // If no group has the rights requested, no need to query
187 if ( !$limitGroups ) {
188 if ( $continuePages !== null ) {
189 // But we still need to continue for the next page's worth
190 // of anoncontributors
191 $this->setContinueEnumParameter( 'continue', $continuePages );
192 }
193
194 return;
195 }
196 } elseif ( $params['excluderights'] ) {
197 $excludeGroups = true;
198 foreach ( $params['excluderights'] as $r ) {
199 $limitGroups = array_merge( $limitGroups,
200 $this->groupPermissionsLookup->getGroupsWithPermission( $r ) );
201 }
202 }
203
204 if ( $limitGroups ) {
205 $limitGroups = array_unique( $limitGroups );
206 $this->addTables( 'user_groups' );
207 $this->addJoinConds( [ 'user_groups' => [
208 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable excludeGroups declared when limitGroups set
209 $excludeGroups ? 'LEFT JOIN' : 'JOIN',
210 [
211 'ug_user=' . $revQuery['fields']['rev_user'],
212 'ug_group' => $limitGroups,
213 'ug_expiry IS NULL OR ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
214 ]
215 ] ] );
216 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
217 // excludeGroups declared when limitGroups set
218 $this->addWhereIf( 'ug_user IS NULL', $excludeGroups );
219 }
220
221 if ( $params['continue'] !== null ) {
222 $cont = explode( '|', $params['continue'] );
223 $this->dieContinueUsageIf( count( $cont ) != 2 );
224 $cont_page = (int)$cont[0];
225 $cont_id = (int)$cont[1];
226 $this->addWhere(
227 "$pageField > $cont_page OR " .
228 "($pageField = $cont_page AND " .
229 "$idField >= $cont_id)"
230 );
231 }
232
233 $res = $this->select( __METHOD__ );
234 $count = 0;
235 foreach ( $res as $row ) {
236 if ( ++$count > $params['limit'] ) {
237 // We've reached the one extra which shows that
238 // there are additional pages to be had. Stop here...
239 $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->id );
240 return;
241 }
242
243 $fit = $this->addPageSubItem( $row->page,
244 [ 'userid' => (int)$row->userid, 'name' => $row->username ],
245 'user'
246 );
247 if ( !$fit ) {
248 $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->id );
249 return;
250 }
251 }
252
253 if ( $continuePages !== null ) {
254 $this->setContinueEnumParameter( 'continue', $continuePages );
255 }
256 }
257
258 public function getCacheMode( $params ) {
259 return 'public';
260 }
261
262 public function getAllowedParams( $flags = 0 ) {
263 $userGroups = $this->userGroupManager->listAllGroups();
264 $userRights = $this->getPermissionManager()->getAllPermissions();
265
266 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
267 sort( $userGroups );
268 }
269
270 return [
271 'group' => [
272 ParamValidator::PARAM_TYPE => $userGroups,
273 ParamValidator::PARAM_ISMULTI => true,
274 ],
275 'excludegroup' => [
276 ParamValidator::PARAM_TYPE => $userGroups,
277 ParamValidator::PARAM_ISMULTI => true,
278 ],
279 'rights' => [
280 ParamValidator::PARAM_TYPE => $userRights,
281 ParamValidator::PARAM_ISMULTI => true,
282 ],
283 'excluderights' => [
284 ParamValidator::PARAM_TYPE => $userRights,
285 ParamValidator::PARAM_ISMULTI => true,
286 ],
287 'limit' => [
288 ParamValidator::PARAM_DEFAULT => 10,
289 ParamValidator::PARAM_TYPE => 'limit',
290 IntegerDef::PARAM_MIN => 1,
291 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
292 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
293 ],
294 'continue' => [
295 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
296 ],
297 ];
298 }
299
300 protected function getExamplesMessages() {
301 $title = Title::newMainPage()->getPrefixedText();
302 $mp = rawurlencode( $title );
303
304 return [
305 "action=query&prop=contributors&titles={$mp}"
306 => 'apihelp-query+contributors-example-simple',
307 ];
308 }
309
310 public function getHelpUrls() {
311 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Contributors';
312 }
313}
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1643
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:686
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:221
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:938
getResult()
Get the result object.
Definition ApiBase.php:629
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:765
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:163
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition ApiBase.php:234
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:223
This is a base class for all Query modules.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addFields( $value)
Add a set of fields to select to the internal array.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
getDB()
Get the Query database connection (read-only)
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
getPageSet()
Get the PageSet object to work on.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
A query module to show contributors to a page.
getExamplesMessages()
Returns usage examples for this module.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
__construct(ApiQuery $query, $moduleName, RevisionStore $revisionStore, ActorMigration $actorMigration, UserGroupManager $userGroupManager, GroupPermissionsLookup $groupPermissionsLookup)
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getHelpUrls()
Return links to more detailed help pages about the module.
This is the main query class.
Definition ApiQuery.php:41
Page revision base class.
Service for looking up page revisions.
Service for formatting and validating API parameters.
Type definition for integer types.