MediaWiki  master
NewPagesPager.php
Go to the documentation of this file.
1 <?php
22 namespace MediaWiki\Pager;
23 
24 use ChangeTags;
25 use HtmlArmor;
26 use IContextSource;
45 use RecentChange;
46 use stdClass;
47 
53 
57  protected $opts;
58 
59  private GroupPermissionsLookup $groupPermissionsLookup;
60  private HookRunner $hookRunner;
61  private LinkBatchFactory $linkBatchFactory;
62  private NamespaceInfo $namespaceInfo;
63  private ChangeTagsStore $changeTagsStore;
64  private CommentStore $commentStore;
65  private CommentFormatter $commentFormatter;
66  private IContentHandlerFactory $contentHandlerFactory;
67 
81  public function __construct(
82  IContextSource $context,
83  LinkRenderer $linkRenderer,
84  GroupPermissionsLookup $groupPermissionsLookup,
85  HookContainer $hookContainer,
86  LinkBatchFactory $linkBatchFactory,
87  NamespaceInfo $namespaceInfo,
88  ChangeTagsStore $changeTagsStore,
89  CommentStore $commentStore,
90  CommentFormatter $commentFormatter,
91  IContentHandlerFactory $contentHandlerFactory,
93  ) {
94  parent::__construct( $context, $linkRenderer );
95  $this->groupPermissionsLookup = $groupPermissionsLookup;
96  $this->hookRunner = new HookRunner( $hookContainer );
97  $this->linkBatchFactory = $linkBatchFactory;
98  $this->namespaceInfo = $namespaceInfo;
99  $this->changeTagsStore = $changeTagsStore;
100  $this->commentStore = $commentStore;
101  $this->commentFormatter = $commentFormatter;
102  $this->contentHandlerFactory = $contentHandlerFactory;
103  $this->opts = $opts;
104  }
105 
106  public function getQueryInfo() {
107  $rcQuery = RecentChange::getQueryInfo();
108 
109  $conds = [];
110  $conds['rc_new'] = 1;
111 
112  $username = $this->opts->getValue( 'username' );
113  $user = Title::makeTitleSafe( NS_USER, $username );
114 
115  $size = abs( intval( $this->opts->getValue( 'size' ) ) );
116  if ( $size > 0 ) {
117  if ( $this->opts->getValue( 'size-mode' ) === 'max' ) {
118  $conds[] = 'page_len <= ' . $size;
119  } else {
120  $conds[] = 'page_len >= ' . $size;
121  }
122  }
123 
124  if ( $user ) {
125  $conds['actor_name'] = $user->getText();
126  } elseif ( $this->canAnonymousUsersCreatePages() && $this->opts->getValue( 'hideliu' ) ) {
127  # If anons cannot make new pages, don't "exclude logged in users"!
128  $conds['actor_user'] = null;
129  }
130 
131  $conds = array_merge( $conds, $this->getNamespaceCond() );
132 
133  # If this user cannot see patrolled edits or they are off, don't do dumb queries!
134  if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
135  $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
136  }
137 
138  if ( $this->opts->getValue( 'hidebots' ) ) {
139  $conds['rc_bot'] = 0;
140  }
141 
142  if ( $this->opts->getValue( 'hideredirs' ) ) {
143  $conds['page_is_redirect'] = 0;
144  }
145 
146  // Allow changes to the New Pages query
147  $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
148  $fields = array_merge( $rcQuery['fields'], [
149  'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title',
150  'page_content_model',
151  ] );
152  $join_conds = [ 'page' => [ 'JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
153 
154  $this->hookRunner->onSpecialNewpagesConditions(
155  $this, $this->opts, $conds, $tables, $fields, $join_conds );
156 
157  $info = [
158  'tables' => $tables,
159  'fields' => $fields,
160  'conds' => $conds,
161  'options' => [],
162  'join_conds' => $join_conds
163  ];
164 
165  // Modify query for tags
166  $this->changeTagsStore->modifyDisplayQuery(
167  $info['tables'],
168  $info['fields'],
169  $info['conds'],
170  $info['join_conds'],
171  $info['options'],
172  $this->opts['tagfilter'],
173  $this->opts['tagInvert']
174  );
175 
176  return $info;
177  }
178 
179  private function canAnonymousUsersCreatePages() {
180  return $this->groupPermissionsLookup->groupHasPermission( '*', 'createpage' ) ||
181  $this->groupPermissionsLookup->groupHasPermission( '*', 'createtalk' );
182  }
183 
184  // Based on ContribsPager.php
185  private function getNamespaceCond() {
186  $namespace = $this->opts->getValue( 'namespace' );
187  if ( $namespace === 'all' || $namespace === '' ) {
188  return [];
189  }
190 
191  $namespace = intval( $namespace );
192  if ( $namespace < NS_MAIN ) {
193  // Negative namespaces are invalid
194  return [];
195  }
196 
197  $invert = $this->opts->getValue( 'invert' );
198  $associated = $this->opts->getValue( 'associated' );
199 
200  $eq_op = $invert ? '!=' : '=';
201  $bool_op = $invert ? 'AND' : 'OR';
202 
203  $dbr = $this->getDatabase();
204  $selectedNS = $dbr->addQuotes( $namespace );
205  if ( !$associated ) {
206  return [ "rc_namespace $eq_op $selectedNS" ];
207  }
208 
209  $associatedNS = $dbr->addQuotes(
210  $this->namespaceInfo->getAssociated( $namespace )
211  );
212  return [
213  "rc_namespace $eq_op $selectedNS " .
214  $bool_op .
215  " rc_namespace $eq_op $associatedNS"
216  ];
217  }
218 
219  public function getIndexField() {
220  return [ [ 'rc_timestamp', 'rc_id' ] ];
221  }
222 
223  public function formatRow( $row ) {
224  $title = Title::newFromRow( $row );
225 
226  // Revision deletion works on revisions,
227  // so cast our recent change row to a revision row.
228  $revRecord = $this->revisionFromRcResult( $row, $title );
229 
230  $classes = [];
231  $attribs = [ 'data-mw-revid' => $row->rev_id ];
232 
233  $lang = $this->getLanguage();
234  $dm = $lang->getDirMark();
235 
236  $spanTime = Html::element( 'span', [ 'class' => 'mw-newpages-time' ],
237  $lang->userTimeAndDate( $row->rc_timestamp, $this->getUser() )
238  );
239  $linkRenderer = $this->getLinkRenderer();
240  $time = $linkRenderer->makeKnownLink(
241  $title,
242  new HtmlArmor( $spanTime ),
243  [],
244  [ 'oldid' => $row->rc_this_oldid ]
245  );
246 
247  $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
248 
249  $plink = $linkRenderer->makeKnownLink(
250  $title,
251  null,
252  [ 'class' => 'mw-newpages-pagename' ],
253  $query
254  );
255  $linkArr = [];
256  $linkArr[] = $linkRenderer->makeKnownLink(
257  $title,
258  $this->msg( 'hist' )->text(),
259  [ 'class' => 'mw-newpages-history' ],
260  [ 'action' => 'history' ]
261  );
262  if ( $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
263  ->supportsDirectEditing()
264  ) {
265  $linkArr[] = $linkRenderer->makeKnownLink(
266  $title,
267  $this->msg( 'editlink' )->text(),
268  [ 'class' => 'mw-newpages-edit' ],
269  [ 'action' => 'edit' ]
270  );
271  }
272  $links = $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
273  ->pipeList( $linkArr ) )->escaped();
274 
275  $length = Html::rawElement(
276  'span',
277  [ 'class' => 'mw-newpages-length' ],
278  $this->msg( 'brackets' )->rawParams(
279  $this->msg( 'nbytes' )->numParams( $row->length )->escaped()
280  )->escaped()
281  );
282 
283  $ulink = Linker::revUserTools( $revRecord );
284  $comment = $this->commentFormatter->formatRevision( $revRecord, $this->getAuthority() );
285 
286  if ( $this->getUser()->useNPPatrol() && !$row->rc_patrolled ) {
287  $classes[] = 'not-patrolled';
288  }
289 
290  # Add a class for zero byte pages
291  if ( $row->length == 0 ) {
292  $classes[] = 'mw-newpages-zero-byte-page';
293  }
294 
295  # Tags, if any.
296  if ( isset( $row->ts_tags ) ) {
297  [ $tagDisplay, $newClasses ] = ChangeTags::formatSummaryRow(
298  $row->ts_tags,
299  'newpages',
300  $this->getContext()
301  );
302  $classes = array_merge( $classes, $newClasses );
303  } else {
304  $tagDisplay = '';
305  }
306 
307  # Display the old title if the namespace/title has been changed
308  $oldTitleText = '';
309  $oldTitle = Title::makeTitle( $row->rc_namespace, $row->rc_title );
310 
311  if ( !$title->equals( $oldTitle ) ) {
312  $oldTitleText = $oldTitle->getPrefixedText();
313  $oldTitleText = Html::rawElement(
314  'span',
315  [ 'class' => 'mw-newpages-oldtitle' ],
316  $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
317  );
318  }
319 
320  $ret = "{$time} {$dm}{$plink} {$links} {$dm}{$length} {$dm}{$ulink} {$comment} "
321  . "{$tagDisplay} {$oldTitleText}";
322 
323  // Let extensions add data
324  $this->hookRunner->onNewPagesLineEnding(
325  $this, $ret, $row, $classes, $attribs );
326  $attribs = array_filter( $attribs,
327  [ Sanitizer::class, 'isReservedDataAttribute' ],
328  ARRAY_FILTER_USE_KEY
329  );
330 
331  if ( $classes ) {
332  $attribs['class'] = $classes;
333  }
334 
335  return Html::rawElement( 'li', $attribs, $ret ) . "\n";
336  }
337 
343  protected function revisionFromRcResult( stdClass $result, Title $title ): RevisionRecord {
344  $revRecord = new MutableRevisionRecord( $title );
345  $revRecord->setComment(
346  $this->commentStore->getComment( 'rc_comment', $result )
347  );
348  $revRecord->setVisibility( (int)$result->rc_deleted );
349 
350  $user = new UserIdentityValue(
351  (int)$result->rc_user,
352  $result->rc_user_text
353  );
354  $revRecord->setUser( $user );
355 
356  return $revRecord;
357  }
358 
359  protected function doBatchLookups() {
360  $linkBatch = $this->linkBatchFactory->newLinkBatch();
361  foreach ( $this->mResult as $row ) {
362  $linkBatch->add( NS_USER, $row->rc_user_text );
363  $linkBatch->add( NS_USER_TALK, $row->rc_user_text );
364  $linkBatch->add( $row->page_namespace, $row->page_title );
365  }
366  $linkBatch->execute();
367  }
368 
369  protected function getStartBody() {
370  return '<ul>';
371  }
372 
373  protected function getEndBody() {
374  return '</ul>';
375  }
376 }
377 
382 class_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.
Definition: ChangeTags.php:147
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
Gateway class for change_tags table.
This is the main service interface for converting single-line comments from various DB comment fields...
Handle database storage of comments such as edit summaries and log reasons.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:41
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:235
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:256
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition: Linker.php:65
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1455
getDatabase()
Get the Database object in use.
Definition: IndexPager.php:256
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.
revisionFromRcResult(stdClass $result, Title $title)
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
__construct(IContextSource $context, LinkRenderer $linkRenderer, GroupPermissionsLookup $groupPermissionsLookup, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, NamespaceInfo $namespaceInfo, ChangeTagsStore $changeTagsStore, CommentStore $commentStore, CommentFormatter $commentFormatter, IContentHandlerFactory $contentHandlerFactory, FormOptions $opts)
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:76
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:559
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:2623
equals(object $other)
Compares with another Title.
Definition: Title.php:3162
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:650
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1080
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
Value object representing a user's identity.
Utility class for creating new RC entries.
const PRC_UNPATROLLED
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Interface for objects which can provide a MediaWiki context on request.