Completed
Pull Request — release-2.1 (#4512)
by Colin
10:36
created

Subs-Auth.php ➔ findMembers()   F

Complexity

Conditions 13
Paths 288

Size

Total Lines 78
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 43
nc 288
nop 4
dl 0
loc 78
rs 3.8902
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has functions in it to do with authentication, user handling, and the like.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Sets the SMF-style login cookie and session based on the id_member and password passed.
21
 * - password should be already encrypted with the cookie salt.
22
 * - logs the user out if id_member is zero.
23
 * - sets the cookie and session to last the number of seconds specified by cookie_length, or
24
 *   ends them if cookie_length is less than 0.
25
 * - when logging out, if the globalCookies setting is enabled, attempts to clear the subdomain's
26
 *   cookie too.
27
 *
28
 * @param int $cookie_length How many seconds the cookie should last. If negative, forces logout.
29
 * @param int $id The ID of the member to set the cookie for
30
 * @param string $password The hashed password
31
 */
32
function setLoginCookie($cookie_length, $id, $password = '')
33
{
34
	global $smcFunc, $cookiename, $boardurl, $modSettings, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
35
36
	$id = (int) $id;
37
38
	$expiry_time = ($cookie_length >= 0 ? time() + $cookie_length : 1);
39
40
	// If changing state force them to re-address some permission caching.
41
	$_SESSION['mc']['time'] = 0;
42
43
	// Extract our cookie domain and path from $boardurl
44
	$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
45
46
	// The cookie may already exist, and have been set with different options.
47
	if (isset($_COOKIE[$cookiename]))
48
	{
49
		// First check for 2.1 json-format cookie
50
		if (preg_match('~^{"0":\d+,"1":"[0-9a-f]*","2":\d+,"3":"[^"]+","4":"[^"]+"~', $_COOKIE[$cookiename]) === 1)
51
			list(,,, $old_domain, $old_path) = $smcFunc['json_decode']($_COOKIE[$cookiename], true);
52
53
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
54
		elseif (preg_match('~^a:[34]:\{i:0;i:\d+;i:1;s:(0|128):"([a-fA-F0-9]{128})?";i:2;[id]:\d+;(i:3;i:\d;)?~', $_COOKIE[$cookiename]) === 1)
55
		{
56
			list(,,, $old_state) = safe_unserialize($_COOKIE[$cookiename]);
57
58
			$cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2);
59
60
			// Maybe we need to temporarily pretend to be using local cookies
61
			if ($cookie_state == 0 && $old_state == 1)
62
				list($old_domain, $old_path) = url_parts(true, false);
63
			else
64
				list($old_domain, $old_path) = url_parts($old_state & 1 > 0, $old_state & 2 > 0);
65
		}
66
67
		// Out with the old, in with the new!
68
		if (isset($old_domain) && $old_domain != $cookie_url[0] || isset($old_path) && $old_path != $cookie_url[1])
0 ignored issues
show
Bug introduced by
The variable $old_domain does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
69
			smf_setcookie($cookiename, $smcFunc['json_encode'](array(0, '', 0, $old_domain, $old_path), JSON_FORCE_OBJECT), 1, $old_path, $old_domain);
0 ignored issues
show
Bug introduced by
The variable $old_path does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
70
	}
71
72
	// Get the data and path to set it on.
73
	$data = empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1]) : array($id, $password, $expiry_time, $cookie_url[0], $cookie_url[1]);
74
75
	// Allow mods to add custom info to the cookie
76
	$custom_data = array();
77
	call_integration_hook('integrate_cookie_data', array($data, &$custom_data));
78
79
	$data = $smcFunc['json_encode'](array_merge($data, $custom_data), JSON_FORCE_OBJECT);
80
81
	// Set the cookie, $_COOKIE, and session variable.
82
	smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], $cookie_url[0]);
83
84
	// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too.
85
	if (empty($id) && !empty($modSettings['globalCookies']))
86
		smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], '');
87
88
	// Any alias URLs?  This is mainly for use with frames, etc.
89
	if (!empty($modSettings['forum_alias_urls']))
