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