105 $this->target = $options[
'target'] ??
'';
106 $this->
namespace = $options['namespace'] ?? '';
107 $this->tagFilter = $options[
'tagfilter'] ??
false;
108 $this->nsInvert = $options[
'nsInvert'] ??
false;
109 $this->associated = $options[
'associated'] ??
false;
111 $this->deletedOnly = !empty( $options[
'deletedOnly'] );
112 $this->topOnly = !empty( $options[
'topOnly'] );
113 $this->newOnly = !empty( $options[
'newOnly'] );
114 $this->hideMinor = !empty( $options[
'hideMinor'] );
125 foreach ( $msgs as $msg ) {
126 $this->messages[$msg] = $this->
msg( $msg )->escaped();
130 $startTimestamp =
'';
132 if ( isset( $options[
'start'] ) && $options[
'start'] ) {
133 $startTimestamp = $options[
'start'] .
' 00:00:00';
135 if ( isset( $options[
'end'] ) && $options[
'end'] ) {
136 $endTimestamp = $options[
'end'] .
' 23:59:59';
176 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->
buildQueryInfo(
182 $options[
'MAX_EXECUTION_TIME'] = $this->
getConfig()->get(
'MaxExecutionTimeForExpensiveQueries' );
201 $data = [ $this->mDb->select(
202 $tables, $fields, $conds, $fname, $options, $join_conds
204 $this->getHookRunner()->onContribsPager__reallyDoQuery(
205 $data, $this, $offset, $limit, $order );
210 foreach ( $data as $query ) {
211 foreach ( $query as $i => $row ) {
213 $index = $order === self::QUERY_ASCENDING ? $i : $limit - 1 - $i;
215 $index = str_pad( $index, strlen( $limit ),
'0', STR_PAD_LEFT );
222 if ( $order === self::QUERY_ASCENDING ) {
229 $result = array_slice( $result, 0, $limit );
232 $result = array_values( $result );
263 $revQuery = MediaWikiServices::getInstance()
265 ->getQueryInfo( [
'page',
'user' ] );
268 'fields' => array_merge(
$revQuery[
'fields'], [
'page_is_new' ] ),
273 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
277 $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) :
null;
278 if ( $ipRangeConds ) {
279 $queryInfo[
'tables'][] =
'ip_changes';
280 $queryInfo[
'join_conds'][
'ip_changes'] = [
281 'LEFT JOIN', [
'ipc_rev_id = rev_id' ]
283 $queryInfo[
'conds'][] = $ipRangeConds;
286 $conds = ActorMigration::newMigration()->getWhere( $this->mDb,
'rev_user', $user );
287 $queryInfo[
'conds'][] = $conds[
'conds'];
289 if ( isset( $conds[
'orconds'][
'actor'] ) ) {
291 $queryInfo[
'options'][
'USE INDEX'][
'temp_rev_user'] =
'actor_timestamp';
295 if ( $this->deletedOnly ) {
296 $queryInfo[
'conds'][] =
'rev_deleted != 0';
299 if ( $this->topOnly ) {
300 $queryInfo[
'conds'][] =
'rev_id = page_latest';
303 if ( $this->newOnly ) {
304 $queryInfo[
'conds'][] =
'rev_parent_id = 0';
307 if ( $this->hideMinor ) {
308 $queryInfo[
'conds'][] =
'rev_minor_edit = 0';
312 $queryInfo[
'conds'] = array_merge( $queryInfo[
'conds'], $this->getNamespaceCond() );
315 if ( !$permissionManager->userHasRight( $user,
'deletedhistory' ) ) {
316 $queryInfo[
'conds'][] = $this->mDb->bitAnd(
317 'rev_deleted', RevisionRecord::DELETED_USER
319 } elseif ( !$permissionManager->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' ) ) {
320 $queryInfo[
'conds'][] = $this->mDb->bitAnd(
321 'rev_deleted', RevisionRecord::SUPPRESSED_USER
322 ) .
' != ' . RevisionRecord::SUPPRESSED_USER;
326 $indexField = $this->getIndexField();
327 if ( $indexField !==
'rev_timestamp' ) {
328 $queryInfo[
'fields'][] = $indexField;
332 $queryInfo[
'tables'],
333 $queryInfo[
'fields'],
335 $queryInfo[
'join_conds'],
336 $queryInfo[
'options'],
340 $this->getHookRunner()->onContribsPager__getQueryInfo( $this, $queryInfo );
485 # Do a link batch query
486 $this->mResult->seek( 0 );
488 $this->mParentLens = [];
490 $isIpRange = $this->isQueryableRange( $this->target );
491 # Give some pointers to make (last) links
492 foreach ( $this->mResult as $row ) {
493 if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
494 $parentRevIds[] = $row->rev_parent_id;
496 if ( isset( $row->rev_id ) ) {
497 $this->mParentLens[$row->rev_id] = $row->rev_len;
502 $batch->add( $row->page_namespace, $row->page_title );
505 # Fetch rev_len for revisions not already scanned above
506 $this->mParentLens += MediaWikiServices::getInstance()
508 ->getRevisionSizes( array_diff( $parentRevIds, array_keys( $this->mParentLens ) ) );
510 $this->mResult->seek( 0 );
590 $linkRenderer = $this->getLinkRenderer();
591 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
596 if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
597 $page = Title::newFromRow( $row );
599 $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
601 $attribs[
'data-mw-revid'] = $revRecord->getId();
603 $link = $linkRenderer->makeLink(
605 $page->getPrefixedText(),
606 [
'class' =>
'mw-contributions-title' ],
607 $page->isRedirect() ? [
'redirect' =>
'no' ] : []
609 # Mark current revisions
613 if ( $row->rev_id === $row->page_latest ) {
614 $topmarktext .=
'<span class="mw-uctop">' . $this->messages[
'uctop'] .
'</span>';
615 $classes[] =
'mw-contributions-current';
617 if ( !$row->page_is_new &&
618 $permissionManager->quickUserCan(
'rollback', $user, $page ) &&
619 $permissionManager->quickUserCan(
'edit', $user, $page )
621 $this->preventClickjacking();
629 # Is there a visible previous revision?
630 if ( $revRecord->getParentId() !== 0 &&
631 RevisionRecord::userCanBitfield(
632 $revRecord->getVisibility(),
633 RevisionRecord::DELETED_TEXT,
637 $difftext = $linkRenderer->makeKnownLink(
639 new HtmlArmor( $this->messages[
'diff'] ),
640 [
'class' =>
'mw-changeslist-diff' ],
643 'oldid' => $row->rev_id
647 $difftext = $this->messages[
'diff'];
649 $histlink = $linkRenderer->makeKnownLink(
651 new HtmlArmor( $this->messages[
'hist'] ),
652 [
'class' =>
'mw-changeslist-history' ],
653 [
'action' =>
'history' ]
656 if ( $row->rev_parent_id ===
null ) {
660 $chardiff =
' <span class="mw-changeslist-separator"></span> ';
662 $chardiff .=
' <span class="mw-changeslist-separator"></span> ';
665 if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
666 $parentLen = $this->mParentLens[$row->rev_parent_id];
669 $chardiff =
' <span class="mw-changeslist-separator"></span> ';
670 $chardiff .= ChangesList::showCharacterDifference(
675 $chardiff .=
' <span class="mw-changeslist-separator"></span> ';
678 $lang = $this->getLanguage();
680 $d = ChangesList::revDateLink( $revRecord, $user,
$lang, $page );
682 # When querying for an IP range, we want to always show user and user talk links.
684 $revUser = $revRecord->getUser();
685 $revUserId = $revUser ? $revUser->getId() : 0;
686 $revUserText = $revUser ? $revUser->getName() :
'';
687 if ( $this->isQueryableRange( $this->target ) ) {
688 $userlink =
' <span class="mw-changeslist-separator"></span> '
689 .
$lang->getDirMark()
691 $userlink .=
' ' . $this->msg(
'parentheses' )->rawParams(
696 if ( $revRecord->getParentId() === 0 ) {
697 $flags[] = ChangesList::flag(
'newpage' );
700 if ( $revRecord->isMinor() ) {
701 $flags[] = ChangesList::flag(
'minor' );
713 $diffHistLinks = Html::rawElement(
'span',
714 [
'class' =>
'mw-changeslist-links' ],
717 Html::rawElement(
'span', [], $difftext ) .
719 Html::rawElement(
'span', [], $histlink )
728 $classes = array_merge( $classes, $newClasses );
730 $this->getHookRunner()->onSpecialContributions__formatRow__flags(
736 'diffHistLinks' => $diffHistLinks,
737 'charDifference' => $chardiff,
739 'articleLink' => $link,
740 'userlink' => $userlink,
741 'logText' => $comment,
742 'topmarktext' => $topmarktext,
743 'tagSummary' => $tagSummary,
746 # Denote if username is redacted for this edit
747 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
748 $templateParams[
'rev-deleted-user-contribs'] =
749 $this->msg(
'rev-deleted-user-contribs' )->escaped();
752 $ret = $this->templateParser->processTemplate(
753 'SpecialContributionsLine',
759 $this->getHookRunner()->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
760 $attribs = array_filter( $attribs,
761 [ Sanitizer::class,
'isReservedDataAttribute' ],
768 if ( $classes === [] && $attribs === [] && $ret ===
'' ) {
769 wfDebug(
"Dropping Special:Contribution row that could not be formatted" );
770 return "<!-- Could not format Special:Contribution row. -->\n";
772 $attribs[
'class'] = $classes;
776 return Html::rawElement(
'li', $attribs, $ret ) .
"\n";