90
	{
91
		$aliases = explode(',', $modSettings['forum_alias_urls']);
92
93
		$temp = $boardurl;
94
		foreach ($aliases as $alias)
95
		{
96
			// Fake the $boardurl so we can set a different cookie.
97
			$alias = strtr(trim($alias), array('http://' => '', 'https://' => ''));
98
			$boardurl = 'http://' . $alias;
99
100
			$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
101
102
			if ($cookie_url[0] == '')
103
				$cookie_url[0] = strtok($alias, '/');
104
105
			$alias_data = $smcFunc['json_decode']($data);
106
			$alias_data[3] = $cookie_url[0];
107
			$alias_data[4] = $cookie_url[1];
108
			$alias_data = $smcFunc['json_encode']($alias_data, JSON_FORCE_OBJECT);
109
110
			smf_setcookie($cookiename, $alias_data, $expiry_time, $cookie_url[1], $cookie_url[0]);
111
		}
112
113
		$boardurl = $temp;
114
	}
115
116
	$_COOKIE[$cookiename] = $data;
117
118
	// Make sure the user logs in with a new session ID.
119
	if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data)
120
	{
121
		// We need to meddle with the session.
122
		require_once($sourcedir . '/Session.php');
123
124
		// Backup and remove the old session.
125
		$oldSessionData = $_SESSION;
126
		$_SESSION = array();
127
		session_destroy();
128
129
		// Recreate and restore the new session.
130
		loadSession();
131
		// @todo should we use session_regenerate_id(true); now that we are 5.1+
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
132
		session_regenerate_id();
133
		$_SESSION = $oldSessionData;
134
135
		$_SESSION['login_' . $cookiename] = $data;
136
	}
137
}
138
139
/**
140
 * Sets Two Factor Auth cookie
141
 *
142
 * @param int $cookie_length How long the cookie should last, in minutes
143
 * @param int $id The ID of the member
144
 * @param string $secret Should be a salted secret using hash_salt
145
 * @param bool $preserve Whether to preserve the cookie for 30 days
146
 */
147
function setTFACookie($cookie_length, $id, $secret, $preserve = false)
148
{
149
	global $smcFunc, $modSettings, $cookiename;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
150
151
	$identifier = $cookiename . '_tfa';
152
	$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
153
154
	if ($preserve)
155
		$cookie_length = 81600 * 30;
156
157
	// Get the data and path to set it on.
158
	$data = $smcFunc['json_encode'](empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1], false) : array($id, $secret, time() + $cookie_length, $cookie_url[0], $cookie_url[1], $preserve), JSON_FORCE_OBJECT);
159
160
	// Set the cookie, $_COOKIE, and session variable.
161
	smf_setcookie($identifier, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
162
163
	// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too.
164
	if (empty($id) && !empty($modSettings['globalCookies']))
165
		smf_setcookie($identifier, $data, time() + $cookie_length, $cookie_url[1], '');
166
167
	$_COOKIE[$identifier] = $data;
168
}
169
170
/**
171
 * Get the domain and path for the cookie
172
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
173
 * - uses boardurl to determine these two things.
174
 *
175
 * @param bool $local Whether we want local cookies
176
 * @param bool $global Whether we want global cookies
177
 * @return array An array to set the cookie on with domain and path in it, in that order
178
 */
179
function url_parts($local, $global)
180
{
181
	global $boardurl, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
182
183
	// Parse the URL with PHP to make life easier.
184
	$parsed_url = parse_url($boardurl);
185
186
	// Is local cookies off?
187
	if (empty($parsed_url['path']) || !$local)
188
		$parsed_url['path'] = '';
189
190
	if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false)
191
		$parsed_url['host'] = $modSettings['globalCookiesDomain'];
192
193
	// Globalize cookies across domains (filter out IP-addresses)?
194
	elseif ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
195
		$parsed_url['host'] = '.' . $parts[1];
196
197
	// We shouldn't use a host at all if both options are off.
198
	elseif (!$local && !$global)
199
		$parsed_url['host'] = '';
200
201
	// The host also shouldn't be set if there aren't any dots in it.
202
	elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false)
203
		$parsed_url['host'] = '';
204
205
	return array($parsed_url['host'], $parsed_url['path'] . '/');
206
}
207
208
/**
209
 * Throws guests out to the login screen when guest access is off.
210
 * - sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL'].
211
 * - uses the 'kick_guest' sub template found in Login.template.php.
212
 */
