wikimedia/mediawiki-core

View on GitHub
includes/specials/SpecialStatistics.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */

namespace MediaWiki\Specials;

use MediaWiki\Html\Html;
use MediaWiki\MainConfigNames;
use MediaWiki\Parser\Sanitizer;
use MediaWiki\SiteStats\SiteStats;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\Title\Title;
use MediaWiki\User\UserGroupManager;
use MediaWiki\User\UserGroupMembership;
use MediaWiki\Xml\Xml;

/**
 * Special page lists various statistics, including the contents of
 * `site_stats`, plus page view details if enabled
 *
 * @ingroup SpecialPage
 */
class SpecialStatistics extends SpecialPage {
    private int $edits;
    private int $good;
    private int $images;
    private int $total;
    private int $users;
    private int $activeUsers;

    private UserGroupManager $userGroupManager;

    /**
     * @param UserGroupManager $userGroupManager
     */
    public function __construct( UserGroupManager $userGroupManager ) {
        parent::__construct( 'Statistics' );
        $this->userGroupManager = $userGroupManager;
    }

    public function execute( $par ) {
        $this->setHeaders();
        $this->outputHeader();
        $this->getOutput()->addModuleStyles( 'mediawiki.special' );

        $this->edits = SiteStats::edits();
        $this->good = SiteStats::articles();
        $this->images = SiteStats::images();
        $this->total = SiteStats::pages();
        $this->users = SiteStats::users();
        $this->activeUsers = SiteStats::activeUsers();

        $text = Xml::openElement( 'table', [ 'class' => 'wikitable mw-statistics-table' ] );

        # Statistic - pages
        $text .= $this->getPageStats();

        # Statistic - edits
        $text .= $this->getEditStats();

        # Statistic - users
        $text .= $this->getUserStats();

        # Statistic - usergroups
        $text .= $this->getGroupStats();

        # Statistic - other
        $extraStats = [];
        if ( $this->getHookRunner()->onSpecialStatsAddExtra(
            $extraStats, $this->getContext() )
        ) {
            $text .= $this->getOtherStats( $extraStats );
        }

        $text .= Xml::closeElement( 'table' );

        # Customizable footer
        $footer = $this->msg( 'statistics-footer' );
        if ( !$footer->isBlank() ) {
            $text .= "\n" . $footer->parse();
        }

        $this->getOutput()->addHTML( $text );
    }

    /**
     * Format a row
     * @param string $text Description of the row
     * @param float|string $number A statistical number
     * @param array $trExtraParams Params to table row, see Html::element
     * @param string $descMsg Message key
     * @param array|string $descMsgParam Message parameters
     * @return string Table row in HTML format
     */
    private function formatRow( $text, $number, $trExtraParams = [],
        $descMsg = '', $descMsgParam = ''
    ) {
        if ( $descMsg ) {
            $msg = $this->msg( $descMsg, $descMsgParam );
            if ( !$msg->isDisabled() ) {
                $descriptionHtml = $this->msg( 'parentheses' )->rawParams( $msg->parse() )
                    ->escaped();
                $text .= "<br />" . Html::rawElement(
                    'small',
                    [ 'class' => 'mw-statistic-desc' ],
                    " $descriptionHtml"
                );
            }
        }

        return Html::rawElement( 'tr', $trExtraParams,
            Html::rawElement( 'td', [], $text ) .
            Html::rawElement( 'td', [ 'class' => 'mw-statistics-numbers' ], $number )
        );
    }

