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