213
function KickGuest()
214
{
215
	global $txt, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
216
217
	loadLanguage('Login');
218
	loadTemplate('Login');
219
	createToken('login');
220
221
	// Never redirect to an attachment
222 View Code Duplication
	if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false)
223
		$_SESSION['login_url'] = $_SERVER['REQUEST_URL'];
224
225
	$context['sub_template'] = 'kick_guest';
226
	$context['page_title'] = $txt['login'];
227
}
228
229
/**
230
 * Display a message about the forum being in maintenance mode.
231
 * - display a login screen with sub template 'maintenance'.
232
 * - sends a 503 header, so search engines don't bother indexing while we're in maintenance mode.
233
 */
234
function InMaintenance()
235
{
236
	global $txt, $mtitle, $mmessage, $context, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
237
238
	loadLanguage('Login');
239
	loadTemplate('Login');
240
	createToken('login');
241
242
	// Send a 503 header, so search engines don't bother indexing while we're in maintenance mode.
243
	header('HTTP/1.1 503 Service Temporarily Unavailable');
244
245
	// Basic template stuff..
246
	$context['sub_template'] = 'maintenance';
247
	$context['title'] = $smcFunc['htmlspecialchars']($mtitle);
248
	$context['description'] = &$mmessage;
249
	$context['page_title'] = $txt['maintain_mode'];
250
}
251
252
/**
253
 * Question the verity of the admin by asking for his or her password.
254
 * - loads Login.template.php and uses the admin_login sub template.
255
 * - sends data to template so the admin is sent on to the page they
256
 *   wanted if their password is correct, otherwise they can try again.
257
 *
258
 * @param string $type What login type is this - can be 'admin' or 'moderate'
259
 */
260
function adminLogin($type = 'admin')
261
{
262
	global $context, $txt, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
263
264
	loadLanguage('Admin');
265
	loadTemplate('Login');
266
267
	// Validate what type of session check this is.
268
	$types = array();
269
	call_integration_hook('integrate_validateSession', array(&$types));
270
	$type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
271
272
	// They used a wrong password, log it and unset that.
273
	if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass']))
274
	{
275
		$txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']);
276
		log_error($txt['security_wrong'], 'critical');
277
278
		if (isset($_POST[$type . '_hash_pass']))
279
			unset($_POST[$type . '_hash_pass']);
280
		if (isset($_POST[$type . '_pass']))
281
			unset($_POST[$type . '_pass']);
282
283
		$context['incorrect_password'] = true;
284
	}
285
286
	createToken('admin-login');
287
288
	// Figure out the get data and post data.
289
	$context['get_data'] = '?' . construct_query_string($_GET);
290
	$context['post_data'] = '';
291
292
	// Now go through $_POST.  Make sure the session hash is sent.
293
	$_POST[$context['session_var']] = $context['session_id'];
294
	foreach ($_POST as $k => $v)
295
		$context['post_data'] .= adminLogin_outputPostVars($k, $v);
296
297
	// Now we'll use the admin_login sub template of the Login template.
298
	$context['sub_template'] = 'admin_login';
299
300
	// And title the page something like "Login".
301
	if (!isset($context['page_title']))
302
		$context['page_title'] = $txt['login'];
303
304
	// The type of action.
305
	$context['sessionCheckType'] = $type;
306
307
	obExit();
308
309
	// We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged.
310
	trigger_error('Hacking attempt...', E_USER_ERROR);
311
}
312
313
/**
314
 * Used by the adminLogin() function.
315
 * if 'value' is an array, the function is called recursively.
316
 *
317
 * @param string $k The keys
318
 * @param string $v The values
319
 * @return string 'hidden' HTML form fields, containing key-value-pairs
320
 */
321
function adminLogin_outputPostVars($k, $v)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $k. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $v. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
322
{
323
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
324
325
	if (!is_array($v))
326
		return '
327
<input type="hidden" name="' . $smcFunc['htmlspecialchars']($k) . '" value="' . strtr($v, array('"' => '&quot;', '<' => '&lt;', '>' => '&gt;')) . '">';
328
	else
329
	{
330
		$ret = '';
331
		foreach ($v as $k2 => $v2)
332
			$ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2);
333
334
		return $ret;
335
	}
