MediaWiki  master
ContributionsLookup.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Revision;
4 
5 use ChangeTags;
6 use ContribsPager;
16 use Message;
17 use NamespaceInfo;
18 use RequestContext;
20 
25 
27  private $revisionStore;
28 
30  private $linkRendererFactory;
31 
33  private $linkBatchFactory;
34 
36  private $hookContainer;
37 
39  private $loadBalancer;
40 
42  private $actorMigration;
43 
45  private $namespaceInfo;
46 
48  private $commentFormatter;
49 
60  public function __construct(
61  RevisionStore $revisionStore,
62  LinkRendererFactory $linkRendererFactory,
63  LinkBatchFactory $linkBatchFactory,
64  HookContainer $hookContainer,
65  ILoadBalancer $loadBalancer,
66  ActorMigration $actorMigration,
67  NamespaceInfo $namespaceInfo,
68  CommentFormatter $commentFormatter
69  ) {
70  $this->revisionStore = $revisionStore;
71  $this->linkRendererFactory = $linkRendererFactory;
72  $this->linkBatchFactory = $linkBatchFactory;
73  $this->hookContainer = $hookContainer;
74  $this->loadBalancer = $loadBalancer;
75  $this->actorMigration = $actorMigration;
76  $this->namespaceInfo = $namespaceInfo;
77  $this->commentFormatter = $commentFormatter;
78  }
79 
91  private function getPagerParams( int $limit, string $segment ): array {
92  $dir = 'next';
93  $seg = explode( '|', $segment, 2 );
94  if ( count( $seg ) > 1 ) {
95  if ( $seg[0] === 'after' ) {
96  $dir = 'prev';
97  $segment = $seg[1];
98  } elseif ( $seg[0] == 'before' ) {
99  $segment = $seg[1];
100  } else {
101  $dir = null;
102  $segment = null;
103  }
104  } else {
105  $segment = null;
106  }
107  return [
108  'limit' => $limit,
109  'offset' => $segment,
110  'dir' => $dir
111  ];
112  }
113 
124  public function getContributions(
125  UserIdentity $target,
126  int $limit,
127  Authority $performer,
128  string $segment = '',
129  string $tag = null
131  $context = new RequestContext();
132  $context->setAuthority( $performer );
133 
134  $paramArr = $this->getPagerParams( $limit, $segment );
135  $context->setRequest( new FauxRequest( $paramArr ) );
136 
137  // TODO: explore moving this to factory method for testing
138  $pager = $this->getContribsPager( $context, $target, [
139  'tagfilter' => $tag,
140  'revisionsOnly' => true
141  ] );
142  $revisions = [];
143  $tags = [];
144  $count = 0;
145  if ( $pager->getNumRows() > 0 ) {
146  foreach ( $pager->mResult as $row ) {
147  // We retrieve and ignore one extra record to see if we are on the oldest segment.
148  if ( ++$count > $limit ) {
149  break;
150  }
151 
152  // TODO: pre-load title batch?
153  $revision = $this->revisionStore->newRevisionFromRow( $row, 0 );
154  $revisions[] = $revision;
155  if ( $row->ts_tags ) {
156  $tagNames = explode( ',', $row->ts_tags );
157  $tags[ $row->rev_id ] = $this->getContributionTags( $tagNames );
158  }
159  }
160  }
161 
162  $deltas = $this->getContributionDeltas( $revisions );
163 
164  $flags = [
165  'newest' => $pager->mIsFirst,
166  'oldest' => $pager->mIsLast,
167  ];
168 
169  // TODO: Make me an option in IndexPager
170  $pager->mIsFirst = false; // XXX: nasty...
171  $pagingQueries = $pager->getPagingQueries();
172 
173  $prev = $pagingQueries['prev']['offset'] ?? null;
174  $next = $pagingQueries['next']['offset'] ?? null;
175 
176  $after = $prev ? 'after|' . $prev : null; // later in time
177  $before = $next ? 'before|' . $next : null; // earlier in time
178 
179  // TODO: Possibly return public $pager properties to segment for populating URLS ($mIsFirst, $mIsLast)
180  // HACK: Force result set order to be descending. Sorting logic in ContribsPager::reallyDoQuery is confusing.
181  if ( $paramArr['dir'] === 'prev' ) {
182  $revisions = array_reverse( $revisions );
183  }
184  return new ContributionsSegment( $revisions, $tags, $before, $after, $deltas, $flags );
185  }
186 
191  private function getContributionTags( array $tagNames ): array {
192  $tagMetadata = [];
193  foreach ( $tagNames as $name ) {
195  if ( $tagDisplay ) {
196  $tagMetadata[$name] = $tagDisplay;
197  }
198  }
199  return $tagMetadata;
200  }
201 
209  private function getContributionDeltas( $revisions ): array {
210  // SpecialContributions uses the size of the revision if the parent revision is unknown. Cases include:
211  // - revision has been deleted
212  // - parent rev id has not been populated (this is the case for very old revisions)
213  $parentIds = [];
214  foreach ( $revisions as $revision ) {
215  $revId = $revision->getId();
216  $parentIds[$revId] = $revision->getParentId();
217  }
218  $parentSizes = $this->revisionStore->getRevisionSizes( $parentIds );
219  $deltas = [];
220  foreach ( $revisions as $revision ) {
221  $parentId = $revision->getParentId();
222  if ( $parentId === 0 ) { // first revision on a page
223  $delta = $revision->getSize();
224  } elseif ( !isset( $parentSizes[$parentId] ) ) { // parent revision is either deleted or untracked
225  $delta = null;
226  } else {
227  $delta = $revision->getSize() - $parentSizes[$parentId];
228  }
229  $deltas[ $revision->getId() ] = $delta;
230  }
231  return $deltas;
232  }
233 
243  public function getContributionCount( UserIdentity $user, Authority $performer, $tag = null ): int {
244  $context = new RequestContext();
245  $context->setAuthority( $performer );
246  $context->setRequest( new FauxRequest( [] ) );
247 
248  // TODO: explore moving this to factory method for testing
249  $pager = $this->getContribsPager( $context, $user, [
250  'tagfilter' => $tag,
251  ] );
252 
253  $query = $pager->getQueryInfo();
254 
255  $count = $pager->mDb->selectField(
256  $query['tables'],
257  'COUNT(*)',
258  $query['conds'],
259  __METHOD__,
260  [],
261  $query['join_conds']
262  );
263 
264  return (int)$count;
265  }
266 
267  private function getContribsPager(
268  IContextSource $context,
269  UserIdentity $targetUser,
270  array $options
271  ) {
272  return new ContribsPager(
273  $context,
274  $options,
275  $this->linkRendererFactory->create(),
276  $this->linkBatchFactory,
277  $this->hookContainer,
278  $this->loadBalancer,
279  $this->actorMigration,
280  $this->revisionStore,
281  $this->namespaceInfo,
282  $targetUser,
283  $this->commentFormatter
284  );
285  }
286 
287 }
static tagShortDescriptionMessage( $tag, MessageLocalizer $context)
Get the message object for the tag's short description.
Definition: ChangeTags.php:258
Pager for Special:Contributions.
This is the main service interface for converting single-line comments from various DB comment fields...
Factory to create LinkRender objects.
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:42
getContributions(UserIdentity $target, int $limit, Authority $performer, string $segment='', string $tag=null)
getContributionCount(UserIdentity $user, Authority $performer, $tag=null)
Returns the number of edits by the given user.
__construct(RevisionStore $revisionStore, LinkRendererFactory $linkRendererFactory, LinkBatchFactory $linkBatchFactory, HookContainer $hookContainer, ILoadBalancer $loadBalancer, ActorMigration $actorMigration, NamespaceInfo $namespaceInfo, CommentFormatter $commentFormatter)
Service for looking up page revisions.
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Group all the pieces relevant to the context of a request into one instance.
setAuthority(Authority $authority)
static getMain()
Get the RequestContext object associated with the main request.
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
Interface for objects representing user identity.
This class is a delegate to ILBFactory for a given database cluster.