Merge "Follow-up I774a89d6 (2fabea7): use $this->msg() in HistoryAction"
[mediawiki.git] / includes / actions / InfoAction.php
blobdb7b61d9f58e70265dbcd4abd4ca101d4cba7880
1 <?php
2 /**
3 * Displays information about a page.
5 * Copyright © 2011 Alexandre Emsenhuber
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 * @file
22 * @ingroup Actions
25 class InfoAction extends FormlessAction {
26 /**
27 * Returns the name of the action this object responds to.
29 * @return string lowercase
31 public function getName() {
32 return 'info';
35 /**
36 * Whether this action can still be executed by a blocked user.
38 * @return bool
40 public function requiresUnblock() {
41 return false;
44 /**
45 * Whether this action requires the wiki not to be locked.
47 * @return bool
49 public function requiresWrite() {
50 return false;
53 /**
54 * Shows page information on GET request.
56 * @return string Page information that will be added to the output
58 public function onView() {
59 global $wgDisableCounters, $wgRCMaxAge, $wgRestrictionTypes;
61 $lang = $this->getLanguage();
62 $title = $this->getTitle();
64 $article = new Article( $title );
65 $id = $title->getArticleID();
67 // Get page information that would be too "expensive" to retrieve by normal means
68 $userCanViewUnwatchedPages = $this->getUser()->isAllowed( 'unwatchedpages' );
69 $pageInfo = self::pageCountInfo( $title, $userCanViewUnwatchedPages, $wgDisableCounters );
71 // Get page properties
72 $dbr = wfGetDB( DB_SLAVE );
73 $result = $dbr->select(
74 'page_props',
75 array( 'pp_propname', 'pp_value' ),
76 array( 'pp_page' => $id ),
77 __METHOD__
80 $pageProperties = array();
81 foreach ( $result as $row ) {
82 $pageProperties[$row->pp_propname] = $row->pp_value;
85 $content = '';
86 $table = '';
88 // Basic information
89 $content = $this->addHeader( $content, $this->msg( 'pageinfo-header-basic' ) );
91 // Display title
92 $displayTitle = $title->getPrefixedText();
93 if ( !empty( $pageProperties['displaytitle'] ) ) {
94 $displayTitle = $pageProperties['displaytitle'];
97 $table = $this->addRow( $table,
98 $this->msg( 'pageinfo-display-title' ), $displayTitle );
100 // Default sort key
101 $sortKey = $title->getCategorySortKey();
102 if ( !empty( $pageProperties['defaultsort'] ) ) {
103 $sortKey = $pageProperties['defaultsort'];
106 $table = $this->addRow( $table,
107 $this->msg( 'pageinfo-default-sort' ), $sortKey );
109 // Page length (in bytes)
110 $table = $this->addRow( $table,
111 $this->msg( 'pageinfo-length' ), $lang->formatNum( $title->getLength() ) );
113 // Page ID
114 $table = $this->addRow( $table,
115 $this->msg( 'pageinfo-article-id' ), $lang->formatNum( $id ) );
117 // Search engine status
118 $pOutput = new ParserOutput();
119 if ( isset( $pageProperties['noindex'] ) ) {
120 $pOutput->setIndexPolicy( 'noindex' );
123 // Use robot policy logic
124 $policy = $article->getRobotPolicy( 'view', $pOutput );
125 $table = $this->addRow( $table,
126 'Search engine status', "Marked as '" . $policy['index'] . "'"
129 if ( !$wgDisableCounters ) {
130 // Number of views
131 $table = $this->addRow( $table,
132 $this->msg( 'pageinfo-views' ), $lang->formatNum( $pageInfo['views'] )
136 if ( $userCanViewUnwatchedPages ) {
137 // Number of page watchers
138 $table = $this->addRow( $table,
139 $this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageInfo['watchers'] ) );
142 // Redirects to this page
143 $whatLinksHere = SpecialPage::getTitleFor( 'WhatLinksHere', $title->getPrefixedText() );
144 $table = $this->addRow( $table,
145 Linker::link(
146 $whatLinksHere,
147 $this->msg( 'pageinfo-redirects-name' ),
148 array(),
149 array( 'hidelinks' => 1, 'hidetrans' => 1 )
151 $this->msg( 'pageinfo-redirects-value',
152 $lang->formatNum( count( $title->getRedirectsHere() ) )
156 // Subpages of this page
157 $prefixIndex = SpecialPage::getTitleFor( 'PrefixIndex', $title->getPrefixedText() . '/' );
158 $table = $this->addRow( $table,
159 Linker::link( $prefixIndex, $this->msg( 'pageinfo-subpages-name' ) ),
160 $this->msg( 'pageinfo-subpages-value',
161 $lang->formatNum( $pageInfo['subpages']['total'] ),
162 $pageInfo['subpages']['redirects'],
163 $pageInfo['subpages']['nonredirects']
167 // Page protection
168 $content = $this->addTable( $content, $table );
169 $content = $this->addHeader( $content, $this->msg( 'pageinfo-header-restrictions' ) );
170 $table = '';
172 // Page protection
173 foreach ( $wgRestrictionTypes as $restrictionType ) {
174 $protectionLevel = implode( ', ', $title->getRestrictions( $restrictionType ) );
175 if ( $protectionLevel == '' ) {
176 // Allow all users
177 $message = $this->msg( "protect-default" );
178 } else {
179 // Administrators only
180 $message = $this->msg( "protect-level-$protectionLevel" );
181 if ( !$message->exists() ) {
182 // Require "$1" permission
183 $message = $this->msg( "protect-fallback", $protectionLevel );
187 $table = $this->addRow( $table,
188 $this->msg( 'pageinfo-restriction', $restrictionType ), $message
192 // Edit history
193 $content = $this->addTable( $content, $table );
194 $content = $this->addHeader( $content, $this->msg( 'pageinfo-header-edits' ) );
195 $table = '';
197 // Page creator
198 $table = $this->addRow( $table,
199 $this->msg( 'pageinfo-firstuser' ), $pageInfo['firstuser']
202 // Date of page creation
203 $table = $this->addRow( $table,
204 $this->msg( 'pageinfo-firsttime' ), $lang->timeanddate( $pageInfo['firsttime'] )
207 // Latest editor
208 $table = $this->addRow( $table,
209 $this->msg( 'pageinfo-lastuser' ), $pageInfo['lastuser']
212 // Date of latest edit
213 $table = $this->addRow( $table,
214 $this->msg( 'pageinfo-lasttime' ), $lang->timeanddate( $pageInfo['lasttime'] )
217 // Total number of edits
218 $table = $this->addRow( $table,
219 $this->msg( 'pageinfo-edits' ), $lang->formatNum( $pageInfo['edits'] )
222 // Total number of distinct authors
223 $table = $this->addRow( $table,
224 $this->msg( 'pageinfo-authors' ), $lang->formatNum( $pageInfo['authors'] )
227 // Recent number of edits (within past 30 days)
228 $table = $this->addRow( $table,
229 $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $wgRCMaxAge ) ),
230 $lang->formatNum( $pageInfo['recent_edits'] )
233 // Recent number of distinct authors
234 $table = $this->addRow( $table,
235 $this->msg( 'pageinfo-recent-authors' ), $lang->formatNum( $pageInfo['recent_authors'] )
238 $content = $this->addTable( $content, $table );
240 // Array of MagicWord objects
241 $magicWords = MagicWord::getDoubleUnderscoreArray();
243 // Array of magic word IDs
244 $wordIDs = $magicWords->names;
246 // Array of IDs => localized magic words
247 $localizedWords = $lang->getMagicWords();
249 $listItems = array();
250 foreach ( $pageProperties as $property => $value ) {
251 if ( in_array( $property, $wordIDs ) ) {
252 $listItems[] = Html::element( 'li', array(), $localizedWords[$property][1] );
256 $localizedList = Html::rawElement( 'ul', array(), implode( '', $listItems ) );
257 $hiddenCategories = $article->getHiddenCategories();
258 $transcludedTemplates = $title->getTemplateLinksFrom();
260 if ( count( $listItems ) > 0
261 || count( $hiddenCategories ) > 0
262 || count( $transcludedTemplates ) > 0 ) {
263 // Page properties
264 $content = $this->addHeader( $content, $this->msg( 'pageinfo-header-properties' ) );
265 $table = '';
267 // Magic words
268 if ( count( $listItems ) > 0 ) {
269 $table = $this->addRow( $table,
270 $this->msg( 'pageinfo-magic-words', count( $listItems ) ), $localizedList
274 // Hide "This page is a member of # hidden categories explanation
275 $content .= Html::element( 'style', array(),
276 '.mw-hiddenCategoriesExplanation { display: none; }' );
278 // Hidden categories
279 if ( count( $hiddenCategories ) > 0 ) {
280 $table = $this->addRow( $table,
281 $this->msg( 'pageinfo-hidden-categories', count( $hiddenCategories ) ),
282 Linker::formatHiddenCategories( $hiddenCategories )
286 // Hide "Templates used on this page:" explanation
287 $content .= Html::element( 'style', array(),
288 '.mw-templatesUsedExplanation { display: none; }' );
290 // Transcluded templates
291 if ( count( $transcludedTemplates ) > 0 ) {
292 $table = $this->addRow( $table,
293 $this->msg( 'pageinfo-templates', count( $transcludedTemplates ) ),
294 Linker::formatTemplates( $transcludedTemplates )
298 $content = $this->addTable( $content, $table );
301 return $content;
305 * Returns page information that would be too "expensive" to retrieve by normal means.
307 * @param $title Title object
308 * @param $canViewUnwatched bool
309 * @param $disableCounter bool
310 * @return array
312 public static function pageCountInfo( $title, $canViewUnwatched, $disableCounter ) {
313 global $wgRCMaxAge;
315 wfProfileIn( __METHOD__ );
316 $id = $title->getArticleID();
318 $dbr = wfGetDB( DB_SLAVE );
319 $result = array();
321 if ( !$disableCounter ) {
322 // Number of views
323 $views = (int) $dbr->selectField(
324 'page',
325 'page_counter',
326 array( 'page_id' => $id ),
327 __METHOD__
329 $result['views'] = $views;
332 if ( $canViewUnwatched ) {
333 // Number of page watchers
334 $watchers = (int) $dbr->selectField(
335 'watchlist',
336 'COUNT(*)',
337 array(
338 'wl_namespace' => $title->getNamespace(),
339 'wl_title' => $title->getDBkey(),
341 __METHOD__
343 $result['watchers'] = $watchers;
346 // Total number of edits
347 $edits = (int) $dbr->selectField(
348 'revision',
349 'COUNT(rev_page)',
350 array( 'rev_page' => $id ),
351 __METHOD__
353 $result['edits'] = $edits;
355 // Total number of distinct authors
356 $authors = (int) $dbr->selectField(
357 'revision',
358 'COUNT(DISTINCT rev_user_text)',
359 array( 'rev_page' => $id ),
360 __METHOD__
362 $result['authors'] = $authors;
364 // "Recent" threshold defined by $wgRCMaxAge
365 $threshold = $dbr->timestamp( time() - $wgRCMaxAge );
367 // Recent number of edits
368 $edits = (int) $dbr->selectField(
369 'revision',
370 'COUNT(rev_page)',
371 array(
372 'rev_page' => $id ,
373 "rev_timestamp >= $threshold"
375 __METHOD__
377 $result['recent_edits'] = $edits;
379 // Recent number of distinct authors
380 $authors = (int) $dbr->selectField(
381 'revision',
382 'COUNT(DISTINCT rev_user_text)',
383 array(
384 'rev_page' => $id,
385 "rev_timestamp >= $threshold"
387 __METHOD__
389 $result['recent_authors'] = $authors;
391 $conds = array( 'page_namespace' => $title->getNamespace(), 'page_is_redirect' => 1 );
392 $conds[] = 'page_title ' . $dbr->buildLike( $title->getDBkey() . '/', $dbr->anyString() );
394 // Subpages of this page (redirects)
395 $result['subpages']['redirects'] = (int) $dbr->selectField(
396 'page',
397 'COUNT(page_id)',
398 $conds,
399 __METHOD__ );
401 // Subpages of this page (non-redirects)
402 $conds['page_is_redirect'] = 0;
403 $result['subpages']['nonredirects'] = (int) $dbr->selectField(
404 'page',
405 'COUNT(page_id)',
406 $conds,
407 __METHOD__
410 // Subpages of this page (total)
411 $result['subpages']['total'] = $result['subpages']['redirects']
412 + $result['subpages']['nonredirects'];
414 // Latest editor + date of latest edit
415 $options = array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 );
416 $row = $dbr->fetchRow( $dbr->select(
417 'revision',
418 array( 'rev_user_text', 'rev_timestamp' ),
419 array( 'rev_page' => $id ),
420 __METHOD__,
421 $options
422 ) );
424 $result['firstuser'] = $row['rev_user_text'];
425 $result['firsttime'] = $row['rev_timestamp'];
427 // Latest editor + date of latest edit
428 $options['ORDER BY'] = 'rev_timestamp DESC';
429 $row = $dbr->fetchRow( $dbr->select(
430 'revision',
431 array( 'rev_user_text', 'rev_timestamp' ),
432 array( 'rev_page' => $id ),
433 __METHOD__,
434 $options
435 ) );
437 $result['lastuser'] = $row['rev_user_text'];
438 $result['lasttime'] = $row['rev_timestamp'];
440 wfProfileOut( __METHOD__ );
441 return $result;
445 * Adds a header to the content that will be added to the output.
447 * @param $content string The content that will be added to the output
448 * @param $header string The value of the header
449 * @return string The content with the header added
451 protected function addHeader( $content, $header ) {
452 return $content . Html::element( 'h2', array(), $header );
456 * Adds a row to a table that will be added to the content.
458 * @param $table string The table that will be added to the content
459 * @param $name string The name of the row
460 * @param $value string The value of the row
461 * @return string The table with the row added
463 protected function addRow( $table, $name, $value ) {
464 return $table . Html::rawElement( 'tr', array(),
465 Html::rawElement( 'td', array(), $name ) .
466 Html::rawElement( 'td', array(), $value )
471 * Adds a table to the content that will be added to the output.
473 * @param $content string The content that will be added to the output
474 * @param $table string The table
475 * @return string The content with the table added
477 protected function addTable( $content, $table ) {
478 return $content . Html::rawElement( 'table', array( 'class' => 'wikitable mw-page-info' ),
479 $table );
483 * Returns the description that goes below the <h1> tag.
485 * @return string
487 protected function getDescription() {
488 return '';
492 * Returns the name that goes in the <h1> page title.
494 * @return string
496 protected function getPageTitle() {
497 return $this->msg( 'pageinfo-title', $this->getTitle()->getPrefixedText() )->text();