336
}
337
338
/**
339
 * Properly urlencodes a string to be used in a query
340
 *
341
 * @param string $get
342
 * @return string Our query string
343
 */
344
function construct_query_string($get)
345
{
346
	global $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
347
348
	$query_string = '';
349
350
	// Awww, darn.  The $scripturl contains GET stuff!
351
	$q = strpos($scripturl, '?');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
352
	if ($q !== false)
353
	{
354
		parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp);
355
356
		foreach ($get as $k => $v)
0 ignored issues
show
Bug introduced by
The expression $get of type string is not traversable.
Loading history...
357
		{
358
			// Only if it's not already in the $scripturl!
359
			if (!isset($temp[$k]))
360
				$query_string .= urlencode($k) . '=' . urlencode($v) . ';';
361
			// If it changed, put it out there, but with an ampersand.
362 View Code Duplication
			elseif ($temp[$k] != $get[$k])
363
				$query_string .= urlencode($k) . '=' . urlencode($v) . '&amp;';
364
		}
365
	}
366 View Code Duplication
	else
367
	{
368
		// Add up all the data from $_GET into get_data.
369
		foreach ($get as $k => $v)
0 ignored issues
show
Bug introduced by
The expression $get of type string is not traversable.
Loading history...
370
			$query_string .= urlencode($k) . '=' . urlencode($v) . ';';
371
	}
372
373
	$query_string = substr($query_string, 0, -1);
374
	return $query_string;
375
}
376
377
/**
378
 * Finds members by email address, username, or real name.
379
 * - searches for members whose username, display name, or e-mail address match the given pattern of array names.
380
 * - searches only buddies if buddies_only is set.
381
 *
382
 * @param array $names The names of members to search for
383
 * @param bool $use_wildcards Whether to use wildcards. Accepts wildcards ? and * in the pattern if true
384
 * @param bool $buddies_only Whether to only search for the user's buddies
385
 * @param int $max The maximum number of results
386
 * @return array An array containing information about the matching members
387
 */
388
function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500)
389
{
390
	global $scripturl, $user_info, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
391
392
	// If it's not already an array, make it one.
393
	if (!is_array($names))
394
		$names = explode(',', $names);
395
396
	$maybe_email = false;
397
	$names_list = array();
398
	foreach ($names as $i => $name)
399
	{
400
		// Trim, and fix wildcards for each name.
401
		$names[$i] = trim($smcFunc['strtolower']($name));
402
403
		$maybe_email |= strpos($name, '@') !== false;
404
405
		// Make it so standard wildcards will work. (* and ?)
406
		if ($use_wildcards)
407
			$names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => '&#039;'));
408
		else
409
			$names[$i] = strtr($names[$i], array('\'' => '&#039;'));
410
		
411
		$names_list[] = '{string:lookup_name_' . $i . '}';
412
		$where_params['lookup_name_' . $i] = $names[$i];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$where_params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $where_params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
413
	}
414
415
	// What are we using to compare?
416
	$comparison = $use_wildcards ? 'LIKE' : '=';
417
418
	// Nothing found yet.
419
	$results = array();
420
421
	// This ensures you can't search someones email address if you can't see it.
422
	if (($use_wildcards || $maybe_email) && allowedTo('moderate_forum'))
423
		$email_condition = '
