Failed Conditions
Push — irc-comment-visibility-fix ( 0810c1...096073 )
by Simon
10:04
created
config.inc.php 1 patch
Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -183,56 +183,56 @@
 block discarded – undo
183 183
 // Initialise the site configuration object
184 184
 global $siteConfiguration;
185 185
 $siteConfiguration->setBaseUrl($baseurl)
186
-    ->setFilePath(__DIR__)
187
-    ->setDebuggingTraceEnabled($enableErrorTrace)
188
-    ->setDebuggingCssBreakpointsEnabled($enableCssBreakpoints)
189
-    ->setForceIdentification($forceIdentification)
190
-    ->setIdentificationCacheExpiry($identificationCacheExpiry)
191
-    ->setMetaWikimediaWebServiceEndpoint($metaWikimediaWebServiceEndpoint)
192
-    ->setEnforceOAuth($enforceOAuth)
193
-    ->setEmailConfirmationEnabled($enableEmailConfirm == 1)
194
-    ->setEmailConfirmationExpiryDays($emailConfirmationExpiryDays)
195
-    ->setMiserModeLimit($requestLimitShowOnly)
196
-    ->setSquidList($squidIpList)
197
-    ->setUseStrictTransportSecurity($strictTransportSecurityExpiry)
198
-    ->setUserAgent($toolUserAgent)
199
-    ->setCurlDisableVerifyPeer($curlDisableSSLVerifyPeer)
200
-    ->setUseOAuthSignup($useOauthSignup)
201
-    ->setOAuthConsumerToken($oauthConsumerToken)
202
-    ->setOAuthLegacyConsumerTokens($oauthLegacyTokens)
203
-    ->setOAuthConsumerSecret($oauthSecretToken)
204
-    ->setOauthMediaWikiCanonicalServer($oauthMediaWikiCanonicalServer)
205
-    ->setDataClearInterval($dataclear_interval)
206
-    ->setXffTrustedHostsFile($xff_trusted_hosts_file)
207
-    ->setIrcNotificationsEnabled($ircBotNotificationsEnabled == 1)
208
-    ->setIrcNotificationsInstance($whichami)
209
-    ->setTitleBlacklistEnabled($enableTitleblacklist == 1)
210
-    ->setTorExitPaths(array_merge(gethostbynamel('en.wikipedia.org'), gethostbynamel('accounts.wmflabs.org')))
211
-    ->setCreationBotUsername($creationBotUsername)
212
-    ->setCreationBotPassword($creationBotPassword)
213
-    ->setCurlCookieJar($curlCookieJar)
214
-    ->setYubicoApiId($yubicoApiId)
215
-    ->setYubicoApiKey($yubicoApiKey)
216
-    ->setTotpEncryptionKey($totpEncryptionKey)
217
-    ->setRegistrationAllowed($allowRegistration)
218
-    ->setCspReportUri($cspReportUri)
219
-    ->setResourceCacheEpoch($resourceCacheEpoch)
220
-    ->setLocationProviderApiKey($locationProviderApiKey)
221
-    ->setCommonEmailDomains($commonEmailDomains)
222
-    ->setBanMaxIpRange($banMaxIpRange)
223
-    ->setBanMaxIpBlockRange($banMaxIpBlockRange)
224
-    ->setJobQueueBatchSize($jobQueueBatchSize)
225
-    ->setAmqpConfiguration($amqpConfiguration)
226
-    ->setEmailSender($emailSender)
227
-    ->setIdentificationNoticeboardPage($identificationNoticeboardPage)
228
-    ->setIdentificationNoticeboardWebserviceEndpoint($identificationNoticeboardApi)
229
-    ->setAcceptClientHints($acceptClientHints)
230
-    ->setOffline(['offline' => $dontUseDb == 1, 'reason' => $dontUseDbReason, 'culprit' => $dontUseDbCulprit])
231
-    ->setDatabaseConfig([
232
-        'datasource' => 'mysql:host=' . $toolserver_host . ';dbname=' . $toolserver_database,
233
-        'username' => $toolserver_username,
234
-        'password' => $toolserver_password,
235
-    ])
236
-    ->setCookiePath($cookiepath)
237
-    ->setCookieSessionName($sessionname)
186
+	->setFilePath(__DIR__)
187
+	->setDebuggingTraceEnabled($enableErrorTrace)
188
+	->setDebuggingCssBreakpointsEnabled($enableCssBreakpoints)
189
+	->setForceIdentification($forceIdentification)
190
+	->setIdentificationCacheExpiry($identificationCacheExpiry)
191
+	->setMetaWikimediaWebServiceEndpoint($metaWikimediaWebServiceEndpoint)
192
+	->setEnforceOAuth($enforceOAuth)
193
+	->setEmailConfirmationEnabled($enableEmailConfirm == 1)
194
+	->setEmailConfirmationExpiryDays($emailConfirmationExpiryDays)
195
+	->setMiserModeLimit($requestLimitShowOnly)
196
+	->setSquidList($squidIpList)
197
+	->setUseStrictTransportSecurity($strictTransportSecurityExpiry)
198
+	->setUserAgent($toolUserAgent)
199
+	->setCurlDisableVerifyPeer($curlDisableSSLVerifyPeer)
200
+	->setUseOAuthSignup($useOauthSignup)
201
+	->setOAuthConsumerToken($oauthConsumerToken)
202
+	->setOAuthLegacyConsumerTokens($oauthLegacyTokens)
203
+	->setOAuthConsumerSecret($oauthSecretToken)
204
+	->setOauthMediaWikiCanonicalServer($oauthMediaWikiCanonicalServer)
205
+	->setDataClearInterval($dataclear_interval)
206
+	->setXffTrustedHostsFile($xff_trusted_hosts_file)
207
+	->setIrcNotificationsEnabled($ircBotNotificationsEnabled == 1)
208
+	->setIrcNotificationsInstance($whichami)
209
+	->setTitleBlacklistEnabled($enableTitleblacklist == 1)
210
+	->setTorExitPaths(array_merge(gethostbynamel('en.wikipedia.org'), gethostbynamel('accounts.wmflabs.org')))
211
+	->setCreationBotUsername($creationBotUsername)
212
+	->setCreationBotPassword($creationBotPassword)
213
+	->setCurlCookieJar($curlCookieJar)
214
+	->setYubicoApiId($yubicoApiId)
215
+	->setYubicoApiKey($yubicoApiKey)
216
+	->setTotpEncryptionKey($totpEncryptionKey)
217
+	->setRegistrationAllowed($allowRegistration)
218
+	->setCspReportUri($cspReportUri)
219
+	->setResourceCacheEpoch($resourceCacheEpoch)
220
+	->setLocationProviderApiKey($locationProviderApiKey)
221
+	->setCommonEmailDomains($commonEmailDomains)
222
+	->setBanMaxIpRange($banMaxIpRange)
223
+	->setBanMaxIpBlockRange($banMaxIpBlockRange)
224
+	->setJobQueueBatchSize($jobQueueBatchSize)
225
+	->setAmqpConfiguration($amqpConfiguration)
226
+	->setEmailSender($emailSender)
227
+	->setIdentificationNoticeboardPage($identificationNoticeboardPage)
228
+	->setIdentificationNoticeboardWebserviceEndpoint($identificationNoticeboardApi)
229
+	->setAcceptClientHints($acceptClientHints)
230
+	->setOffline(['offline' => $dontUseDb == 1, 'reason' => $dontUseDbReason, 'culprit' => $dontUseDbCulprit])
231
+	->setDatabaseConfig([
232
+		'datasource' => 'mysql:host=' . $toolserver_host . ';dbname=' . $toolserver_database,
233
+		'username' => $toolserver_username,
234
+		'password' => $toolserver_password,
235
+	])
236
+	->setCookiePath($cookiepath)
237
+	->setCookieSessionName($sessionname)
238 238
 ;
Please login to merge, or discard this patch.
includes/Exceptions/AccessDeniedException.php 1 patch
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -27,94 +27,94 @@
 block discarded – undo
27 27
  */
28 28
 class AccessDeniedException extends ReadableException
29 29
 {
30
-    use NavigationMenuAccessControl;
31
-
32
-    /** @var SecurityManager */
33
-    private $securityManager;
34
-    /** @var DomainAccessManager */
35
-    private $domainAccessManager;
36
-
37
-    /**
38
-     * AccessDeniedException constructor.
39
-     *
40
-     * @param SecurityManager     $securityManager
41
-     * @param DomainAccessManager $domainAccessManager
42
-     */
43
-    public function __construct(SecurityManager $securityManager, DomainAccessManager $domainAccessManager)
44
-    {
45
-        $this->securityManager = $securityManager;
46
-        $this->domainAccessManager = $domainAccessManager;
47
-    }
48
-
49
-    public function getReadableError()
50
-    {
51
-        if (!headers_sent()) {
52
-            header("HTTP/1.1 403 Forbidden");
53
-        }
54
-
55
-        $this->setUpSmarty();
56
-
57
-        // uck. We should still be able to access the database in this situation though.
58
-        $database = PdoDatabase::getDatabaseConnection($this->getSiteConfiguration());
59
-        $currentUser = User::getCurrent($database);
60
-        $this->assign('skin', PreferenceManager::getForCurrent($database)->getPreference(PreferenceManager::PREF_SKIN));
61
-        $this->assign('currentUser', $currentUser);
62
-        $this->assign('currentDomain', Domain::getCurrent($database));
63
-
64
-        if ($this->securityManager !== null) {
65
-            $this->setupNavMenuAccess($currentUser);
66
-        }
67
-
68
-        if ($currentUser->isDeclined()) {
69
-            $this->assign('htmlTitle', 'Account Declined');
70
-            $this->assign('declineReason', $this->getLogEntry('Declined', $currentUser, $database));
71
-
72
-            return $this->fetchTemplate("exception/account-declined.tpl");
73
-        }
74
-
75
-        if ($currentUser->isSuspended()) {
76
-            $this->assign('htmlTitle', 'Account Suspended');
77
-            $this->assign('suspendReason', $this->getLogEntry('Suspended', $currentUser, $database));
78
-
79
-            return $this->fetchTemplate("exception/account-suspended.tpl");
80
-        }
81
-
82
-        if ($currentUser->isNewUser()) {
83
-            $this->assign('htmlTitle', 'Account Pending');
84
-
85
-            return $this->fetchTemplate("exception/account-new.tpl");
86
-        }
87
-
88
-        return $this->fetchTemplate("exception/access-denied.tpl");
89
-    }
90
-
91
-    /**
92
-     * @param string      $action
93
-     * @param User        $user
94
-     * @param PdoDatabase $database
95
-     *
96
-     * @return null|string
97
-     */
98
-    private function getLogEntry($action, User $user, PdoDatabase $database)
99
-    {
100
-        /** @var Log[] $logs */
101
-        $logs = LogSearchHelper::get($database, null)
102
-            ->byAction($action)
103
-            ->byObjectType('User')
104
-            ->byObjectId($user->getId())
105
-            ->limit(1)
106
-            ->fetch();
107
-
108
-        return $logs[0]->getComment();
109
-    }
110
-
111
-    protected function getSecurityManager(): SecurityManager
112
-    {
113
-        return $this->securityManager;
114
-    }
115
-
116
-    public function getDomainAccessManager(): DomainAccessManager
117
-    {
118
-        return $this->domainAccessManager;
119
-    }
30
+	use NavigationMenuAccessControl;
31
+
32
+	/** @var SecurityManager */
33
+	private $securityManager;
34
+	/** @var DomainAccessManager */
35
+	private $domainAccessManager;
36
+
37
+	/**
38
+	 * AccessDeniedException constructor.
39
+	 *
40
+	 * @param SecurityManager     $securityManager
41
+	 * @param DomainAccessManager $domainAccessManager
42
+	 */
43
+	public function __construct(SecurityManager $securityManager, DomainAccessManager $domainAccessManager)
44
+	{
45
+		$this->securityManager = $securityManager;
46
+		$this->domainAccessManager = $domainAccessManager;
47
+	}
48
+
49
+	public function getReadableError()
50
+	{
51
+		if (!headers_sent()) {
52
+			header("HTTP/1.1 403 Forbidden");
53
+		}
54
+
55
+		$this->setUpSmarty();
56
+
57
+		// uck. We should still be able to access the database in this situation though.
58
+		$database = PdoDatabase::getDatabaseConnection($this->getSiteConfiguration());
59
+		$currentUser = User::getCurrent($database);
60
+		$this->assign('skin', PreferenceManager::getForCurrent($database)->getPreference(PreferenceManager::PREF_SKIN));
61
+		$this->assign('currentUser', $currentUser);
62
+		$this->assign('currentDomain', Domain::getCurrent($database));
63
+
64
+		if ($this->securityManager !== null) {
65
+			$this->setupNavMenuAccess($currentUser);
66
+		}
67
+
68
+		if ($currentUser->isDeclined()) {
69
+			$this->assign('htmlTitle', 'Account Declined');
70
+			$this->assign('declineReason', $this->getLogEntry('Declined', $currentUser, $database));
71
+
72
+			return $this->fetchTemplate("exception/account-declined.tpl");
73
+		}
74
+
75
+		if ($currentUser->isSuspended()) {
76
+			$this->assign('htmlTitle', 'Account Suspended');
77
+			$this->assign('suspendReason', $this->getLogEntry('Suspended', $currentUser, $database));
78
+
79
+			return $this->fetchTemplate("exception/account-suspended.tpl");
80
+		}
81
+
82
+		if ($currentUser->isNewUser()) {
83
+			$this->assign('htmlTitle', 'Account Pending');
84
+
85
+			return $this->fetchTemplate("exception/account-new.tpl");
86
+		}
87
+
88
+		return $this->fetchTemplate("exception/access-denied.tpl");
89
+	}
90
+
91
+	/**
92
+	 * @param string      $action
93
+	 * @param User        $user
94
+	 * @param PdoDatabase $database
95
+	 *
96
+	 * @return null|string
97
+	 */
98
+	private function getLogEntry($action, User $user, PdoDatabase $database)
99
+	{
100
+		/** @var Log[] $logs */
101
+		$logs = LogSearchHelper::get($database, null)
102
+			->byAction($action)
103
+			->byObjectType('User')
104
+			->byObjectId($user->getId())
105
+			->limit(1)
106
+			->fetch();
107
+
108
+		return $logs[0]->getComment();
109
+	}
110
+
111
+	protected function getSecurityManager(): SecurityManager
112
+	{
113
+		return $this->securityManager;
114
+	}
115
+
116
+	public function getDomainAccessManager(): DomainAccessManager
117
+	{
118
+		return $this->domainAccessManager;
119
+	}
120 120
 }
121 121
\ No newline at end of file
Please login to merge, or discard this patch.
includes/Exceptions/NotIdentifiedException.php 1 patch
Indentation   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -18,58 +18,58 @@
 block discarded – undo
18 18
 
19 19
 class NotIdentifiedException extends ReadableException
20 20
 {
21
-    use NavigationMenuAccessControl;
21
+	use NavigationMenuAccessControl;
22 22
 
23
-    /** @var SecurityManager */
24
-    private $securityManager;
25
-    /** @var DomainAccessManager */
26
-    private $domainAccessManager;
23
+	/** @var SecurityManager */
24
+	private $securityManager;
25
+	/** @var DomainAccessManager */
26
+	private $domainAccessManager;
27 27
 
28
-    /**
29
-     * NotIdentifiedException constructor.
30
-     *
31
-     * @param SecurityManager     $securityManager
32
-     * @param DomainAccessManager $domainAccessManager
33
-     */
34
-    public function __construct(SecurityManager $securityManager, DomainAccessManager $domainAccessManager)
35
-    {
36
-        $this->securityManager = $securityManager;
37
-        $this->domainAccessManager = $domainAccessManager;
38
-    }
28
+	/**
29
+	 * NotIdentifiedException constructor.
30
+	 *
31
+	 * @param SecurityManager     $securityManager
32
+	 * @param DomainAccessManager $domainAccessManager
33
+	 */
34
+	public function __construct(SecurityManager $securityManager, DomainAccessManager $domainAccessManager)
35
+	{
36
+		$this->securityManager = $securityManager;
37
+		$this->domainAccessManager = $domainAccessManager;
38
+	}
39 39
 
40
-    /**
41
-     * Returns a readable HTML error message that's displayable to the user using templates.
42
-     * @return string
43
-     */
44
-    public function getReadableError()
45
-    {
46
-        if (!headers_sent()) {
47
-            header("HTTP/1.1 403 Forbidden");
48
-        }
40
+	/**
41
+	 * Returns a readable HTML error message that's displayable to the user using templates.
42
+	 * @return string
43
+	 */
44
+	public function getReadableError()
45
+	{
46
+		if (!headers_sent()) {
47
+			header("HTTP/1.1 403 Forbidden");
48
+		}
49 49
 
50
-        $this->setUpSmarty();
50
+		$this->setUpSmarty();
51 51
 
52
-        // uck. We should still be able to access the database in this situation though.
53
-        $database = PdoDatabase::getDatabaseConnection($this->getSiteConfiguration());
54
-        $currentUser = User::getCurrent($database);
55
-        $this->assign('skin', PreferenceManager::getForCurrent($database)->getPreference(PreferenceManager::PREF_SKIN));
56
-        $this->assign('currentUser', $currentUser);
57
-        $this->assign('currentDomain', Domain::getCurrent($database));
52
+		// uck. We should still be able to access the database in this situation though.
53
+		$database = PdoDatabase::getDatabaseConnection($this->getSiteConfiguration());
54
+		$currentUser = User::getCurrent($database);
55
+		$this->assign('skin', PreferenceManager::getForCurrent($database)->getPreference(PreferenceManager::PREF_SKIN));
56
+		$this->assign('currentUser', $currentUser);
57
+		$this->assign('currentDomain', Domain::getCurrent($database));
58 58
 
59
-        if ($this->securityManager !== null) {
60
-            $this->setupNavMenuAccess($currentUser);
61
-        }
59
+		if ($this->securityManager !== null) {
60
+			$this->setupNavMenuAccess($currentUser);
61
+		}
62 62
 
63
-        return $this->fetchTemplate("exception/not-identified.tpl");
64
-    }
63
+		return $this->fetchTemplate("exception/not-identified.tpl");
64
+	}
65 65
 
66
-    protected function getSecurityManager(): SecurityManager
67
-    {
68
-        return $this->securityManager;
69
-    }
66
+	protected function getSecurityManager(): SecurityManager
67
+	{
68
+		return $this->securityManager;
69
+	}
70 70
 
71
-    public function getDomainAccessManager(): DomainAccessManager
72
-    {
73
-        return $this->domainAccessManager;
74
-    }
71
+	public function getDomainAccessManager(): DomainAccessManager
72
+	{
73
+		return $this->domainAccessManager;
74
+	}
75 75
 }
76 76
\ No newline at end of file
Please login to merge, or discard this patch.
includes/Pages/PageBan.php 1 patch
Indentation   +657 added lines, -657 removed lines patch added patch discarded remove patch
@@ -26,663 +26,663 @@
 block discarded – undo
26 26
 
27 27
 class PageBan extends InternalPageBase
