MediaWiki master
NewPagesPager.php
Go to the documentation of this file.
1<?php
9
10use MediaWiki\Cache\LinkBatchFactory;
33use stdClass;
36
43
44 protected FormOptions $opts;
46
48 private array $formattedComments = [];
52 public $mGroupByDate = true;
53
54 private GroupPermissionsLookup $groupPermissionsLookup;
55 private HookRunner $hookRunner;
56 private LinkBatchFactory $linkBatchFactory;
57 private NamespaceInfo $namespaceInfo;
58 private ChangeTagsStore $changeTagsStore;
59 private RowCommentFormatter $rowCommentFormatter;
60 private IContentHandlerFactory $contentHandlerFactory;
61 private TempUserConfig $tempUserConfig;
62
63 public function __construct(
64 IContextSource $context,
65 LinkRenderer $linkRenderer,
66 GroupPermissionsLookup $groupPermissionsLookup,
67 HookContainer $hookContainer,
68 LinkBatchFactory $linkBatchFactory,
69 NamespaceInfo $namespaceInfo,
70 ChangeTagsStore $changeTagsStore,
71 RowCommentFormatter $rowCommentFormatter,
72 IContentHandlerFactory $contentHandlerFactory,
73 TempUserConfig $tempUserConfig,
75 ) {
76 parent::__construct( $context, $linkRenderer );
77 $this->groupPermissionsLookup = $groupPermissionsLookup;
78 $this->hookRunner = new HookRunner( $hookContainer );
79 $this->linkBatchFactory = $linkBatchFactory;
80 $this->namespaceInfo = $namespaceInfo;
81 $this->changeTagsStore = $changeTagsStore;
82 $this->rowCommentFormatter = $rowCommentFormatter;
83 $this->contentHandlerFactory = $contentHandlerFactory;
84 $this->tempUserConfig = $tempUserConfig;
85 $this->opts = $opts;
86 $this->tagsCache = new MapCacheLRU( 50 );
87 }
88
90 public function getQueryInfo() {
91 $conds = [];
92 $conds['rc_source'] = RecentChange::SRC_NEW;
93
94 $username = $this->opts->getValue( 'username' );
95 $user = Title::makeTitleSafe( NS_USER, $username );
96
97 $size = abs( intval( $this->opts->getValue( 'size' ) ) );
98 if ( $size > 0 ) {
99 $db = $this->getDatabase();
100 if ( $this->opts->getValue( 'size-mode' ) === 'max' ) {
101 $conds[] = $db->expr( 'page_len', '<=', $size );
102 } else {
103 $conds[] = $db->expr( 'page_len', '>=', $size );
104 }
105 }
106
107 if ( $user ) {
108 $conds['actor_name'] = $user->getText();
109 $joinFlags = 0;
110 } elseif ( $this->opts->getValue( 'hideliu' ) ) {
111 // Only include anonymous users if the 'hideliu' option has been provided.
112 $anonOnlyExpr = $this->getDatabase()->expr( 'actor_user', '=', null );
113 if ( $this->tempUserConfig->isKnown() ) {
114 $anonOnlyExpr = $anonOnlyExpr->orExpr( $this->tempUserConfig->getMatchCondition(
115 $this->getDatabase(), 'actor_name', IExpression::LIKE
116 ) );
117 }
118 $conds[] = $anonOnlyExpr;
119 $joinFlags = 0;
120 } else {
121 $joinFlags = RecentChange::STRAIGHT_JOIN_ACTOR;
122 }
123
124 $conds = array_merge( $conds, $this->getNamespaceCond() );
125
126 # If this user cannot see patrolled edits or they are off, don't do dumb queries!
127 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
128 $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
129 }
130
131 if ( $this->opts->getValue( 'hidebots' ) ) {
132 $conds['rc_bot'] = 0;
133 }
134
135 if ( $this->opts->getValue( 'hideredirs' ) ) {
136 $conds['page_is_redirect'] = 0;
137 }
138
139 // Allow changes to the New Pages query
140 $rcQuery = RecentChange::getQueryInfo( $joinFlags );
141 $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
142 $fields = array_merge( $rcQuery['fields'], [
143 'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title',
144 'page_content_model',
145 ] );
146 $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
147
148 $this->hookRunner->onSpecialNewpagesConditions(
149 $this, $this->opts, $conds, $tables, $fields, $join_conds );
150
151 $info = [
152 'tables' => $tables,
153 'fields' => $fields,
154 'conds' => $conds,
155 'options' => [],
156 'join_conds' => $join_conds
157 ];
158
159 // Modify query for tags
160 $this->changeTagsStore->modifyDisplayQuery(
161 $info['tables'],
162 $info['fields'],
163 $info['conds'],
164 $info['join_conds'],
165 $info['options'],
166 $this->opts['tagfilter'],
167 $this->opts['tagInvert']
168 );
169
170 return $info;
171 }
172
173 private function getNamespaceCond(): array {
174 $namespace = $this->opts->getValue( 'namespace' );
175 if ( $namespace === 'all' || $namespace === '' ) {
176 return [];
177 }
178
179 $namespace = intval( $namespace );
180 if ( $namespace < NS_MAIN ) {
181 // Negative namespaces are invalid
182 return [];
183 }
184
185 $invert = $this->opts->getValue( 'invert' );
186 $associated = $this->opts->getValue( 'associated' );
187
188 $eq_op = $invert ? '!=' : '=';
189 $dbr = $this->getDatabase();
190 $namespaces = [ $namespace ];
191 if ( $associated ) {
192 $namespaces[] = $this->namespaceInfo->getAssociated( $namespace );
193 }
194
195 return [ $dbr->expr( 'rc_namespace', $eq_op, $namespaces ) ];
196 }
197
199 public function getIndexField() {
200 return [ [ 'rc_timestamp', 'rc_id' ] ];
201 }
202
204 public function formatRow( $row ) {
205 $title = Title::newFromRow( $row );
206
207 // Revision deletion works on revisions,
208 // so cast our recent change row to a revision row.
209 $revRecord = $this->revisionFromRcResult( $row, $title );
210
211 $classes = [];
212 $attribs = [ 'data-mw-revid' => $row->rc_this_oldid ];
213
214 $lang = $this->getLanguage();
215 $time = ChangesList::revDateLink( $revRecord, $this->getUser(), $lang, null, 'mw-newpages-time' );
216
217 $linkRenderer = $this->getLinkRenderer();
218
219 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
220
221 $plink = Html::rawElement( 'bdi', [ 'dir' => $lang->getDir() ], $linkRenderer->makeKnownLink(
222 $title,
223 null,
224 [ 'class' => 'mw-newpages-pagename' ],
225 $query
226 ) );
227 $linkArr = [];
228 $linkArr[] = $linkRenderer->makeKnownLink(
229 $title,
230 $this->msg( 'hist' )->text(),
231 [ 'class' => 'mw-newpages-history' ],
232 [ 'action' => 'history' ]
233 );
234 if ( $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
235 ->supportsDirectEditing()
236 ) {
237 $linkArr[] = $linkRenderer->makeKnownLink(
238 $title,
239 $this->msg( 'editlink' )->text(),
240 [ 'class' => 'mw-newpages-edit' ],
241 [ 'action' => 'edit' ]
242 );
243 }
244 $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
245 ->pipeList( $linkArr ) )->escaped();
246
247 $length = Html::rawElement(
248 'span',
249 [ 'class' => 'mw-newpages-length' ],
250 $this->msg( 'brackets' )->rawParams(
251 $this->msg( 'nbytes' )->numParams( $row->length )->escaped()
252 )->escaped()
253 );
254
255 $ulink = Linker::revUserTools( $revRecord );
256 $rc = RecentChange::newFromRow( $row );
257 if ( ChangesList::userCan( $rc, RevisionRecord::DELETED_COMMENT, $this->getAuthority() ) ) {
258 $comment = $this->formattedComments[$rc->mAttribs['rc_id']];
259 } else {
260 $comment = '<span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
261 }
262 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
263 $deletedClass = 'history-deleted';
264 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
265 $deletedClass .= ' mw-history-suppressed';
266 }
267 $comment = '<span class="' . $deletedClass . ' comment">' . $comment . '</span>';
268 }
269
270 if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled ) {
271 $classes[] = 'not-patrolled';
272 }
273
274 # Add a class for zero byte pages
275 if ( $row->length == 0 ) {
276 $classes[] = 'mw-newpages-zero-byte-page';
277 }
278
279 # Tags, if any.
280 if ( isset( $row->ts_tags ) ) {
281 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
282 $this->tagsCache->makeKey(
283 $row->ts_tags,
284 $this->getUser()->getName(),
285 $lang->getCode()
286 ),
287 fn () => ChangeTags::formatSummaryRow(
288 $row->ts_tags,
289 'newpages',
290 $this->getContext()
291 )
292 );
293 $classes = array_merge( $classes, $newClasses );
294 } else {
295 $tagDisplay = '';
296 }
297
298 # Display the old title if the namespace/title has been changed
299 $oldTitleText = '';
300 $oldTitle = Title::makeTitle( $row->rc_namespace, $row->rc_title );
301
302 if ( !$title->equals( $oldTitle ) ) {
303 $oldTitleText = $oldTitle->getPrefixedText();
304 $oldTitleText = Html::rawElement(
305 'span',
306 [ 'class' => 'mw-newpages-oldtitle' ],
307 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
308 );
309 }
310
311 $ret = "{$time} {$plink} {$links} {$length} {$ulink} {$comment} "
312 . "{$tagDisplay} {$oldTitleText}";
313
314 // Let extensions add data
315 $this->hookRunner->onNewPagesLineEnding(
316 $this, $ret, $row, $classes, $attribs );
317 $attribs = array_filter( $attribs,
318 Sanitizer::isReservedDataAttribute( ... ),
319 ARRAY_FILTER_USE_KEY
320 );
321
322 if ( $classes ) {
323 $attribs['class'] = $classes;
324 }
325
326 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
327 }
328
334 protected function revisionFromRcResult( stdClass $result, Title $title ): RevisionRecord {
335 $revRecord = new MutableRevisionRecord( $title );
336 $revRecord->setTimestamp( $result->rc_timestamp );
337 $revRecord->setId( $result->rc_this_oldid );
338 $revRecord->setVisibility( (int)$result->rc_deleted );
339
340 $user = new UserIdentityValue(
341 (int)$result->rc_user,
342 $result->rc_user_text
343 );
344 $revRecord->setUser( $user );
345
346 return $revRecord;
347 }
348
349 protected function doBatchLookups() {
350 $linkBatch = $this->linkBatchFactory->newLinkBatch();
351 foreach ( $this->mResult as $row ) {
352 $linkBatch->addUser( new UserIdentityValue( (int)$row->rc_user, $row->rc_user_text ) );
353 $linkBatch->add( $row->page_namespace, $row->page_title );
354 }
355 $linkBatch->execute();
356
357 $this->formattedComments = $this->rowCommentFormatter->formatRows(
358 $this->mResult, 'rc_comment', 'page_namespace', 'page_title', 'rc_id', true
359 );
360 }
361
365 protected function getStartBody() {
366 return "<section class='mw-pager-body'>\n";
367 }
368
372 protected function getEndBody() {
373 return "</section>\n";
374 }
375}
376
381class_alias( NewPagesPager::class, 'NewPagesPager' );
382
384class_alias( NewPagesPager::class, 'MediaWiki\\Pager\\NewPagesPager' );
const NS_USER
Definition Defines.php:53
const NS_MAIN
Definition Defines.php:51
Read-write access to the change_tags table.
Recent changes tagging.
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:43
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:47
getDatabase()
Get the Database object in use.
IndexPager with a formatted navigation bar.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:32
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, 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.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:70
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:3087
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1060
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.