1<?php
2/**
3 * Implements Special:Activeusers
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup SpecialPage
22 */
23
24use MediaWiki\Cache\LinkBatchFactory;
25use MediaWiki\User\UserGroupManager;
26use Wikimedia\Rdbms\ILoadBalancer;
27
28/**
29 * Implements Special:Activeusers
30 *
31 * @ingroup SpecialPage
32 */
33class SpecialActiveUsers extends SpecialPage {
34
35	/** @var LinkBatchFactory */
36	private $linkBatchFactory;
37
38	/** @var ILoadBalancer */
39	private $loadBalancer;
40
41	/** @var UserGroupManager */
42	private $userGroupManager;
43
44	/**
45	 * @param LinkBatchFactory $linkBatchFactory
46	 * @param ILoadBalancer $loadBalancer
47	 * @param UserGroupManager $userGroupManager
48	 */
49	public function __construct(
50		LinkBatchFactory $linkBatchFactory,
51		ILoadBalancer $loadBalancer,
52		UserGroupManager $userGroupManager
53	) {
54		parent::__construct( 'Activeusers' );
55		$this->linkBatchFactory = $linkBatchFactory;
56		$this->loadBalancer = $loadBalancer;
57		$this->userGroupManager = $userGroupManager;
58	}
59
60	/**
61	 * @param string|null $par Parameter passed to the page or null
62	 */
63	public function execute( $par ) {
64		$out = $this->getOutput();
65
66		$this->setHeaders();
67		$this->outputHeader();
68
69		$opts = new FormOptions();
70
71		$opts->add( 'username', '' );
72		$opts->add( 'groups', [] );
73		$opts->add( 'excludegroups', [] );
74		// Backwards-compatibility with old URLs
75		$opts->add( 'hidebots', false, FormOptions::BOOL );
76		$opts->add( 'hidesysops', false, FormOptions::BOOL );
77
78		$opts->fetchValuesFromRequest( $this->getRequest() );
79
80		if ( $par !== null ) {
81			$opts->setValue( 'username', $par );
82		}
83
84		$pager = new ActiveUsersPager(
85			$this->getContext(),
86			$opts,
87			$this->linkBatchFactory,
88			$this->getHookContainer(),
89			$this->loadBalancer,
90			$this->userGroupManager
91		);
92		$usersBody = $pager->getBody();
93
94		$this->buildForm();
95
96		if ( $usersBody ) {
97			$out->addHTML(
98				$pager->getNavigationBar() .
99				Html::rawElement( 'ul', [], $usersBody ) .
100				$pager->getNavigationBar()
101			);
102			$out->addModuleStyles( 'mediawiki.interface.helpers.styles' );
103		} else {
104			$out->addWikiMsg( 'activeusers-noresult' );
105		}
106	}
107
108	/**
109	 * Generate and output the form
110	 */
111	protected function buildForm() {
112		$groups = $this->userGroupManager->listAllGroups();
113
114		$options = [];
115		foreach ( $groups as $group ) {
116			$msg = htmlspecialchars( UserGroupMembership::getGroupName( $group ) );
117			$options[$msg] = $group;
118		}
119		ksort( $options );
120
121		// Backwards-compatibility with old URLs
122		$req = $this->getRequest();
123		$excludeDefault = [];
124		if ( $req->getCheck( 'hidebots' ) ) {
125			$excludeDefault[] = 'bot';
126		}
127		if ( $req->getCheck( 'hidesysops' ) ) {
128			$excludeDefault[] = 'sysop';
129		}
130
131		$formDescriptor = [
132			'username' => [
133				'type' => 'user',
134				'name' => 'username',
135				'label-message' => 'activeusers-from',
136			],
137			'groups' => [
138				'type' => 'multiselect',
139				'dropdown' => true,
140				'flatlist' => true,
141				'name' => 'groups',
142				'label-message' => 'activeusers-groups',
143				'options' => $options,
144			],
145			'excludegroups' => [
146				'type' => 'multiselect',
147				'dropdown' => true,
148				'flatlist' => true,
149				'name' => 'excludegroups',
150				'label-message' => 'activeusers-excludegroups',
151				'options' => $options,
152				'default' => $excludeDefault,
153			],
154		];
155
156		HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
157			// For the 'multiselect' field values to be preserved on submit
158			->setFormIdentifier( 'specialactiveusers' )
159			->setIntro( $this->getIntroText() )
160			->setWrapperLegendMsg( 'activeusers' )
161			->setSubmitTextMsg( 'activeusers-submit' )
162			// prevent setting subpage and 'username' parameter at the same time
163			->setAction( $this->getPageTitle()->getLocalURL() )
164			->setMethod( 'get' )
165			->prepareForm()
166			->displayForm( false );
167	}
168
169	/**
170	 * Return introductory message.
171	 * @return string
172	 */
173	protected function getIntroText() {
174		$days = $this->getConfig()->get( 'ActiveUserDays' );
175
176		$intro = $this->msg( 'activeusers-intro' )->numParams( $days )->parse();
177
178		// Mention the level of cache staleness...
179		$dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'recentchanges' );
180		$rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
181		if ( $rcMax ) {
182			$cTime = $dbr->selectField( 'querycache_info',
183				'qci_timestamp',
184				[ 'qci_type' => 'activeusers' ],
185				__METHOD__
186			);
187			if ( $cTime ) {
188				$secondsOld = wfTimestamp( TS_UNIX, $rcMax ) - wfTimestamp( TS_UNIX, $cTime );
189			} else {
190				$rcMin = $dbr->selectField( 'recentchanges', 'MIN(rc_timestamp)', '', __METHOD__ );
191				$secondsOld = time() - wfTimestamp( TS_UNIX, $rcMin );
192			}
193			if ( $secondsOld > 0 ) {
194				$intro .= $this->msg( 'cachedspecial-viewing-cached-ttl' )
195					->durationParams( $secondsOld )->parseAsBlock();
196			}
197		}
198
199		return $intro;
200	}
201
202	protected function getGroupName() {
203		return 'users';
204	}
205}
206