MediaWiki master
NewPagesPager.php
Go to the documentation of this file.
1<?php
9
34use stdClass;
37
45
47 private array $formattedComments = [];
51 public $mGroupByDate = true;
52
53 private HookRunner $hookRunner;
54
55 public function __construct(
56 IContextSource $context,
57 LinkRenderer $linkRenderer,
58 private readonly GroupPermissionsLookup $groupPermissionsLookup,
59 HookContainer $hookContainer,
60 private readonly LinkBatchFactory $linkBatchFactory,
61 private readonly NamespaceInfo $namespaceInfo,
62 private readonly ChangeTagsStore $changeTagsStore,
63 private readonly RowCommentFormatter $rowCommentFormatter,
64 private readonly IContentHandlerFactory $contentHandlerFactory,
65 private readonly TempUserConfig $tempUserConfig,
66 private readonly RecentChangeFactory $rcFactory,
67 private readonly ChangeTagsFormatter $changeTagsFormatter,
68 protected readonly FormOptions $opts,
69 ) {
70 parent::__construct( $context, $linkRenderer );
71 $this->hookRunner = new HookRunner( $hookContainer );
72 $this->tagsCache = new MapCacheLRU( 50 );
73 }
74
76 public function getQueryInfo() {
77 $conds = [];
78 $conds['rc_source'] = RecentChange::SRC_NEW;
79
80 $username = $this->opts->getValue( 'username' );
81 $user = Title::makeTitleSafe( NS_USER, $username );
82
83 $size = abs( intval( $this->opts->getValue( 'size' ) ) );
84 if ( $size > 0 ) {
85 $db = $this->getDatabase();
86 if ( $this->opts->getValue( 'size-mode' ) === 'max' ) {
87 $conds[] = $db->expr( 'page_len', '<=', $size );
88 } else {
89 $conds[] = $db->expr( 'page_len', '>=', $size );
90 }
91 }
92
93 if ( $user ) {
94 $conds['actor_name'] = $user->getText();
95 $joinFlags = 0;
96 } elseif ( $this->opts->getValue( 'hideliu' ) ) {
97 // Only include anonymous users if the 'hideliu' option has been provided.
98 $anonOnlyExpr = $this->getDatabase()->expr( 'actor_user', '=', null );
99 if ( $this->tempUserConfig->isKnown() ) {
100 $anonOnlyExpr = $anonOnlyExpr->orExpr( $this->tempUserConfig->getMatchCondition(
101 $this->getDatabase(), 'actor_name', IExpression::LIKE
102 ) );
103 }
104 $conds[] = $anonOnlyExpr;
105 $joinFlags = 0;
106 } else {
107 $joinFlags = RecentChange::STRAIGHT_JOIN_ACTOR;
108 }
109
110 $conds = array_merge( $conds, $this->getNamespaceCond() );
111
112 # If this user cannot see patrolled edits or they are off, don't do dumb queries!
113 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
114 $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
115 }
116
117 if ( $this->opts->getValue( 'hidebots' ) ) {
118 $conds['rc_bot'] = 0;
119 }
120
121 if ( $this->opts->getValue( 'hideredirs' ) ) {
122 $conds['page_is_redirect'] = 0;
123 }
124
125 // Allow changes to the New Pages query
126 $rcQuery = RecentChange::getQueryInfo( $joinFlags );
127 $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
128 $fields = array_merge( $rcQuery['fields'], [
129 'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title',
130 'page_content_model',
131 ] );
132 $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
133
134 $this->hookRunner->onSpecialNewpagesConditions(
135 $this, $this->opts, $conds, $tables, $fields, $join_conds );
136
137 $info = [
138 'tables' => $tables,
139 'fields' => $fields,
140 'conds' => $conds,
141 'options' => [],
142 'join_conds' => $join_conds
143 ];
144
145 // Modify query for tags
146 $queryBuilder = $this->getDatabase()->newSelectQueryBuilder()->queryInfo( $info );
147 $this->changeTagsStore->addTagsToDisplayQuery(
148 $queryBuilder, 'recentchanges', $this->getAuthority(), $this->opts['tagfilter'], $this->opts['tagInvert']
149 );
150 $info = $queryBuilder->getQueryInfo( 'join_conds' );
151
152 return $info;
153 }
154
155 private function getNamespaceCond(): array {
156 $namespace = $this->opts->getValue( 'namespace' );
157 if ( $namespace === 'all' || $namespace === '' ) {
158 return [];
159 }
160
161 $namespace = intval( $namespace );
162 if ( $namespace < NS_MAIN ) {
163 // Negative namespaces are invalid
164 return [];
165 }
166
167 $invert = $this->opts->getValue( 'invert' );
168 $associated = $this->opts->getValue( 'associated' );
169
170 $eq_op = $invert ? '!=' : '=';
171 $dbr = $this->getDatabase();
172 $namespaces = [ $namespace ];
173 if ( $associated ) {
174 $namespaces[] = $this->namespaceInfo->getAssociated( $namespace );
175 }
176
177 return [ $dbr->expr( 'rc_namespace', $eq_op, $namespaces ) ];
178 }
179
181 public function getIndexField() {
182 return [ [ 'rc_timestamp', 'rc_id' ] ];
183 }
184
186 public function formatRow( $row ) {
187 $title = Title::newFromRow( $row );
188
189 // Revision deletion works on revisions,
190 // so cast our recent change row to a revision row.
191 $revRecord = $this->revisionFromRcResult( $row, $title );
192
193 $classes = [];
194 $attribs = [ 'data-mw-revid' => $row->rc_this_oldid ];
195
196 $lang = $this->getLanguage();
197 $time = ChangesList::revDateLink( $revRecord, $this->getUser(), $lang, null, 'mw-newpages-time' );
198
199 $linkRenderer = $this->getLinkRenderer();
200
201 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
202
203 $plink = Html::rawElement( 'bdi', [ 'dir' => $lang->getDir() ], $linkRenderer->makeKnownLink(
204 $title,
205 null,
206 [ 'class' => 'mw-newpages-pagename' ],
207 $query
208 ) );
209 $linkArr = [];
210 $linkArr[] = $linkRenderer->makeKnownLink(
211 $title,
212 $this->msg( 'hist' )->text(),
213 [ 'class' => 'mw-newpages-history' ],
214 [ 'action' => 'history' ]
215 );
216 if ( $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
217 ->supportsDirectEditing()
218 ) {
219 $linkArr[] = $linkRenderer->makeKnownLink(
220 $title,
221 $this->msg( 'editlink' )->text(),
222 [ 'class' => 'mw-newpages-edit' ],
223 [ 'action' => 'edit' ]
224 );
225 }
226 $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
227 ->pipeList( $linkArr ) )->escaped();
228
229 $length = Html::rawElement(
230 'span',
231 [ 'class' => 'mw-newpages-length' ],
232 $this->msg( 'brackets' )->rawParams(
233 $this->msg( 'nbytes' )->numParams( $row->length )->escaped()
234 )->escaped()
235 );
236
237 $ulink = Linker::revUserTools( $revRecord );
238 $rc = $this->rcFactory->newRecentChangeFromRow( $row );
239 if ( ChangesList::userCan( $rc, RevisionRecord::DELETED_COMMENT, $this->getAuthority() ) ) {
240 $comment = $this->formattedComments[$rc->mAttribs['rc_id']];
241 } else {
242 $comment = '<span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
243 }
244 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
245 $deletedClass = 'history-deleted';
246 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
247 $deletedClass .= ' mw-history-suppressed';
248 }
249 $comment = '<span class="' . $deletedClass . ' comment">' . $comment . '</span>';
250 }
251
252 if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled ) {
253 $classes[] = 'not-patrolled';
254 }
255
256 # Add a class for zero byte pages
257 if ( $row->length == 0 ) {
258 $classes[] = 'mw-newpages-zero-byte-page';
259 }
260
261 # Tags, if any.
262 if ( isset( $row->ts_tags ) ) {
263 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
264 $this->tagsCache->makeKey(
265 $row->ts_tags,
266 $this->getUser()->getName(),
267 $lang->getCode()
268 ),
269 fn () => $this->changeTagsFormatter->formatTagsAsSummaryList(
270 $row->ts_tags,
271 $this->getContext(),
272 $this->getAuthority()
273 )
274 );
275 $classes = array_merge( $classes, $newClasses );
276 } else {
277 $tagDisplay = '';
278 }
279
280 # Display the old title if the namespace/title has been changed
281 $oldTitleText = '';
282 $oldTitle = Title::makeTitle( $row->rc_namespace, $row->rc_title );
283
284 if ( !$title->equals( $oldTitle ) ) {
285 $oldTitleText = $oldTitle->getPrefixedText();
286 $oldTitleText = Html::element( 'span',
287 [ 'class' => 'mw-newpages-oldtitle' ],
288 $this->msg( 'rc-old-title', $oldTitleText )->text()
289 );
290 }
291
292 $ret = "{$time} {$plink} {$links} {$length} {$ulink} {$comment} "
293 . "{$tagDisplay} {$oldTitleText}";
294
295 // Let extensions add data
296 $this->hookRunner->onNewPagesLineEnding(
297 $this, $ret, $row, $classes, $attribs );
298 $attribs = array_filter( $attribs,
299 Sanitizer::isReservedDataAttribute( ... ),
300 ARRAY_FILTER_USE_KEY
301 );
302
303 if ( $classes ) {
304 $attribs['class'] = $classes;
305 }
306
307 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
308 }
309
315 protected function revisionFromRcResult( stdClass $result, Title $title ): RevisionRecord {
316 $revRecord = new MutableRevisionRecord( $title );
317 $revRecord->setTimestamp( $result->rc_timestamp );
318 $revRecord->setId( $result->rc_this_oldid );
319 $revRecord->setVisibility( (int)$result->rc_deleted );
320
321 $user = new UserIdentityValue(
322 (int)$result->rc_user,
323 $result->rc_user_text
324 );
325 $revRecord->setUser( $user );
326
327 return $revRecord;
328 }
329
330 protected function doBatchLookups() {
331 $linkBatch = $this->linkBatchFactory->newLinkBatch();
332 foreach ( $this->mResult as $row ) {
333 $linkBatch->addUser( new UserIdentityValue( (int)$row->rc_user, $row->rc_user_text ) );
334 $linkBatch->add( $row->page_namespace, $row->page_title );
335 }
336 $linkBatch->execute();
337
338 $this->formattedComments = $this->rowCommentFormatter->formatRows(
339 $this->mResult, 'rc_comment', 'page_namespace', 'page_title', 'rc_id', true
340 );
341 }
342
346 protected function getStartBody() {
347 return "<section class='mw-pager-body'>\n";
348 }
349
353 protected function getEndBody() {
354 return "</section>\n";
355 }
356}
357
358// @codeCoverageIgnoreStart
363class_alias( NewPagesPager::class, 'NewPagesPager' );
364
366class_alias( NewPagesPager::class, 'MediaWiki\\Pager\\NewPagesPager' );
367// @codeCoverageIgnoreEnd
const NS_USER
Definition Defines.php:53
const NS_MAIN
Definition Defines.php:51
Formats change tags for display in HTML and use filter dropdown menus.
Read-write access to the change_tags table.
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Helper class to keep track of options when mixing links and form elements.
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:48
Factory for LinkBatch objects to batch query page metadata.
getDatabase()
Get the Database object in use.
IndexPager with a formatted navigation bar.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:34
Base class for lists of recent changes shown on special pages.
Utility class for creating and reading rows in the recentchanges table.
Page revision base class.
revisionFromRcResult(stdClass $result, Title $title)
getQueryInfo()
Provides all parameters needed for the main paged query.It returns an associative array with the foll...
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
getStartBody()
Hook into getBody(), allows text to be inserted at the start.This will be called even if there are no...
getIndexField()
Returns the name of the index field.If the pager supports multiple orders, it may return an array of ...
bool $mGroupByDate
Whether to group items by date by default this is disabled, but eventually the intention should be to...
__construct(IContextSource $context, LinkRenderer $linkRenderer, private readonly GroupPermissionsLookup $groupPermissionsLookup, HookContainer $hookContainer, private readonly LinkBatchFactory $linkBatchFactory, private readonly NamespaceInfo $namespaceInfo, private readonly ChangeTagsStore $changeTagsStore, private readonly RowCommentFormatter $rowCommentFormatter, private readonly IContentHandlerFactory $contentHandlerFactory, private readonly TempUserConfig $tempUserConfig, private readonly RecentChangeFactory $rcFactory, private readonly ChangeTagsFormatter $changeTagsFormatter, protected readonly FormOptions $opts,)
formatRow( $row)
Returns an HTML string representing the result row $row.Rows will be concatenated and returned by get...
getEndBody()
Hook into getBody() for the end of the list.to overridestring
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:69
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:2587
equals(object $other)
Compares with another Title.
Definition Title.php:3089
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1056
Value object representing a user's identity.
Store key-value entries in a size-limited in-memory LRU cache.
Interface for objects which can provide a MediaWiki context on request.
Interface for temporary user creation config and name matching.
element(SerializerNode $parent, SerializerNode $node, $contents)