3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
22 namespace MediaWiki\Pager
;
27 use MediaWiki\Cache\LinkBatchFactory
;
28 use MediaWiki\ChangeTags\ChangeTagsStore
;
29 use MediaWiki\CommentFormatter\RowCommentFormatter
;
30 use MediaWiki\Content\IContentHandlerFactory
;
31 use MediaWiki\Context\IContextSource
;
32 use MediaWiki\HookContainer\HookContainer
;
33 use MediaWiki\HookContainer\HookRunner
;
34 use MediaWiki\Html\FormOptions
;
35 use MediaWiki\Html\Html
;
36 use MediaWiki\Linker\Linker
;
37 use MediaWiki\Linker\LinkRenderer
;
38 use MediaWiki\Parser\Sanitizer
;
39 use MediaWiki\Permissions\GroupPermissionsLookup
;
40 use MediaWiki\Revision\MutableRevisionRecord
;
41 use MediaWiki\Revision\RevisionRecord
;
42 use MediaWiki\Title\NamespaceInfo
;
43 use MediaWiki\Title\Title
;
44 use MediaWiki\User\TempUser\TempUserConfig
;
45 use MediaWiki\User\UserIdentityValue
;
48 use Wikimedia\Rdbms\IExpression
;
51 * @internal For use by SpecialNewPages
52 * @ingroup RecentChanges
55 class NewPagesPager
extends ReverseChronologicalPager
{
62 protected MapCacheLRU
$tagsCache;
65 private $formattedComments = [];
66 /** @var bool Whether to group items by date by default this is disabled, but eventually the intention
67 * should be to default to true once all pages have been transitioned to support date grouping.
69 public $mGroupByDate = true;
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;
81 * @param IContextSource $context
82 * @param LinkRenderer $linkRenderer
83 * @param GroupPermissionsLookup $groupPermissionsLookup
84 * @param HookContainer $hookContainer
85 * @param LinkBatchFactory $linkBatchFactory
86 * @param NamespaceInfo $namespaceInfo
87 * @param ChangeTagsStore $changeTagsStore
88 * @param RowCommentFormatter $rowCommentFormatter
89 * @param IContentHandlerFactory $contentHandlerFactory
90 * @param TempUserConfig $tempUserConfig
91 * @param FormOptions $opts
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,
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;
116 $this->tagsCache
= new MapCacheLRU( 50 );
119 public function getQueryInfo() {
120 $rcQuery = RecentChange
::getQueryInfo();
123 $conds['rc_new'] = 1;
125 $username = $this->opts
->getValue( 'username' );
126 $user = Title
::makeTitleSafe( NS_USER
, $username );
128 $size = abs( intval( $this->opts
->getValue( 'size' ) ) );
130 $db = $this->getDatabase();
131 if ( $this->opts
->getValue( 'size-mode' ) === 'max' ) {
132 $conds[] = $db->expr( 'page_len', '<=', $size );
134 $conds[] = $db->expr( 'page_len', '>=', $size );
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
148 $conds[] = $anonOnlyExpr;
151 $conds = array_merge( $conds, $this->getNamespaceCond() );
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
;
158 if ( $this->opts
->getValue( 'hidebots' ) ) {
159 $conds['rc_bot'] = 0;
162 if ( $this->opts
->getValue( 'hideredirs' ) ) {
163 $conds['page_is_redirect'] = 0;
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',
172 $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] +
$rcQuery['joins'];
174 $this->hookRunner
->onSpecialNewpagesConditions(
175 $this, $this->opts
, $conds, $tables, $fields, $join_conds );
182 'join_conds' => $join_conds
185 // Modify query for tags
186 $this->changeTagsStore
->modifyDisplayQuery(
192 $this->opts
['tagfilter'],
193 $this->opts
['tagInvert']
199 // Based on ContribsPager.php
200 private function getNamespaceCond() {
201 $namespace = $this->opts
->getValue( 'namespace' );
202 if ( $namespace === 'all' ||
$namespace === '' ) {
206 $namespace = intval( $namespace );
207 if ( $namespace < NS_MAIN
) {
208 // Negative namespaces are invalid
212 $invert = $this->opts
->getValue( 'invert' );
213 $associated = $this->opts
->getValue( 'associated' );
215 $eq_op = $invert ?
'!=' : '=';
216 $dbr = $this->getDatabase();
217 $namespaces = [ $namespace ];
219 $namespaces[] = $this->namespaceInfo
->getAssociated( $namespace );
222 return [ $dbr->expr( 'rc_namespace', $eq_op, $namespaces ) ];
225 public function getIndexField() {
226 return [ [ 'rc_timestamp', 'rc_id' ] ];
229 public function formatRow( $row ) {
230 $title = Title
::newFromRow( $row );
232 // Revision deletion works on revisions,
233 // so cast our recent change row to a revision row.
234 $revRecord = $this->revisionFromRcResult( $row, $title );
237 $attribs = [ 'data-mw-revid' => $row->rc_this_oldid
];
239 $lang = $this->getLanguage();
240 $time = ChangesList
::revDateLink( $revRecord, $this->getUser(), $lang, null, 'mw-newpages-time' );
242 $linkRenderer = $this->getLinkRenderer();
244 $query = $title->isRedirect() ?
[ 'redirect' => 'no' ] : [];
246 $plink = Html
::rawElement( 'bdi', [ 'dir' => $lang->getDir() ], $linkRenderer->makeKnownLink(
249 [ 'class' => 'mw-newpages-pagename' ],
253 $linkArr[] = $linkRenderer->makeKnownLink(
255 $this->msg( 'hist' )->text(),
256 [ 'class' => 'mw-newpages-history' ],
257 [ 'action' => 'history' ]
259 if ( $this->contentHandlerFactory
->getContentHandler( $title->getContentModel() )
260 ->supportsDirectEditing()
262 $linkArr[] = $linkRenderer->makeKnownLink(
264 $this->msg( 'editlink' )->text(),
265 [ 'class' => 'mw-newpages-edit' ],
266 [ 'action' => 'edit' ]
269 $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
270 ->pipeList( $linkArr ) )->escaped();
272 $length = Html
::rawElement(
274 [ 'class' => 'mw-newpages-length' ],
275 $this->msg( 'brackets' )->rawParams(
276 $this->msg( 'nbytes' )->numParams( $row->length
)->escaped()
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']];
285 $comment = '<span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
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';
292 $comment = '<span class="' . $deletedClass . ' comment">' . $comment . '</span>';
295 if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled
) {
296 $classes[] = 'not-patrolled';
299 # Add a class for zero byte pages
300 if ( $row->length
== 0 ) {
301 $classes[] = 'mw-newpages-zero-byte-page';
305 if ( isset( $row->ts_tags
) ) {
306 [ $tagDisplay, $newClasses ] = $this->tagsCache
->getWithSetCallback(
307 $this->tagsCache
->makeKey(
309 $this->getUser()->getName(),
312 fn () => ChangeTags
::formatSummaryRow(
318 $classes = array_merge( $classes, $newClasses );
323 # Display the old title if the namespace/title has been changed
325 $oldTitle = Title
::makeTitle( $row->rc_namespace
, $row->rc_title
);
327 if ( !$title->equals( $oldTitle ) ) {
328 $oldTitleText = $oldTitle->getPrefixedText();
329 $oldTitleText = Html
::rawElement(
331 [ 'class' => 'mw-newpages-oldtitle' ],
332 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
336 $ret = "{$time} {$plink} {$links} {$length} {$ulink} {$comment} "
337 . "{$tagDisplay} {$oldTitleText}";
339 // Let extensions add data
340 $this->hookRunner
->onNewPagesLineEnding(
341 $this, $ret, $row, $classes, $attribs );
342 $attribs = array_filter( $attribs,
343 [ Sanitizer
::class, 'isReservedDataAttribute' ],
348 $attribs['class'] = $classes;
351 return Html
::rawElement( 'li', $attribs, $ret ) . "\n";
355 * @param stdClass $result Result row from recent changes
356 * @param Title $title
357 * @return RevisionRecord
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
);
365 $user = new UserIdentityValue(
366 (int)$result->rc_user
,
367 $result->rc_user_text
369 $revRecord->setUser( $user );
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
);
381 $linkBatch->execute();
383 $this->formattedComments
= $this->rowCommentFormatter
->formatRows(
384 $this->mResult
, 'rc_comment', 'page_namespace', 'page_title', 'rc_id', true
391 protected function getStartBody() {
392 return "<section class='mw-pager-body'>\n";
398 protected function getEndBody() {
399 return "</section>\n";
404 * Retain the old class name for backwards compatibility.
405 * @deprecated since 1.41
407 class_alias( NewPagesPager
::class, 'NewPagesPager' );