MediaWiki master
NewPagesPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use ChangesList;
25use ChangeTags;
26use MapCacheLRU;
46use RecentChange;
47use stdClass;
49
56
60 protected $opts;
61
63
65 private $formattedComments = [];
69 public $mGroupByDate = true;
70
71 private GroupPermissionsLookup $groupPermissionsLookup;
72 private HookRunner $hookRunner;
73 private LinkBatchFactory $linkBatchFactory;
74 private NamespaceInfo $namespaceInfo;
75 private ChangeTagsStore $changeTagsStore;
76 private RowCommentFormatter $rowCommentFormatter;
77 private IContentHandlerFactory $contentHandlerFactory;
78 private TempUserConfig $tempUserConfig;
79
93 public function __construct(
94 IContextSource $context,
95 LinkRenderer $linkRenderer,
96 GroupPermissionsLookup $groupPermissionsLookup,
97 HookContainer $hookContainer,
98 LinkBatchFactory $linkBatchFactory,
99 NamespaceInfo $namespaceInfo,
100 ChangeTagsStore $changeTagsStore,
101 RowCommentFormatter $rowCommentFormatter,
102 IContentHandlerFactory $contentHandlerFactory,
103 TempUserConfig $tempUserConfig,
105 ) {
106 parent::__construct( $context, $linkRenderer );
107 $this->groupPermissionsLookup = $groupPermissionsLookup;
108 $this->hookRunner = new HookRunner( $hookContainer );
109 $this->linkBatchFactory = $linkBatchFactory;
110 $this->namespaceInfo = $namespaceInfo;
111 $this->changeTagsStore = $changeTagsStore;
112 $this->rowCommentFormatter = $rowCommentFormatter;
113 $this->contentHandlerFactory = $contentHandlerFactory;
114 $this->tempUserConfig = $tempUserConfig;
115 $this->opts = $opts;
116 $this->tagsCache = new MapCacheLRU( 50 );
117 }
118
119 public function getQueryInfo() {
120 $rcQuery = RecentChange::getQueryInfo();
121
122 $conds = [];
123 $conds['rc_new'] = 1;
124
125 $username = $this->opts->getValue( 'username' );
126 $user = Title::makeTitleSafe( NS_USER, $username );
127
128 $size = abs( intval( $this->opts->getValue( 'size' ) ) );
129 if ( $size > 0 ) {
130 $db = $this->getDatabase();
131 if ( $this->opts->getValue( 'size-mode' ) === 'max' ) {
132 $conds[] = $db->expr( 'page_len', '<=', $size );
133 } else {
134 $conds[] = $db->expr( 'page_len', '>=', $size );
135 }
136 }
137
138 if ( $user ) {
139 $conds['actor_name'] = $user->getText();
140 } elseif ( $this->opts->getValue( 'hideliu' ) ) {
141 // Only include anonymous users if the 'hideliu' option has been provided.
142 $anonOnlyExpr = $this->getDatabase()->expr( 'actor_user', '=', null );
143 if ( $this->tempUserConfig->isKnown() ) {
144 $anonOnlyExpr = $anonOnlyExpr->orExpr( $this->tempUserConfig->getMatchCondition(
145 $this->getDatabase(), 'actor_name', IExpression::LIKE
146 ) );
147 }
148 $conds[] = $anonOnlyExpr;
149 }
150
151 $conds = array_merge( $conds, $this->getNamespaceCond() );
152
153 # If this user cannot see patrolled edits or they are off, don't do dumb queries!
154 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
155 $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
156 }
157
158 if ( $this->opts->getValue( 'hidebots' ) ) {
159 $conds['rc_bot'] = 0;
160 }
161
162 if ( $this->opts->getValue( 'hideredirs' ) ) {
163 $conds['page_is_redirect'] = 0;
164 }
165
166 // Allow changes to the New Pages query
167 $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
168 $fields = array_merge( $rcQuery['fields'], [
169 'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title',
170 'page_content_model',
171 ] );
172 $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
173
174 $this->hookRunner->onSpecialNewpagesConditions(
175 $this, $this->opts, $conds, $tables, $fields, $join_conds );
176
177 $info = [
178 'tables' => $tables,
179 'fields' => $fields,
180 'conds' => $conds,
181 'options' => [],
182 'join_conds' => $join_conds
183 ];
184
185 // Modify query for tags
186 $this->changeTagsStore->modifyDisplayQuery(
187 $info['tables'],
188 $info['fields'],
189 $info['conds'],
190 $info['join_conds'],
191 $info['options'],
192 $this->opts['tagfilter'],
193 $this->opts['tagInvert']
194 );
195
196 return $info;
197 }
198
199 // Based on ContribsPager.php
200 private function getNamespaceCond() {
201 $namespace = $this->opts->getValue( 'namespace' );
202 if ( $namespace === 'all' || $namespace === '' ) {
203 return [];
204 }
205
206 $namespace = intval( $namespace );
207 if ( $namespace < NS_MAIN ) {
208 // Negative namespaces are invalid
209 return [];
210 }
211
212 $invert = $this->opts->getValue( 'invert' );
213 $associated = $this->opts->getValue( 'associated' );
214
215 $eq_op = $invert ? '!=' : '=';
216 $dbr = $this->getDatabase();
217 $namespaces = [ $namespace ];
218 if ( $associated ) {
219 $namespaces[] = $this->namespaceInfo->getAssociated( $namespace );
220 }
221
222 return [ $dbr->expr( 'rc_namespace', $eq_op, $namespaces ) ];
223 }
224
225 public function getIndexField() {
226 return [ [ 'rc_timestamp', 'rc_id' ] ];
227 }
228
229 public function formatRow( $row ) {
230 $title = Title::newFromRow( $row );
231
232 // Revision deletion works on revisions,
233 // so cast our recent change row to a revision row.
234 $revRecord = $this->revisionFromRcResult( $row, $title );
235
236 $classes = [];
237 $attribs = [ 'data-mw-revid' => $row->rc_this_oldid ];
238
239 $lang = $this->getLanguage();
240 $time = ChangesList::revDateLink( $revRecord, $this->getUser(), $lang, null, 'mw-newpages-time' );
241
242 $linkRenderer = $this->getLinkRenderer();
243
244 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
245
246 $plink = Html::rawElement( 'bdi', [ 'dir' => $lang->getDir() ], $linkRenderer->makeKnownLink(
247 $title,
248 null,
249 [ 'class' => 'mw-newpages-pagename' ],
250 $query
251 ) );
252 $linkArr = [];
253 $linkArr[] = $linkRenderer->makeKnownLink(
254 $title,
255 $this->msg( 'hist' )->text(),
256 [ 'class' => 'mw-newpages-history' ],
257 [ 'action' => 'history' ]
258 );
259 if ( $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
260 ->supportsDirectEditing()
261 ) {
262 $linkArr[] = $linkRenderer->makeKnownLink(
263 $title,
264 $this->msg( 'editlink' )->text(),
265 [ 'class' => 'mw-newpages-edit' ],
266 [ 'action' => 'edit' ]
267 );
268 }
269 $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
270 ->pipeList( $linkArr ) )->escaped();
271
272 $length = Html::rawElement(
273 'span',
274 [ 'class' => 'mw-newpages-length' ],
275 $this->msg( 'brackets' )->rawParams(
276 $this->msg( 'nbytes' )->numParams( $row->length )->escaped()
277 )->escaped()
278 );
279
280 $ulink = Linker::revUserTools( $revRecord );
281 $rc = RecentChange::newFromRow( $row );
282 if ( ChangesList::userCan( $rc, RevisionRecord::DELETED_COMMENT, $this->getAuthority() ) ) {
283 $comment = $this->formattedComments[$rc->mAttribs['rc_id']];
284 } else {
285 $comment = '<span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
286 }
287 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
288 $deletedClass = 'history-deleted';
289 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
290 $deletedClass .= ' mw-history-suppressed';
291 }
292 $comment = '<span class="' . $deletedClass . ' comment">' . $comment . '</span>';
293 }
294
295 if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled ) {
296 $classes[] = 'not-patrolled';
297 }
298
299 # Add a class for zero byte pages
300 if ( $row->length == 0 ) {
301 $classes[] = 'mw-newpages-zero-byte-page';
302 }
303
304 # Tags, if any.
305 if ( isset( $row->ts_tags ) ) {
306 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
307 $this->tagsCache->makeKey(
308 $row->ts_tags,
309 $this->getUser()->getName(),
310 $lang->getCode()
311 ),
313 $row->ts_tags,
314 'newpages',
315 $this->getContext()
316 )
317 );
318 $classes = array_merge( $classes, $newClasses );
319 } else {
320 $tagDisplay = '';
321 }
322
323 # Display the old title if the namespace/title has been changed
324 $oldTitleText = '';
325 $oldTitle = Title::makeTitle( $row->rc_namespace, $row->rc_title );
326
327 if ( !$title->equals( $oldTitle ) ) {
328 $oldTitleText = $oldTitle->getPrefixedText();
329 $oldTitleText = Html::rawElement(
330 'span',
331 [ 'class' => 'mw-newpages-oldtitle' ],
332 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
333 );
334 }
335
336 $ret = "{$time} {$plink} {$links} {$length} {$ulink} {$comment} "
337 . "{$tagDisplay} {$oldTitleText}";
338
339 // Let extensions add data
340 $this->hookRunner->onNewPagesLineEnding(
341 $this, $ret, $row, $classes, $attribs );
342 $attribs = array_filter( $attribs,
343 [ Sanitizer::class, 'isReservedDataAttribute' ],
344 ARRAY_FILTER_USE_KEY
345 );
346
347 if ( $classes ) {
348 $attribs['class'] = $classes;
349 }
350
351 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
352 }
353
359 protected function revisionFromRcResult( stdClass $result, Title $title ): RevisionRecord {
360 $revRecord = new MutableRevisionRecord( $title );
361 $revRecord->setTimestamp( $result->rc_timestamp );
362 $revRecord->setId( $result->rc_this_oldid );
363 $revRecord->setVisibility( (int)$result->rc_deleted );
364
365 $user = new UserIdentityValue(
366 (int)$result->rc_user,
367 $result->rc_user_text
368 );
369 $revRecord->setUser( $user );
370
371 return $revRecord;
372 }
373
374 protected function doBatchLookups() {
375 $linkBatch = $this->linkBatchFactory->newLinkBatch();
376 foreach ( $this->mResult as $row ) {
377 $linkBatch->add( NS_USER, $row->rc_user_text );
378 $linkBatch->add( NS_USER_TALK, $row->rc_user_text );
379 $linkBatch->add( $row->page_namespace, $row->page_title );
380 }
381 $linkBatch->execute();
382
383 $this->formattedComments = $this->rowCommentFormatter->formatRows(
384 $this->mResult, 'rc_comment', 'page_namespace', 'page_title', 'rc_id', true
385 );
386 }
387
391 protected function getStartBody() {
392 return "<section class='mw-pager-body'>\n";
393 }
394
398 protected function getEndBody() {
399 return "</section>\n";
400 }
401}
402
407class_alias( NewPagesPager::class, 'NewPagesPager' );
const NS_USER
Definition Defines.php:67
const NS_MAIN
Definition Defines.php:65
const NS_USER_TALK
Definition Defines.php:68
Recent changes tagging.
static formatSummaryRow( $tags, $unused, ?MessageLocalizer $localizer=null)
Creates HTML for the given tags.
Base class for lists of recent changes shown on special pages.
Store key-value entries in a size-limited in-memory LRU cache.
Read-write access to the change_tags table.
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
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:56
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:63
getDatabase()
Get the Database object in use.
__construct(IContextSource $context, LinkRenderer $linkRenderer, GroupPermissionsLookup $groupPermissionsLookup, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, NamespaceInfo $namespaceInfo, ChangeTagsStore $changeTagsStore, RowCommentFormatter $rowCommentFormatter, IContentHandlerFactory $contentHandlerFactory, TempUserConfig $tempUserConfig, FormOptions $opts)
formatRow( $row)
Returns an HTML string representing the result row $row.
getEndBody()
Hook into getBody() for the end of the list.to overridestring
getIndexField()
Returns the name of the index field.
bool $mGroupByDate
Whether to group items by date by default this is disabled, but eventually the intention should be to...
revisionFromRcResult(stdClass $result, Title $title)
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
getQueryInfo()
Provides all parameters needed for the main paged query.
getStartBody()
Hook into getBody(), allows text to be inserted at the start.This will be called even if there are no...
IndexPager with a formatted navigation bar.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Page revision base class.
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:78
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:2613
equals(object $other)
Compares with another Title.
Definition Title.php:3095
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1065
Value object representing a user's identity.
Utility class for creating and reading rows in the recentchanges table.
Interface for objects which can provide a MediaWiki context on request.
Interface for temporary user creation config and name matching.