28 28
 {
29
-    /**
30
-     * Main function for this page, when no specific actions are called.
31
-     */
32
-    protected function main(): void
33
-    {
34
-        $this->assignCSRFToken();
35
-        $this->setHtmlTitle('Bans');
36
-
37
-        $database = $this->getDatabase();
38
-        $currentDomain = Domain::getCurrent($database);
39
-        $bans = Ban::getActiveBans($database, $currentDomain->getId());
40
-
41
-        $this->setupBanList($bans);
42
-
43
-        $this->assign('isFiltered', false);
44
-        $this->setTemplate('bans/main.tpl');
45
-    }
46
-
47
-    protected function show(): void
48
-    {
49
-        $this->assignCSRFToken();
50
-        $this->setHtmlTitle('Bans');
51
-
52
-        $rawIdList = WebRequest::getString('id');
53
-        if ($rawIdList === null) {
54
-            $this->redirect('bans');
55
-
56
-            return;
57
-        }
58
-
59
-        $idList = explode(',', $rawIdList);
60
-
61
-        $database = $this->getDatabase();
62
-        $currentDomain = Domain::getCurrent($database);
63
-        $bans = Ban::getByIdList($idList, $database, $currentDomain->getId());
64
-
65
-        $this->setupBanList($bans);
66
-        $this->assign('isFiltered', true);
67
-        $this->setTemplate('bans/main.tpl');
68
-    }
69
-
70
-    /**
71
-     * Entry point for the ban set action
72
-     * @throws SmartyException
73
-     * @throws Exception
74
-     */
75
-    protected function set(): void
76
-    {
77
-        $this->setHtmlTitle('Bans');
78
-
79
-        // dual-mode action
80
-        if (WebRequest::wasPosted()) {
81
-            try {
82
-                $this->handlePostMethodForSetBan();
83
-            }
84
-            catch (ApplicationLogicException $ex) {
85
-                SessionAlert::error($ex->getMessage());
86
-                $this->redirect("bans", "set");
87
-            }
88
-        }
89
-        else {
90
-            $this->handleGetMethodForSetBan();
91
-
92
-            $user = User::getCurrent($this->getDatabase());
93
-            $banType = WebRequest::getString('type');
94
-            $banRequest = WebRequest::getInt('request');
95
-
96
-            // if the parameters are null, skip loading a request.
97
-            if ($banType !== null && $banRequest !== null && $banRequest !== 0) {
98
-                $this->preloadFormForRequest($banRequest, $banType, $user);
99
-            }
100
-        }
101
-    }
102
-
103
-    protected function replace(): void
104
-    {
105
-        $this->setHtmlTitle('Bans');
106
-
107
-        $database = $this->getDatabase();
108
-        $domain = Domain::getCurrent($database);
109
-
110
-        // dual-mode action
111
-        if (WebRequest::wasPosted()) {
112
-            try {
113
-                $originalBanId = WebRequest::postInt('replaceBanId');
114
-                $originalBanUpdateVersion = WebRequest::postInt('replaceBanUpdateVersion');
115
-
116
-                $originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId());
117
-
118
-                if ($originalBan === false) {
119
-                    throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
120
-                }
121
-
122
-                // Discard original ban; we're replacing it.
123
-                $originalBan->setUpdateVersion($originalBanUpdateVersion);
124
-                $originalBan->setActive(false);
125
-                $originalBan->save();
126
-
127
-                Logger::banReplaced($database, $originalBan);
128
-
129
-                // Proceed as normal to save the new ban.
130
-                $this->handlePostMethodForSetBan();
131
-            }
132
-            catch (ApplicationLogicException $ex) {
133
-                $database->rollback();
134
-                SessionAlert::error($ex->getMessage());
135
-                $this->redirect("bans", "set");
136
-            }
137
-        }
138
-        else {
139
-            $this->handleGetMethodForSetBan();
140
-
141
-            $user = User::getCurrent($database);
142
-            $originalBanId = WebRequest::getString('id');
143
-
144
-            $originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId());
145
-
146
-            if ($originalBan === false) {
147
-                throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
148
-            }
149
-
150
-            if ($originalBan->getName() !== null) {
151
-                if (!$this->barrierTest('name', $user, 'BanType')) {
152
-                    SessionAlert::error("You are not allowed to set this type of ban.");
153
-                    $this->redirect("bans", "set");
154
-                    return;
155
-                }
156
-
157
-                $this->assign('banName', $originalBan->getName());
158
-            }
159
-
160
-            if ($originalBan->getEmail() !== null) {
161
-                if (!$this->barrierTest('email', $user, 'BanType')) {
162
-                    SessionAlert::error("You are not allowed to set this type of ban.");
163
-                    $this->redirect("bans", "set");
164
-                    return;
165
-                }
166
-
167
-                $this->assign('banEmail', $originalBan->getEmail());
168
-            }
169
-
170
-            if ($originalBan->getUseragent() !== null) {
171
-                if (!$this->barrierTest('useragent', $user, 'BanType')) {
172
-                    SessionAlert::error("You are not allowed to set this type of ban.");
173
-                    $this->redirect("bans", "set");
174
-                    return;
175
-                }
176
-
177
-                $this->assign('banUseragent', $originalBan->getUseragent());
178
-            }
179
-
180
-            if ($originalBan->getIp() !== null) {
181
-                if (!$this->barrierTest('ip', $user, 'BanType')) {
182
-                    SessionAlert::error("You are not allowed to set this type of ban.");
183
-                    $this->redirect("bans", "set");
184
-                    return;
185
-                }
186
-
187
-                $this->assign('banIP', $originalBan->getIp() . '/' . $originalBan->getIpMask());
188
-            }
189
-
190
-            $banIsGlobal = $originalBan->getDomain() === null;
191
-            if ($banIsGlobal) {
192
-                if (!$this->barrierTest('global', $user, 'BanType')) {
193
-                    SessionAlert::error("You are not allowed to set this type of ban.");
194
-                    $this->redirect("bans", "set");
195
-                    return;
196
-                }
197
-            }
198
-
199
-            if (!$this->barrierTest($originalBan->getVisibility(), $user, 'BanVisibility')) {
200
-                SessionAlert::error("You are not allowed to set this type of ban.");
201
-                $this->redirect("bans", "set");
202
-                return;
203
-            }
204
-
205
-            $this->assign('banGlobal', $banIsGlobal);
206
-            $this->assign('banVisibility', $originalBan->getVisibility());
207
-
208
-            if ($originalBan->getDuration() !== null) {
209
-                $this->assign('banDuration', date('c', $originalBan->getDuration()));
210
-            }
211
-
212
-            $this->assign('banReason', $originalBan->getReason());
213
-            $this->assign('banAction', $originalBan->getAction());
214
-            $this->assign('banQueue', $originalBan->getTargetQueue());
215
-
216
-            $this->assign('replaceBanId', $originalBan->getId());
217
-            $this->assign('replaceBanUpdateVersion', $originalBan->getUpdateVersion());
218
-        }
219
-    }
220
-
221
-    /**
222
-     * Entry point for the ban remove action
223
-     *
224
-     * @throws AccessDeniedException
225
-     * @throws ApplicationLogicException
226
-     * @throws SmartyException
227
-     */
228
-    protected function remove(): void
229
-    {
230
-        $this->setHtmlTitle('Bans');
231
-
232
-        $ban = $this->getBanForUnban();
233
-
234
-        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
235
-        if (!$banHelper->canUnban($ban)) {
236
-            // triggered when a user tries to unban a ban they can't see the entirety of.
237
-            // there's no UI way to get to this, so a raw exception is fine.
238
-            throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
239
-        }
240
-
241
-        // dual mode
242
-        if (WebRequest::wasPosted()) {
243
-            $this->validateCSRFToken();
244
-            $unbanReason = WebRequest::postString('unbanreason');
245
-
246
-            if ($unbanReason === null || trim($unbanReason) === "") {
247
-                SessionAlert::error('No unban reason specified');
248
-                $this->redirect("bans", "remove", array('id' => $ban->getId()));
249
-            }
250
-
251
-            // set optimistic locking from delete form page load
252
-            $updateVersion = WebRequest::postInt('updateversion');
253
-            $ban->setUpdateVersion($updateVersion);
254
-
255
-            $database = $this->getDatabase();
256
-            $ban->setActive(false);
257
-            $ban->save();
258
-
259
-            Logger::unbanned($database, $ban, $unbanReason);
260
-
261
-            SessionAlert::quick('Disabled ban.');
262
-            $this->getNotificationHelper()->unbanned($ban, $unbanReason);
263
-
264
-            $this->redirect('bans');
265
-        }
266
-        else {
267
-            $this->assignCSRFToken();
268
-            $this->assign('ban', $ban);
269
-            $this->setTemplate('bans/unban.tpl');
270
-        }
271
-    }
272
-
273
-    /**
274
-     * Retrieves the requested ban duration from the WebRequest
275
-     *
276
-     * @throws ApplicationLogicException
277
-     */
278
-    private function getBanDuration(): ?int
279
-    {
280
-        $duration = WebRequest::postString('duration');
281
-        if ($duration === "other") {
282
-            $duration = strtotime(WebRequest::postString('otherduration'));
283
-
284
-            if (!$duration) {
285
-                throw new ApplicationLogicException('Invalid ban time');
286
-            }
287
-            elseif (time() > $duration) {
288
-                throw new ApplicationLogicException('Ban time has already expired!');
289
-            }
290
-
291
-            return $duration;
292
-        }
293
-        elseif ($duration === "-1") {
294
-            return null;
295
-        }
296
-        else {
297
-            return WebRequest::postInt('duration') + time();
298
-        }
299
-    }
300
-
301
-    /**
302
-     * Handles the POST method on the set action
303
-     *
304
-     * @throws ApplicationLogicException
305
-     * @throws Exception
306
-     */
307
-    private function handlePostMethodForSetBan()
308
-    {
309
-        $this->validateCSRFToken();
310
-        $database = $this->getDatabase();
311
-        $user = User::getCurrent($database);
312
-        $currentDomain = Domain::getCurrent($database);
313
-
314
-        // Checks whether there is a reason entered for ban.
315
-        $reason = WebRequest::postString('banreason');
316
-        if ($reason === null || trim($reason) === "") {
317
-            throw new ApplicationLogicException('You must specify a ban reason');
318
-        }
319
-
320
-        // ban targets
321
-        list($targetName, $targetIp, $targetEmail, $targetUseragent) = $this->getRawBanTargets($user);
322
-
323
-        $visibility = $this->getBanVisibility();
324
-
325
-        // Validate ban duration
326
-        $duration = $this->getBanDuration();
327
-
328
-        $action = WebRequest::postString('banAction') ?? Ban::ACTION_NONE;
329
-
330
-        $global = WebRequest::postBoolean('banGlobal');
331
-        if (!$this->barrierTest('global', $user, 'BanType')) {
332
-            $global = false;
333
-        }
334
-
335
-        if ($action === Ban::ACTION_DEFER && $global) {
336
-            throw new ApplicationLogicException("Cannot set a global ban in defer-to-queue mode.");
337
-        }
338
-
339
-        // handle CIDR ranges
340
-        $targetMask = null;
341
-        if ($targetIp !== null) {
342
-            list($targetIp, $targetMask) = $this->splitCidrRange($targetIp);
343
-            $this->validateIpBan($targetIp, $targetMask, $user, $action);
344
-        }
345
-
346
-        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
347
-
348
-        $bansByTarget = $banHelper->getBansByTarget(
349
-            $targetName,
350
-            $targetEmail,
351
-            $targetIp,
352
-            $targetMask,
353
-            $targetUseragent,
354
-            $currentDomain->getId());
355
-
356
-        if (count($bansByTarget) > 0) {
357
-            throw new ApplicationLogicException('This target is already banned!');
358
-        }
359
-
360
-        $ban = new Ban();
361
-        $ban->setDatabase($database);
362
-        $ban->setActive(true);
363
-
364
-        $ban->setName($targetName);
365
-        $ban->setIp($targetIp, $targetMask);
366
-        $ban->setEmail($targetEmail);
367
-        $ban->setUseragent($targetUseragent);
368
-
369
-        $ban->setUser($user->getId());
370
-        $ban->setReason($reason);
371
-        $ban->setDuration($duration);
372
-        $ban->setVisibility($visibility);
373
-
374
-        $ban->setDomain($global ? null : $currentDomain->getId());
375
-
376
-        $ban->setAction($action);
377
-        if ($ban->getAction() === Ban::ACTION_DEFER) {
378
-            //FIXME: domains
379
-            $queue = RequestQueue::getByApiName($database, WebRequest::postString('banActionTarget'), 1);
380
-            if ($queue === false) {
381
-                throw new ApplicationLogicException("Unknown target queue");
382
-            }
383
-
384
-            if (!$queue->isEnabled()) {
385
-                throw new ApplicationLogicException("Target queue is not enabled");
386
-            }
387
-
388
-            $ban->setTargetQueue($queue->getId());
389
-        }
390
-
391
-        $ban->save();
392
-
393
-        Logger::banned($database, $ban, $reason);
394
-
395
-        $this->getNotificationHelper()->banned($ban);
396
-        SessionAlert::quick('Ban has been set.');
397
-
398
-        $this->redirect('bans');
399
-    }
400
-
401
-    /**
402
-     * Handles the GET method on the set action
403
-     * @throws Exception
404
-     */
405
-    private function handleGetMethodForSetBan()
406
-    {
407
-        $this->setTemplate('bans/banform.tpl');
408
-        $this->assignCSRFToken();
409
-
410
-        $this->assign('maxIpRange', $this->getSiteConfiguration()->getBanMaxIpRange());
411
-        $this->assign('maxIpBlockRange', $this->getSiteConfiguration()->getBanMaxIpBlockRange());
412
-
413
-        $this->assign('banVisibility', 'user');
414
-        $this->assign('banGlobal', false);
415
-        $this->assign('banQueue', false);
416
-        $this->assign('banAction', Ban::ACTION_BLOCK);
417
-        $this->assign('banDuration', '');
418
-        $this->assign('banReason', '');
419
-
420
-        $this->assign('banEmail', '');
421
-        $this->assign('banIP', '');
422
-        $this->assign('banName', '');
423
-        $this->assign('banUseragent', '');
424
-
425
-        $this->assign('replaceBanId', null);
426
-
427
-
428
-
429
-        $database = $this->getDatabase();
430
-
431
-        $user = User::getCurrent($database);
432
-        $this->setupSecurity($user);
433
-
434
-        $queues = RequestQueue::getEnabledQueues($database);
435
-
436
-        $this->assign('requestQueues', $queues);
437
-    }
438
-
439
-    /**
440
-     * Finds the Ban object referenced in the WebRequest if it is valid
441
-     *
442
-     * @return Ban
443
-     * @throws ApplicationLogicException
444
-     */
445
-    private function getBanForUnban(): Ban
446
-    {
447
-        $banId = WebRequest::getInt('id');
448
-        if ($banId === null || $banId === 0) {
449
-            throw new ApplicationLogicException("The ban ID appears to be missing. This is probably a bug.");
450
-        }
451
-
452
-        $database = $this->getDatabase();
453
-        $this->setupSecurity(User::getCurrent($database));
454
-        $currentDomain = Domain::getCurrent($database);
455
-        $ban = Ban::getActiveId($banId, $database, $currentDomain->getId());
456
-
457
-        if ($ban === false) {
458
-            throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
459
-        }
460
-
461
-        return $ban;
462
-    }
463
-
464
-    /**
465
-     * Sets up Smarty variables for access control
466
-     */
467
-    private function setupSecurity(User $user): void
468
-    {
469
-        $this->assign('canSeeIpBan', $this->barrierTest('ip', $user, 'BanType'));
470
-        $this->assign('canSeeNameBan', $this->barrierTest('name', $user, 'BanType'));
471
-        $this->assign('canSeeEmailBan', $this->barrierTest('email', $user, 'BanType'));
472
-        $this->assign('canSeeUseragentBan', $this->barrierTest('useragent', $user, 'BanType'));
473
-
474
-        $this->assign('canGlobalBan', $this->barrierTest('global', $user, 'BanType'));
475
-
476
-        $this->assign('canSeeUserVisibility', $this->barrierTest('user', $user, 'BanVisibility'));
477
-        $this->assign('canSeeAdminVisibility', $this->barrierTest('admin', $user, 'BanVisibility'));
478
-        $this->assign('canSeeCheckuserVisibility', $this->barrierTest('checkuser', $user, 'BanVisibility'));
479
-    }
480
-
481
-    /**
482
-     * Validates that the provided IP is acceptable for a ban of this type
483
-     *
484
-     * @param string $targetIp   IP address
485
-     * @param int    $targetMask CIDR prefix length
486
-     * @param User   $user       User performing the ban
487
-     * @param string $action     Ban action to take
488
-     *
489
-     * @throws ApplicationLogicException
490
-     */
491
-    private function validateIpBan(string $targetIp, int $targetMask, User $user, string $action): void
492
-    {
493
-        // validate this is an IP
494
-        if (!filter_var($targetIp, FILTER_VALIDATE_IP)) {
495
-            throw new ApplicationLogicException("Not a valid IP address");
496
-        }
497
-
498
-        $canLargeIpBan = $this->barrierTest('ip-largerange', $user, 'BanType');
499
-        $maxIpBlockRange = $this->getSiteConfiguration()->getBanMaxIpBlockRange();
500
-        $maxIpRange = $this->getSiteConfiguration()->getBanMaxIpRange();
501
-
502
-        // validate CIDR ranges
503
-        if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
504
-            if ($targetMask < 0 || $targetMask > 128) {
505
-                throw new ApplicationLogicException("CIDR mask out of range for IPv6");
506
-            }
507
-
508
-            // prevent setting the ban if:
509
-            //  * the user isn't allowed to set large bans, AND
510
-            //  * the ban is a drop or a block (preventing human review of the request), AND
511
-            //  * the mask is too wide-reaching
512
-            if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[6]) {
513
-                throw new ApplicationLogicException("The requested IP range for this ban is too wide for the block/drop action.");
514
-            }
515
-
516
-            if (!$canLargeIpBan && $targetMask < $maxIpRange[6]) {
517
-                throw new ApplicationLogicException("The requested IP range for this ban is too wide.");
518
-            }
519
-        }
520
-
521
-        if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
522
-            if ($targetMask < 0 || $targetMask > 32) {
523
-                throw new ApplicationLogicException("CIDR mask out of range for IPv4");
524
-            }
525
-
526
-            if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[4]) {
527
-                throw new ApplicationLogicException("The IP range for this ban is too wide for the block/drop action.");
528
-            }
529
-
530
-            if (!$canLargeIpBan && $targetMask < $maxIpRange[4]) {
531
-                throw new ApplicationLogicException("The requested IP range for this ban is too wide.");
532
-            }
533
-        }
534
-
535
-        $squidIpList = $this->getSiteConfiguration()->getSquidList();
536
-        if (in_array($targetIp, $squidIpList)) {
537
-            throw new ApplicationLogicException("This IP address is on the protected list of proxies, and cannot be banned.");
538
-        }
539
-    }
540
-
541
-    /**
542
-     * Configures a ban list template for display
543
-     *
544
-     * @param Ban[] $bans
545
-     */
546
-    private function setupBanList(array $bans): void
547
-    {
548
-        $userIds = array_map(fn(Ban $entry) => $entry->getUser(), $bans);
549
-        $userList = UserSearchHelper::get($this->getDatabase())->inIds($userIds)->fetchMap('username');
550
-
551
-        $domainIds = array_filter(array_unique(array_map(fn(Ban $entry) => $entry->getDomain(), $bans)));
552
-        $domains = [];
553
-        foreach ($domainIds as $d) {
554
-            if ($d === null) {
555
-                continue;
556
-            }
557
-            $domains[$d] = Domain::getById($d, $this->getDatabase());
558
-        }
559
-
560
-        $this->assign('domains', $domains);
561
-
562
-        $user = User::getCurrent($this->getDatabase());
563
-        $this->assign('canSet', $this->barrierTest('set', $user));
564
-        $this->assign('canRemove', $this->barrierTest('remove', $user));
565
-
566
-        $this->setupSecurity($user);
567
-
568
-        $this->assign('usernames', $userList);
569
-        $this->assign('activebans', $bans);
570
-
571
-        $banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
572
-        $this->assign('banHelper', $banHelper);
573
-    }
574
-
575
-    /**
576
-     * Converts a plain IP or CIDR mask into an IP and a CIDR suffix
577
-     *
578
-     * @param string $targetIp IP or CIDR range
579
-     *
580
-     * @return array
581
-     */
582
-    private function splitCidrRange(string $targetIp): array
583
-    {
584
-        if (strpos($targetIp, '/') !== false) {
585
-            $ipParts = explode('/', $targetIp, 2);
586
-            $targetIp = $ipParts[0];
587
-            $targetMask = (int)$ipParts[1];
588
-        }
589
-        else {
590
-            // Default the CIDR range based on the IP type
591
-            $targetMask = filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 128 : 32;
592
-        }
593
-
594
-        return array($targetIp, $targetMask);
29
+	/**
30
+	 * Main function for this page, when no specific actions are called.
31
+	 */
32
+	protected function main(): void
33
+	{
34
+		$this->assignCSRFToken();
35
+		$this->setHtmlTitle('Bans');
36
+
37
+		$database = $this->getDatabase();
38
+		$currentDomain = Domain::getCurrent($database);
39
+		$bans = Ban::getActiveBans($database, $currentDomain->getId());
40
+
41
+		$this->setupBanList($bans);
42
+
43
+		$this->assign('isFiltered', false);
44
+		$this->setTemplate('bans/main.tpl');
45
+	}
46
+
47
+	protected function show(): void
48
+	{
49
+		$this->assignCSRFToken();
50
+		$this->setHtmlTitle('Bans');
51
+
52
+		$rawIdList = WebRequest::getString('id');
53
+		if ($rawIdList === null) {
54
+			$this->redirect('bans');
55
+
56
+			return;
57
+		}
58
+
59
+		$idList = explode(',', $rawIdList);
60
+
61
+		$database = $this->getDatabase();
62
+		$currentDomain = Domain::getCurrent($database);
63
+		$bans = Ban::getByIdList($idList, $database, $currentDomain->getId());
64
+
65
+		$this->setupBanList($bans);
66
+		$this->assign('isFiltered', true);
67
+		$this->setTemplate('bans/main.tpl');
68
+	}
69
+
70
+	/**
71
+	 * Entry point for the ban set action
72
+	 * @throws SmartyException
73
+	 * @throws Exception
74
+	 */
75
+	protected function set(): void
76
+	{
77
+		$this->setHtmlTitle('Bans');
78
+
79
+		// dual-mode action
80
+		if (WebRequest::wasPosted()) {
81
+			try {
82
+				$this->handlePostMethodForSetBan();
83
+			}
84
+			catch (ApplicationLogicException $ex) {
85
+				SessionAlert::error($ex->getMessage());
86
+				$this->redirect("bans", "set");
87
+			}
88
+		}
89
+		else {
90
+			$this->handleGetMethodForSetBan();
91
+
92
+			$user = User::getCurrent($this->getDatabase());
93
+			$banType = WebRequest::getString('type');
94
+			$banRequest = WebRequest::getInt('request');
95
+
96
+			// if the parameters are null, skip loading a request.
97
+			if ($banType !== null && $banRequest !== null && $banRequest !== 0) {
98
+				$this->preloadFormForRequest($banRequest, $banType, $user);
99
+			}
100
+		}
101
+	}
102
+
103
+	protected function replace(): void
104
+	{
105
+		$this->setHtmlTitle('Bans');
106
+
107
+		$database = $this->getDatabase();
108
+		$domain = Domain::getCurrent($database);
109
+
110
+		// dual-mode action
111
+		if (WebRequest::wasPosted()) {
112
+			try {
113
+				$originalBanId = WebRequest::postInt('replaceBanId');
114
+				$originalBanUpdateVersion = WebRequest::postInt('replaceBanUpdateVersion');
115
+
116
+				$originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId());
117
+
118
+				if ($originalBan === false) {
119
+					throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
120
+				}
121
+
122
+				// Discard original ban; we're replacing it.
123
+				$originalBan->setUpdateVersion($originalBanUpdateVersion);
124
+				$originalBan->setActive(false);
125
+				$originalBan->save();
126
+
127
+				Logger::banReplaced($database, $originalBan);
128
+
129
+				// Proceed as normal to save the new ban.
130
+				$this->handlePostMethodForSetBan();
131
+			}
132
+			catch (ApplicationLogicException $ex) {
133
+				$database->rollback();
134
+				SessionAlert::error($ex->getMessage());
135
+				$this->redirect("bans", "set");
136
+			}
137
+		}
138
+		else {
139
+			$this->handleGetMethodForSetBan();
140
+
141
+			$user = User::getCurrent($database);
142
+			$originalBanId = WebRequest::getString('id');
143
+
144
+			$originalBan = Ban::getActiveId($originalBanId, $database, $domain->getId());
145
+
146
+			if ($originalBan === false) {
147
+				throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
148
+			}
149
+
150
+			if ($originalBan->getName() !== null) {
151
+				if (!$this->barrierTest('name', $user, 'BanType')) {
152
+					SessionAlert::error("You are not allowed to set this type of ban.");
153
+					$this->redirect("bans", "set");
154
+					return;
155
+				}
156
+
157
+				$this->assign('banName', $originalBan->getName());
158
+			}
159
+
160
+			if ($originalBan->getEmail() !== null) {
161
+				if (!$this->barrierTest('email', $user, 'BanType')) {
162
+					SessionAlert::error("You are not allowed to set this type of ban.");
163
+					$this->redirect("bans", "set");
164
+					return;
165
+				}
166
+
167
+				$this->assign('banEmail', $originalBan->getEmail());
168
+			}
169
+
170
+			if ($originalBan->getUseragent() !== null) {
171
+				if (!$this->barrierTest('useragent', $user, 'BanType')) {
172
+					SessionAlert::error("You are not allowed to set this type of ban.");
173
+					$this->redirect("bans", "set");
174
+					return;
175
+				}
176
+
177
+				$this->assign('banUseragent', $originalBan->getUseragent());
178
+			}
179
+
180
+			if ($originalBan->getIp() !== null) {
181
+				if (!$this->barrierTest('ip', $user, 'BanType')) {
182
+					SessionAlert::error("You are not allowed to set this type of ban.");
183
+					$this->redirect("bans", "set");
184
+					return;
185
+				}
186
+
187
+				$this->assign('banIP', $originalBan->getIp() . '/' . $originalBan->getIpMask());
188
+			}
189
+
190
+			$banIsGlobal = $originalBan->getDomain() === null;
191
+			if ($banIsGlobal) {
192
+				if (!$this->barrierTest('global', $user, 'BanType')) {
193
+					SessionAlert::error("You are not allowed to set this type of ban.");
194
+					$this->redirect("bans", "set");
195
+					return;
196
+				}
197
+			}
198
+
199
+			if (!$this->barrierTest($originalBan->getVisibility(), $user, 'BanVisibility')) {
200
+				SessionAlert::error("You are not allowed to set this type of ban.");
201
+				$this->redirect("bans", "set");
202
+				return;
203
+			}
204
+
205
+			$this->assign('banGlobal', $banIsGlobal);
206
+			$this->assign('banVisibility', $originalBan->getVisibility());
207
+
208
+			if ($originalBan->getDuration() !== null) {
209
+				$this->assign('banDuration', date('c', $originalBan->getDuration()));
210
+			}
211
+
212
+			$this->assign('banReason', $originalBan->getReason());
213
+			$this->assign('banAction', $originalBan->getAction());
214
+			$this->assign('banQueue', $originalBan->getTargetQueue());
215
+
216
+			$this->assign('replaceBanId', $originalBan->getId());
217
+			$this->assign('replaceBanUpdateVersion', $originalBan->getUpdateVersion());
218
+		}
219
+	}
220
+
221
+	/**
222
+	 * Entry point for the ban remove action
223
+	 *
224
+	 * @throws AccessDeniedException
225
+	 * @throws ApplicationLogicException
226
+	 * @throws SmartyException
227
+	 */
228
+	protected function remove(): void
229
+	{
230
+		$this->setHtmlTitle('Bans');
231
+
232
+		$ban = $this->getBanForUnban();
233
+
234
+		$banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
235
+		if (!$banHelper->canUnban($ban)) {
236
+			// triggered when a user tries to unban a ban they can't see the entirety of.
237
+			// there's no UI way to get to this, so a raw exception is fine.
238
+			throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
239
+		}
240
+
241
+		// dual mode
242
+		if (WebRequest::wasPosted()) {
243
+			$this->validateCSRFToken();
244
+			$unbanReason = WebRequest::postString('unbanreason');
245
+
246
+			if ($unbanReason === null || trim($unbanReason) === "") {
247
+				SessionAlert::error('No unban reason specified');
248
+				$this->redirect("bans", "remove", array('id' => $ban->getId()));
249
+			}
250
+
251
+			// set optimistic locking from delete form page load
252
+			$updateVersion = WebRequest::postInt('updateversion');
253
+			$ban->setUpdateVersion($updateVersion);
254
+
255
+			$database = $this->getDatabase();
256
+			$ban->setActive(false);
257
+			$ban->save();
258
+
259
+			Logger::unbanned($database, $ban, $unbanReason);
260
+
261
+			SessionAlert::quick('Disabled ban.');
262
+			$this->getNotificationHelper()->unbanned($ban, $unbanReason);
263
+
264
+			$this->redirect('bans');
265
+		}
266
+		else {
267
+			$this->assignCSRFToken();
268
+			$this->assign('ban', $ban);
269
+			$this->setTemplate('bans/unban.tpl');
270
+		}
271
+	}
272
+
273
+	/**
274
+	 * Retrieves the requested ban duration from the WebRequest
275
+	 *
276
+	 * @throws ApplicationLogicException
277
+	 */
278
+	private function getBanDuration(): ?int
279
+	{
280
+		$duration = WebRequest::postString('duration');
281
+		if ($duration === "other") {
282
+			$duration = strtotime(WebRequest::postString('otherduration'));
283
+
284
+			if (!$duration) {
285
+				throw new ApplicationLogicException('Invalid ban time');
286
+			}
287
+			elseif (time() > $duration) {
288
+				throw new ApplicationLogicException('Ban time has already expired!');
289
+			}
290
+
291
+			return $duration;
292
+		}
293
+		elseif ($duration === "-1") {
294
+			return null;
295
+		}
296
+		else {
297
+			return WebRequest::postInt('duration') + time();
298
+		}
299
+	}
300
+
301
+	/**
302
+	 * Handles the POST method on the set action
303
+	 *
304
+	 * @throws ApplicationLogicException
305
+	 * @throws Exception
306
+	 */
307
+	private function handlePostMethodForSetBan()
308
+	{
309
+		$this->validateCSRFToken();
310
+		$database = $this->getDatabase();
311
+		$user = User::getCurrent($database);
312
+		$currentDomain = Domain::getCurrent($database);
313
+
314
+		// Checks whether there is a reason entered for ban.
315
+		$reason = WebRequest::postString('banreason');
316
+		if ($reason === null || trim($reason) === "") {
317
+			throw new ApplicationLogicException('You must specify a ban reason');
318
+		}
319
+
320
+		// ban targets
321
+		list($targetName, $targetIp, $targetEmail, $targetUseragent) = $this->getRawBanTargets($user);
322
+
323
+		$visibility = $this->getBanVisibility();
324
+
325
+		// Validate ban duration
326
+		$duration = $this->getBanDuration();
327
+
328
+		$action = WebRequest::postString('banAction') ?? Ban::ACTION_NONE;
329
+
330
+		$global = WebRequest::postBoolean('banGlobal');
331
+		if (!$this->barrierTest('global', $user, 'BanType')) {
332
+			$global = false;
333
+		}
334
+
335
+		if ($action === Ban::ACTION_DEFER && $global) {
336
+			throw new ApplicationLogicException("Cannot set a global ban in defer-to-queue mode.");
337
+		}
338
+
339
+		// handle CIDR ranges
340
+		$targetMask = null;
341
+		if ($targetIp !== null) {
342
+			list($targetIp, $targetMask) = $this->splitCidrRange($targetIp);
343
+			$this->validateIpBan($targetIp, $targetMask, $user, $action);
344
+		}
345
+
346
+		$banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
347
+
348
+		$bansByTarget = $banHelper->getBansByTarget(
349
+			$targetName,
350
+			$targetEmail,
351
+			$targetIp,
352
+			$targetMask,
353
+			$targetUseragent,
354
+			$currentDomain->getId());
355
+
356
+		if (count($bansByTarget) > 0) {
357
+			throw new ApplicationLogicException('This target is already banned!');
358
+		}
359
+
360
+		$ban = new Ban();
361
+		$ban->setDatabase($database);
362
+		$ban->setActive(true);
363
+
364
+		$ban->setName($targetName);
365
+		$ban->setIp($targetIp, $targetMask);
366
+		$ban->setEmail($targetEmail);
367
+		$ban->setUseragent($targetUseragent);
368
+
369
+		$ban->setUser($user->getId());
370
+		$ban->setReason($reason);
371
+		$ban->setDuration($duration);
372
+		$ban->setVisibility($visibility);
373
+
374
+		$ban->setDomain($global ? null : $currentDomain->getId());
375
+
376
+		$ban->setAction($action);
377
+		if ($ban->getAction() === Ban::ACTION_DEFER) {
378
+			//FIXME: domains
379
+			$queue = RequestQueue::getByApiName($database, WebRequest::postString('banActionTarget'), 1);
380
+			if ($queue === false) {
381
+				throw new ApplicationLogicException("Unknown target queue");
382
+			}
383
+
384
+			if (!$queue->isEnabled()) {
385
+				throw new ApplicationLogicException("Target queue is not enabled");
386
+			}
387
+
388
+			$ban->setTargetQueue($queue->getId());
389
+		}
390
+
391
+		$ban->save();
392
+
393
+		Logger::banned($database, $ban, $reason);
394
+
395
+		$this->getNotificationHelper()->banned($ban);
396
+		SessionAlert::quick('Ban has been set.');
397
+
398
+		$this->redirect('bans');
399
+	}
400
+
401
+	/**
402
+	 * Handles the GET method on the set action
403
+	 * @throws Exception
404
+	 */
405
+	private function handleGetMethodForSetBan()
406
+	{
407
+		$this->setTemplate('bans/banform.tpl');
408
+		$this->assignCSRFToken();
409
+
410
+		$this->assign('maxIpRange', $this->getSiteConfiguration()->getBanMaxIpRange());
411
+		$this->assign('maxIpBlockRange', $this->getSiteConfiguration()->getBanMaxIpBlockRange());
412
+
413
+		$this->assign('banVisibility', 'user');
414
+		$this->assign('banGlobal', false);
415
+		$this->assign('banQueue', false);
416
+		$this->assign('banAction', Ban::ACTION_BLOCK);
417
+		$this->assign('banDuration', '');
418
+		$this->assign('banReason', '');
419
+
420
+		$this->assign('banEmail', '');
421
+		$this->assign('banIP', '');
422
+		$this->assign('banName', '');
423
+		$this->assign('banUseragent', '');
424
+
425
+		$this->assign('replaceBanId', null);
426
+
427
+
428
+
429
+		$database = $this->getDatabase();
430
+
431
+		$user = User::getCurrent($database);
432
+		$this->setupSecurity($user);
433
+
434
+		$queues = RequestQueue::getEnabledQueues($database);
435
+
436
+		$this->assign('requestQueues', $queues);
437
+	}
438
+
439
+	/**
440
+	 * Finds the Ban object referenced in the WebRequest if it is valid
441
+	 *
442
+	 * @return Ban
443
+	 * @throws ApplicationLogicException
444
+	 */
445
+	private function getBanForUnban(): Ban
446
+	{
447
+		$banId = WebRequest::getInt('id');
448
+		if ($banId === null || $banId === 0) {
449
+			throw new ApplicationLogicException("The ban ID appears to be missing. This is probably a bug.");
450
+		}
451
+
452
+		$database = $this->getDatabase();
453
+		$this->setupSecurity(User::getCurrent($database));
454
+		$currentDomain = Domain::getCurrent($database);
455
+		$ban = Ban::getActiveId($banId, $database, $currentDomain->getId());
456
+
457
+		if ($ban === false) {
458
+			throw new ApplicationLogicException("The specified ban is not currently active, or doesn't exist.");
459
+		}
460
+
461
+		return $ban;
462
+	}
463
+
464
+	/**
465
+	 * Sets up Smarty variables for access control
466
+	 */
467
+	private function setupSecurity(User $user): void
468
+	{
469
+		$this->assign('canSeeIpBan', $this->barrierTest('ip', $user, 'BanType'));
470
+		$this->assign('canSeeNameBan', $this->barrierTest('name', $user, 'BanType'));
471
+		$this->assign('canSeeEmailBan', $this->barrierTest('email', $user, 'BanType'));
472
+		$this->assign('canSeeUseragentBan', $this->barrierTest('useragent', $user, 'BanType'));
473
+
474
+		$this->assign('canGlobalBan', $this->barrierTest('global', $user, 'BanType'));
475
+
476
+		$this->assign('canSeeUserVisibility', $this->barrierTest('user', $user, 'BanVisibility'));
477
+		$this->assign('canSeeAdminVisibility', $this->barrierTest('admin', $user, 'BanVisibility'));
478
+		$this->assign('canSeeCheckuserVisibility', $this->barrierTest('checkuser', $user, 'BanVisibility'));
479
+	}
480
+
481
+	/**
482
+	 * Validates that the provided IP is acceptable for a ban of this type
483
+	 *
484
+	 * @param string $targetIp   IP address
485
+	 * @param int    $targetMask CIDR prefix length
486
+	 * @param User   $user       User performing the ban
487
+	 * @param string $action     Ban action to take
488
+	 *
489
+	 * @throws ApplicationLogicException
490
+	 */
491
+	private function validateIpBan(string $targetIp, int $targetMask, User $user, string $action): void
492
+	{
493
+		// validate this is an IP
494
+		if (!filter_var($targetIp, FILTER_VALIDATE_IP)) {
495
+			throw new ApplicationLogicException("Not a valid IP address");
496
+		}
497
+
498
+		$canLargeIpBan = $this->barrierTest('ip-largerange', $user, 'BanType');
499
+		$maxIpBlockRange = $this->getSiteConfiguration()->getBanMaxIpBlockRange();
500
+		$maxIpRange = $this->getSiteConfiguration()->getBanMaxIpRange();
501
+
502
+		// validate CIDR ranges
503
+		if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
504
+			if ($targetMask < 0 || $targetMask > 128) {
505
+				throw new ApplicationLogicException("CIDR mask out of range for IPv6");
506
+			}
507
+
508
+			// prevent setting the ban if:
509
+			//  * the user isn't allowed to set large bans, AND
510
+			//  * the ban is a drop or a block (preventing human review of the request), AND
511
+			//  * the mask is too wide-reaching
512
+			if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[6]) {
513
+				throw new ApplicationLogicException("The requested IP range for this ban is too wide for the block/drop action.");
514
+			}
515
+
516
+			if (!$canLargeIpBan && $targetMask < $maxIpRange[6]) {
517
+				throw new ApplicationLogicException("The requested IP range for this ban is too wide.");
518
+			}
519
+		}
520
+
521
+		if (filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
522
+			if ($targetMask < 0 || $targetMask > 32) {
523
+				throw new ApplicationLogicException("CIDR mask out of range for IPv4");
524
+			}
525
+
526
+			if (!$canLargeIpBan && ($action == Ban::ACTION_BLOCK || $action == Ban::ACTION_DROP) && $targetMask < $maxIpBlockRange[4]) {
527
+				throw new ApplicationLogicException("The IP range for this ban is too wide for the block/drop action.");
528
+			}
529
+
530
+			if (!$canLargeIpBan && $targetMask < $maxIpRange[4]) {
531
+				throw new ApplicationLogicException("The requested IP range for this ban is too wide.");
532
+			}
533
+		}
534
+
535
+		$squidIpList = $this->getSiteConfiguration()->getSquidList();
536
+		if (in_array($targetIp, $squidIpList)) {
537
+			throw new ApplicationLogicException("This IP address is on the protected list of proxies, and cannot be banned.");
538
+		}
539
+	}
540
+
541
+	/**
542
+	 * Configures a ban list template for display
543
+	 *
544
+	 * @param Ban[] $bans
545
+	 */
546
+	private function setupBanList(array $bans): void
547
+	{
548
+		$userIds = array_map(fn(Ban $entry) => $entry->getUser(), $bans);
549
+		$userList = UserSearchHelper::get($this->getDatabase())->inIds($userIds)->fetchMap('username');
550
+
551
+		$domainIds = array_filter(array_unique(array_map(fn(Ban $entry) => $entry->getDomain(), $bans)));
552
+		$domains = [];
553
+		foreach ($domainIds as $d) {
554
+			if ($d === null) {
555
+				continue;
556
+			}
557
+			$domains[$d] = Domain::getById($d, $this->getDatabase());
558
+		}
559
+
560
+		$this->assign('domains', $domains);
561
+
562
+		$user = User::getCurrent($this->getDatabase());
563
+		$this->assign('canSet', $this->barrierTest('set', $user));
564
+		$this->assign('canRemove', $this->barrierTest('remove', $user));
565
+
566
+		$this->setupSecurity($user);
567
+
568
+		$this->assign('usernames', $userList);
569
+		$this->assign('activebans', $bans);
570
+
571
+		$banHelper = new BanHelper($this->getDatabase(), $this->getXffTrustProvider(), $this->getSecurityManager());
572
+		$this->assign('banHelper', $banHelper);
573
+	}
574
+
575
+	/**
576
+	 * Converts a plain IP or CIDR mask into an IP and a CIDR suffix
577
+	 *
578
+	 * @param string $targetIp IP or CIDR range
579
+	 *
580
+	 * @return array
581
+	 */
582
+	private function splitCidrRange(string $targetIp): array
583
+	{
584
+		if (strpos($targetIp, '/') !== false) {
585
+			$ipParts = explode('/', $targetIp, 2);
586
+			$targetIp = $ipParts[0];
587
+			$targetMask = (int)$ipParts[1];
588
+		}
589
+		else {
590
+			// Default the CIDR range based on the IP type
591
+			$targetMask = filter_var($targetIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 128 : 32;
592
+		}
593
+
594
+		return array($targetIp, $targetMask);
595 595
 }
596 596
 
597
-    /**
598
-     * Returns the validated ban visibility from WebRequest
599
-     *
600
-     * @throws ApplicationLogicException
601
-     */
602
-    private function getBanVisibility(): string
603
-    {
604
-        $visibility = WebRequest::postString('banVisibility');
605
-        if ($visibility !== 'user' && $visibility !== 'admin' && $visibility !== 'checkuser') {
606
-            throw new ApplicationLogicException('Invalid ban visibility');
607
-        }
608
-
609
-        return $visibility;
610
-    }
611
-
612
-    /**
613
-     * Returns array of [username, ip, email, ua] as ban targets from WebRequest,
614
-     * filtered for whether the user is allowed to set bans including those types.
615
-     *
616
-     * @return string[]
617
-     * @throws ApplicationLogicException
618
-     */
619
-    private function getRawBanTargets(User $user): array
620
-    {
621
-        $targetName = WebRequest::postString('banName');
622
-        $targetIp = WebRequest::postString('banIP');
623
-        $targetEmail = WebRequest::postString('banEmail');
624
-        $targetUseragent = WebRequest::postString('banUseragent');
625
-
626
-        // check the user is allowed to use provided targets
627
-        if (!$this->barrierTest('name', $user, 'BanType')) {
628
-            $targetName = null;
629
-        }
630
-        if (!$this->barrierTest('ip', $user, 'BanType')) {
631
-            $targetIp = null;
632
-        }
633
-        if (!$this->barrierTest('email', $user, 'BanType')) {
634
-            $targetEmail = null;
635
-        }
636
-        if (!$this->barrierTest('useragent', $user, 'BanType')) {
637
-            $targetUseragent = null;
638
-        }
639
-
640
-        // Checks whether there is a target entered to ban.
641
-        if ($targetName === null && $targetIp === null && $targetEmail === null && $targetUseragent === null) {
642
-            throw new ApplicationLogicException('You must specify a target to be banned');
643
-        }
644
-
645
-        return array($targetName, $targetIp, $targetEmail, $targetUseragent);
646
-    }
647
-
648
-    private function preloadFormForRequest(int $banRequest, string $banType, User $user): void
649
-    {
650
-        $database = $this->getDatabase();
651
-
652
-        // Attempt to resolve the correct target
653
-        /** @var Request|false $request */
654
-        $request = Request::getById($banRequest, $database);
655
-        if ($request === false) {
656
-            $this->assign('bantarget', '');
657
-
658
-            return;
659
-        }
660
-
661
-        switch ($banType) {
662
-            case 'EMail':
663
-                if ($this->barrierTest('email', $user, 'BanType')) {
664
-                    $this->assign('banEmail', $request->getEmail());
665
-                }
666
-                break;
667
-            case 'IP':
668
-                if ($this->barrierTest('ip', $user, 'BanType')) {
669
-                    $trustedIp = $this->getXffTrustProvider()->getTrustedClientIp(
670
-                        $request->getIp(),
671
-                        $request->getForwardedIp());
672
-
673
-                    $this->assign('banIP', $trustedIp);
674
-                }
675
-                break;
676
-            case 'Name':
677
-                if ($this->barrierTest('name', $user, 'BanType')) {
678
-                    $this->assign('banName', $request->getName());
679
-                }
680
-                break;
681
-            case 'UA':
682
-                if ($this->barrierTest('useragent', $user, 'BanType')) {
683
-                    $this->assign('banUseragent', $request->getEmail());
684
-                }
685
-                break;
686
-        }
687
-    }
597
+	/**
598
+	 * Returns the validated ban visibility from WebRequest
599
+	 *
600
+	 * @throws ApplicationLogicException
601
+	 */
602
+	private function getBanVisibility(): string
603
+	{
604
+		$visibility = WebRequest::postString('banVisibility');
605
+		if ($visibility !== 'user' && $visibility !== 'admin' && $visibility !== 'checkuser') {
606
+			throw new ApplicationLogicException('Invalid ban visibility');
607
+		}
608
+
609
+		return $visibility;
610
+	}
611
+
612
+	/**
613
+	 * Returns array of [username, ip, email, ua] as ban targets from WebRequest,
614
+	 * filtered for whether the user is allowed to set bans including those types.
615
+	 *
616
+	 * @return string[]
617
+	 * @throws ApplicationLogicException
618
+	 */
619
+	private function getRawBanTargets(User $user): array
620
+	{
621
+		$targetName = WebRequest::postString('banName');
622
+		$targetIp = WebRequest::postString('banIP');
623
+		$targetEmail = WebRequest::postString('banEmail');
624
+		$targetUseragent = WebRequest::postString('banUseragent');
625
+
626
+		// check the user is allowed to use provided targets
627
+		if (!$this->barrierTest('name', $user, 'BanType')) {
628
+			$targetName = null;
629
+		}
630
+		if (!$this->barrierTest('ip', $user, 'BanType')) {
631
+			$targetIp = null;
632
+		}
633
+		if (!$this->barrierTest('email', $user, 'BanType')) {
634
+			$targetEmail = null;
635
+		}
636
+		if (!$this->barrierTest('useragent', $user, 'BanType')) {
637
+			$targetUseragent = null;
638
+		}
639
+
640
+		// Checks whether there is a target entered to ban.
641
+		if ($targetName === null && $targetIp === null && $targetEmail === null && $targetUseragent === null) {
642
+			throw new ApplicationLogicException('You must specify a target to be banned');
643
+		}
644
+
645
+		return array($targetName, $targetIp, $targetEmail, $targetUseragent);
646
+	}
647
+
648
+	private function preloadFormForRequest(int $banRequest, string $banType, User $user): void
649
+	{
650
+		$database = $this->getDatabase();
651
+
652
+		// Attempt to resolve the correct target
653
+		/** @var Request|false $request */
654
+		$request = Request::getById($banRequest, $database);
655
+		if ($request === false) {
656
+			$this->assign('bantarget', '');
657
+
658
+			return;
659
+		}
660
+
661
+		switch ($banType) {
662
+			case 'EMail':
663
+				if ($this->barrierTest('email', $user, 'BanType')) {
664
+					$this->assign('banEmail', $request->getEmail());
665
+				}
666
+				break;
667
+			case 'IP':
668
+				if ($this->barrierTest('ip', $user, 'BanType')) {
669
+					$trustedIp = $this->getXffTrustProvider()->getTrustedClientIp(
670
+						$request->getIp(),
671
+						$request->getForwardedIp());
672
+
673
+					$this->assign('banIP', $trustedIp);
674
+				}
675
+				break;
676
+			case 'Name':
677
+				if ($this->barrierTest('name', $user, 'BanType')) {
678
+					$this->assign('banName', $request->getName());
679
+				}
680
+				break;
681
+			case 'UA':
682
+				if ($this->barrierTest('useragent', $user, 'BanType')) {
683
+					$this->assign('banUseragent', $request->getEmail());
684
+				}
685
+				break;
686
+		}
687
+	}
688 688
 }