424
			OR (email_address ' . $comparison . ' \'' . implode('\') OR (email_address ' . $comparison . ' \'', $names) . '\')';
425
	else
426
		$email_condition = '';
427
428
	// Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise.
429
	$member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name';
430
	$real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
431
432
	// Searches.
433
	$member_name_search = $member_name . ' ' . $comparison . ' ' . implode( ' OR ' . $member_name . ' ' . $comparison . ' ', $names_list);
434
	$real_name_search = $real_name . ' ' . $comparison . ' ' . implode( ' OR ' . $real_name . ' ' . $comparison . ' ', $names_list);
435
436
	// Search by username, display name, and email address.
437
	$request = $smcFunc['db_query']('', '
438
		SELECT id_member, member_name, real_name, email_address
439
		FROM {db_prefix}members
440
		WHERE (' . $member_name_search . '
441
			OR ' . $real_name_search . ' ' . $email_condition . ')
442
			' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . '
443
			AND is_activated IN (1, 11)
444
		LIMIT {int:limit}',
445
		array_merge($where_params, array(
0 ignored issues
show
Bug introduced by
The variable $where_params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
446
			'buddy_list' => $user_info['buddies'],
447
			'limit' => $max,
448
		))
449
	);
450
	while ($row = $smcFunc['db_fetch_assoc']($request))
451
	{
452
		$results[$row['id_member']] = array(
453
			'id' => $row['id_member'],
454
			'name' => $row['real_name'],
455
			'username' => $row['member_name'],
456
			'email' => allowedTo('moderate_forum') ? $row['email_address'] : '',
457
			'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
458
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>'
459
		);
460
	}
461
	$smcFunc['db_free_result']($request);
462
463
	// Return all the results.
464
	return $results;
465
}
466
467
/**
468
 * Called by index.php?action=findmember.
469
 * - is used as a popup for searching members.
470
 * - uses sub template find_members of the Help template.
471
 * - also used to add members for PM's sent using wap2/imode protocol.
472
 */
473
function JSMembers()
474
{
475
	global $context, $scripturl, $user_info, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
476
477
	checkSession('get');
478
479
	// Why is this in the Help template, you ask?  Well, erm... it helps you.  Does that work?
480
	loadTemplate('Help');
481
482
	$context['template_layers'] = array();
483
	$context['sub_template'] = 'find_members';
484
485
	if (isset($_REQUEST['search']))
486
		$context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
487
	else
488
		$_REQUEST['start'] = 0;
489
490
	// Allow the user to pass the input to be added to to the box.
491
	$context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to';
492
493
	// Take the delimiter over GET in case it's \n or something.
494
	$context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', ';
495
	$context['quote_results'] = !empty($_REQUEST['quote']);
496
497
	// List all the results.
498
	$context['results'] = array();
499
500
	// Some buddy related settings ;)
501
	$context['show_buddies'] = !empty($user_info['buddies']);
502
	$context['buddy_search'] = isset($_REQUEST['buddies']);
503
504
	// If the user has done a search, well - search.
505
	if (isset($_REQUEST['search']))
506
	{
507
		$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
508
509
		$context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']);
510
		$total_results = count($context['results']);
511
512
		$context['page_index'] = constructPageIndex($scripturl . '?action=findmember;search=' . $context['last_search'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';input=' . $context['input_box_name'] . ($context['quote_results'] ? ';quote=1' : '') . ($context['buddy_search'] ? ';buddies' : ''), $_REQUEST['start'], $total_results, 7);
513
514
		// Determine the navigation context.
515
		$base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id'];
516
		$context['links'] = array(
517
			'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '',
518
			'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '',
519
			'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '',
520
			'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '',
521
			'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']),
522
		);
523
		$context['page_info'] = array(
524
			'current_page' => $_REQUEST['start'] / 7 + 1,
525
			'num_pages' => floor(($total_results - 1) / 7) + 1
526
		);
527
528
		$context['results'] = array_slice($context['results'], $_REQUEST['start'], 7);
529
	}
530
	else
531
		$context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']);
532
}
533
534
/**
535
 * Outputs each member name on its own line.
536
 * - used by javascript to find members matching the request.
537
 */
538
function RequestMembers()
539
{
540
	global $user_info, $txt, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
541
542
	checkSession('get');
543
544
	$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*';
545
	$_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search']));
546
	$_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&#038;' => '&amp;'));
547
548
	if (function_exists('iconv'))
549
		header('Content-Type: text/plain; charset=UTF-8');
550
551
	$request = $smcFunc['db_query']('', '
552
		SELECT real_name
553
		FROM {db_prefix}members
554
		WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
555
			AND id_member IN ({array_int:buddy_list})' : '') . '
556
			AND is_activated IN (1, 11)
557
		LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'),
558
		array(
559
			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
560
			'buddy_list' => $user_info['buddies'],
561
			'search' => $_REQUEST['search'],
562
		)
563
	);
564
	while ($row = $smcFunc['db_fetch_assoc']($request))
565
	{
566
		if (function_exists('iconv'))
567
		{
568
			$utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']);
569
			if ($utf8)
570
				$row['real_name'] = $utf8;
571
		}
572
573
		$row['real_name'] = strtr($row['real_name'], array('&amp;' => '&#038;', '&lt;' => '&#060;', '&gt;' => '&#062;', '&quot;' => '&#034;'));
574
575
		if (preg_match('~&#\d+;~', $row['real_name']) != 0)
576
			$row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']);
577
578
		echo $row['real_name'], "\n";
579
	}
580
	$smcFunc['db_free_result']($request);
581
582
	obExit(false);
583
}
584
585
/**
586
 * Generates a random password for a user and emails it to them.
587
 * - called by Profile.php when changing someone's username.
588
 * - checks the validity of the new username.
589
 * - generates and sets a new password for the given user.
590
 * - mails the new password to the email address of the user.
591
 * - if username is not set, only a new password is generated and sent.
592
 *
593
 * @param int $memID The ID of the member
594
 * @param string $username The new username. If set, also checks the validity of the username
0 ignored issues
show
Documentation introduced by
Should the type for parameter $username not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
595
 */
