Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialActiveUsers
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 5
182
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
12
 buildForm
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
20
 getIntroText
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
20
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Block\HideUserUtils;
10use MediaWiki\Html\FormOptions;
11use MediaWiki\Html\Html;
12use MediaWiki\HTMLForm\HTMLForm;
13use MediaWiki\MainConfigNames;
14use MediaWiki\Page\LinkBatchFactory;
15use MediaWiki\Pager\ActiveUsersPager;
16use MediaWiki\RecentChanges\RecentChangeLookup;
17use MediaWiki\SpecialPage\SpecialPage;
18use MediaWiki\User\TempUser\TempUserConfig;
19use MediaWiki\User\UserGroupManager;
20use MediaWiki\User\UserIdentityLookup;
21use Wikimedia\Rdbms\IConnectionProvider;
22use Wikimedia\Timestamp\TimestampFormat as TS;
23
24/**
25 * Implements Special:Activeusers
26 *
27 * @ingroup SpecialPage
28 */
29class SpecialActiveUsers extends SpecialPage {
30
31    public function __construct(
32        private readonly LinkBatchFactory $linkBatchFactory,
33        private readonly IConnectionProvider $dbProvider,
34        private readonly UserGroupManager $userGroupManager,
35        private readonly UserIdentityLookup $userIdentityLookup,
36        private readonly HideUserUtils $hideUserUtils,
37        private readonly TempUserConfig $tempUserConfig,
38        private readonly RecentChangeLookup $recentChangeLookup
39    ) {
40        parent::__construct( 'Activeusers' );
41    }
42
43    /**
44     * @param string|null $par Parameter passed to the page or null
45     */
46    public function execute( $par ) {
47        $out = $this->getOutput();
48
49        $this->setHeaders();
50        $this->outputHeader();
51
52        $opts = new FormOptions();
53
54        $opts->add( 'username', '' );
55        $opts->add( 'groups', [] );
56        $opts->add( 'excludegroups', [] );
57        // Backwards-compatibility with old URLs
58        $opts->add( 'hidebots', false, FormOptions::BOOL );
59        $opts->add( 'hidesysops', false, FormOptions::BOOL );
60
61        $opts->fetchValuesFromRequest( $this->getRequest() );
62
63        if ( $par !== null ) {
64            $opts->setValue( 'username', $par );
65        }
66
67        $pager = new ActiveUsersPager(
68            $this->getContext(),
69            $this->getHookContainer(),
70            $this->linkBatchFactory,
71            $this->dbProvider,
72            $this->userGroupManager,
73            $this->userIdentityLookup,
74            $this->hideUserUtils,
75            $this->tempUserConfig,
76            $this->recentChangeLookup,
77            $opts
78        );
79        $usersBody = $pager->getBody();
80
81        $this->buildForm();
82
83        if ( $usersBody ) {
84            $out->addHTML(
85                $pager->getNavigationBar() .
86                Html::rawElement( 'ul', [], $usersBody ) .
87                $pager->getNavigationBar()
88            );
89            $out->addModuleStyles( 'mediawiki.interface.helpers.styles' );
90        } else {
91            $out->addWikiMsg( 'activeusers-noresult' );
92        }
93    }
94
95    /**
96     * Generate and output the form
97     */
98    protected function buildForm() {
99        $groups = $this->userGroupManager->listAllGroups();
100
101        $options = [];
102        $lang = $this->getLanguage();
103        foreach ( $groups as $group ) {
104            $msg = htmlspecialchars( $lang->getGroupName( $group ) );
105            $options[$msg] = $group;
106        }
107        ksort( $options );
108
109        // Backwards-compatibility with old URLs
110        $req = $this->getRequest();
111        $excludeDefault = [];
112        if ( $req->getCheck( 'hidebots' ) ) {
113            $excludeDefault[] = 'bot';
114        }
115        if ( $req->getCheck( 'hidesysops' ) ) {
116            $excludeDefault[] = 'sysop';
117        }
118
119        $formDescriptor = [
120            'username' => [
121                'type' => 'user',
122                'name' => 'username',
123                'label-message' => 'activeusers-from',
124            ],
125            'groups' => [
126                'type' => 'multiselect',
127                'dropdown' => true,
128                'flatlist' => true,
129                'name' => 'groups',
130                'label-message' => 'activeusers-groups',
131                'options' => $options,
132            ],
133            'excludegroups' => [
134                'type' => 'multiselect',
135                'dropdown' => true,
136                'flatlist' => true,
137                'name' => 'excludegroups',
138                'label-message' => 'activeusers-excludegroups',
139                'options' => $options,
140                'default' => $excludeDefault,
141            ],
142        ];
143
144        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
145            // For the 'multiselect' field values to be preserved on submit
146            ->setFormIdentifier( 'specialactiveusers' )
147            ->setPreHtml( $this->getIntroText() )
148            ->setWrapperLegendMsg( 'activeusers' )
149            ->setSubmitTextMsg( 'activeusers-submit' )
150            // prevent setting subpage and 'username' parameter at the same time
151            ->setTitle( $this->getPageTitle() )
152            ->setMethod( 'get' )
153            ->prepareForm()
154            ->displayForm( false );
155    }
156
157    /**
158     * Return introductory message.
159     * @return string
160     */
161    protected function getIntroText() {
162        $days = $this->getConfig()->get( MainConfigNames::ActiveUserDays );
163
164        $intro = $this->msg( 'activeusers-intro' )->numParams( $days )->parse();
165
166        // Mention the level of cache staleness...
167        $dbr = $this->dbProvider->getReplicaDatabase();
168
169        $rcMax = $dbr->newSelectQueryBuilder()
170            ->select( 'MAX(rc_timestamp)' )
171            ->from( 'recentchanges' )
172            ->caller( __METHOD__ )->fetchField();
173        if ( $rcMax ) {
174            $cTime = $dbr->newSelectQueryBuilder()
175                ->select( 'qci_timestamp' )
176                ->from( 'querycache_info' )
177                ->where( [ 'qci_type' => 'activeusers' ] )
178                ->caller( __METHOD__ )->fetchField();
179            if ( $cTime ) {
180                $secondsOld = (int)wfTimestamp( TS::UNIX, $rcMax ) - (int)wfTimestamp( TS::UNIX, $cTime );
181            } else {
182                $rcMin = $dbr->newSelectQueryBuilder()
183                    ->select( 'MIN(rc_timestamp)' )
184                    ->from( 'recentchanges' )
185                    ->caller( __METHOD__ )->fetchField();
186                $secondsOld = time() - (int)wfTimestamp( TS::UNIX, $rcMin );
187            }
188            if ( $secondsOld > 0 ) {
189                $intro .= $this->msg( 'cachedspecial-viewing-cached-ttl' )
190                    ->durationParams( $secondsOld )->parseAsBlock();
191            }
192        }
193
194        return $intro;
195    }
196
197    /** @inheritDoc */
198    protected function getGroupName() {
199        return 'users';
200    }
201}
202
203/** @deprecated class alias since 1.41 */
204class_alias( SpecialActiveUsers::class, 'SpecialActiveUsers' );