Please login to merge, or discard this patch.
includes/Pages/Statistics/StatsUsers.php 1 patch
Indentation   +94 added lines, -94 removed lines patch added patch discarded remove patch
@@ -24,13 +24,13 @@  discard block
 block discarded – undo
24 24
 
25 25
 class StatsUsers extends InternalPageBase
26 26
 {
27
-    public function main()
28
-    {
29
-        $this->setHtmlTitle('Users :: Statistics');
27
+	public function main()
28
+	{
29
+		$this->setHtmlTitle('Users :: Statistics');
30 30
 
31
-        $database = $this->getDatabase();
31
+		$database = $this->getDatabase();
32 32
 
33
-        $query = <<<SQL
33
+		$query = <<<SQL
34 34
 SELECT
35 35
     u.id
36 36
     , u.username
@@ -46,34 +46,34 @@  discard block
 block discarded – undo
46 46
 WHERE u.status = 'Active'
47 47
 SQL;
48 48
 
49
-        $users = $database->query($query)->fetchAll(PDO::FETCH_ASSOC);
50
-        $this->assign('users', $users);
49
+		$users = $database->query($query)->fetchAll(PDO::FETCH_ASSOC);
50
+		$this->assign('users', $users);
51 51
 
52
-        $this->assign('statsPageTitle', 'Account Creation Tool users');
53
-        $this->setTemplate("statistics/users.tpl");
54
-    }
52
+		$this->assign('statsPageTitle', 'Account Creation Tool users');
53
+		$this->setTemplate("statistics/users.tpl");
54
+	}
55 55
 
56
-    /**
57
-     * Entry point for the detail action.
58
-     *
59
-     * @throws ApplicationLogicException
60
-     */
61
-    protected function detail()
62
-    {
63
-        $userId = WebRequest::getInt('user');
64
-        if ($userId === null) {
65
-            throw new ApplicationLogicException("User not found");
66
-        }
56
+	/**
57
+	 * Entry point for the detail action.
58
+	 *
59
+	 * @throws ApplicationLogicException
60
+	 */
61
+	protected function detail()
62
+	{
63
+		$userId = WebRequest::getInt('user');
64
+		if ($userId === null) {
65
+			throw new ApplicationLogicException("User not found");
66
+		}
67 67
 
68
-        $database = $this->getDatabase();
68
+		$database = $this->getDatabase();
69 69
 
70
-        $user = User::getById($userId, $database);
71
-        if ($user == false) {
72
-            throw new ApplicationLogicException('User not found');
73
-        }
70
+		$user = User::getById($userId, $database);
71
+		if ($user == false) {
72
+			throw new ApplicationLogicException('User not found');
73
+		}
74 74
 
75 75
 
76
-        $activitySummary = $database->prepare(<<<SQL
76
+		$activitySummary = $database->prepare(<<<SQL
77 77
 SELECT COALESCE(closes.mail_desc, log.action) AS action, COUNT(*) AS count
78 78
 FROM log
79 79
 INNER JOIN user ON log.user = user.id
@@ -81,14 +81,14 @@  discard block
 block discarded – undo
81 81
 WHERE user.username = :username
82 82
 GROUP BY action;
83 83
 SQL
84
-        );
85
-        $activitySummary->execute(array(":username" => $user->getUsername()));
86
-        $activitySummaryData = $activitySummary->fetchAll(PDO::FETCH_ASSOC);
84
+		);
85
+		$activitySummary->execute(array(":username" => $user->getUsername()));
86
+		$activitySummaryData = $activitySummary->fetchAll(PDO::FETCH_ASSOC);
87 87
 
88
-        $this->assign("user", $user);
89
-        $this->assign("activity", $activitySummaryData);
88
+		$this->assign("user", $user);
89
+		$this->assign("activity", $activitySummaryData);
90 90
 
91
-        $usersCreatedQuery = $database->prepare(<<<SQL
91
+		$usersCreatedQuery = $database->prepare(<<<SQL
92 92
 SELECT log.timestamp time, request.name name, request.id id
93 93
 FROM log
94 94
 INNER JOIN request ON (request.id = log.objectid AND log.objecttype = 'Request')
@@ -99,12 +99,12 @@  discard block
 block discarded – undo
99 99
     AND (emailtemplate.defaultaction = :created OR log.action = 'Closed custom-y')
100 100
 ORDER BY log.timestamp;
101 101
 SQL
102
-        );
103
-        $usersCreatedQuery->execute(array(":username" => $user->getUsername(), ':created' => EmailTemplate::ACTION_CREATED));
104
-        $usersCreated = $usersCreatedQuery->fetchAll(PDO::FETCH_ASSOC);
105
-        $this->assign("created", $usersCreated);
102
+		);
103
+		$usersCreatedQuery->execute(array(":username" => $user->getUsername(), ':created' => EmailTemplate::ACTION_CREATED));
104
+		$usersCreated = $usersCreatedQuery->fetchAll(PDO::FETCH_ASSOC);
105
+		$this->assign("created", $usersCreated);
106 106
 
107
-        $usersNotCreatedQuery = $database->prepare(<<<SQL
107
+		$usersNotCreatedQuery = $database->prepare(<<<SQL
108 108
 SELECT log.timestamp time, request.name name, request.id id
109 109
 FROM log
110 110
 JOIN request ON request.id = log.objectid AND log.objecttype = 'Request'
@@ -115,60 +115,60 @@  discard block
 block discarded – undo
115 115
     AND (emailtemplate.defaultaction = :created OR log.action = 'Closed custom-n' OR log.action = 'Closed 0')
116 116
 ORDER BY log.timestamp;
117 117
 SQL
118
-        );
119
-        $usersNotCreatedQuery->execute(array(":username" => $user->getUsername(), ':created' => EmailTemplate::ACTION_NOT_CREATED));
120
-        $usersNotCreated = $usersNotCreatedQuery->fetchAll(PDO::FETCH_ASSOC);
121
-        $this->assign("notcreated", $usersNotCreated);
122
-
123
-        /** @var Log[] $logs */
124
-        $logs = LogSearchHelper::get($database, Domain::getCurrent($database)->getId())
125
-            ->byObjectType('User')
126
-            ->byObjectId($user->getId())
127
-            ->getRecordCount($logCount)
128
-            ->fetch();
129
-
130
-        if ($logCount === 0) {
131
-            $this->assign('accountlog', array());
132
-        }
133
-        else {
134
-            list($users, $logData) = LogHelper::prepareLogsForTemplate($logs, $database, $this->getSiteConfiguration());
135
-
136
-            $this->assign("accountlog", $logData);
137
-            $this->assign("users", $users);
138
-        }
139
-
140
-        $currentUser = User::getCurrent($database);
141
-        $this->assign('canApprove', $this->barrierTest('approve', $currentUser, PageUserManagement::class));
142
-        $this->assign('canDecline', $this->barrierTest('decline', $currentUser, PageUserManagement::class));
143
-        $this->assign('canRename', $this->barrierTest('rename', $currentUser, PageUserManagement::class));
144
-        $this->assign('canEditUser', $this->barrierTest('editUser', $currentUser, PageUserManagement::class));
145
-        $this->assign('canSuspend', $this->barrierTest('suspend', $currentUser, PageUserManagement::class));
146
-        $this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser, PageUserManagement::class));
147
-
148
-        $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
149
-        $this->assign('oauth', $oauth);
150
-
151
-        if ($user->getForceIdentified() === null) {
152
-            $idVerifier = new IdentificationVerifier($this->getHttpHelper(), $this->getSiteConfiguration(), $this->getDatabase());
153
-            $this->assign('identificationStatus', $idVerifier->isUserIdentified($user->getOnWikiName()) ? 'detected' : 'missing');
154
-        }
155
-        else {
156
-            $this->assign('identificationStatus', $user->getForceIdentified() == 1 ? 'forced-on' : 'forced-off');
157
-        }
158
-
159
-        if ($oauth->isFullyLinked()) {
160
-            $this->assign('identity', $oauth->getIdentity(true));
161
-            $this->assign('identityExpired', $oauth->identityExpired());
162
-        }
163
-
164
-        $this->assign('statsPageTitle', 'Account Creation Tool users');
165
-
166
-        // FIXME: domains!
167
-        /** @var Domain $domain */
168
-        $domain = Domain::getById(1, $this->getDatabase());
169
-        $this->assign('mediawikiScriptPath', $domain->getWikiArticlePath());
170
-
171
-        $this->setHtmlTitle('{$user->getUsername()|escape} :: Users :: Statistics');
172
-        $this->setTemplate("statistics/userdetail.tpl");
173
-    }
118
+		);
119
+		$usersNotCreatedQuery->execute(array(":username" => $user->getUsername(), ':created' => EmailTemplate::ACTION_NOT_CREATED));
120
+		$usersNotCreated = $usersNotCreatedQuery->fetchAll(PDO::FETCH_ASSOC);
121
+		$this->assign("notcreated", $usersNotCreated);
122
+
123
+		/** @var Log[] $logs */
124
+		$logs = LogSearchHelper::get($database, Domain::getCurrent($database)->getId())
125
+			->byObjectType('User')
126
+			->byObjectId($user->getId())
127
+			->getRecordCount($logCount)
128
+			->fetch();
129
+
130
+		if ($logCount === 0) {
131
+			$this->assign('accountlog', array());
132
+		}
133
+		else {
134
+			list($users, $logData) = LogHelper::prepareLogsForTemplate($logs, $database, $this->getSiteConfiguration());
135
+
136
+			$this->assign("accountlog", $logData);
137
+			$this->assign("users", $users);
138
+		}
139
+
140
+		$currentUser = User::getCurrent($database);
141
+		$this->assign('canApprove', $this->barrierTest('approve', $currentUser, PageUserManagement::class));
142
+		$this->assign('canDecline', $this->barrierTest('decline', $currentUser, PageUserManagement::class));
143
+		$this->assign('canRename', $this->barrierTest('rename', $currentUser, PageUserManagement::class));
144
+		$this->assign('canEditUser', $this->barrierTest('editUser', $currentUser, PageUserManagement::class));
145
+		$this->assign('canSuspend', $this->barrierTest('suspend', $currentUser, PageUserManagement::class));
146
+		$this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser, PageUserManagement::class));
147
+
148
+		$oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
149
+		$this->assign('oauth', $oauth);
150
+
151
+		if ($user->getForceIdentified() === null) {
152
+			$idVerifier = new IdentificationVerifier($this->getHttpHelper(), $this->getSiteConfiguration(), $this->getDatabase());
153
+			$this->assign('identificationStatus', $idVerifier->isUserIdentified($user->getOnWikiName()) ? 'detected' : 'missing');
154
+		}
155
+		else {
156
+			$this->assign('identificationStatus', $user->getForceIdentified() == 1 ? 'forced-on' : 'forced-off');
157
+		}
158
+
159
+		if ($oauth->isFullyLinked()) {
160
+			$this->assign('identity', $oauth->getIdentity(true));
161
+			$this->assign('identityExpired', $oauth->identityExpired());
162
+		}
163
+
164
+		$this->assign('statsPageTitle', 'Account Creation Tool users');
165
+
166
+		// FIXME: domains!
167
+		/** @var Domain $domain */
168
+		$domain = Domain::getById(1, $this->getDatabase());
169
+		$this->assign('mediawikiScriptPath', $domain->getWikiArticlePath());
170
+
171
+		$this->setHtmlTitle('{$user->getUsername()|escape} :: Users :: Statistics');
172
+		$this->setTemplate("statistics/userdetail.tpl");
173
+	}
174 174
 }