    /**
     * Each of these methods is pretty self-explanatory, get a particular
     * row for the table of statistics
     * @return string
     */
    private function getPageStats() {
        $linkRenderer = $this->getLinkRenderer();

        $specialAllPagesTitle = SpecialPage::getTitleFor( 'Allpages' );
        $pageStatsHtml = Html::rawElement( 'tr', [],
            Xml::tags( 'th', [ 'colspan' => '2' ],
                $this->msg( 'statistics-header-pages' )->parse()
            ) ) .
                $this->formatRow(
                    $this->getConfig()->get( MainConfigNames::MiserMode )
                        ? $this->msg( 'statistics-articles' )->escaped()
                        : $linkRenderer->makeKnownLink(
                            $specialAllPagesTitle,
                            $this->msg( 'statistics-articles' )->text(),
                            [], [ 'hideredirects' => 1 ] ),
                    $this->getLanguage()->formatNum( $this->good ),
                    [ 'class' => 'mw-statistics-articles' ],
                    'statistics-articles-desc' ) .
                $this->formatRow( $linkRenderer->makeKnownLink( $specialAllPagesTitle,
                    $this->msg( 'statistics-pages' )->text() ),
                    $this->getLanguage()->formatNum( $this->total ),
                    [ 'class' => 'mw-statistics-pages' ],
                    'statistics-pages-desc' );

        // Show the image row only, when there are files or upload is possible
        if ( $this->images !== 0 || $this->getConfig()->get( MainConfigNames::EnableUploads ) ) {
            $pageStatsHtml .= $this->formatRow(
                $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'MediaStatistics' ),
                $this->msg( 'statistics-files' )->text() ),
                $this->getLanguage()->formatNum( $this->images ),
                [ 'class' => 'mw-statistics-files' ], 'statistics-files-desc' );
        }

        return $pageStatsHtml;
    }

    private function getEditStats() {
        return Html::rawElement( 'tr', [],
            Xml::tags( 'th', [ 'colspan' => '2' ],
                $this->msg( 'statistics-header-edits' )->parse()
            ) ) .
            $this->formatRow( $this->msg( 'statistics-edits' )->parse(),
                $this->getLanguage()->formatNum( $this->edits ),
                [ 'class' => 'mw-statistics-edits' ]
            ) .
            $this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
                $this->getLanguage()->formatNum(
                    sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 )
                ), [ 'class' => 'mw-statistics-edits-average' ]
            );
    }

    private function getUserStats() {
        return Html::rawElement( 'tr', [],
            Xml::tags( 'th', [ 'colspan' => '2' ],
                $this->msg( 'statistics-header-users' )->parse()
            ) ) .
            $this->formatRow( $this->msg( 'statistics-users' )->parse() . ' ' .
                $this->getLinkRenderer()->makeKnownLink(
                    SpecialPage::getTitleFor( 'Listusers' ),
                    $this->msg( 'listgrouprights-members' )->text()
                ),
                $this->getLanguage()->formatNum( $this->users ),
                [ 'class' => 'mw-statistics-users' ]
            ) .
            $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
                $this->getLinkRenderer()->makeKnownLink(
                    SpecialPage::getTitleFor( 'Activeusers' ),
                    $this->msg( 'listgrouprights-members' )->text()
                ),
                $this->getLanguage()->formatNum( $this->activeUsers ),
                [ 'class' => 'mw-statistics-users-active' ],
                'statistics-users-active-desc',
                $this->getLanguage()->formatNum(
                    $this->getConfig()->get( MainConfigNames::ActiveUserDays ) )
            );
    }

    private function getGroupStats() {
        $linkRenderer = $this->getLinkRenderer();
        $lang = $this->getLanguage();
        $text = '';
        foreach ( $this->userGroupManager->listAllGroups() as $group ) {
            $groupnameLocalized = $lang->getGroupName( $group );
            $linkTarget = UserGroupMembership::getGroupPage( $group )
                ?: Title::makeTitleSafe( NS_PROJECT, $group );

            if ( $linkTarget ) {
                $grouppage = $linkRenderer->makeLink(
                    $linkTarget,
                    $groupnameLocalized
                );
            } else {
                $grouppage = htmlspecialchars( $groupnameLocalized );
            }

            $grouplink = $linkRenderer->makeKnownLink(
                SpecialPage::getTitleFor( 'Listusers' ),
                $this->msg( 'listgrouprights-members' )->text(),
                [],
                [ 'group' => $group ]
            );
            # Add a class when a usergroup contains no members to allow hiding these rows
            $classZero = '';
            $countUsers = SiteStats::numberingroup( $group );
            if ( $countUsers == 0 ) {
                $classZero = ' statistics-group-zero';
            }
            $text .= $this->formatRow( $grouppage . ' ' . $grouplink,
                $this->getLanguage()->formatNum( $countUsers ),
                [ 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) .
                    $classZero ] );
        }

        return $text;
    }

    /**
     * Conversion of external statistics into an internal representation
     * Following a ([<header-message>][<item-message>] = number) pattern
     *
     * @param array $stats
     * @return string
     */
    private function getOtherStats( array $stats ) {
        $return = '';

        foreach ( $stats as $header => $items ) {
            // Identify the structure used
            if ( is_array( $items ) ) {
                // Ignore headers that are recursively set as legacy header
                if ( $header !== 'statistics-header-hooks' ) {
                    $return .= $this->formatRowHeader( $header );
                }

                // Collect all items that belong to the same header
                foreach ( $items as $key => $value ) {
                    if ( is_array( $value ) ) {
                        $name = $value['name'];
                        $number = $value['number'];
                    } else {
                        $name = $this->msg( $key )->parse();
                        $number = $value;
                    }

                    $return .= $this->formatRow(
                        $name,
                        $this->getLanguage()->formatNum( htmlspecialchars( $number ) ),
                        [ 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ]
                    );
                }
            } else {
                // Create the legacy header only once
                if ( $return === '' ) {
                    $return .= $this->formatRowHeader( 'statistics-header-hooks' );
                }

                // Recursively remap the legacy structure
                $return .= $this->getOtherStats( [ 'statistics-header-hooks' =>
                    [ $header => $items ] ] );
            }
        }

        return $return;
    }

    /**
     * Format row header
     *
     * @param string $header
     * @return string
     */
    private function formatRowHeader( $header ) {
        return Html::rawElement( 'tr', [],
            Xml::tags( 'th', [ 'colspan' => '2' ], $this->msg( $header )->parse() )
        );
    }

    protected function getGroupName() {
        return 'wiki';
    }
}

/**
 * Retain the old class name for backwards compatibility.
 * @deprecated since 1.41
 */
class_alias( SpecialStatistics::class, 'SpecialStatistics' );