596
function resetPassword($memID, $username = null)
597
{
598
	global $sourcedir, $modSettings, $smcFunc, $language;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
599
600
	// Language... and a required file.
601
	loadLanguage('Login');
602
	require_once($sourcedir . '/Subs-Post.php');
603
604
	// Get some important details.
605
	$request = $smcFunc['db_query']('', '
606
		SELECT member_name, email_address, lngfile
607
		FROM {db_prefix}members
608
		WHERE id_member = {int:id_member}',
609
		array(
610
			'id_member' => $memID,
611
		)
612
	);
613
	list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request);
614
	$smcFunc['db_free_result']($request);
615
616
	if ($username !== null)
617
	{
618
		$old_user = $user;
619
		$user = trim($username);
620
	}
621
622
	// Generate a random password.
623
	$newPassword = substr(preg_replace('/\W/', '', md5(mt_rand())), 0, 10);
624
	$newPassword_sha1 = hash_password($user, $newPassword);
625
626
	// Do some checks on the username if needed.
627
	if ($username !== null)
628
	{
629
		validateUsername($memID, $user);
630
631
		// Update the database...
632
		updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1));
633
	}
634
	else
635
		updateMemberData($memID, array('passwd' => $newPassword_sha1));
636
637
	call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword));
0 ignored issues
show
Bug introduced by
The variable $old_user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
638
639
	$replacements = array(
640
		'USERNAME' => $user,
641
		'PASSWORD' => $newPassword,
642
	);
643
644
	$emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile);
645
646
	// Send them the email informing them of the change - then we're done!
647
	sendmail($email, $emaildata['subject'], $emaildata['body'], null, 'chgpass' . $memID, $emaildata['is_html'], 0);
648
}
649
650
/**
651
 * Checks a username obeys a load of rules
652
 *
653
 * @param int $memID The ID of the member
654
 * @param string $username The username to validate
655
 * @param boolean $return_error Whether to return errors
656
 * @param boolean $check_reserved_name Whether to check this against the list of reserved names
657
 * @return array|null Null if there are no errors, otherwise an array of errors if return_error is true
658
 */
659
function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true)
660
{
661
	global $sourcedir, $txt, $smcFunc, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
662
663
	$errors = array();
664
665
	// Don't use too long a name.
666
	if ($smcFunc['strlen']($username) > 25)
667
		$errors[] = array('lang', 'error_long_name');
668
669
	// No name?!  How can you register with no name?
670
	if ($username == '')
671
		$errors[] = array('lang', 'need_username');
672
673
	// Only these characters are permitted.
674
	if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false)
675
		$errors[] = array('lang', 'error_invalid_characters_username');
676
677
	if (stristr($username, $txt['guest_title']) !== false)
678
		$errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title']));
679
680
	if ($check_reserved_name)
681
	{
682
		require_once($sourcedir . '/Subs-Members.php');
683
		if (isReservedName($username, $memID, false))
684
			$errors[] = array('done', '(' . $smcFunc['htmlspecialchars']($username) . ') ' . $txt['name_in_use']);
685
	}
686
687
	if ($return_error)
688
		return $errors;
689
	elseif (empty($errors))
690
		return null;
691
692
	loadLanguage('Errors');
693
	$error = $errors[0];
694
695
	$message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1];
