diff options
Diffstat (limited to 'includes/actions/InfoAction.php')
-rw-r--r-- | includes/actions/InfoAction.php | 373 |
1 files changed, 287 insertions, 86 deletions
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php index ae550391..7fc90339 100644 --- a/includes/actions/InfoAction.php +++ b/includes/actions/InfoAction.php @@ -22,7 +22,14 @@ * @ingroup Actions */ +/** + * Displays information about a page. + * + * @ingroup Actions + */ class InfoAction extends FormlessAction { + const CACHE_VERSION = '2013-03-17'; + /** * Returns the name of the action this object responds to. * @@ -51,6 +58,22 @@ class InfoAction extends FormlessAction { } /** + * Clear the info cache for a given Title. + * + * @since 1.22 + * @param Title $title Title to clear cache for + */ + public static function invalidateCache( Title $title ) { + global $wgMemc; + // Clear page info. + $revision = WikiPage::factory( $title )->getRevision(); + if ( $revision !== null ) { + $key = wfMemcKey( 'infoaction', sha1( $title->getPrefixedText() ), $revision->getId() ); + $wgMemc->delete( $key ); + } + } + + /** * Shows page information on GET request. * * @return string Page information that will be added to the output @@ -81,11 +104,11 @@ class InfoAction extends FormlessAction { // Hide "This page is a member of # hidden categories" explanation $content .= Html::element( 'style', array(), - '.mw-hiddenCategoriesExplanation { display: none; }' ); + '.mw-hiddenCategoriesExplanation { display: none; }' ) . "\n"; // Hide "Templates used on this page" explanation $content .= Html::element( 'style', array(), - '.mw-templatesUsedExplanation { display: none; }' ); + '.mw-templatesUsedExplanation { display: none; }' ) . "\n"; // Get page information $pageInfo = $this->pageInfo(); @@ -95,14 +118,18 @@ class InfoAction extends FormlessAction { // Render page information foreach ( $pageInfo as $header => $infoTable ) { - $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() ); - $table = ''; + // Messages: + // pageinfo-header-basic, pageinfo-header-edits, pageinfo-header-restrictions, + // pageinfo-header-properties, pageinfo-category-info + $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() ) . "\n"; + $table = "\n"; foreach ( $infoTable as $infoRow ) { $name = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->escaped() : $infoRow[0]; $value = ( $infoRow[1] instanceof Message ) ? $infoRow[1]->escaped() : $infoRow[1]; - $table = $this->addRow( $table, $name, $value ); + $id = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->getKey() : null; + $table = $this->addRow( $table, $name, $value, $id ) . "\n"; } - $content = $this->addTable( $content, $table ); + $content = $this->addTable( $content, $table ) . "\n"; } // Page footer @@ -121,25 +148,25 @@ class InfoAction extends FormlessAction { /** * Creates a header that can be added to the output. * - * @param $header The header text. + * @param string $header The header text. * @return string The HTML. */ protected function makeHeader( $header ) { - global $wgParser; - $spanAttribs = array( 'class' => 'mw-headline', 'id' => $wgParser->guessSectionNameFromWikiText( $header ) ); + $spanAttribs = array( 'class' => 'mw-headline', 'id' => Sanitizer::escapeId( $header ) ); return Html::rawElement( 'h2', array(), Html::element( 'span', $spanAttribs, $header ) ); } /** * Adds a row to a table that will be added to the content. * - * @param $table string The table that will be added to the content - * @param $name string The name of the row - * @param $value string The value of the row + * @param string $table The table that will be added to the content + * @param string $name The name of the row + * @param string $value The value of the row + * @param string $id The ID to use for the 'tr' element * @return string The table with the row added */ - protected function addRow( $table, $name, $value ) { - return $table . Html::rawElement( 'tr', array(), + protected function addRow( $table, $name, $value, $id ) { + return $table . Html::rawElement( 'tr', $id === null ? array() : array( 'id' => 'mw-' . $id ), Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) . Html::rawElement( 'td', array(), $value ) ); @@ -148,8 +175,8 @@ class InfoAction extends FormlessAction { /** * Adds a table to the content that will be added to the output. * - * @param $content string The content that will be added to the output - * @param $table string The table + * @param string $content The content that will be added to the output + * @param string $table The table * @return string The content with the table added */ protected function addTable( $content, $table ) { @@ -161,17 +188,29 @@ class InfoAction extends FormlessAction { * Returns page information in an easily-manipulated format. Array keys are used so extensions * may add additional information in arbitrary positions. Array values are arrays with one * element to be rendered as a header, arrays with two elements to be rendered as a table row. + * + * @return array */ protected function pageInfo() { - global $wgContLang, $wgRCMaxAge; + global $wgContLang, $wgRCMaxAge, $wgMemc, + $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit; $user = $this->getUser(); $lang = $this->getLanguage(); $title = $this->getTitle(); $id = $title->getArticleID(); - // Get page information that would be too "expensive" to retrieve by normal means - $pageCounts = self::pageCounts( $title, $user ); + $memcKey = wfMemcKey( 'infoaction', + sha1( $title->getPrefixedText() ), $this->page->getLatest() ); + $pageCounts = $wgMemc->get( $memcKey ); + $version = isset( $pageCounts['cacheversion'] ) ? $pageCounts['cacheversion'] : false; + if ( $pageCounts === false || $version !== self::CACHE_VERSION ) { + // Get page information that would be too "expensive" to retrieve by normal means + $pageCounts = self::pageCounts( $title ); + $pageCounts['cacheversion'] = self::CACHE_VERSION; + + $wgMemc->set( $memcKey, $pageCounts ); + } // Get page properties $dbr = wfGetDB( DB_SLAVE ); @@ -201,8 +240,23 @@ class InfoAction extends FormlessAction { $this->msg( 'pageinfo-display-title' ), $displayTitle ); + // Is it a redirect? If so, where to? + if ( $title->isRedirect() ) { + $pageInfo['header-basic'][] = array( + $this->msg( 'pageinfo-redirectsto' ), + Linker::link( $this->page->getRedirectTarget() ) . + $this->msg( 'word-separator' )->text() . + $this->msg( 'parentheses', Linker::link( + $this->page->getRedirectTarget(), + $this->msg( 'pageinfo-redirectsto-info' )->escaped(), + array(), + array( 'action' => 'info' ) + ) )->text() + ); + } + // Default sort key - $sortKey = $title->getCategorySortKey(); + $sortKey = $title->getCategorySortkey(); if ( !empty( $pageProperties['defaultsort'] ) ) { $sortKey = $pageProperties['defaultsort']; } @@ -217,6 +271,12 @@ class InfoAction extends FormlessAction { // Page ID (number not localised, as it's a database ID) $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-article-id' ), $id ); + // Language in which the page content is (supposed to be) written + $pageLang = $title->getPageLanguage()->getCode(); + $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-language' ), + Language::fetchLanguageName( $pageLang, $lang->getCode() ) + . ' ' . $this->msg( 'parentheses', $pageLang ) ); + // Search engine status $pOutput = new ParserOutput(); if ( isset( $pageProperties['noindex'] ) ) { @@ -226,6 +286,7 @@ class InfoAction extends FormlessAction { // Use robot policy logic $policy = $this->page->getRobotPolicy( 'view', $pOutput ); $pageInfo['header-basic'][] = array( + // Messages: pageinfo-robot-index, pageinfo-robot-noindex $this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" ) ); @@ -236,11 +297,20 @@ class InfoAction extends FormlessAction { ); } - if ( isset( $pageCounts['watchers'] ) ) { + if ( + $user->isAllowed( 'unwatchedpages' ) || + ( $wgUnwatchedPageThreshold !== false && + $pageCounts['watchers'] >= $wgUnwatchedPageThreshold ) + ) { // Number of page watchers $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-watchers' ), $lang->formatNum( $pageCounts['watchers'] ) ); + } elseif ( $wgUnwatchedPageThreshold !== false ) { + $pageInfo['header-basic'][] = array( + $this->msg( 'pageinfo-watchers' ), + $this->msg( 'pageinfo-few-watchers' )->numParams( $wgUnwatchedPageThreshold ) + ); } // Redirects to this page @@ -256,6 +326,14 @@ class InfoAction extends FormlessAction { ->numParams( count( $title->getRedirectsHere() ) ) ); + // Is it counted as a content page? + if ( $this->page->isCountable() ) { + $pageInfo['header-basic'][] = array( + $this->msg( 'pageinfo-contentpage' ), + $this->msg( 'pageinfo-contentpage-yes' ) + ); + } + // Subpages of this page, if subpages are enabled for the current NS if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) { $prefixIndex = SpecialPage::getTitleFor( 'Prefixindex', $title->getPrefixedText() . '/' ); @@ -269,9 +347,51 @@ class InfoAction extends FormlessAction { ); } + if ( $title->inNamespace( NS_CATEGORY ) ) { + $category = Category::newFromTitle( $title ); + $pageInfo['category-info'] = array( + array( + $this->msg( 'pageinfo-category-pages' ), + $lang->formatNum( $category->getPageCount() ) + ), + array( + $this->msg( 'pageinfo-category-subcats' ), + $lang->formatNum( $category->getSubcatCount() ) + ), + array( + $this->msg( 'pageinfo-category-files' ), + $lang->formatNum( $category->getFileCount() ) + ) + ); + } + // Page protection $pageInfo['header-restrictions'] = array(); + // Is this page effected by the cascading protection of something which includes it? + if ( $title->isCascadeProtected() ) { + $cascadingFrom = ''; + $sources = $title->getCascadeProtectionSources(); // Array deferencing is in PHP 5.4 :( + + foreach ( $sources[0] as $sourceTitle ) { + $cascadingFrom .= Html::rawElement( 'li', array(), Linker::linkKnown( $sourceTitle ) ); + } + + $cascadingFrom = Html::rawElement( 'ul', array(), $cascadingFrom ); + $pageInfo['header-restrictions'][] = array( + $this->msg( 'pageinfo-protect-cascading-from' ), + $cascadingFrom + ); + } + + // Is out protection set to cascade to other pages? + if ( $title->areRestrictionsCascading() ) { + $pageInfo['header-restrictions'][] = array( + $this->msg( 'pageinfo-protect-cascading' ), + $this->msg( 'pageinfo-protect-cascading-yes' ) + ); + } + // Page protection foreach ( $title->getRestrictionTypes() as $restrictionType ) { $protectionLevel = implode( ', ', $title->getRestrictions( $restrictionType ) ); @@ -281,6 +401,7 @@ class InfoAction extends FormlessAction { $message = $this->msg( 'protect-default' )->escaped(); } else { // Administrators only + // Messages: protect-level-autoconfirmed, protect-level-sysop $message = $this->msg( "protect-level-$protectionLevel" ); if ( $message->isDisabled() ) { // Require "$1" permission @@ -290,6 +411,8 @@ class InfoAction extends FormlessAction { } } + // Messages: restriction-edit, restriction-move, restriction-create, + // restriction-upload $pageInfo['header-restrictions'][] = array( $this->msg( "restriction-$restrictionType" ), $message ); @@ -303,40 +426,64 @@ class InfoAction extends FormlessAction { $pageInfo['header-edits'] = array(); $firstRev = $this->page->getOldestRevision(); + $lastRev = $this->page->getRevision(); + $batch = new LinkBatch; + + if ( $firstRev ) { + $firstRevUser = $firstRev->getUserText( Revision::FOR_THIS_USER ); + if ( $firstRevUser !== '' ) { + $batch->add( NS_USER, $firstRevUser ); + $batch->add( NS_USER_TALK, $firstRevUser ); + } + } - // Page creator - $pageInfo['header-edits'][] = array( - $this->msg( 'pageinfo-firstuser' ), - Linker::revUserTools( $firstRev ) - ); + if ( $lastRev ) { + $lastRevUser = $lastRev->getUserText( Revision::FOR_THIS_USER ); + if ( $lastRevUser !== '' ) { + $batch->add( NS_USER, $lastRevUser ); + $batch->add( NS_USER_TALK, $lastRevUser ); + } + } - // Date of page creation - $pageInfo['header-edits'][] = array( - $this->msg( 'pageinfo-firsttime' ), - Linker::linkKnown( - $title, - $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ), - array(), - array( 'oldid' => $firstRev->getId() ) - ) - ); + $batch->execute(); - // Latest editor - $pageInfo['header-edits'][] = array( - $this->msg( 'pageinfo-lastuser' ), - Linker::revUserTools( $this->page->getRevision() ) - ); + if ( $firstRev ) { + // Page creator + $pageInfo['header-edits'][] = array( + $this->msg( 'pageinfo-firstuser' ), + Linker::revUserTools( $firstRev ) + ); - // Date of latest edit - $pageInfo['header-edits'][] = array( - $this->msg( 'pageinfo-lasttime' ), - Linker::linkKnown( - $title, - $lang->userTimeAndDate( $this->page->getTimestamp(), $user ), - array(), - array( 'oldid' => $this->page->getLatest() ) - ) - ); + // Date of page creation + $pageInfo['header-edits'][] = array( + $this->msg( 'pageinfo-firsttime' ), + Linker::linkKnown( + $title, + $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ), + array(), + array( 'oldid' => $firstRev->getId() ) + ) + ); + } + + if ( $lastRev ) { + // Latest editor + $pageInfo['header-edits'][] = array( + $this->msg( 'pageinfo-lastuser' ), + Linker::revUserTools( $lastRev ) + ); + + // Date of latest edit + $pageInfo['header-edits'][] = array( + $this->msg( 'pageinfo-lasttime' ), + Linker::linkKnown( + $title, + $lang->userTimeAndDate( $this->page->getTimestamp(), $user ), + array(), + array( 'oldid' => $this->page->getLatest() ) + ) + ); + } // Total number of edits $pageInfo['header-edits'][] = array( @@ -377,11 +524,17 @@ class InfoAction extends FormlessAction { $localizedList = Html::rawElement( 'ul', array(), implode( '', $listItems ) ); $hiddenCategories = $this->page->getHiddenCategories(); - $transcludedTemplates = $title->getTemplateLinksFrom(); - if ( count( $listItems ) > 0 - || count( $hiddenCategories ) > 0 - || count( $transcludedTemplates ) > 0 ) { + if ( + count( $listItems ) > 0 || + count( $hiddenCategories ) > 0 || + $pageCounts['transclusion']['from'] > 0 || + $pageCounts['transclusion']['to'] > 0 + ) { + $options = array( 'LIMIT' => $wgPageInfoTransclusionLimit ); + $transcludedTemplates = $title->getTemplateLinksFrom( $options ); + $transcludedTargets = $title->getTemplateLinksTo( $options ); + // Page properties $pageInfo['header-properties'] = array(); @@ -403,11 +556,44 @@ class InfoAction extends FormlessAction { } // Transcluded templates - if ( count( $transcludedTemplates ) > 0 ) { + if ( $pageCounts['transclusion']['from'] > 0 ) { + if ( $pageCounts['transclusion']['from'] > count( $transcludedTemplates ) ) { + $more = $this->msg( 'morenotlisted' )->escaped(); + } else { + $more = null; + } + $pageInfo['header-properties'][] = array( $this->msg( 'pageinfo-templates' ) - ->numParams( count( $transcludedTemplates ) ), - Linker::formatTemplates( $transcludedTemplates ) + ->numParams( $pageCounts['transclusion']['from'] ), + Linker::formatTemplates( + $transcludedTemplates, + false, + false, + $more ) + ); + } + + if ( $pageCounts['transclusion']['to'] > 0 ) { + if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) { + $more = Linker::link( + $whatLinksHere, + $this->msg( 'moredotdotdot' )->escaped(), + array(), + array( 'hidelinks' => 1, 'hideredirs' => 1 ) + ); + } else { + $more = null; + } + + $pageInfo['header-properties'][] = array( + $this->msg( 'pageinfo-transclusions' ) + ->numParams( $pageCounts['transclusion']['to'] ), + Linker::formatTemplates( + $transcludedTargets, + false, + false, + $more ) ); } } @@ -418,11 +604,10 @@ class InfoAction extends FormlessAction { /** * Returns page counts that would be too "expensive" to retrieve by normal means. * - * @param $title Title object - * @param $user User object + * @param Title $title Title to get counts for * @return array */ - protected static function pageCounts( $title, $user ) { + protected static function pageCounts( Title $title ) { global $wgRCMaxAge, $wgDisableCounters; wfProfileIn( __METHOD__ ); @@ -433,7 +618,7 @@ class InfoAction extends FormlessAction { if ( !$wgDisableCounters ) { // Number of views - $views = (int) $dbr->selectField( + $views = (int)$dbr->selectField( 'page', 'page_counter', array( 'page_id' => $id ), @@ -442,22 +627,20 @@ class InfoAction extends FormlessAction { $result['views'] = $views; } - if ( $user->isAllowed( 'unwatchedpages' ) ) { - // Number of page watchers - $watchers = (int) $dbr->selectField( - 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $title->getNamespace(), - 'wl_title' => $title->getDBkey(), - ), - __METHOD__ - ); - $result['watchers'] = $watchers; - } + // Number of page watchers + $watchers = (int)$dbr->selectField( + 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $title->getNamespace(), + 'wl_title' => $title->getDBkey(), + ), + __METHOD__ + ); + $result['watchers'] = $watchers; // Total number of edits - $edits = (int) $dbr->selectField( + $edits = (int)$dbr->selectField( 'revision', 'COUNT(rev_page)', array( 'rev_page' => $id ), @@ -466,7 +649,7 @@ class InfoAction extends FormlessAction { $result['edits'] = $edits; // Total number of distinct authors - $authors = (int) $dbr->selectField( + $authors = (int)$dbr->selectField( 'revision', 'COUNT(DISTINCT rev_user_text)', array( 'rev_page' => $id ), @@ -478,24 +661,24 @@ class InfoAction extends FormlessAction { $threshold = $dbr->timestamp( time() - $wgRCMaxAge ); // Recent number of edits - $edits = (int) $dbr->selectField( + $edits = (int)$dbr->selectField( 'revision', 'COUNT(rev_page)', array( - 'rev_page' => $id , - "rev_timestamp >= $threshold" + 'rev_page' => $id, + "rev_timestamp >= " . $dbr->addQuotes( $threshold ) ), __METHOD__ ); $result['recent_edits'] = $edits; // Recent number of distinct authors - $authors = (int) $dbr->selectField( + $authors = (int)$dbr->selectField( 'revision', 'COUNT(DISTINCT rev_user_text)', array( 'rev_page' => $id, - "rev_timestamp >= $threshold" + "rev_timestamp >= " . $dbr->addQuotes( $threshold ) ), __METHOD__ ); @@ -508,7 +691,7 @@ class InfoAction extends FormlessAction { // Subpages of this page (redirects) $conds['page_is_redirect'] = 1; - $result['subpages']['redirects'] = (int) $dbr->selectField( + $result['subpages']['redirects'] = (int)$dbr->selectField( 'page', 'COUNT(page_id)', $conds, @@ -516,7 +699,7 @@ class InfoAction extends FormlessAction { // Subpages of this page (non-redirects) $conds['page_is_redirect'] = 0; - $result['subpages']['nonredirects'] = (int) $dbr->selectField( + $result['subpages']['nonredirects'] = (int)$dbr->selectField( 'page', 'COUNT(page_id)', $conds, @@ -528,12 +711,30 @@ class InfoAction extends FormlessAction { + $result['subpages']['nonredirects']; } + // Counts for the number of transclusion links (to/from) + $result['transclusion']['to'] = (int)$dbr->selectField( + 'templatelinks', + 'COUNT(tl_from)', + array( + 'tl_namespace' => $title->getNamespace(), + 'tl_title' => $title->getDBkey() + ), + __METHOD__ + ); + + $result['transclusion']['from'] = (int)$dbr->selectField( + 'templatelinks', + 'COUNT(*)', + array( 'tl_from' => $title->getArticleID() ), + __METHOD__ + ); + wfProfileOut( __METHOD__ ); return $result; } /** - * Returns the name that goes in the <h1> page title. + * Returns the name that goes in the "<h1>" page title. * * @return string */ @@ -604,7 +805,7 @@ class InfoAction extends FormlessAction { } /** - * Returns the description that goes below the <h1> tag. + * Returns the description that goes below the "<h1>" tag. * * @return string */ |