Please login to merge, or discard this patch.
includes/Pages/PageUserManagement.php 1 patch
Indentation   +634 added lines, -634 removed lines patch added patch discarded remove patch
@@ -29,638 +29,638 @@
 block discarded – undo
29 29
  */
30 30
 class PageUserManagement extends InternalPageBase
31 31
 {
32
-    // FIXME: domains
33
-    /** @var string */
34
-    private $adminMailingList = '[email protected]';
35
-
36
-    /**
37
-     * Main function for this page, when no specific actions are called.
38
-     */
39
-    protected function main()
40
-    {
41
-        $this->setHtmlTitle('User Management');
42
-
43
-        $database = $this->getDatabase();
44
-        $currentUser = User::getCurrent($database);
45
-
46
-        $userSearchRequest = WebRequest::getString('usersearch');
47
-        if ($userSearchRequest !== null) {
48
-            $searchedUser = User::getByUsername($userSearchRequest, $database);
49
-            if ($searchedUser !== false) {
50
-                $this->redirect('statistics/users', 'detail', ['user' => $searchedUser->getId()]);
51
-                return;
52
-            }
53
-        }
54
-
55
-        // A bit hacky, but it's better than my last solution of creating an object for each user and passing that to
56
-        // the template. I still don't have a particularly good way of handling this.
57
-        OAuthUserHelper::prepareTokenCountStatement($database);
58
-
59
-        if (WebRequest::getBoolean("showAll")) {
60
-            $this->assign("showAll", true);
61
-
62
-            $suspendedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_SUSPENDED)->fetch();
63
-            $this->assign("suspendedUsers", $suspendedUsers);
64
-
65
-            $declinedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_DECLINED)->fetch();
66
-            $this->assign("declinedUsers", $declinedUsers);
67
-
68
-            UserSearchHelper::get($database)->getRoleMap($roleMap);
69
-        }
70
-        else {
71
-            $this->assign("showAll", false);
72
-            $this->assign("suspendedUsers", array());
73
-            $this->assign("declinedUsers", array());
74
-
75
-            UserSearchHelper::get($database)->statusIn(array('New', 'Active'))->getRoleMap($roleMap);
76
-        }
77
-
78
-        $newUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_NEW)->fetch();
79
-        $normalUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('user')->fetch();
80
-        $adminUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('admin')->fetch();
81
-        $checkUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('checkuser')->fetch();
82
-        $toolRoots = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('toolRoot')->fetch();
83
-        $this->assign('newUsers', $newUsers);
84
-        $this->assign('normalUsers', $normalUsers);
85
-        $this->assign('adminUsers', $adminUsers);
86
-        $this->assign('checkUsers', $checkUsers);
87
-        $this->assign('toolRoots', $toolRoots);
88
-
89
-        $this->assign('roles', $roleMap);
90
-
91
-        $this->addJs("/api.php?action=users&all=true&targetVariable=typeaheaddata");
92
-
93
-        $this->assign('canApprove', $this->barrierTest('approve', $currentUser));
94
-        $this->assign('canDecline', $this->barrierTest('decline', $currentUser));
95
-        $this->assign('canRename', $this->barrierTest('rename', $currentUser));
96
-        $this->assign('canEditUser', $this->barrierTest('editUser', $currentUser));
97
-        $this->assign('canSuspend', $this->barrierTest('suspend', $currentUser));
98
-        $this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser));
99
-
100
-        // FIXME: domains!
101
-        /** @var Domain $domain */
102
-        $domain = Domain::getById(1, $this->getDatabase());
103
-        $this->assign('mediawikiScriptPath', $domain->getWikiArticlePath());
104
-
105
-        $this->setTemplate("usermanagement/main.tpl");
106
-    }
107
-
108
-    #region Access control
109
-
110
-    /**
111
-     * Action target for editing the roles assigned to a user
112
-     *
113
-     * @throws ApplicationLogicException
114
-     * @throws SmartyException
115
-     * @throws OptimisticLockFailedException
116
-     * @throws Exception
117
-     */
118
-    protected function editRoles(): void
119
-    {
120
-        $this->setHtmlTitle('User Management');
121
-        $database = $this->getDatabase();
122
-        $domain = Domain::getCurrent($database);
123
-        $userId = WebRequest::getInt('user');
124
-
125
-        /** @var User|false $user */
126
-        $user = User::getById($userId, $database);
127
-
128
-        if ($user === false || $user->isCommunityUser()) {
129
-            throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
130
-        }
131
-
132
-        $roleData = $this->getRoleData(UserRole::getForUser($user->getId(), $database, $domain->getId()));
133
-
134
-        // Dual-mode action
135
-        if (WebRequest::wasPosted()) {
136
-            $this->validateCSRFToken();
137
-
138
-            $reason = WebRequest::postString('reason');
139
-            if ($reason === false || trim($reason) === '') {
140
-                throw new ApplicationLogicException('No reason specified for roles change');
141
-            }
142
-
143
-            /** @var UserRole[] $delete */
144
-            $delete = array();
145
-            /** @var string[] $add */
146
-            $add = array();
147
-
148
-            /** @var UserRole[] $globalDelete */
149
-            $globalDelete = array();
150
-            /** @var string[] $globalAdd */
151
-            $globalAdd = array();
152
-
153
-            foreach ($roleData as $name => $r) {
154
-                if ($r['allowEdit'] !== 1) {
155
-                    // not allowed, to touch this, so ignore it
156
-                    continue;
157
-                }
158
-
159
-                $newValue = WebRequest::postBoolean('role-' . $name) ? 1 : 0;
160
-                if ($newValue !== $r['active']) {
161
-                    if ($newValue === 0) {
162
-                        if ($r['globalOnly']) {
163
-                            $globalDelete[] = $r['object'];
164
-                        }
165
-                        else {
166
-                            $delete[] = $r['object'];
167
-                        }
168
-                    }
169
-
170
-                    if ($newValue === 1) {
171
-                        if ($r['globalOnly']) {
172
-                            $globalAdd[] = $name;
173
-                        }
174
-                        else {
175
-                            $add[] = $name;
176
-                        }
177
-                    }
178
-                }
179
-            }
180
-
181
-            // Check there's something to do
182
-            if ((count($add) + count($delete) + count($globalAdd) + count($globalDelete)) === 0) {
183
-                $this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
184
-                SessionAlert::warning('No changes made to roles.');
185
-
186
-                return;
187
-            }
188
-
189
-            $removed = array();
190
-            $globalRemoved = array();
191
-
192
-            foreach ($delete as $d) {
193
-                $removed[] = $d->getRole();
194
-                $d->delete();
195
-            }
196
-
197
-            foreach ($globalDelete as $d) {
198
-                $globalRemoved[] = $d->getRole();
199
-                $d->delete();
200
-            }
201
-
202
-            foreach ($add as $x) {
203
-                $a = new UserRole();
204
-                $a->setUser($user->getId());
205
-                $a->setRole($x);
206
-                $a->setDomain($domain->getId());
207
-                $a->setDatabase($database);
208
-                $a->save();
209
-            }
210
-
211
-            foreach ($globalAdd as $x) {
212
-                $a = new UserRole();
213
-                $a->setUser($user->getId());
214
-                $a->setRole($x);
215
-                $a->setDomain(null);
216
-                $a->setDatabase($database);
217
-                $a->save();
218
-            }
219
-
220
-            if ((count($add) + count($delete)) > 0) {
221
-                Logger::userRolesEdited($database, $user, $reason, $add, $removed, $domain->getId());
222
-            }
223
-
224
-            if ((count($globalAdd) + count($globalDelete)) > 0) {
225
-                Logger::userGlobalRolesEdited($database, $user, $reason, $globalAdd, $globalRemoved);
226
-            }
227
-
228
-            // dummy save for optimistic locking. If this fails, the entire txn will roll back.
229
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
230
-            $user->save();
231
-
232
-            $this->getNotificationHelper()->userRolesEdited($user, $reason);
233
-            SessionAlert::quick('Roles changed for user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
234
-
235
-            $this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
236
-        }
237
-        else {
238
-            $this->assignCSRFToken();
239
-            $this->setTemplate('usermanagement/roleedit.tpl');
240
-            $this->assign('user', $user);
241
-            $this->assign('roleData', $roleData);
242
-        }
243
-    }
244
-
245
-    /**
246
-     * Action target for suspending users
247
-     *
248
-     * @throws ApplicationLogicException
249
-     */
250
-    protected function suspend()
251
-    {
252
-        $this->setHtmlTitle('User Management');
253
-
254
-        $database = $this->getDatabase();
255
-
256
-        $userId = WebRequest::getInt('user');
257
-
258
-        /** @var User $user */
259
-        $user = User::getById($userId, $database);
260
-
261
-        if ($user === false || $user->isCommunityUser()) {
262
-            throw new ApplicationLogicException('Sorry, the user you are trying to suspend could not be found.');
263
-        }
264
-
265
-        if ($user->isSuspended()) {
266
-            throw new ApplicationLogicException('Sorry, the user you are trying to suspend is already suspended.');
267
-        }
268
-
269
-        // Dual-mode action
270
-        if (WebRequest::wasPosted()) {
271
-            $this->validateCSRFToken();
272
-            $reason = WebRequest::postString('reason');
273
-
274
-            if ($reason === null || trim($reason) === "") {
275
-                throw new ApplicationLogicException('No reason provided');
276
-            }
277
-
278
-            $user->setStatus(User::STATUS_SUSPENDED);
279
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
280
-            $user->save();
281
-            Logger::suspendedUser($database, $user, $reason);
282
-
283
-            $this->getNotificationHelper()->userSuspended($user, $reason);
284
-            SessionAlert::quick('Suspended user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
285
-
286
-            // send email
287
-            $this->sendStatusChangeEmail(
288
-                'Your WP:ACC account has been suspended',
289
-                'usermanagement/emails/suspended.tpl',
290
-                $reason,
291
-                $user,
292
-                User::getCurrent($database)->getUsername()
293
-            );
294
-
295
-            $this->redirect('userManagement');
296
-
297
-            return;
298
-        }
299
-        else {
300
-            $this->assignCSRFToken();
301
-            $this->setTemplate('usermanagement/changelevel-reason.tpl');
302
-            $this->assign('user', $user);
303
-            $this->assign('status', 'Suspended');
304
-            $this->assign("showReason", true);
305
-
306
-            if (WebRequest::getString('preload')) {
307
-                $this->assign('preload', WebRequest::getString('preload'));
308
-            }
309
-        }
310
-    }
311
-
312
-    /**
313
-     * Entry point for the decline action
314
-     *
315
-     * @throws ApplicationLogicException
316
-     */
317
-    protected function decline()
318
-    {
319
-        $this->setHtmlTitle('User Management');
320
-
321
-        $database = $this->getDatabase();
322
-
323
-        $userId = WebRequest::getInt('user');
324
-        $user = User::getById($userId, $database);
325
-
326
-        if ($user === false || $user->isCommunityUser()) {
327
-            throw new ApplicationLogicException('Sorry, the user you are trying to decline could not be found.');
328
-        }
329
-
330
-        if (!$user->isNewUser()) {
331
-            throw new ApplicationLogicException('Sorry, the user you are trying to decline is not new.');
332
-        }
333
-
334
-        // Dual-mode action
335
-        if (WebRequest::wasPosted()) {
336
-            $this->validateCSRFToken();
337
-            $reason = WebRequest::postString('reason');
338
-
339
-            if ($reason === null || trim($reason) === "") {
340
-                throw new ApplicationLogicException('No reason provided');
341
-            }
342
-
343
-            $user->setStatus(User::STATUS_DECLINED);
344
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
345
-            $user->save();
346
-            Logger::declinedUser($database, $user, $reason);
347
-
348
-            $this->getNotificationHelper()->userDeclined($user, $reason);
349
-            SessionAlert::quick('Declined user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
350
-
351
-            // send email
352
-            $this->sendStatusChangeEmail(
353
-                'Your WP:ACC account has been declined',
354
-                'usermanagement/emails/declined.tpl',
355
-                $reason,
356
-                $user,
357
-                User::getCurrent($database)->getUsername()
358
-            );
359
-
360
-            $this->redirect('userManagement');
361
-
362
-            return;
363
-        }
364
-        else {
365
-            $this->assignCSRFToken();
366
-            $this->setTemplate('usermanagement/changelevel-reason.tpl');
367
-            $this->assign('user', $user);
368
-            $this->assign('status', 'Declined');
369
-            $this->assign("showReason", true);
370
-        }
371
-    }
372
-
373
-    /**
374
-     * Entry point for the approve action
375
-     *
376
-     * @throws ApplicationLogicException
377
-     */
378
-    protected function approve()
379
-    {
380
-        $this->setHtmlTitle('User Management');
381
-
382
-        $database = $this->getDatabase();
383
-
384
-        $userId = WebRequest::getInt('user');
385
-        $user = User::getById($userId, $database);
386
-
387
-        if ($user === false || $user->isCommunityUser()) {
388
-            throw new ApplicationLogicException('Sorry, the user you are trying to approve could not be found.');
389
-        }
390
-
391
-        if ($user->isActive()) {
392
-            throw new ApplicationLogicException('Sorry, the user you are trying to approve is already an active user.');
393
-        }
394
-
395
-        // Dual-mode action
396
-        if (WebRequest::wasPosted()) {
397
-            $this->validateCSRFToken();
398
-            $user->setStatus(User::STATUS_ACTIVE);
399
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
400
-            $user->save();
401
-            Logger::approvedUser($database, $user);
402
-
403
-            $this->getNotificationHelper()->userApproved($user);
404
-            SessionAlert::quick('Approved user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
405
-
406
-            // send email
407
-            $this->sendStatusChangeEmail(
408
-                'Your WP:ACC account has been approved',
409
-                'usermanagement/emails/approved.tpl',
410
-                null,
411
-                $user,
412
-                User::getCurrent($database)->getUsername()
413
-            );
414
-
415
-            $this->redirect("userManagement");
416
-
417
-            return;
418
-        }
419
-        else {
420
-            $this->assignCSRFToken();
421
-            $this->setTemplate("usermanagement/changelevel-reason.tpl");
422
-            $this->assign("user", $user);
423
-            $this->assign("status", "Active");
424
-            $this->assign("showReason", false);
425
-        }
426
-    }
427
-
428
-    #endregion
429
-
430
-    #region Renaming / Editing
431
-
432
-    /**
433
-     * Entry point for the rename action
434
-     *
435
-     * @throws ApplicationLogicException
436
-     */
437
-    protected function rename()
438
-    {
439
-        $this->setHtmlTitle('User Management');
440
-
441
-        $database = $this->getDatabase();
442
-
443
-        $userId = WebRequest::getInt('user');
444
-        $user = User::getById($userId, $database);
445
-
446
-        if ($user === false || $user->isCommunityUser()) {
447
-            throw new ApplicationLogicException('Sorry, the user you are trying to rename could not be found.');
448
-        }
449
-
450
-        // Dual-mode action
451
-        if (WebRequest::wasPosted()) {
452
-            $this->validateCSRFToken();
453
-            $newUsername = WebRequest::postString('newname');
454
-
455
-            if ($newUsername === null || trim($newUsername) === "") {
456
-                throw new ApplicationLogicException('The new username cannot be empty');
457
-            }
458
-
459
-            if (User::getByUsername($newUsername, $database) != false) {
460
-                throw new ApplicationLogicException('The new username already exists');
461
-            }
462
-
463
-            $oldUsername = $user->getUsername();
464
-            $user->setUsername($newUsername);
465
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
466
-
467
-            $user->save();
468
-
469
-            $logEntryData = serialize(array(
470
-                'old' => $oldUsername,
471
-                'new' => $newUsername,
472
-            ));
473
-
474
-            Logger::renamedUser($database, $user, $logEntryData);
475
-
476
-            SessionAlert::quick("Changed User "
477
-                . htmlentities($oldUsername, ENT_COMPAT, 'UTF-8')
478
-                . " name to "
479
-                . htmlentities($newUsername, ENT_COMPAT, 'UTF-8'));
480
-
481
-            $this->getNotificationHelper()->userRenamed($user, $oldUsername);
482
-
483
-            // send an email to the user.
484
-            $this->assign('targetUsername', $user->getUsername());
485
-            $this->assign('toolAdmin', User::getCurrent($database)->getUsername());
486
-            $this->assign('oldUsername', $oldUsername);
487
-            $this->assign('mailingList', $this->adminMailingList);
488
-
489
-            // FIXME: domains!
490
-            /** @var Domain $domain */
491
-            $domain = Domain::getById(1, $database);
492
-            $this->getEmailHelper()->sendMail(
493
-                $this->adminMailingList,
494
-                $user->getEmail(),
495
-                'Your username on WP:ACC has been changed',
496
-                $this->fetchTemplate('usermanagement/emails/renamed.tpl')
497
-            );
498
-
499
-            $this->redirect("userManagement");
500
-
501
-            return;
502
-        }
503
-        else {
504
-            $this->assignCSRFToken();
505
-            $this->setTemplate('usermanagement/renameuser.tpl');
506
-            $this->assign('user', $user);
507
-        }
508
-    }
509
-
510
-    /**
511
-     * Entry point for the edit action
512
-     *
513
-     * @throws ApplicationLogicException
514
-     */
515
-    protected function editUser()
516
-    {
517
-        $this->setHtmlTitle('User Management');
518
-
519
-        $database = $this->getDatabase();
520
-
521
-        $userId = WebRequest::getInt('user');
522
-        $user = User::getById($userId, $database);
523
-        $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
524
-
525
-        if ($user === false || $user->isCommunityUser()) {
526
-            throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
527
-        }
528
-
529
-        // FIXME: domains
530
-        $prefs = new PreferenceManager($database, $user->getId(), 1);
531
-
532
-        // Dual-mode action
533
-        if (WebRequest::wasPosted()) {
534
-            $this->validateCSRFToken();
535
-            $newEmail = WebRequest::postEmail('user_email');
536
-            $newOnWikiName = WebRequest::postString('user_onwikiname');
537
-
538
-            if ($newEmail === null) {
539
-                throw new ApplicationLogicException('Invalid email address');
540
-            }
541
-
542
-            if ($this->validateUnusedEmail($newEmail, $userId)) {
543
-                throw new ApplicationLogicException('The specified email address is already in use.');
544
-            }
545
-
546
-            if (!($oauth->isFullyLinked() || $oauth->isPartiallyLinked())) {
547
-                if (trim($newOnWikiName) == "") {
548
-                    throw new ApplicationLogicException('New on-wiki username cannot be blank');
549
-                }
550
-
551
-                $user->setOnWikiName($newOnWikiName);
552
-            }
553
-
554
-            $user->setEmail($newEmail);
555
-
556
-            $prefs->setLocalPreference(PreferenceManager::PREF_CREATION_MODE, WebRequest::postInt('creationmode'));
557
-
558
-            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
559
-
560
-            $user->save();
561
-
562
-            Logger::userPreferencesChange($database, $user);
563
-            $this->getNotificationHelper()->userPrefChange($user);
564
-            SessionAlert::quick('Changes to user\'s preferences have been saved');
565
-
566
-            $this->redirect("userManagement");
567
-
568
-            return;
569
-        }
570
-        else {
571
-            $this->assignCSRFToken();
572
-            $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(),
573
-                $this->getSiteConfiguration());
574
-            $this->setTemplate('usermanagement/edituser.tpl');
575
-            $this->assign('user', $user);
576
-            $this->assign('oauth', $oauth);
577
-
578
-            $this->assign('preferredCreationMode', (int)$prefs->getPreference(PreferenceManager::PREF_CREATION_MODE));
579
-            $this->assign('emailSignature', $prefs->getPreference(PreferenceManager::PREF_EMAIL_SIGNATURE));
580
-
581
-            $this->assign('canManualCreate',
582
-                $this->barrierTest(PreferenceManager::CREATION_MANUAL, $user, 'RequestCreation'));
583
-            $this->assign('canOauthCreate',
584
-                $this->barrierTest(PreferenceManager::CREATION_OAUTH, $user, 'RequestCreation'));
585
-            $this->assign('canBotCreate',
586
-                $this->barrierTest(PreferenceManager::CREATION_BOT, $user, 'RequestCreation'));
587
-        }
588
-    }
589
-
590
-    #endregion
591
-
592
-    private function validateUnusedEmail(string $email, int $userId) : bool {
593
-        $query = 'SELECT COUNT(id) FROM user WHERE email = :email AND id <> :uid';
594
-        $statement = $this->getDatabase()->prepare($query);
595
-        $statement->execute(array(':email' => $email, ':uid' => $userId));
596
-        $inUse = $statement->fetchColumn() > 0;
597
-        $statement->closeCursor();
598
-
599
-        return $inUse;
600
-    }
601
-
602
-    /**
603
-     * Sends a status change email to the user.
604
-     *
605
-     * @param string      $subject           The subject of the email
606
-     * @param string      $template          The smarty template to use
607
-     * @param string|null $reason            The reason for performing the status change
608
-     * @param User        $user              The user affected
609
-     * @param string      $toolAdminUsername The tool admin's username who is making the edit
610
-     */
611
-    private function sendStatusChangeEmail($subject, $template, $reason, $user, $toolAdminUsername)
612
-    {
613
-        $this->assign('targetUsername', $user->getUsername());
614
-        $this->assign('toolAdmin', $toolAdminUsername);
615
-        $this->assign('actionReason', $reason);
616
-        $this->assign('mailingList', $this->adminMailingList);
617
-
618
-        // FIXME: domains!
619
-        /** @var Domain $domain */
620
-        $domain = Domain::getById(1, $this->getDatabase());
621
-        $this->getEmailHelper()->sendMail(
622
-            $this->adminMailingList,
623
-            $user->getEmail(),
624
-            $subject,
625
-            $this->fetchTemplate($template)
626
-        );
627
-    }
628
-
629
-    /**
630
-     * @param UserRole[] $activeRoles
631
-     *
632
-     * @return array
633
-     */
634
-    private function getRoleData($activeRoles)
635
-    {
636
-        $availableRoles = $this->getSecurityManager()->getRoleConfiguration()->getAvailableRoles();
637
-
638
-        $currentUser = User::getCurrent($this->getDatabase());
639
-        $this->getSecurityManager()->getActiveRoles($currentUser, $userRoles, $inactiveRoles);
640
-
641
-        $initialValue = array('active' => 0, 'allowEdit' => 0, 'description' => '???', 'object' => null);
642
-
643
-        $roleData = array();
644
-        foreach ($availableRoles as $role => $data) {
645
-            $intersection = array_intersect($data['editableBy'], $userRoles);
646
-
647
-            $roleData[$role] = $initialValue;
648
-            $roleData[$role]['allowEdit'] = count($intersection) > 0 ? 1 : 0;
649
-            $roleData[$role]['description'] = $data['description'];
650
-            $roleData[$role]['globalOnly'] = $data['globalOnly'];
651
-        }
652
-
653
-        foreach ($activeRoles as $role) {
654
-            if (!isset($roleData[$role->getRole()])) {
655
-                // This value is no longer available in the configuration, allow changing (aka removing) it.
656
-                $roleData[$role->getRole()] = $initialValue;
657
-                $roleData[$role->getRole()]['allowEdit'] = 1;
658
-            }
659
-
660
-            $roleData[$role->getRole()]['object'] = $role;
661
-            $roleData[$role->getRole()]['active'] = 1;
662
-        }
663
-
664
-        return $roleData;
665
-    }
32
+	// FIXME: domains
33
+	/** @var string */
34
+	private $adminMailingList = '[email protected]';
35
+
36
+	/**
37
+	 * Main function for this page, when no specific actions are called.
38
+	 */
39
+	protected function main()
40
+	{
41
+		$this->setHtmlTitle('User Management');
42
+
43
+		$database = $this->getDatabase();
44
+		$currentUser = User::getCurrent($database);
45
+
46
+		$userSearchRequest = WebRequest::getString('usersearch');
47
+		if ($userSearchRequest !== null) {
48
+			$searchedUser = User::getByUsername($userSearchRequest, $database);
49
+			if ($searchedUser !== false) {
50
+				$this->redirect('statistics/users', 'detail', ['user' => $searchedUser->getId()]);
51
+				return;
52
+			}
53
+		}
54
+
55
+		// A bit hacky, but it's better than my last solution of creating an object for each user and passing that to
56
+		// the template. I still don't have a particularly good way of handling this.
57
+		OAuthUserHelper::prepareTokenCountStatement($database);
58
+
59
+		if (WebRequest::getBoolean("showAll")) {
60
+			$this->assign("showAll", true);
61
+
62
+			$suspendedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_SUSPENDED)->fetch();
63
+			$this->assign("suspendedUsers", $suspendedUsers);
64
+
65
+			$declinedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_DECLINED)->fetch();
66
+			$this->assign("declinedUsers", $declinedUsers);
67
+
68
+			UserSearchHelper::get($database)->getRoleMap($roleMap);
69
+		}
70
+		else {
71
+			$this->assign("showAll", false);
72
+			$this->assign("suspendedUsers", array());
73
+			$this->assign("declinedUsers", array());
74
+
75
+			UserSearchHelper::get($database)->statusIn(array('New', 'Active'))->getRoleMap($roleMap);
76
+		}
77
+
78
+		$newUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_NEW)->fetch();
79
+		$normalUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('user')->fetch();
80
+		$adminUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('admin')->fetch();
81
+		$checkUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('checkuser')->fetch();
82
+		$toolRoots = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('toolRoot')->fetch();
83
+		$this->assign('newUsers', $newUsers);
84
+		$this->assign('normalUsers', $normalUsers);
85
+		$this->assign('adminUsers', $adminUsers);
86
+		$this->assign('checkUsers', $checkUsers);
87
+		$this->assign('toolRoots', $toolRoots);
88
+
89
+		$this->assign('roles', $roleMap);
90
+
91
+		$this->addJs("/api.php?action=users&all=true&targetVariable=typeaheaddata");
92
+
93
+		$this->assign('canApprove', $this->barrierTest('approve', $currentUser));
94
+		$this->assign('canDecline', $this->barrierTest('decline', $currentUser));
95
+		$this->assign('canRename', $this->barrierTest('rename', $currentUser));
96
+		$this->assign('canEditUser', $this->barrierTest('editUser', $currentUser));
97
+		$this->assign('canSuspend', $this->barrierTest('suspend', $currentUser));
98
+		$this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser));
99
+
100
+		// FIXME: domains!
101
+		/** @var Domain $domain */
102
+		$domain = Domain::getById(1, $this->getDatabase());
103
+		$this->assign('mediawikiScriptPath', $domain->getWikiArticlePath());
104
+
105
+		$this->setTemplate("usermanagement/main.tpl");
106
+	}
107
+
108
+	#region Access control
109
+
110
+	/**
111
+	 * Action target for editing the roles assigned to a user
112
+	 *
113
+	 * @throws ApplicationLogicException
114
+	 * @throws SmartyException
115
+	 * @throws OptimisticLockFailedException
116
+	 * @throws Exception
117
+	 */
118
+	protected function editRoles(): void
119
+	{
120
+		$this->setHtmlTitle('User Management');
121
+		$database = $this->getDatabase();
122
+		$domain = Domain::getCurrent($database);
123
+		$userId = WebRequest::getInt('user');
124
+
125
+		/** @var User|false $user */
126
+		$user = User::getById($userId, $database);
127
+
128
+		if ($user === false || $user->isCommunityUser()) {
129
+			throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
130
+		}
131
+
132
+		$roleData = $this->getRoleData(UserRole::getForUser($user->getId(), $database, $domain->getId()));
133
+
134
+		// Dual-mode action
135
+		if (WebRequest::wasPosted()) {
136
+			$this->validateCSRFToken();
137
+
138
+			$reason = WebRequest::postString('reason');
139
+			if ($reason === false || trim($reason) === '') {
140
+				throw new ApplicationLogicException('No reason specified for roles change');
141
+			}
142
+
143
+			/** @var UserRole[] $delete */
144
+			$delete = array();
145
+			/** @var string[] $add */
146
+			$add = array();
147
+
148
+			/** @var UserRole[] $globalDelete */
149
+			$globalDelete = array();
150
+			/** @var string[] $globalAdd */
151
+			$globalAdd = array();
152
+
153
+			foreach ($roleData as $name => $r) {
154
+				if ($r['allowEdit'] !== 1) {
155
+					// not allowed, to touch this, so ignore it
156
+					continue;
157
+				}
158
+
159
+				$newValue = WebRequest::postBoolean('role-' . $name) ? 1 : 0;
160
+				if ($newValue !== $r['active']) {
161
+					if ($newValue === 0) {
162
+						if ($r['globalOnly']) {
163
+							$globalDelete[] = $r['object'];
164
+						}
165
+						else {
166
+							$delete[] = $r['object'];
167
+						}
168
+					}
169
+
170
+					if ($newValue === 1) {
171
+						if ($r['globalOnly']) {
172
+							$globalAdd[] = $name;
173
+						}
174
+						else {
175
+							$add[] = $name;
176
+						}
177
+					}
178
+				}
179
+			}
180
+
181
+			// Check there's something to do
182
+			if ((count($add) + count($delete) + count($globalAdd) + count($globalDelete)) === 0) {
183
+				$this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
184
+				SessionAlert::warning('No changes made to roles.');
185
+
186
+				return;
187
+			}
188
+
189
+			$removed = array();
190
+			$globalRemoved = array();
191
+
192
+			foreach ($delete as $d) {
193
+				$removed[] = $d->getRole();
194
+				$d->delete();
195
+			}
196
+
197
+			foreach ($globalDelete as $d) {
198
+				$globalRemoved[] = $d->getRole();
199
+				$d->delete();
200
+			}
201
+
202
+			foreach ($add as $x) {
203
+				$a = new UserRole();
204
+				$a->setUser($user->getId());
205
+				$a->setRole($x);
206
+				$a->setDomain($domain->getId());
207
+				$a->setDatabase($database);
208
+				$a->save();
209
+			}
210
+
211
+			foreach ($globalAdd as $x) {
212
+				$a = new UserRole();
213
+				$a->setUser($user->getId());
214
+				$a->setRole($x);
215
+				$a->setDomain(null);
216
+				$a->setDatabase($database);
217
+				$a->save();
218
+			}
219
+
220
+			if ((count($add) + count($delete)) > 0) {
221
+				Logger::userRolesEdited($database, $user, $reason, $add, $removed, $domain->getId());
222
+			}
223
+
224
+			if ((count($globalAdd) + count($globalDelete)) > 0) {
225
+				Logger::userGlobalRolesEdited($database, $user, $reason, $globalAdd, $globalRemoved);
226
+			}
227
+
228
+			// dummy save for optimistic locking. If this fails, the entire txn will roll back.
229
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
230
+			$user->save();
231
+
232
+			$this->getNotificationHelper()->userRolesEdited($user, $reason);
233
+			SessionAlert::quick('Roles changed for user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
234
+
235
+			$this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
236
+		}
237
+		else {
238
+			$this->assignCSRFToken();
239
+			$this->setTemplate('usermanagement/roleedit.tpl');
240
+			$this->assign('user', $user);
241
+			$this->assign('roleData', $roleData);
242
+		}
243
+	}
244
+
245
+	/**
246
+	 * Action target for suspending users
247
+	 *
248
+	 * @throws ApplicationLogicException
249
+	 */
250
+	protected function suspend()
251
+	{
252
+		$this->setHtmlTitle('User Management');
253
+
254
+		$database = $this->getDatabase();
255
+
256
+		$userId = WebRequest::getInt('user');
257
+
258
+		/** @var User $user */
259
+		$user = User::getById($userId, $database);
260
+
261
+		if ($user === false || $user->isCommunityUser()) {
262
+			throw new ApplicationLogicException('Sorry, the user you are trying to suspend could not be found.');
263
+		}
264
+
265
+		if ($user->isSuspended()) {
266
+			throw new ApplicationLogicException('Sorry, the user you are trying to suspend is already suspended.');
267
+		}
268
+
269
+		// Dual-mode action
270
+		if (WebRequest::wasPosted()) {
271
+			$this->validateCSRFToken();
272
+			$reason = WebRequest::postString('reason');
273
+
274
+			if ($reason === null || trim($reason) === "") {
275
+				throw new ApplicationLogicException('No reason provided');
276
+			}
277
+
278
+			$user->setStatus(User::STATUS_SUSPENDED);
279
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
280
+			$user->save();
281
+			Logger::suspendedUser($database, $user, $reason);
282
+
283
+			$this->getNotificationHelper()->userSuspended($user, $reason);
284
+			SessionAlert::quick('Suspended user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
285
+
286
+			// send email
287
+			$this->sendStatusChangeEmail(
288
+				'Your WP:ACC account has been suspended',
289
+				'usermanagement/emails/suspended.tpl',
290
+				$reason,
291
+				$user,
292
+				User::getCurrent($database)->getUsername()
293
+			);
294
+
295
+			$this->redirect('userManagement');
296
+
297
+			return;
298
+		}
299
+		else {
300
+			$this->assignCSRFToken();
301
+			$this->setTemplate('usermanagement/changelevel-reason.tpl');
302
+			$this->assign('user', $user);
303
+			$this->assign('status', 'Suspended');
304
+			$this->assign("showReason", true);
305
+
306
+			if (WebRequest::getString('preload')) {
307
+				$this->assign('preload', WebRequest::getString('preload'));
308
+			}
309
+		}
310
+	}
311
+
312
+	/**
313
+	 * Entry point for the decline action
314
+	 *
315
+	 * @throws ApplicationLogicException
316
+	 */
317
+	protected function decline()
318
+	{
319
+		$this->setHtmlTitle('User Management');
320
+
321
+		$database = $this->getDatabase();
322
+
323
+		$userId = WebRequest::getInt('user');
324
+		$user = User::getById($userId, $database);
325
+
326
+		if ($user === false || $user->isCommunityUser()) {
327
+			throw new ApplicationLogicException('Sorry, the user you are trying to decline could not be found.');
328
+		}
329
+
330
+		if (!$user->isNewUser()) {
331
+			throw new ApplicationLogicException('Sorry, the user you are trying to decline is not new.');
332
+		}
333
+
334
+		// Dual-mode action
335
+		if (WebRequest::wasPosted()) {
336
+			$this->validateCSRFToken();
337
+			$reason = WebRequest::postString('reason');
338
+
339
+			if ($reason === null || trim($reason) === "") {
340
+				throw new ApplicationLogicException('No reason provided');
341
+			}
342
+
343
+			$user->setStatus(User::STATUS_DECLINED);
344
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
345
+			$user->save();
346
+			Logger::declinedUser($database, $user, $reason);
347
+
348
+			$this->getNotificationHelper()->userDeclined($user, $reason);
349
+			SessionAlert::quick('Declined user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
350
+
351
+			// send email
352
+			$this->sendStatusChangeEmail(
353
+				'Your WP:ACC account has been declined',
354
+				'usermanagement/emails/declined.tpl',
355
+				$reason,
356
+				$user,
357
+				User::getCurrent($database)->getUsername()
358
+			);
359
+
360
+			$this->redirect('userManagement');
361
+
362
+			return;
363
+		}
364
+		else {
365
+			$this->assignCSRFToken();
366
+			$this->setTemplate('usermanagement/changelevel-reason.tpl');
367
+			$this->assign('user', $user);
368
+			$this->assign('status', 'Declined');
369
+			$this->assign("showReason", true);
370
+		}
371
+	}
372
+
373
+	/**
374
+	 * Entry point for the approve action
375
+	 *
376
+	 * @throws ApplicationLogicException
377
+	 */
378
+	protected function approve()
379
+	{
380
+		$this->setHtmlTitle('User Management');
381
+
382
+		$database = $this->getDatabase();
383
+
384
+		$userId = WebRequest::getInt('user');
385
+		$user = User::getById($userId, $database);
386
+
387
+		if ($user === false || $user->isCommunityUser()) {
388
+			throw new ApplicationLogicException('Sorry, the user you are trying to approve could not be found.');
389
+		}
390
+
391
+		if ($user->isActive()) {
392
+			throw new ApplicationLogicException('Sorry, the user you are trying to approve is already an active user.');
393
+		}
394
+
395
+		// Dual-mode action
396
+		if (WebRequest::wasPosted()) {
397
+			$this->validateCSRFToken();
398
+			$user->setStatus(User::STATUS_ACTIVE);
399
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
400
+			$user->save();
401
+			Logger::approvedUser($database, $user);
402
+
403
+			$this->getNotificationHelper()->userApproved($user);
404
+			SessionAlert::quick('Approved user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
405
+
406
+			// send email
407
+			$this->sendStatusChangeEmail(
408
+				'Your WP:ACC account has been approved',
409
+				'usermanagement/emails/approved.tpl',
410
+				null,
411
+				$user,
412
+				User::getCurrent($database)->getUsername()
413
+			);
414
+
415
+			$this->redirect("userManagement");
416
+
417
+			return;
418
+		}
419
+		else {
420
+			$this->assignCSRFToken();
421
+			$this->setTemplate("usermanagement/changelevel-reason.tpl");
422
+			$this->assign("user", $user);
423
+			$this->assign("status", "Active");
424
+			$this->assign("showReason", false);
425
+		}
426
+	}
427
+
428
+	#endregion
429
+
430
+	#region Renaming / Editing
431
+
432
+	/**
433
+	 * Entry point for the rename action
434
+	 *
435
+	 * @throws ApplicationLogicException
436
+	 */
437
+	protected function rename()
438
+	{
439
+		$this->setHtmlTitle('User Management');
440
+
441
+		$database = $this->getDatabase();
442
+
443
+		$userId = WebRequest::getInt('user');
444
+		$user = User::getById($userId, $database);
445
+
446
+		if ($user === false || $user->isCommunityUser()) {
447
+			throw new ApplicationLogicException('Sorry, the user you are trying to rename could not be found.');
448
+		}
449
+
450
+		// Dual-mode action
451
+		if (WebRequest::wasPosted()) {
452
+			$this->validateCSRFToken();
453
+			$newUsername = WebRequest::postString('newname');
454
+
455
+			if ($newUsername === null || trim($newUsername) === "") {
456
+				throw new ApplicationLogicException('The new username cannot be empty');
457
+			}
458
+
459
+			if (User::getByUsername($newUsername, $database) != false) {
460
+				throw new ApplicationLogicException('The new username already exists');
461
+			}
462
+
463
+			$oldUsername = $user->getUsername();
464
+			$user->setUsername($newUsername);
465
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
466
+
467
+			$user->save();
468
+
469
+			$logEntryData = serialize(array(
470
+				'old' => $oldUsername,
471
+				'new' => $newUsername,
472
+			));
473
+
474
+			Logger::renamedUser($database, $user, $logEntryData);
475
+
476
+			SessionAlert::quick("Changed User "
477
+				. htmlentities($oldUsername, ENT_COMPAT, 'UTF-8')
478
+				. " name to "
479
+				. htmlentities($newUsername, ENT_COMPAT, 'UTF-8'));
480
+
481
+			$this->getNotificationHelper()->userRenamed($user, $oldUsername);
482
+
483
+			// send an email to the user.
484
+			$this->assign('targetUsername', $user->getUsername());
485
+			$this->assign('toolAdmin', User::getCurrent($database)->getUsername());
486
+			$this->assign('oldUsername', $oldUsername);
487
+			$this->assign('mailingList', $this->adminMailingList);
488
+
489
+			// FIXME: domains!
490
+			/** @var Domain $domain */
491
+			$domain = Domain::getById(1, $database);
492
+			$this->getEmailHelper()->sendMail(
493
+				$this->adminMailingList,
494
+				$user->getEmail(),
495
+				'Your username on WP:ACC has been changed',
496
+				$this->fetchTemplate('usermanagement/emails/renamed.tpl')
497
+			);
498
+
499
+			$this->redirect("userManagement");
500
+
501
+			return;
502
+		}
503
+		else {
504
+			$this->assignCSRFToken();
505
+			$this->setTemplate('usermanagement/renameuser.tpl');
506
+			$this->assign('user', $user);
507
+		}
508
+	}
509
+
510
+	/**
511
+	 * Entry point for the edit action
512
+	 *
513
+	 * @throws ApplicationLogicException
514
+	 */
515
+	protected function editUser()
516
+	{
517
+		$this->setHtmlTitle('User Management');
518
+
519
+		$database = $this->getDatabase();
520
+
521
+		$userId = WebRequest::getInt('user');
522
+		$user = User::getById($userId, $database);
523
+		$oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
524
+
525
+		if ($user === false || $user->isCommunityUser()) {
526
+			throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
527
+		}
528
+
529
+		// FIXME: domains
530
+		$prefs = new PreferenceManager($database, $user->getId(), 1);
531
+
532
+		// Dual-mode action
533
+		if (WebRequest::wasPosted()) {
534
+			$this->validateCSRFToken();
535
+			$newEmail = WebRequest::postEmail('user_email');
536
+			$newOnWikiName = WebRequest::postString('user_onwikiname');
537
+
538
+			if ($newEmail === null) {
539
+				throw new ApplicationLogicException('Invalid email address');
540
+			}
541
+
542
+			if ($this->validateUnusedEmail($newEmail, $userId)) {
543
+				throw new ApplicationLogicException('The specified email address is already in use.');
544
+			}
545
+
546
+			if (!($oauth->isFullyLinked() || $oauth->isPartiallyLinked())) {
547
+				if (trim($newOnWikiName) == "") {
548
+					throw new ApplicationLogicException('New on-wiki username cannot be blank');
549
+				}
550
+
551
+				$user->setOnWikiName($newOnWikiName);
552
+			}
553
+
554
+			$user->setEmail($newEmail);
555
+
556
+			$prefs->setLocalPreference(PreferenceManager::PREF_CREATION_MODE, WebRequest::postInt('creationmode'));
557
+
558
+			$user->setUpdateVersion(WebRequest::postInt('updateversion'));
559
+
560
+			$user->save();
561
+
562
+			Logger::userPreferencesChange($database, $user);
563
+			$this->getNotificationHelper()->userPrefChange($user);
564
+			SessionAlert::quick('Changes to user\'s preferences have been saved');
565
+
566
+			$this->redirect("userManagement");
567
+
568
+			return;
569
+		}
570
+		else {
571
+			$this->assignCSRFToken();
572
+			$oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(),
573
+				$this->getSiteConfiguration());
574
+			$this->setTemplate('usermanagement/edituser.tpl');
575
+			$this->assign('user', $user);
576
+			$this->assign('oauth', $oauth);
577
+
578
+			$this->assign('preferredCreationMode', (int)$prefs->getPreference(PreferenceManager::PREF_CREATION_MODE));
579
+			$this->assign('emailSignature', $prefs->getPreference(PreferenceManager::PREF_EMAIL_SIGNATURE));
580
+
581
+			$this->assign('canManualCreate',
582
+				$this->barrierTest(PreferenceManager::CREATION_MANUAL, $user, 'RequestCreation'));
583
+			$this->assign('canOauthCreate',
584
+				$this->barrierTest(PreferenceManager::CREATION_OAUTH, $user, 'RequestCreation'));
585
+			$this->assign('canBotCreate',
586
+				$this->barrierTest(PreferenceManager::CREATION_BOT, $user, 'RequestCreation'));
587
+		}
588
+	}
589
+
590
+	#endregion
591
+
592
+	private function validateUnusedEmail(string $email, int $userId) : bool {
593
+		$query = 'SELECT COUNT(id) FROM user WHERE email = :email AND id <> :uid';
594
+		$statement = $this->getDatabase()->prepare($query);
595
+		$statement->execute(array(':email' => $email, ':uid' => $userId));
596
+		$inUse = $statement->fetchColumn() > 0;
597
+		$statement->closeCursor();
598
+
599
+		return $inUse;
600
+	}
601
+
602
+	/**
603
+	 * Sends a status change email to the user.
604
+	 *
605
+	 * @param string      $subject           The subject of the email
606
+	 * @param string      $template          The smarty template to use
607
+	 * @param string|null $reason            The reason for performing the status change
608
+	 * @param User        $user              The user affected
609
+	 * @param string      $toolAdminUsername The tool admin's username who is making the edit
610
+	 */
611
+	private function sendStatusChangeEmail($subject, $template, $reason, $user, $toolAdminUsername)
612
+	{
613
+		$this->assign('targetUsername', $user->getUsername());
614
+		$this->assign('toolAdmin', $toolAdminUsername);
615
+		$this->assign('actionReason', $reason);
616
+		$this->assign('mailingList', $this->adminMailingList);
617
+
618
+		// FIXME: domains!
619
+		/** @var Domain $domain */
620
+		$domain = Domain::getById(1, $this->getDatabase());
621
+		$this->getEmailHelper()->sendMail(
622
+			$this->adminMailingList,
623
+			$user->getEmail(),
624
+			$subject,
625
+			$this->fetchTemplate($template)
626
+		);
627
+	}
628
+
629
+	/**
630
+	 * @param UserRole[] $activeRoles
631
+	 *
632
+	 * @return array
633
+	 */
634
+	private function getRoleData($activeRoles)
635
+	{
636
+		$availableRoles = $this->getSecurityManager()->getRoleConfiguration()->getAvailableRoles();
637
+
638
+		$currentUser = User::getCurrent($this->getDatabase());
639
+		$this->getSecurityManager()->getActiveRoles($currentUser, $userRoles, $inactiveRoles);
640
+
641
+		$initialValue = array('active' => 0, 'allowEdit' => 0, 'description' => '???', 'object' => null);
642
+
643
+		$roleData = array();
644
+		foreach ($availableRoles as $role => $data) {
645
+			$intersection = array_intersect($data['editableBy'], $userRoles);
646
+
647
+			$roleData[$role] = $initialValue;
648
+			$roleData[$role]['allowEdit'] = count($intersection) > 0 ? 1 : 0;
649
+			$roleData[$role]['description'] = $data['description'];
650
+			$roleData[$role]['globalOnly'] = $data['globalOnly'];
651
+		}
652
+
653
+		foreach ($activeRoles as $role) {
654
+			if (!isset($roleData[$role->getRole()])) {
655
+				// This value is no longer available in the configuration, allow changing (aka removing) it.
656
+				$roleData[$role->getRole()] = $initialValue;
657
+				$roleData[$role->getRole()]['allowEdit'] = 1;
658
+			}
659
+
660
+			$roleData[$role->getRole()]['object'] = $role;
661
+			$roleData[$role->getRole()]['active'] = 1;
662
+		}
663
+
664
+		return $roleData;
665
+	}
666 666
 }
Please login to merge, or discard this patch.
includes/Router/RequestRouter.php 1 patch
Indentation   +474 added lines, -474 removed lines patch added patch discarded remove patch
@@ -70,478 +70,478 @@
 block discarded – undo
70 70
  */
71 71
 class RequestRouter implements IRequestRouter
72 72
 {
73
-    /**
74
-     * This is the core routing table for the application. The basic idea is:
75
-     *
76
-     *      array(
77
-     *          "foo" =>
78
-     *              array(
79
-     *                  "class"   => PageFoo::class,
80
-     *                  "actions" => array("bar", "other")
81
-     *              ),
82
-     * );
83
-     *
84
-     * Things to note:
85
-     *     - If no page is requested, we go to PageMain. PageMain can't have actions defined.
86
-     *
87
-     *     - If a page is defined and requested, but no action is requested, go to that page's main() method
88
-     *     - If a page is defined and requested, and an action is defined and requested, go to that action's method.
89
-     *     - If a page is defined and requested, and an action NOT defined and requested, go to Page404 and it's main()
90
-     *       method.
91
-     *     - If a page is NOT defined and requested, go to Page404 and it's main() method.
92
-     *
93
-     *     - Query parameters are ignored.
94
-     *
95
-     * The key point here is request routing with validation that this is allowed, before we start hitting the
96
-     * filesystem through the AutoLoader, and opening random files. Also, so that we validate the action requested
97
-     * before we start calling random methods through the web UI.
98
-     *
99
-     * Examples:
100
-     * /internal.php                => returns instance of PageMain, routed to main()
101
-     * /internal.php?query          => returns instance of PageMain, routed to main()
102
-     * /internal.php/foo            => returns instance of PageFoo, routed to main()
103
-     * /internal.php/foo?query      => returns instance of PageFoo, routed to main()
104
-     * /internal.php/foo/bar        => returns instance of PageFoo, routed to bar()
105
-     * /internal.php/foo/bar?query  => returns instance of PageFoo, routed to bar()
106
-     * /internal.php/foo/baz        => returns instance of Page404, routed to main()
107
-     * /internal.php/foo/baz?query  => returns instance of Page404, routed to main()
108
-     * /internal.php/bar            => returns instance of Page404, routed to main()
109
-     * /internal.php/bar?query      => returns instance of Page404, routed to main()
110
-     * /internal.php/bar/baz        => returns instance of Page404, routed to main()
111
-     * /internal.php/bar/baz?query  => returns instance of Page404, routed to main()
112
-     *
113
-     * Take care when changing this - a lot of places rely on the array key for redirects and other links. If you need
114
-     * to change the key, then you'll likely have to update a lot of files.
115
-     *
116
-     * @var array
117
-     */
118
-    private $routeMap = array(
119
-
120
-        //////////////////////////////////////////////////////////////////////////////////////////////////
121
-        // Login and registration
122
-        'logout'                      =>
123
-            array(
124
-                'class'   => PageLogout::class,
125
-                'actions' => array(),
126
-            ),
127
-        'login'                       =>
128
-            array(
129
-                'class'   => PagePasswordLogin::class,
130
-                'actions' => array(),
131
-            ),
132
-        'login/otp'                   =>
133
-            array(
134
-                'class'   => PageOtpLogin::class,
135
-                'actions' => array(),
136
-            ),
137
-        'forgotPassword'              =>
138
-            array(
139
-                'class'   => PageForgotPassword::class,
140
-                'actions' => array('reset'),
141
-            ),
142
-        'register'                    =>
143
-            array(
144
-                'class'   => PageRegisterOption::class,
145
-                'actions' => array(),
146
-            ),
147
-        'register/standard'           =>
148
-            array(
149
-                'class'   => PageRegisterStandard::class,
150
-                'actions' => array('done'),
151
-            ),
152
-        'domainSwitch'                =>
153
-            array(
154
-                'class'   => PageDomainSwitch::class,
155
-                'actions' => array(),
156
-            ),
157
-
158
-        //////////////////////////////////////////////////////////////////////////////////////////////////
159
-        // Discovery
160
-        'search'                      =>
161
-            array(
162
-                'class'   => PageSearch::class,
163
-                'actions' => array(),
164
-            ),
165
-        'logs'                        =>
166
-            array(
167
-                'class'   => PageLog::class,
168
-                'actions' => array(),
169
-            ),
170
-
171
-        //////////////////////////////////////////////////////////////////////////////////////////////////
172
-        // Administration
173
-        'bans'                        =>
174
-            array(
175
-                'class'   => PageBan::class,
176
-                'actions' => array('set', 'remove', 'show', 'replace'),
177
-            ),
178
-        'userManagement'              =>
179
-            array(
180
-                'class'   => PageUserManagement::class,
181
-                'actions' => array(
182
-                    'approve',
183
-                    'decline',
184
-                    'rename',
185
-                    'editUser',
186
-                    'suspend',
187
-                    'editRoles',
188
-                ),
189
-            ),
190
-        'siteNotice'                  =>
191
-            array(
192
-                'class'   => PageSiteNotice::class,
193
-                'actions' => array(),
194
-            ),
195
-        'emailManagement'             =>
196
-            array(
197
-                'class'   => PageEmailManagement::class,
198
-                'actions' => array('create', 'edit', 'view'),
199
-            ),
200
-        'queueManagement'             =>
201
-            array(
202
-                'class'   => PageQueueManagement::class,
203
-                'actions' => array('create', 'edit'),
204
-            ),
205
-        'requestFormManagement'       =>
206
-            array(
207
-                'class'   => PageRequestFormManagement::class,
208
-                'actions' => array('create', 'edit', 'view', 'preview'),
209
-            ),
210
-        'jobQueue'                    =>
211
-            array(
212
-                'class'   => PageJobQueue::class,
213
-                'actions' => array('acknowledge', 'requeue', 'view', 'all', 'cancel'),
214
-            ),
215
-        'domainManagement'            =>
216
-            array(
217
-                'class'   => PageDomainManagement::class,
218
-                'actions' => array('create', 'edit'),
219
-            ),
220
-        'flaggedComments'             =>
221
-            array(
222
-                'class'   => PageListFlaggedComments::class,
223
-                'actions' => array(),
224
-            ),
225
-
226
-        //////////////////////////////////////////////////////////////////////////////////////////////////
227
-        // Personal preferences
228
-        'preferences'                 =>
229
-            array(
230
-                'class'   => PagePreferences::class,
231
-                'actions' => array(
232
-                    'refreshOAuth'
233
-                ),
234
-            ),
235
-        'changePassword'              =>
236
-            array(
237
-                'class'   => PageChangePassword::class,
238
-                'actions' => array(),
239
-            ),
240
-        'multiFactor'                 =>
241
-            array(
242
-                'class'   => PageMultiFactor::class,
243
-                'actions' => array(
244
-                    'scratch',
245
-                    'enableYubikeyOtp',
246
-                    'disableYubikeyOtp',
247
-                    'enableTotp',
248
-                    'disableTotp',
249
-                ),
250
-            ),
251
-        'oauth'                       =>
252
-            array(
253
-                'class'   => PageOAuth::class,
254
-                'actions' => array('detach', 'attach'),
255
-            ),
256
-        'oauth/callback'              =>
257
-            array(
258
-                'class'   => PageOAuthCallback::class,
259
-                'actions' => array('authorise', 'create'),
260
-            ),
261
-
262
-        //////////////////////////////////////////////////////////////////////////////////////////////////
263
-        // Welcomer configuration
264
-        'welcomeTemplates'            =>
265
-            array(
266
-                'class'   => PageWelcomeTemplateManagement::class,
267
-                'actions' => array('select', 'edit', 'delete', 'add', 'view'),
268
-            ),
269
-
270
-        //////////////////////////////////////////////////////////////////////////////////////////////////
271
-        // Statistics
272
-        'statistics'                  =>
273
-            array(
274
-                'class'   => StatsMain::class,
275
-                'actions' => array(),
276
-            ),
277
-        'statistics/fastCloses'       =>
278
-            array(
279
-                'class'   => StatsFastCloses::class,
280
-                'actions' => array(),
281
-            ),
282
-        'statistics/inactiveUsers'    =>
283
-            array(
284
-                'class'   => StatsInactiveUsers::class,
285
-                'actions' => array(),
286
-            ),
287
-        'statistics/monthlyStats'     =>
288
-            array(
289
-                'class'   => StatsMonthlyStats::class,
290
-                'actions' => array(),
291
-            ),
292
-        'statistics/reservedRequests' =>
293
-            array(
294
-                'class'   => StatsReservedRequests::class,
295
-                'actions' => array(),
296
-            ),
297
-        'statistics/templateStats'    =>
298
-            array(
299
-                'class'   => StatsTemplateStats::class,
300
-                'actions' => array(),
301
-            ),
302
-        'statistics/topCreators'      =>
303
-            array(
304
-                'class'   => StatsTopCreators::class,
305
-                'actions' => array(),
306
-            ),
307
-        'statistics/users'            =>
308
-            array(
309
-                'class'   => StatsUsers::class,
310
-                'actions' => array('detail'),
311
-            ),
312
-
313
-        //////////////////////////////////////////////////////////////////////////////////////////////////
314
-        // Zoom page
315
-        'viewRequest'                 =>
316
-            array(
317
-                'class'   => PageViewRequest::class,
318
-                'actions' => array(),
319
-            ),
320
-        'viewRequest/confirm'         =>
321
-            array(
322
-                'class'   => PageManuallyConfirm::class,
323
-                'actions' => array(),
324
-            ),
325
-        'viewRequest/reserve'         =>
326
-            array(
327
-                'class'   => PageReservation::class,
328
-                'actions' => array(),
329
-            ),
330
-        'viewRequest/breakReserve'    =>
331
-            array(
332
-                'class'   => PageBreakReservation::class,
333
-                'actions' => array(),
334
-            ),
335
-        'viewRequest/defer'           =>
336
-            array(
337
-                'class'   => PageDeferRequest::class,
338
-                'actions' => array(),
339
-            ),
340
-        'viewRequest/comment'         =>
341
-            array(
342
-                'class'   => PageComment::class,
343
-                'actions' => array(),
344
-            ),
345
-        'viewRequest/sendToUser'      =>
346
-            array(
347
-                'class'   => PageSendToUser::class,
348
-                'actions' => array(),
349
-            ),
350
-        'viewRequest/close'           =>
351
-            array(
352
-                'class'   => PageCloseRequest::class,
353
-                'actions' => array(),
354
-            ),
355
-        'viewRequest/create'          =>
356
-            array(
357
-                'class'   => PageCreateRequest::class,
358
-                'actions' => array(),
359
-            ),
360
-        'viewRequest/drop'            =>
361
-            array(
362
-                'class'   => PageDropRequest::class,
363
-                'actions' => array(),
364
-            ),
365
-        'viewRequest/custom'          =>
366
-            array(
367
-                'class'   => PageCustomClose::class,
368
-                'actions' => array(),
369
-            ),
370
-        'editComment'                 =>
371
-            array(
372
-                'class'   => PageEditComment::class,
373
-                'actions' => array(),
374
-            ),
375
-        'flagComment'                 =>
376
-            array(
377
-                'class'   => PageFlagComment::class,
378
-                'actions' => array(),
379
-            ),
380
-
381
-        //////////////////////////////////////////////////////////////////////////////////////////////////
382
-        // Misc stuff
383
-        'team'                        =>
384
-            array(
385
-                'class'   => PageTeam::class,
386
-                'actions' => array(),
387
-            ),
388
-        'requestList'                 =>
389
-            array(
390
-                'class'   => PageExpandedRequestList::class,
391
-                'actions' => array(),
392
-            ),
393
-        'xffdemo'                     =>
394
-            array(
395
-                'class'   => PageXffDemo::class,
396
-                'actions' => array(),
397
-            ),
398
-        'errorLog'                    =>
399
-            array(
400
-                'class'   => PageErrorLogViewer::class,
401
-                'actions' => array('remove', 'view'),
402
-            ),
403
-    );
404
-
405
-    /**
406
-     * @return IRoutedTask
407
-     * @throws Exception
408
-     */
409
-    final public function route()
410
-    {
411
-        $pathInfo = WebRequest::pathInfo();
412
-
413
-        list($pageClass, $action) = $this->getRouteFromPath($pathInfo);
414
-
415
-        /** @var IRoutedTask $page */
416
-        $page = new $pageClass();
417
-
418
-        // Dynamic creation, so we've got to be careful here. We can't use built-in language type protection, so
419
-        // let's use our own.
420
-        if (!($page instanceof IRoutedTask)) {
421
-            throw new Exception('Expected a page, but this is not a page.');
422
-        }
423
-
424
-        // OK, I'm happy at this point that we know we're running a page, and we know it's probably what we want if it
425
-        // inherits PageBase and has been created from the routing map.
426
-        $page->setRoute($action);
427
-
428
-        return $page;
429
-    }
430
-
431
-    /**
432
-     * @param $pathInfo
433
-     *
434
-     * @return array
435
-     */
436
-    public function getRouteFromPath($pathInfo)
437
-    {
438
-        if (count($pathInfo) === 0) {
439
-            // No pathInfo, so no page to load. Load the main page.
440
-            return $this->getDefaultRoute();
441
-        }
442
-        elseif (count($pathInfo) === 1) {
443
-            // Exactly one path info segment, it's got to be a page.
444
-            $classSegment = $pathInfo[0];
445
-
446
-            return $this->routeSinglePathSegment($classSegment);
447
-        }
448
-
449
-        // OK, we have two or more segments now.
450
-        if (count($pathInfo) > 2) {
451
-            // Let's handle more than two, and collapse it down into two.
452
-            $requestedAction = array_pop($pathInfo);
453
-            $classSegment = implode('/', $pathInfo);
454
-        }
455
-        else {
456
-            // Two path info segments.
457
-            $classSegment = $pathInfo[0];
458
-            $requestedAction = $pathInfo[1];
459
-        }
460
-
461
-        $routeMap = $this->routePathSegments($classSegment, $requestedAction);
462
-
463
-        if ($routeMap[0] === Page404::class) {
464
-            $routeMap = $this->routeSinglePathSegment($classSegment . '/' . $requestedAction);
465
-        }
466
-
467
-        return $routeMap;
468
-    }
469
-
470
-    /**
471
-     * @param $classSegment
472
-     *
473
-     * @return array
474
-     */
475
-    final protected function routeSinglePathSegment($classSegment)
476
-    {
477
-        $routeMap = $this->getRouteMap();
478
-        if (array_key_exists($classSegment, $routeMap)) {
479
-            // Route exists, but we don't have an action in path info, so default to main.
480
-            $pageClass = $routeMap[$classSegment]['class'];
481
-            $action = 'main';
482
-
483
-            return array($pageClass, $action);
484
-        }
485
-        else {
486
-            // Doesn't exist in map. Fall back to 404
487
-            $pageClass = Page404::class;
488
-            $action = "main";
489
-
490
-            return array($pageClass, $action);
491
-        }
492
-    }
493
-
494
-    /**
495
-     * @param $classSegment
496
-     * @param $requestedAction
497
-     *
498
-     * @return array
499
-     */
500
-    final protected function routePathSegments($classSegment, $requestedAction)
501
-    {
502
-        $routeMap = $this->getRouteMap();
503
-        if (array_key_exists($classSegment, $routeMap)) {
504
-            // Route exists, but we don't have an action in path info, so default to main.
505
-
506
-            if (isset($routeMap[$classSegment]['actions'])
507
-                && array_search($requestedAction, $routeMap[$classSegment]['actions']) !== false
508
-            ) {
509
-                // Action exists in allowed action list. Allow both the page and the action
510
-                $pageClass = $routeMap[$classSegment]['class'];
511
-                $action = $requestedAction;
512
-
513
-                return array($pageClass, $action);
514
-            }
515
-            else {
516
-                // Valid page, invalid action. 404 our way out.
517
-                $pageClass = Page404::class;
518
-                $action = 'main';
519
-
520
-                return array($pageClass, $action);
521
-            }
522
-        }
523
-        else {
524
-            // Class doesn't exist in map. Fall back to 404
525
-            $pageClass = Page404::class;
526
-            $action = 'main';
527
-
528
-            return array($pageClass, $action);
529
-        }
530
-    }
531
-
532
-    /**
533
-     * @return array
534
-     */
535
-    protected function getRouteMap()
536
-    {
537
-        return $this->routeMap;
538
-    }
539
-
540
-    /**
541
-     * @return array
542
-     */
543
-    protected function getDefaultRoute()
544
-    {
545
-        return array(PageMain::class, "main");
546
-    }
73
+	/**
74
+	 * This is the core routing table for the application. The basic idea is:
75
+	 *
76
+	 *      array(
77
+	 *          "foo" =>
78
+	 *              array(
79
+	 *                  "class"   => PageFoo::class,
80
+	 *                  "actions" => array("bar", "other")
81
+	 *              ),
82
+	 * );
83
+	 *
84
+	 * Things to note:
85
+	 *     - If no page is requested, we go to PageMain. PageMain can't have actions defined.
86
+	 *
87
+	 *     - If a page is defined and requested, but no action is requested, go to that page's main() method
88
+	 *     - If a page is defined and requested, and an action is defined and requested, go to that action's method.
89
+	 *     - If a page is defined and requested, and an action NOT defined and requested, go to Page404 and it's main()
90
+	 *       method.
91
+	 *     - If a page is NOT defined and requested, go to Page404 and it's main() method.
92
+	 *
93
+	 *     - Query parameters are ignored.
94
+	 *
95
+	 * The key point here is request routing with validation that this is allowed, before we start hitting the
96
+	 * filesystem through the AutoLoader, and opening random files. Also, so that we validate the action requested
97
+	 * before we start calling random methods through the web UI.
98
+	 *
99
+	 * Examples:
100
+	 * /internal.php                => returns instance of PageMain, routed to main()
101
+	 * /internal.php?query          => returns instance of PageMain, routed to main()
102
+	 * /internal.php/foo            => returns instance of PageFoo, routed to main()
103
+	 * /internal.php/foo?query      => returns instance of PageFoo, routed to main()
104
+	 * /internal.php/foo/bar        => returns instance of PageFoo, routed to bar()
105
+	 * /internal.php/foo/bar?query  => returns instance of PageFoo, routed to bar()
106
+	 * /internal.php/foo/baz        => returns instance of Page404, routed to main()
107
+	 * /internal.php/foo/baz?query  => returns instance of Page404, routed to main()
108
+	 * /internal.php/bar            => returns instance of Page404, routed to main()
109
+	 * /internal.php/bar?query      => returns instance of Page404, routed to main()
110
+	 * /internal.php/bar/baz        => returns instance of Page404, routed to main()
111
+	 * /internal.php/bar/baz?query  => returns instance of Page404, routed to main()
112
+	 *
113
+	 * Take care when changing this - a lot of places rely on the array key for redirects and other links. If you need
114
+	 * to change the key, then you'll likely have to update a lot of files.
115
+	 *
116
+	 * @var array
117
+	 */
118
+	private $routeMap = array(
119
+
120
+		//////////////////////////////////////////////////////////////////////////////////////////////////
121
+		// Login and registration
122
+		'logout'                      =>
123
+			array(
124
+				'class'   => PageLogout::class,
125
+				'actions' => array(),
126
+			),
127
+		'login'                       =>
128
+			array(
129
+				'class'   => PagePasswordLogin::class,
130
+				'actions' => array(),
131
+			),
132
+		'login/otp'                   =>
133
+			array(
134
+				'class'   => PageOtpLogin::class,
135
+				'actions' => array(),
136
+			),
137
+		'forgotPassword'              =>
138
+			array(
139
+				'class'   => PageForgotPassword::class,
140
+				'actions' => array('reset'),
141
+			),
142
+		'register'                    =>
143
+			array(
144
+				'class'   => PageRegisterOption::class,
145
+				'actions' => array(),
146
+			),
147
+		'register/standard'           =>
148
+			array(
149
+				'class'   => PageRegisterStandard::class,
150
+				'actions' => array('done'),
151
+			),
152
+		'domainSwitch'                =>
153
+			array(
154
+				'class'   => PageDomainSwitch::class,
155
+				'actions' => array(),
156
+			),
157
+
158
+		//////////////////////////////////////////////////////////////////////////////////////////////////
159
+		// Discovery
160
+		'search'                      =>
161
+			array(
162
+				'class'   => PageSearch::class,
163
+				'actions' => array(),
164
+			),
165
+		'logs'                        =>
166
+			array(
167
+				'class'   => PageLog::class,
168
+				'actions' => array(),
169
+			),
170
+
171
+		//////////////////////////////////////////////////////////////////////////////////////////////////
172
+		// Administration
173
+		'bans'                        =>
174
+			array(
175
+				'class'   => PageBan::class,
176
+				'actions' => array('set', 'remove', 'show', 'replace'),
177
+			),
178
+		'userManagement'              =>
179
+			array(
180
+				'class'   => PageUserManagement::class,
181
+				'actions' => array(
182
+					'approve',
183
+					'decline',
184
+					'rename',
185
+					'editUser',
186
+					'suspend',
187
+					'editRoles',
188
+				),
189
+			),
190
+		'siteNotice'                  =>
191
+			array(
192
+				'class'   => PageSiteNotice::class,
193
+				'actions' => array(),
194
+			),
195
+		'emailManagement'             =>
196
+			array(
197
+				'class'   => PageEmailManagement::class,
198
+				'actions' => array('create', 'edit', 'view'),
199
+			),
200
+		'queueManagement'             =>
201
+			array(
202
+				'class'   => PageQueueManagement::class,
203
+				'actions' => array('create', 'edit'),
204
+			),
205
+		'requestFormManagement'       =>
206
+			array(
207
+				'class'   => PageRequestFormManagement::class,
208
+				'actions' => array('create', 'edit', 'view', 'preview'),
209
+			),
210
+		'jobQueue'                    =>
211
+			array(
212
+				'class'   => PageJobQueue::class,
213
+				'actions' => array('acknowledge', 'requeue', 'view', 'all', 'cancel'),
214
+			),
215
+		'domainManagement'            =>
216
+			array(
217
+				'class'   => PageDomainManagement::class,
218
+				'actions' => array('create', 'edit'),
219
+			),
220
+		'flaggedComments'             =>
221
+			array(
222
+				'class'   => PageListFlaggedComments::class,
223
+				'actions' => array(),
224
+			),
225
+
226
+		//////////////////////////////////////////////////////////////////////////////////////////////////
227
+		// Personal preferences
228
+		'preferences'                 =>
229
+			array(
230
+				'class'   => PagePreferences::class,
231
+				'actions' => array(
232
+					'refreshOAuth'
233
+				),
234
+			),
235
+		'changePassword'              =>
236
+			array(
237
+				'class'   => PageChangePassword::class,
238
+				'actions' => array(),
239
+			),
240
+		'multiFactor'                 =>
241
+			array(
242
+				'class'   => PageMultiFactor::class,
243
+				'actions' => array(
244
+					'scratch',
245
+					'enableYubikeyOtp',
246
+					'disableYubikeyOtp',
247
+					'enableTotp',
248
+					'disableTotp',
249
+				),
250
+			),
251
+		'oauth'                       =>
252
+			array(
253
+				'class'   => PageOAuth::class,
254
+				'actions' => array('detach', 'attach'),
255
+			),
256
+		'oauth/callback'              =>
257
+			array(
258
+				'class'   => PageOAuthCallback::class,
259
+				'actions' => array('authorise', 'create'),
260
+			),
261
+
262
+		//////////////////////////////////////////////////////////////////////////////////////////////////
263
+		// Welcomer configuration
264
+		'welcomeTemplates'            =>
265
+			array(
266
+				'class'   => PageWelcomeTemplateManagement::class,
267
+				'actions' => array('select', 'edit', 'delete', 'add', 'view'),
268
+			),
269
+
270
+		//////////////////////////////////////////////////////////////////////////////////////////////////
271
+		// Statistics
272
+		'statistics'                  =>
273
+			array(
274
+				'class'   => StatsMain::class,
275
+				'actions' => array(),
276
+			),
277
+		'statistics/fastCloses'       =>
278
+			array(
279
+				'class'   => StatsFastCloses::class,
280
+				'actions' => array(),
281
+			),
282
+		'statistics/inactiveUsers'    =>
283
+			array(
284
+				'class'   => StatsInactiveUsers::class,
285
+				'actions' => array(),
286
+			),
287
+		'statistics/monthlyStats'     =>
288
+			array(
289
+				'class'   => StatsMonthlyStats::class,
290
+				'actions' => array(),
291
+			),
292
+		'statistics/reservedRequests' =>
293
+			array(
294
+				'class'   => StatsReservedRequests::class,
295
+				'actions' => array(),
296
+			),
297
+		'statistics/templateStats'    =>
298
+			array(
299
+				'class'   => StatsTemplateStats::class,
300
+				'actions' => array(),
301
+			),
302
+		'statistics/topCreators'      =>
303
+			array(
304
+				'class'   => StatsTopCreators::class,
305
+				'actions' => array(),
306
+			),
307
+		'statistics/users'            =>
308
+			array(
309
+				'class'   => StatsUsers::class,
310
+				'actions' => array('detail'),
311
+			),
312
+
313
+		//////////////////////////////////////////////////////////////////////////////////////////////////
314
+		// Zoom page
315
+		'viewRequest'                 =>
316
+			array(
317
+				'class'   => PageViewRequest::class,
318
+				'actions' => array(),
319
+			),
320
+		'viewRequest/confirm'         =>
321
+			array(
322
+				'class'   => PageManuallyConfirm::class,
323
+				'actions' => array(),
324
+			),
325
+		'viewRequest/reserve'         =>
326
+			array(
327
+				'class'   => PageReservation::class,
328
+				'actions' => array(),
329
+			),
330
+		'viewRequest/breakReserve'    =>
331
+			array(
332
+				'class'   => PageBreakReservation::class,
333
+				'actions' => array(),
334
+			),
335
+		'viewRequest/defer'           =>
336
+			array(
337
+				'class'   => PageDeferRequest::class,
338
+				'actions' => array(),
339
+			),
340
+		'viewRequest/comment'         =>
341
+			array(
342
+				'class'   => PageComment::class,
343
+				'actions' => array(),
344
+			),
345
+		'viewRequest/sendToUser'      =>
346
+			array(
347
+				'class'   => PageSendToUser::class,
348
+				'actions' => array(),
349
+			),
350
+		'viewRequest/close'           =>
351
+			array(
352
+				'class'   => PageCloseRequest::class,
353
+				'actions' => array(),
354
+			),
355
+		'viewRequest/create'          =>
356
+			array(
357
+				'class'   => PageCreateRequest::class,
358
+				'actions' => array(),
359
+			),
360
+		'viewRequest/drop'            =>
361
+			array(
362
+				'class'   => PageDropRequest::class,
363
+				'actions' => array(),
364
+			),
365
+		'viewRequest/custom'          =>
366
+			array(
367
+				'class'   => PageCustomClose::class,
368
+				'actions' => array(),
369
+			),
370
+		'editComment'                 =>
371
+			array(
372
+				'class'   => PageEditComment::class,
373
+				'actions' => array(),
374
+			),
375
+		'flagComment'                 =>
376
+			array(
377
+				'class'   => PageFlagComment::class,
378
+				'actions' => array(),
379
+			),
380
+
381
+		//////////////////////////////////////////////////////////////////////////////////////////////////
382
+		// Misc stuff
383
+		'team'                        =>
384
+			array(
385
+				'class'   => PageTeam::class,
386
+				'actions' => array(),
387
+			),
388
+		'requestList'                 =>
389
+			array(
390
+				'class'   => PageExpandedRequestList::class,
391
+				'actions' => array(),
392
+			),
393
+		'xffdemo'                     =>
394
+			array(
395
+				'class'   => PageXffDemo::class,
396
+				'actions' => array(),
397
+			),
398
+		'errorLog'                    =>
399
+			array(
400
+				'class'   => PageErrorLogViewer::class,
401
+				'actions' => array('remove', 'view'),
402
+			),
403
+	);
404
+
405
+	/**
406
+	 * @return IRoutedTask
407
+	 * @throws Exception
408
+	 */
409
+	final public function route()
410
+	{
411
+		$pathInfo = WebRequest::pathInfo();
412
+
413
+		list($pageClass, $action) = $this->getRouteFromPath($pathInfo);
414
+
415
+		/** @var IRoutedTask $page */
416
+		$page = new $pageClass();
417
+
418
+		// Dynamic creation, so we've got to be careful here. We can't use built-in language type protection, so
419
+		// let's use our own.
420
+		if (!($page instanceof IRoutedTask)) {
421
+			throw new Exception('Expected a page, but this is not a page.');
422
+		}
423
+
424
+		// OK, I'm happy at this point that we know we're running a page, and we know it's probably what we want if it
425
+		// inherits PageBase and has been created from the routing map.
426
+		$page->setRoute($action);
427
+
428
+		return $page;
429
+	}
430
+
431
+	/**
432
+	 * @param $pathInfo
433
+	 *
434
+	 * @return array
435
+	 */
436
+	public function getRouteFromPath($pathInfo)
437
+	{
438
+		if (count($pathInfo) === 0) {
439
+			// No pathInfo, so no page to load. Load the main page.
440
+			return $this->getDefaultRoute();
441
+		}
442
+		elseif (count($pathInfo) === 1) {
443
+			// Exactly one path info segment, it's got to be a page.
444
+			$classSegment = $pathInfo[0];
445
+
446
+			return $this->routeSinglePathSegment($classSegment);
447
+		}
448
+
449
+		// OK, we have two or more segments now.
450
+		if (count($pathInfo) > 2) {
451
+			// Let's handle more than two, and collapse it down into two.
452
+			$requestedAction = array_pop($pathInfo);
453
+			$classSegment = implode('/', $pathInfo);
454
+		}
455
+		else {
456
+			// Two path info segments.
457
+			$classSegment = $pathInfo[0];
458
+			$requestedAction = $pathInfo[1];
459
+		}
460
+
461
+		$routeMap = $this->routePathSegments($classSegment, $requestedAction);
462
+
463
+		if ($routeMap[0] === Page404::class) {
464
+			$routeMap = $this->routeSinglePathSegment($classSegment . '/' . $requestedAction);
465
+		}
466
+
467
+		return $routeMap;
468
+	}
469
+
470
+	/**
471
+	 * @param $classSegment
472
+	 *
473
+	 * @return array
474
+	 */
475
+	final protected function routeSinglePathSegment($classSegment)
476
+	{
477
+		$routeMap = $this->getRouteMap();
478
+		if (array_key_exists($classSegment, $routeMap)) {
479
+			// Route exists, but we don't have an action in path info, so default to main.
480
+			$pageClass = $routeMap[$classSegment]['class'];
481
+			$action = 'main';
482
+
483
+			return array($pageClass, $action);
484
+		}
485
+		else {
486
+			// Doesn't exist in map. Fall back to 404
487
+			$pageClass = Page404::class;
488
+			$action = "main";
489
+
490
+			return array($pageClass, $action);
491
+		}
492
+	}
493
+
494
+	/**
495
+	 * @param $classSegment
496
+	 * @param $requestedAction
497
+	 *
498
+	 * @return array
499
+	 */
500
+	final protected function routePathSegments($classSegment, $requestedAction)
501
+	{
502
+		$routeMap = $this->getRouteMap();
503
+		if (array_key_exists($classSegment, $routeMap)) {
504
+			// Route exists, but we don't have an action in path info, so default to main.
505
+
506
+			if (isset($routeMap[$classSegment]['actions'])
507
+				&& array_search($requestedAction, $routeMap[$classSegment]['actions']) !== false
508
+			) {
509
+				// Action exists in allowed action list. Allow both the page and the action
510
+				$pageClass = $routeMap[$classSegment]['class'];
511
+				$action = $requestedAction;
512
+
513
+				return array($pageClass, $action);
514
+			}
515
+			else {
516
+				// Valid page, invalid action. 404 our way out.
517
+				$pageClass = Page404::class;
518
+				$action = 'main';
519
+
520
+				return array($pageClass, $action);
521
+			}
522
+		}
523
+		else {
524
+			// Class doesn't exist in map. Fall back to 404
525
+			$pageClass = Page404::class;
526
+			$action = 'main';
527
+
528
+			return array($pageClass, $action);
529
+		}
530
+	}
531
+
532
+	/**
533
+	 * @return array
534
+	 */
535
+	protected function getRouteMap()
536
+	{
537
+		return $this->routeMap;
538
+	}
539
+
540
+	/**
541
+	 * @return array
542
+	 */
543
+	protected function getDefaultRoute()
544
+	{
545
+		return array(PageMain::class, "main");
546
+	}
547 547
 }
Please login to merge, or discard this patch.
includes/ApplicationBase.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -24,162 +24,162 @@
 block discarded – undo
24 24
 
25 25
 abstract class ApplicationBase
26 26
 {
27
-    private $configuration;
28
-
29
-    public function __construct(SiteConfiguration $configuration)
30
-    {
31
-        $this->configuration = $configuration;
32
-    }
33
-
34
-    /**
35
-     * Application entry point.
36
-     *
37
-     * Sets up the environment and runs the application, performing any global cleanup operations when done.
38
-     */
39
-    public function run()
40
-    {
41
-        try {
42
-            if ($this->setupEnvironment()) {
43
-                $this->main();
44
-            }
45
-        }
46
-        catch (Exception $ex) {
47
-            print $ex->getMessage();
48
-        }
49
-        finally {
50
-            $this->cleanupEnvironment();
51
-        }
52
-    }
53
-
54
-    /**
55
-     * Environment setup
56
-     *
57
-     * This method initialises the tool environment. If the tool cannot be initialised correctly, it will return false
58
-     * and shut down prematurely.
59
-     *
60
-     * @return bool
61
-     * @throws EnvironmentException
62
-     */
63
-    protected function setupEnvironment()
64
-    {
65
-        foreach ([
66
-            'mbstring', // unicode and stuff
67
-            'pdo',
68
-            'pdo_mysql', // new database module
69
-            'session',
70
-            'date',
71
-            'pcre', // core stuff
72
-            'curl', // mediawiki api access etc
73
-            'openssl', // token generation
74
-        ] as $x) {
75
-            if (!extension_loaded($x)) {
76
-                throw new EnvironmentException("PHP extension `$x` is required.");
77
-            }
78
-        }
79
-
80
-        ini_set('user_agent', $this->getConfiguration()->getUserAgent());
81
-
82
-        $this->setupDatabase();
83
-
84
-        return true;
85
-    }
86
-
87
-    /**
88
-     * Checks the database is connectable and at the correct version
89
-     *
90
-     * @throws EnvironmentException
91
-     * @throws Exception
92
-     */
93
-    private function setupDatabase(): void
94
-    {
95
-        // check the schema version
96
-        $database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
97
-
98
-        $actualVersion = (int)$database->query('SELECT version FROM schemaversion')->fetchColumn();
99
-        if ($actualVersion !== $this->getConfiguration()->getSchemaVersion()) {
100
-            throw new EnvironmentException('Database schema is wrong version! Please either update configuration or database.');
101
-        }
102
-    }
103
-
104
-    /**
105
-     * @return SiteConfiguration
106
-     */
107
-    public function getConfiguration()
108
-    {
109
-        return $this->configuration;
110
-    }
111
-
112
-    /**
113
-     * Main application logic
114
-     * @return void
115
-     */
116
-    abstract protected function main();
117
-
118
-    /**
119
-     * Any cleanup tasks should go here
120
-     *
121
-     * Note that we need to be very careful here, as exceptions may have been thrown and handled.
122
-     * This should *only* be for cleaning up, no logic should go here.
123
-     *
124
-     * @return void
125
-     */
126
-    abstract protected function cleanupEnvironment();
127
-
128
-    /**
129
-     * @param ITask             $page
130
-     * @param SiteConfiguration $siteConfiguration
131
-     * @param PdoDatabase       $database
132
-     *
133
-     * @return void
134
-     */
135
-    protected function setupHelpers(
136
-        ITask $page,
137
-        SiteConfiguration $siteConfiguration,
138
-        PdoDatabase $database
139
-    ) {
140
-        $page->setSiteConfiguration($siteConfiguration);
141
-
142
-        // setup the global database object
143
-        $page->setDatabase($database);
144
-
145
-        // set up helpers and inject them into the page.
146
-        $httpHelper = new HttpHelper($siteConfiguration);
147
-
148
-        $page->setEmailHelper(
149
-            new EmailHelper($siteConfiguration->getEmailSender(), $siteConfiguration->getIrcNotificationsInstance())
150
-        );
151
-
152
-        $page->setHttpHelper($httpHelper);
153
-
154
-        if ($siteConfiguration->getLocationProviderApiKey() === null) {
155
-            $page->setLocationProvider(new FakeLocationProvider());
156
-        }
157
-        else {
158
-            $page->setLocationProvider(
159
-                new IpLocationProvider(
160
-                    $database,
161
-                    $siteConfiguration->getLocationProviderApiKey(),
162
-                    $httpHelper
163
-                ));
164
-        }
165
-
166
-        $page->setXffTrustProvider(new XffTrustProvider($siteConfiguration->getSquidList(), $database));
167
-
168
-        $page->setRdnsProvider(new CachedRDnsLookupProvider($database));
169
-
170
-        $page->setAntiSpoofProvider(new CachedApiAntispoofProvider($database, $httpHelper));
171
-
172
-        $page->setOAuthProtocolHelper(new OAuthProtocolHelper(
173
-            $siteConfiguration->getOAuthConsumerToken(),
174
-            $siteConfiguration->getOAuthConsumerSecret(),
175
-            $database,
176
-            $siteConfiguration->getUserAgent()
177
-        ));
178
-
179
-        $page->setNotificationHelper(new IrcNotificationHelper(
180
-            $siteConfiguration,
181
-            $database));
182
-
183
-        $page->setTorExitProvider(new TorExitProvider($database));
184
-    }
27
+	private $configuration;
28
+
29
+	public function __construct(SiteConfiguration $configuration)
30
+	{
31
+		$this->configuration = $configuration;
32
+	}
33
+
34
+	/**
35
+	 * Application entry point.
36
+	 *
37
+	 * Sets up the environment and runs the application, performing any global cleanup operations when done.
38
+	 */
39
+	public function run()
40
+	{
41
+		try {
42
+			if ($this->setupEnvironment()) {
43
+				$this->main();
44
+			}
45
+		}
46
+		catch (Exception $ex) {
47
+			print $ex->getMessage();
48
+		}
49
+		finally {
50
+			$this->cleanupEnvironment();
51
+		}
52
+	}
53
+
54
+	/**
55
+	 * Environment setup
56
+	 *
57
+	 * This method initialises the tool environment. If the tool cannot be initialised correctly, it will return false
58
+	 * and shut down prematurely.
59
+	 *
60
+	 * @return bool
61
+	 * @throws EnvironmentException
62
+	 */
63
+	protected function setupEnvironment()
64
+	{
65
+		foreach ([
66
+			'mbstring', // unicode and stuff
67
+			'pdo',
68
+			'pdo_mysql', // new database module
69
+			'session',
70
+			'date',
71
+			'pcre', // core stuff
72
+			'curl', // mediawiki api access etc
73
+			'openssl', // token generation
74
+		] as $x) {
75
+			if (!extension_loaded($x)) {
76
+				throw new EnvironmentException("PHP extension `$x` is required.");
77
+			}
78
+		}
79
+
80
+		ini_set('user_agent', $this->getConfiguration()->getUserAgent());
81
+
82
+		$this->setupDatabase();
83
+
84
+		return true;
85
+	}
86
+
87
+	/**
88
+	 * Checks the database is connectable and at the correct version
89
+	 *
90
+	 * @throws EnvironmentException
91
+	 * @throws Exception
92
+	 */
93
+	private function setupDatabase(): void
94
+	{
95
+		// check the schema version
96
+		$database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
97
+
98
+		$actualVersion = (int)$database->query('SELECT version FROM schemaversion')->fetchColumn();
99
+		if ($actualVersion !== $this->getConfiguration()->getSchemaVersion()) {
100
+			throw new EnvironmentException('Database schema is wrong version! Please either update configuration or database.');
101
+		}
102
+	}
103
+
104
+	/**
105
+	 * @return SiteConfiguration
106
+	 */
107
+	public function getConfiguration()
108
+	{
109
+		return $this->configuration;
110
+	}
111
+
112
+	/**
113
+	 * Main application logic
114
+	 * @return void
115
+	 */
116
+	abstract protected function main();
117
+
118
+	/**
119
+	 * Any cleanup tasks should go here
120
+	 *
121
+	 * Note that we need to be very careful here, as exceptions may have been thrown and handled.
122
+	 * This should *only* be for cleaning up, no logic should go here.
123
+	 *
124
+	 * @return void
125
+	 */
126
+	abstract protected function cleanupEnvironment();
127
+
128
+	/**
129
+	 * @param ITask             $page
130
+	 * @param SiteConfiguration $siteConfiguration
131
+	 * @param PdoDatabase       $database
132
+	 *
133
+	 * @return void
134
+	 */
135
+	protected function setupHelpers(
136
+		ITask $page,
137
+		SiteConfiguration $siteConfiguration,
138
+		PdoDatabase $database
139
+	) {
140
+		$page->setSiteConfiguration($siteConfiguration);
141
+
142
+		// setup the global database object
143
+		$page->setDatabase($database);
144
+
145
+		// set up helpers and inject them into the page.
146
+		$httpHelper = new HttpHelper($siteConfiguration);
147
+
148
+		$page->setEmailHelper(
149
+			new EmailHelper($siteConfiguration->getEmailSender(), $siteConfiguration->getIrcNotificationsInstance())
150
+		);
151
+
152
+		$page->setHttpHelper($httpHelper);
153
+
154
+		if ($siteConfiguration->getLocationProviderApiKey() === null) {
155
+			$page->setLocationProvider(new FakeLocationProvider());
156
+		}
157
+		else {
158
+			$page->setLocationProvider(
159
+				new IpLocationProvider(
160
+					$database,
161
+					$siteConfiguration->getLocationProviderApiKey(),
162
+					$httpHelper
163
+				));
164
+		}
165
+
166
+		$page->setXffTrustProvider(new XffTrustProvider($siteConfiguration->getSquidList(), $database));
167
+
168
+		$page->setRdnsProvider(new CachedRDnsLookupProvider($database));
169
+
170
+		$page->setAntiSpoofProvider(new CachedApiAntispoofProvider($database, $httpHelper));
171
+
172
+		$page->setOAuthProtocolHelper(new OAuthProtocolHelper(
173
+			$siteConfiguration->getOAuthConsumerToken(),
174
+			$siteConfiguration->getOAuthConsumerSecret(),
175
+			$database,
176
+			$siteConfiguration->getUserAgent()
177
+		));
178
+
179
+		$page->setNotificationHelper(new IrcNotificationHelper(
180
+			$siteConfiguration,
181
+			$database));
182
+
183
+		$page->setTorExitProvider(new TorExitProvider($database));
184
+	}
185 185
 }
Please login to merge, or discard this patch.
includes/WebStart.php 1 patch
Indentation   +204 added lines, -204 removed lines patch added patch discarded remove patch
@@ -33,208 +33,208 @@
 block discarded – undo
33 33
  */
34 34
 class WebStart extends ApplicationBase
35 35
 {
36
-    /**
37
-     * @var IRequestRouter $requestRouter The request router to use. Note that different entry points have different
38
-     *                                    routers and hence different URL mappings
39
-     */
40
-    private $requestRouter;
41
-    /**
42
-     * @var bool $isPublic Determines whether to use public interface objects or internal interface objects
43
-     */
44
-    private bool $isPublic = false;
45
-
46
-    /**
47
-     * WebStart constructor.
48
-     *
49
-     * @param SiteConfiguration $configuration The site configuration
50
-     * @param IRequestRouter    $router        The request router to use
51
-     */
52
-    public function __construct(SiteConfiguration $configuration, IRequestRouter $router)
53
-    {
54
-        parent::__construct($configuration);
55
-
56
-        $this->requestRouter = $router;
57
-    }
58
-
59
-    /**
60
-     * @param ITask             $page
61
-     * @param SiteConfiguration $siteConfiguration
62
-     * @param PdoDatabase       $database
63
-     *
64
-     * @return void
65
-     */
66
-    protected function setupHelpers(
67
-        ITask $page,
68
-        SiteConfiguration $siteConfiguration,
69
-        PdoDatabase $database
70
-    ) {
71
-        parent::setupHelpers($page, $siteConfiguration, $database);
72
-
73
-        if ($page instanceof PageBase) {
74
-            $page->setTokenManager(new TokenManager());
75
-            $page->setCspManager(new ContentSecurityPolicyManager($siteConfiguration));
76
-
77
-            if ($page instanceof InternalPageBase) {
78
-                $page->setTypeAheadHelper(new TypeAheadHelper());
79
-
80
-                $identificationVerifier = new IdentificationVerifier($page->getHttpHelper(), $siteConfiguration, $database);
81
-                $page->setSecurityManager(new SecurityManager($identificationVerifier, new RoleConfiguration()));
82
-
83
-                if ($siteConfiguration->getTitleBlacklistEnabled()) {
84
-                    $page->setBlacklistHelper(new BlacklistHelper($page->getHttpHelper(), $database, $siteConfiguration));
85
-                }
86
-                else {
87
-                    $page->setBlacklistHelper(new FakeBlacklistHelper());
88
-                }
89
-
90
-                $page->setDomainAccessManager(new DomainAccessManager($page->getSecurityManager()));
91
-            }
92
-        }
93
-    }
94
-
95
-    /**
96
-     * Application entry point.
97
-     *
98
-     * Sets up the environment and runs the application, performing any global cleanup operations when done.
99
-     */
100
-    public function run()
101
-    {
102
-        try {
103
-            if ($this->setupEnvironment()) {
104
-                $this->main();
105
-            }
106
-        }
107
-        catch (EnvironmentException $ex) {
108
-            ob_end_clean();
109
-            print Offline::getOfflineMessage($this->isPublic(), $this->getConfiguration(), $ex->getMessage());
110
-        }
111
-            /** @noinspection PhpRedundantCatchClauseInspection */
112
-        catch (ReadableException $ex) {
113
-            ob_end_clean();
114
-            print $ex->getReadableError();
115
-        }
116
-        finally {
117
-            $this->cleanupEnvironment();
118
-        }
119
-    }
120
-
121
-    /**
122
-     * Environment setup
123
-     *
124
-     * This method initialises the tool environment. If the tool cannot be initialised correctly, it will return false
125
-     * and shut down prematurely.
126
-     *
127
-     * @return bool
128
-     * @throws EnvironmentException
129
-     */
130
-    protected function setupEnvironment()
131
-    {
132
-        // initialise global exception handler
133
-        set_exception_handler(array(ExceptionHandler::class, 'exceptionHandler'));
134
-        set_error_handler(array(ExceptionHandler::class, 'errorHandler'), E_RECOVERABLE_ERROR);
135
-
136
-        // start output buffering if necessary
137
-        if (ob_get_level() === 0) {
138
-            ob_start();
139
-        }
140
-
141
-        // initialise super-global providers
142
-        WebRequest::setGlobalStateProvider(new GlobalStateProvider());
143
-
144
-        if (Offline::isOffline($this->getConfiguration())) {
145
-            print Offline::getOfflineMessage($this->isPublic(), $this->getConfiguration());
146
-            ob_end_flush();
147
-
148
-            return false;
149
-        }
150
-
151
-        // Call parent setup
152
-        if (!parent::setupEnvironment()) {
153
-            return false;
154
-        }
155
-
156
-        // Start up sessions
157
-        ini_set('session.cookie_path', $this->getConfiguration()->getCookiePath());
158
-        ini_set('session.name', $this->getConfiguration()->getCookieSessionName());
159
-        Session::start();
160
-
161
-        // Check the user is allowed to be logged in still. This must be before we call any user-loading functions and
162
-        // get the current user cached.
163
-        // I'm not sure if this function call being here is particularly a good thing, but it's part of starting up a
164
-        // session I suppose.
165
-        $this->checkForceLogout();
166
-
167
-        // environment initialised!
168
-        return true;
169
-    }
170
-
171
-    /**
172
-     * Main application logic
173
-     */
174
-    protected function main()
175
-    {
176
-        // Get the right route for the request
177
-        $page = $this->requestRouter->route();
178
-
179
-        $siteConfiguration = $this->getConfiguration();
180
-        $database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
181
-
182
-        $this->setupHelpers($page, $siteConfiguration, $database);
183
-
184
-        // run the route code for the request.
185
-        $page->execute();
186
-    }
187
-
188
-    /**
189
-     * Any cleanup tasks should go here
190
-     *
191
-     * Note that we need to be very careful here, as exceptions may have been thrown and handled.
192
-     * This should *only* be for cleaning up, no logic should go here.
193
-     */
194
-    protected function cleanupEnvironment()
195
-    {
196
-        // Clean up anything we splurged after sending the page.
197
-        if (ob_get_level() > 0) {
198
-            for ($i = ob_get_level(); $i > 0; $i--) {
199
-                ob_end_clean();
200
-            }
201
-        }
202
-    }
203
-
204
-    private function checkForceLogout()
205
-    {
206
-        $database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
207
-
208
-        $sessionUserId = WebRequest::getSessionUserId();
209
-        iF ($sessionUserId === null) {
210
-            return;
211
-        }
212
-
213
-        // Note, User::getCurrent() caches it's result, which we *really* don't want to trigger.
214
-        $currentUser = User::getById($sessionUserId, $database);
215
-
216
-        if ($currentUser === false) {
217
-            // Umm... this user has a session cookie with a userId set, but no user exists...
218
-            Session::restart();
219
-
220
-            $currentUser = User::getCurrent($database);
221
-        }
222
-
223
-        if ($currentUser->getForceLogout()) {
224
-            Session::restart();
225
-
226
-            $currentUser->setForceLogout(false);
227
-            $currentUser->save();
228
-        }
229
-    }
230
-
231
-    public function isPublic(): bool
232
-    {
233
-        return $this->isPublic;
234
-    }
235
-
236
-    public function setPublic(bool $isPublic): void
237
-    {
238
-        $this->isPublic = $isPublic;
239
-    }
36
+	/**
37
+	 * @var IRequestRouter $requestRouter The request router to use. Note that different entry points have different
38
+	 *                                    routers and hence different URL mappings
39
+	 */
40
+	private $requestRouter;
41
+	/**
42
+	 * @var bool $isPublic Determines whether to use public interface objects or internal interface objects
43
+	 */
44
+	private bool $isPublic = false;
45
+
46
+	/**
47
+	 * WebStart constructor.
48
+	 *
49
+	 * @param SiteConfiguration $configuration The site configuration
50
+	 * @param IRequestRouter    $router        The request router to use
51
+	 */
52
+	public function __construct(SiteConfiguration $configuration, IRequestRouter $router)
53
+	{
54
+		parent::__construct($configuration);
55
+
56
+		$this->requestRouter = $router;
57
+	}
58
+
59
+	/**
60
+	 * @param ITask             $page
61
+	 * @param SiteConfiguration $siteConfiguration
62
+	 * @param PdoDatabase       $database
63
+	 *
64
+	 * @return void
65
+	 */
66
+	protected function setupHelpers(
67
+		ITask $page,
68
+		SiteConfiguration $siteConfiguration,
69
+		PdoDatabase $database
70
+	) {
71
+		parent::setupHelpers($page, $siteConfiguration, $database);
72
+
73
+		if ($page instanceof PageBase) {
74
+			$page->setTokenManager(new TokenManager());
75
+			$page->setCspManager(new ContentSecurityPolicyManager($siteConfiguration));
76
+
77
+			if ($page instanceof InternalPageBase) {
78
+				$page->setTypeAheadHelper(new TypeAheadHelper());
79
+
80
+				$identificationVerifier = new IdentificationVerifier($page->getHttpHelper(), $siteConfiguration, $database);
81
+				$page->setSecurityManager(new SecurityManager($identificationVerifier, new RoleConfiguration()));
82
+
83
+				if ($siteConfiguration->getTitleBlacklistEnabled()) {
84
+					$page->setBlacklistHelper(new BlacklistHelper($page->getHttpHelper(), $database, $siteConfiguration));
85
+				}
86
+				else {
87
+					$page->setBlacklistHelper(new FakeBlacklistHelper());
88
+				}
89
+
90
+				$page->setDomainAccessManager(new DomainAccessManager($page->getSecurityManager()));
91
+			}
92
+		}
93
+	}
94
+
95
+	/**
96
+	 * Application entry point.
97
+	 *
98
+	 * Sets up the environment and runs the application, performing any global cleanup operations when done.
99
+	 */
100
+	public function run()
101
+	{
102
+		try {
103
+			if ($this->setupEnvironment()) {
104
+				$this->main();
105
+			}
106
+		}
107
+		catch (EnvironmentException $ex) {
108
+			ob_end_clean();
109
+			print Offline::getOfflineMessage($this->isPublic(), $this->getConfiguration(), $ex->getMessage());
110
+		}
111
+			/** @noinspection PhpRedundantCatchClauseInspection */
112
+		catch (ReadableException $ex) {
113
+			ob_end_clean();
114
+			print $ex->getReadableError();
115
+		}
116
+		finally {
117
+			$this->cleanupEnvironment();
118
+		}
119
+	}
120
+
121
+	/**
122
+	 * Environment setup
123
+	 *
124
+	 * This method initialises the tool environment. If the tool cannot be initialised correctly, it will return false
125
+	 * and shut down prematurely.
126
+	 *
127
+	 * @return bool
128
+	 * @throws EnvironmentException
129
+	 */
130
+	protected function setupEnvironment()
131
+	{
132
+		// initialise global exception handler
133
+		set_exception_handler(array(ExceptionHandler::class, 'exceptionHandler'));
134
+		set_error_handler(array(ExceptionHandler::class, 'errorHandler'), E_RECOVERABLE_ERROR);
135
+
136
+		// start output buffering if necessary
137
+		if (ob_get_level() === 0) {
138
+			ob_start();
139
+		}
140
+
141
+		// initialise super-global providers
142
+		WebRequest::setGlobalStateProvider(new GlobalStateProvider());
143
+
144
+		if (Offline::isOffline($this->getConfiguration())) {
145
+			print Offline::getOfflineMessage($this->isPublic(), $this->getConfiguration());
146
+			ob_end_flush();
147
+
148
+			return false;
149
+		}
150
+
151
+		// Call parent setup
152
+		if (!parent::setupEnvironment()) {
153
+			return false;
154
+		}
155
+
156
+		// Start up sessions
157
+		ini_set('session.cookie_path', $this->getConfiguration()->getCookiePath());
158
+		ini_set('session.name', $this->getConfiguration()->getCookieSessionName());
159
+		Session::start();
160
+
161
+		// Check the user is allowed to be logged in still. This must be before we call any user-loading functions and
162
+		// get the current user cached.
163
+		// I'm not sure if this function call being here is particularly a good thing, but it's part of starting up a
164
+		// session I suppose.
165
+		$this->checkForceLogout();
166
+
167
+		// environment initialised!
168
+		return true;
169
+	}
170
+
171
+	/**
172
+	 * Main application logic
173
+	 */
174
+	protected function main()
175
+	{
176
+		// Get the right route for the request
177
+		$page = $this->requestRouter->route();
178
+
179
+		$siteConfiguration = $this->getConfiguration();
180
+		$database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
181
+
182
+		$this->setupHelpers($page, $siteConfiguration, $database);
183
+
184
+		// run the route code for the request.
185
+		$page->execute();
186
+	}
187
+
188
+	/**
189
+	 * Any cleanup tasks should go here
190
+	 *
191
+	 * Note that we need to be very careful here, as exceptions may have been thrown and handled.
192
+	 * This should *only* be for cleaning up, no logic should go here.
193
+	 */
194
+	protected function cleanupEnvironment()
195
+	{
196
+		// Clean up anything we splurged after sending the page.
197
+		if (ob_get_level() > 0) {
198
+			for ($i = ob_get_level(); $i > 0; $i--) {
199
+				ob_end_clean();
200
+			}
201
+		}
202
+	}
203
+
204
+	private function checkForceLogout()
205
+	{
206
+		$database = PdoDatabase::getDatabaseConnection($this->getConfiguration());
207
+
208
+		$sessionUserId = WebRequest::getSessionUserId();
209
+		iF ($sessionUserId === null) {
210
+			return;
211
+		}
212
+
213
+		// Note, User::getCurrent() caches it's result, which we *really* don't want to trigger.
214
+		$currentUser = User::getById($sessionUserId, $database);
215
+
216
+		if ($currentUser === false) {
217
+			// Umm... this user has a session cookie with a userId set, but no user exists...
218
+			Session::restart();
219
+
220
+			$currentUser = User::getCurrent($database);
221
+		}
222
+
223
+		if ($currentUser->getForceLogout()) {
224
+			Session::restart();
225
+
226
+			$currentUser->setForceLogout(false);
227
+			$currentUser->save();
228
+		}
229
+	}
230
+
231
+	public function isPublic(): bool
232
+	{
233
+		return $this->isPublic;
234
+	}
235
+
236
+	public function setPublic(bool $isPublic): void
237
+	{
238
+		$this->isPublic = $isPublic;
239
+	}
240 240
 }
Please login to merge, or discard this patch.