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