MediaWiki master
NewPagesPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use ChangesList;
25use ChangeTags;
26use MapCacheLRU;
45use RecentChange;
46use stdClass;
47
53
57 protected $opts;
58
60
62 private $formattedComments = [];
66 public $mGroupByDate = true;
67
68 private GroupPermissionsLookup $groupPermissionsLookup;
69 private HookRunner $hookRunner;
70 private LinkBatchFactory $linkBatchFactory;
71 private NamespaceInfo $namespaceInfo;
72 private ChangeTagsStore $changeTagsStore;
73 private RowCommentFormatter $rowCommentFormatter;
74 private IContentHandlerFactory $contentHandlerFactory;
75
88 public function __construct(
89 IContextSource $context,
90 LinkRenderer $linkRenderer,
91 GroupPermissionsLookup $groupPermissionsLookup,
92 HookContainer $hookContainer,
93 LinkBatchFactory $linkBatchFactory,
94 NamespaceInfo $namespaceInfo,
95 ChangeTagsStore $changeTagsStore,
96 RowCommentFormatter $rowCommentFormatter,
97 IContentHandlerFactory $contentHandlerFactory,
99 ) {
100 parent::__construct( $context, $linkRenderer );
101 $this->groupPermissionsLookup = $groupPermissionsLookup;
102 $this->hookRunner = new HookRunner( $hookContainer );
103 $this->linkBatchFactory = $linkBatchFactory;
104 $this->namespaceInfo = $namespaceInfo;
105 $this->changeTagsStore = $changeTagsStore;
106 $this->rowCommentFormatter = $rowCommentFormatter;
107 $this->contentHandlerFactory = $contentHandlerFactory;
108 $this->opts = $opts;
109 $this->tagsCache = new MapCacheLRU( 50 );
110 }
111
112 public function getQueryInfo() {
113 $rcQuery = RecentChange::getQueryInfo();
114
115 $conds = [];
116 $conds['rc_new'] = 1;
117
118 $username = $this->opts->getValue( 'username' );
119 $user = Title::makeTitleSafe( NS_USER, $username );
120
121 $size = abs( intval( $this->opts->getValue( 'size' ) ) );
122 if ( $size > 0 ) {
123 if ( $this->opts->getValue( 'size-mode' ) === 'max' ) {
124 $conds[] = 'page_len <= ' . $size;
125 } else {
126 $conds[] = 'page_len >= ' . $size;
127 }
128 }
129
130 if ( $user ) {
131 $conds['actor_name'] = $user->getText();
132 } elseif ( $this->canAnonymousUsersCreatePages() && $this->opts->getValue( 'hideliu' ) ) {
133 # If anons cannot make new pages, don't "exclude logged in users"!
134 $conds['actor_user'] = null;
135 }
136
137 $conds = array_merge( $conds, $this->getNamespaceCond() );
138
139 # If this user cannot see patrolled edits or they are off, don't do dumb queries!
140 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
141 $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
142 }
143
144 if ( $this->opts->getValue( 'hidebots' ) ) {
145 $conds['rc_bot'] = 0;
146 }
147
148 if ( $this->opts->getValue( 'hideredirs' ) ) {
149 $conds['page_is_redirect'] = 0;
150 }
151
152 // Allow changes to the New Pages query
153 $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
154 $fields = array_merge( $rcQuery['fields'], [
155 'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title',
156 'page_content_model',
157 ] );
158 $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
159
160 $this->hookRunner->onSpecialNewpagesConditions(
161 $this, $this->opts, $conds, $tables, $fields, $join_conds );
162
163 $info = [
164 'tables' => $tables,
165 'fields' => $fields,
166 'conds' => $conds,
167 'options' => [],
168 'join_conds' => $join_conds
169 ];
170
171 // Modify query for tags
172 $this->changeTagsStore->modifyDisplayQuery(
173 $info['tables'],
174 $info['fields'],
175 $info['conds'],
176 $info['join_conds'],
177 $info['options'],
178 $this->opts['tagfilter'],
179 $this->opts['tagInvert']
180 );
181
182 return $info;
183 }
184
185 private function canAnonymousUsersCreatePages() {
186 return $this->groupPermissionsLookup->groupHasPermission( '*', 'createpage' ) ||
187 $this->groupPermissionsLookup->groupHasPermission( '*', 'createtalk' );
188 }
189
190 // Based on ContribsPager.php
191 private function getNamespaceCond() {
192 $namespace = $this->opts->getValue( 'namespace' );
193 if ( $namespace === 'all' || $namespace === '' ) {
194 return [];
195 }
196
197 $namespace = intval( $namespace );
198 if ( $namespace < NS_MAIN ) {
199 // Negative namespaces are invalid
200 return [];
201 }
202
203 $invert = $this->opts->getValue( 'invert' );
204 $associated = $this->opts->getValue( 'associated' );
205
206 $eq_op = $invert ? '!=' : '=';
207 $bool_op = $invert ? 'AND' : 'OR';
208
209 $dbr = $this->getDatabase();
210 $selectedNS = $dbr->addQuotes( $namespace );
211 if ( !$associated ) {
212 return [ "rc_namespace $eq_op $selectedNS" ];
213 }
214
215 $associatedNS = $dbr->addQuotes(
216 $this->namespaceInfo->getAssociated( $namespace )
217 );
218 return [
219 "rc_namespace $eq_op $selectedNS " .
220 $bool_op .
221 " rc_namespace $eq_op $associatedNS"
222 ];
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->rev_id ];
238
239 $lang = $this->getLanguage();
240 $dm = $lang->getDirMark();
241 $time = ChangesList::revDateLink( $revRecord, $this->getUser(), $lang, null, 'mw-newpages-time' );
242
243 $linkRenderer = $this->getLinkRenderer();
244
245 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
246
247 $plink = $linkRenderer->makeKnownLink(
248 $title,
249 null,
250 [ 'class' => 'mw-newpages-pagename' ],
251 $query
252 );
253 $linkArr = [];
254 $linkArr[] = $linkRenderer->makeKnownLink(
255 $title,
256 $this->msg( 'hist' )->text(),
257 [ 'class' => 'mw-newpages-history' ],
258 [ 'action' => 'history' ]
259 );
260 if ( $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
261 ->supportsDirectEditing()
262 ) {
263 $linkArr[] = $linkRenderer->makeKnownLink(
264 $title,
265 $this->msg( 'editlink' )->text(),
266 [ 'class' => 'mw-newpages-edit' ],
267 [ 'action' => 'edit' ]
268 );
269 }
270 $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
271 ->pipeList( $linkArr ) )->escaped();
272
273 $length = Html::rawElement(
274 'span',
275 [ 'class' => 'mw-newpages-length' ],
276 $this->msg( 'brackets' )->rawParams(
277 $this->msg( 'nbytes' )->numParams( $row->length )->escaped()
278 )->escaped()
279 );
280
281 $ulink = Linker::revUserTools( $revRecord );
282 $rc = RecentChange::newFromRow( $row );
283 if ( ChangesList::userCan( $rc, RevisionRecord::DELETED_COMMENT, $this->getAuthority() ) ) {
284 $comment = $this->formattedComments[$rc->mAttribs['rc_id']];
285 } else {
286 $comment = '<span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
287 }
288 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
289 $deletedClass = 'history-deleted';
290 if ( ChangesList::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
291 $deletedClass .= ' mw-history-suppressed';
292 }
293 $comment = '<span class="' . $deletedClass . ' comment">' . $comment . '</span>';
294 }
295
296 if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled ) {
297 $classes[] = 'not-patrolled';
298 }
299
300 # Add a class for zero byte pages
301 if ( $row->length == 0 ) {
302 $classes[] = 'mw-newpages-zero-byte-page';
303 }
304
305 # Tags, if any.
306 if ( isset( $row->ts_tags ) ) {
307 [ $tagDisplay, $newClasses ] = $this->tagsCache->getWithSetCallback(
308 $this->tagsCache->makeKey(
309 $row->ts_tags,
310 $this->getUser()->getName(),
311 $lang->getCode()
312 ),
314 $row->ts_tags,
315 'newpages',
316 $this->getContext()
317 )
318 );
319 $classes = array_merge( $classes, $newClasses );
320 } else {
321 $tagDisplay = '';
322 }
323
324 # Display the old title if the namespace/title has been changed
325 $oldTitleText = '';
326 $oldTitle = Title::makeTitle( $row->rc_namespace, $row->rc_title );
327
328 if ( !$title->equals( $oldTitle ) ) {
329 $oldTitleText = $oldTitle->getPrefixedText();
330 $oldTitleText = Html::rawElement(
331 'span',
332 [ 'class' => 'mw-newpages-oldtitle' ],
333 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
334 );
335 }
336
337 $ret = "{$time} {$dm}{$plink} {$links} {$dm}{$length} {$dm}{$ulink} {$comment} "
338 . "{$tagDisplay} {$oldTitleText}";
339
340 // Let extensions add data
341 $this->hookRunner->onNewPagesLineEnding(
342 $this, $ret, $row, $classes, $attribs );
343 $attribs = array_filter( $attribs,
344 [ Sanitizer::class, 'isReservedDataAttribute' ],
345 ARRAY_FILTER_USE_KEY
346 );
347
348 if ( $classes ) {
349 $attribs['class'] = $classes;
350 }
351
352 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
353 }
354
360 protected function revisionFromRcResult( stdClass $result, Title $title ): RevisionRecord {
361 $revRecord = new MutableRevisionRecord( $title );
362 $revRecord->setTimestamp( $result->rc_timestamp );
363 $revRecord->setId( $result->rc_this_oldid );
364 $revRecord->setVisibility( (int)$result->rc_deleted );
365
366 $user = new UserIdentityValue(
367 (int)$result->rc_user,
368 $result->rc_user_text
369 );
370 $revRecord->setUser( $user );
371
372 return $revRecord;
373 }
374
375 protected function doBatchLookups() {
376 $linkBatch = $this->linkBatchFactory->newLinkBatch();
377 foreach ( $this->mResult as $row ) {
378 $linkBatch->add( NS_USER, $row->rc_user_text );
379 $linkBatch->add( NS_USER_TALK, $row->rc_user_text );
380 $linkBatch->add( $row->page_namespace, $row->page_title );
381 }
382 $linkBatch->execute();
383
384 $this->formattedComments = $this->rowCommentFormatter->formatRows(
385 $this->mResult, 'rc_comment', 'page_namespace', 'page_title', 'rc_id', true
386 );
387 }
388
392 protected function getStartBody() {
393 return "<section class='mw-pager-body'>\n";
394 }
395
399 protected function getEndBody() {
400 return "</section>\n";
401 }
402}
403
408class_alias( NewPagesPager::class, 'NewPagesPager' );
const NS_USER
Definition Defines.php:66
const NS_MAIN
Definition Defines.php:64
const NS_USER_TALK
Definition Defines.php:67
static formatSummaryRow( $tags, $unused, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
Store key-value entries in a size-limited in-memory LRU cache.
Gateway class for 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:65
getDatabase()
Get the Database object in use.
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...
__construct(IContextSource $context, LinkRenderer $linkRenderer, GroupPermissionsLookup $groupPermissionsLookup, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, NamespaceInfo $namespaceInfo, ChangeTagsStore $changeTagsStore, RowCommentFormatter $rowCommentFormatter, IContentHandlerFactory $contentHandlerFactory, FormOptions $opts)
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:2618
equals(object $other)
Compares with another Title.
Definition Title.php:3158
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1066
Value object representing a user's identity.
Utility class for creating new RC entries.
Interface for objects which can provide a MediaWiki context on request.