696
	fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]);
697
}
698
699
/**
700
 * Checks whether a password meets the current forum rules
701
 * - called when registering/choosing a password.
702
 * - checks the password obeys the current forum settings for password strength.
703
 * - if password checking is enabled, will check that none of the words in restrict_in appear in the password.
704
 * - returns an error identifier if the password is invalid, or null.
705
 *
706
 * @param string $password The desired password
707
 * @param string $username The username
708
 * @param array $restrict_in An array of restricted strings that cannot be part of the password (email address, username, etc.)
709
 * @return null|string Null if valid or a string indicating what the problem was
710
 */
711
function validatePassword($password, $username, $restrict_in = array())
712
{
713
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
714
715
	// Perform basic requirements first.
716
	if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8))
717
		return 'short';
718
719
	// Is this enough?
720
	if (empty($modSettings['password_strength']))
721
		return null;
722
723
	// Otherwise, perform the medium strength test - checking if password appears in the restricted string.
724
	if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0)
725
		return 'restricted_words';
726
	elseif ($smcFunc['strpos']($password, $username) !== false)
727
		return 'restricted_words';
728
729
	// If just medium, we're done.
730
	if ($modSettings['password_strength'] == 1)
731
		return null;
732
733
	// Otherwise, hard test next, check for numbers and letters, uppercase too.
734
	$good = preg_match('~(\D\d|\d\D)~', $password) != 0;
735
	$good &= $smcFunc['strtolower']($password) != $password;
736
737
	return $good ? null : 'chars';
738
}
739
740
/**
741
 * Quickly find out what moderation authority this user has
742
 * - builds the moderator, group and board level querys for the user
743
 * - stores the information on the current users moderation powers in $user_info['mod_cache'] and $_SESSION['mc']
744
 */
745
function rebuildModCache()
746
{
747
	global $user_info, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
748
749
	// What groups can they moderate?
750
	$group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1';
751
752
	if ($group_query == '0=1' && !$user_info['is_guest'])
753
	{
754
		$request = $smcFunc['db_query']('', '
755
			SELECT id_group
756
			FROM {db_prefix}group_moderators
757
			WHERE id_member = {int:current_member}',
758
			array(
759
				'current_member' => $user_info['id'],
760
			)
761
		);
762
		$groups = array();
763
		while ($row = $smcFunc['db_fetch_assoc']($request))
764
			$groups[] = $row['id_group'];
765
		$smcFunc['db_free_result']($request);
766
767
		if (empty($groups))
768
			$group_query = '0=1';
769
		else
770
			$group_query = 'id_group IN (' . implode(',', $groups) . ')';
771
	}
772
773
	// Then, same again, just the boards this time!
774
	$board_query = allowedTo('moderate_forum') ? '1=1' : '0=1';
775
776
	if ($board_query == '0=1' && !$user_info['is_guest'])
777
	{
778
		$boards = boardsAllowedTo('moderate_board', true);
779
780
		if (empty($boards))
781
			$board_query = '0=1';
782
		else
783
			$board_query = 'id_board IN (' . implode(',', $boards) . ')';
784
	}
785
786
	// What boards are they the moderator of?
787
	$boards_mod = array();
788
	if (!$user_info['is_guest'])
789
	{
790
		$request = $smcFunc['db_query']('', '
791
			SELECT id_board
792
			FROM {db_prefix}moderators
793
			WHERE id_member = {int:current_member}',
794
			array(
795
				'current_member' => $user_info['id'],
796
			)
797
		);
798
		while ($row = $smcFunc['db_fetch_assoc']($request))
799
			$boards_mod[] = $row['id_board'];
800
		$smcFunc['db_free_result']($request);
801
802
		// Can any of the groups they're in moderate any of the boards?
803
		$request = $smcFunc['db_query']('', '
804
			SELECT id_board
805
			FROM {db_prefix}moderator_groups
806
			WHERE id_group IN({array_int:groups})',
807
			array(
808
				'groups' => $user_info['groups'],
809
			)
810
		);
811
		while ($row = $smcFunc['db_fetch_assoc']($request))
812
			$boards_mod[] = $row['id_board'];
813
		$smcFunc['db_free_result']($request);
814
815
		// Just in case we've got duplicates here...
816
		$boards_mod = array_unique($boards_mod);
817
	}
818
819
	$mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')';
820
821
	$_SESSION['mc'] = array(
822
		'time' => time(),
823
		// This looks a bit funny but protects against the login redirect.
824
		'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0,
825
		// If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php.
826
		'gq' => $group_query,
827
		'bq' => $board_query,
828
		'ap' => boardsAllowedTo('approve_posts'),
829
		'mb' => $boards_mod,
830
		'mq' => $mod_query,
831
	);
832
	call_integration_hook('integrate_mod_cache');
833
834
	$user_info['mod_cache'] = $_SESSION['mc'];
835
836
	// Might as well clean up some tokens while we are at it.
837
	cleanTokens();
838
}
839
840
/**
841
 * The same thing as setcookie but gives support for HTTP-Only cookies in PHP < 5.2
842
 * @todo We can remove this since SMF requires PHP >= 5.3.8 now
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
843
 *
844
 * @param string $name
845
 * @param string $value = ''
846
 * @param int $expire = 0
847
 * @param string $path = ''
848
 * @param string $domain = ''
849
 * @param bool $secure = false
0 ignored issues
show
Documentation introduced by
Should the type for parameter $secure not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
850
 * @param bool $httponly = true
851
 */
852
function smf_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = true)
853
{
854
	global $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
855
856
	// In case a customization wants to override the default settings
857
	if ($httponly === null)
858
		$httponly = !empty($modSettings['httponlyCookies']);
859
	if ($secure === null)
860
		$secure = !empty($modSettings['secureCookies']);
861
862
	// Intercept cookie?
863
	call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly));
864
865
	// This function is pointless if we have PHP >= 5.2.
866
	return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
867
}
868
869
/**
870
 * Hashes username with password
871
 *
872
 * @param string $username The username
873
 * @param string $password The unhashed password
874
 * @param int $cost The cost
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cost not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
875
 * @return string The hashed password
0 ignored issues
show
Documentation introduced by
Should the return type not be null|false|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
876
 */
877
function hash_password($username, $password, $cost = null)
878
{
879
	global $sourcedir, $smcFunc, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
880
	if (!function_exists('password_hash'))
881
		require_once($sourcedir . '/Subs-Password.php');
882
883
	$cost = empty($cost) ? (empty($modSettings['bcrypt_hash_cost']) ? 10 : $modSettings['bcrypt_hash_cost']) : $cost;
884
885
	return password_hash($smcFunc['strtolower']($username) . $password, PASSWORD_BCRYPT, array(
886
		'cost' => $cost,
887
	));
888
}
889
890
/**
891
 * Hashes password with salt, this is solely used for cookies.
892
 *
893
 * @param string $password The password
894
 * @param string $salt The salt
895
 * @return string The hashed password
896
 */
897
function hash_salt($password, $salt)
898
{
899
	return hash('sha512', $password . $salt);
900
}
901
902
/**
903
 * Verifies a raw SMF password against the bcrypt'd string
904
 *
905
 * @param string $username The username
906
 * @param string $password The password
907
 * @param string $hash The hashed string
908
 * @return bool Whether the hashed password matches the string
909
 */
910
function hash_verify_password($username, $password, $hash)
911
{
912
	global $sourcedir, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
913
	if (!function_exists('password_verify'))
914
		require_once($sourcedir . '/Subs-Password.php');
915
916
	return password_verify($smcFunc['strtolower']($username) . $password, $hash);
917
}
918
919
/**
920
 * Returns the length for current hash
921
 *
922
 * @return int The length for the current hash
923
 */
924
function hash_length()
925
{
926
	return 60;
927
}
928
929
/**
930
 * Benchmarks the server to figure out an appropriate cost factor (minimum 9)
931
 *
932
 * @param float $hashTime Time to target, in seconds
933
 * @return int The cost
934
 */
935
function hash_benchmark($hashTime = 0.2)
936
{
937
	$cost = 9;
938
	do {
939
		$timeStart = microtime(true);
940
		hash_password('test', 'thisisatestpassword', $cost);
941
		$timeTaken = microtime(true) - $timeStart;
942
		$cost++;
943
	} while ($timeTaken < $hashTime);
944
945
	return $cost;
946
}
947
948
?>