Completed
Pull Request — release-2.1 (#4993)
by Jeremy
06:18
created

Reminder.php ➔ SecretAnswer2()   C

Complexity

Conditions 10
Paths 64

Size

Total Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 64
nop 0
dl 0
loc 70
rs 6.7878
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
 * Handle sending out reminders, and checking the secret answer and question.  It uses just a few functions to do this, which are:
5
 * Simple Machines Forum (SMF)
6
 *
7
 * @package SMF
8
 * @author Simple Machines http://www.simplemachines.org
9
 * @copyright 2018 Simple Machines and individual contributors
10
 * @license http://www.simplemachines.org/about/smf/license.php BSD
11
 *
12
 * @version 2.1 Beta 4
13
 */
14
15
if (!defined('SMF'))
16
	die('No direct access...');
17
18
/**
19
 * This is the controlling delegator
20
 * @uses Profile language files and Reminder template
21
 */
22
function RemindMe()
23
{
24
	global $txt, $context;
25
26
	loadLanguage('Profile');
27
	loadTemplate('Reminder');
28
29
	$context['page_title'] = $txt['authentication_reminder'];
30
	$context['robot_no_index'] = true;
31
32
	// Delegation can be useful sometimes.
33
	$subActions = array(
34
		'picktype' => 'RemindPick',
35
		'secret2' => 'SecretAnswer2',
36
		'setpassword' =>'setPassword',
37
		'setpassword2' =>'setPassword2'
38
	);
39
40
	// Any subaction?  If none, fall through to the main template, which will ask for one.
41 View Code Duplication
	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
42
		call_helper($subActions[$_REQUEST['sa']]);
43
44
	// Creating a one time token.
45
	else
46
		createToken('remind');
47
}
48
49
/**
50
 * Allows the user to pick how they wish to be reminded
51
 */
52
function RemindPick()
53
{
54
	global $context, $txt, $scripturl, $sourcedir, $user_info, $webmaster_email, $smcFunc, $language, $modSettings;
55
56
	checkSession();
57
	validateToken('remind');
58
	createToken('remind');
59
60
	// Coming with a known ID?
61
	if (!empty($_REQUEST['uid']))
62
	{
63
		$where = 'id_member = {int:id_member}';
64
		$where_params['id_member'] = (int) $_REQUEST['uid'];
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...
65
	}
66
	elseif (isset($_POST['user']) && $_POST['user'] != '')
67
	{
68
		$where = 'member_name = {string:member_name}';
69
		$where_params['member_name'] = $_POST['user'];
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...
70
		$where_params['email_address'] = $_POST['user'];
71
	}
72
73
	// You must enter a username/email address.
74
	if (empty($where))
75
		fatal_lang_error('username_no_exist', false);
76
77
	// Make sure we are not being slammed
78
	// Don't call this if you're coming from the "Choose a reminder type" page - otherwise you'll likely get an error
79
	if (!isset($_POST['reminder_type']) || !in_array($_POST['reminder_type'], array('email', 'secret')))
80
	{
81
		spamProtection('remind');
82
	}
83
84
	// Find the user!
85
	$request = $smcFunc['db_query']('', '
86
		SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, secret_question
87
		FROM {db_prefix}members
88
		WHERE ' . $where . '
0 ignored issues
show
Bug introduced by
The variable $where 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...
89
		LIMIT 1',
90
		$where_params
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...
91
	);
92
	// Maybe email?
93
	if ($smcFunc['db_num_rows']($request) == 0 && empty($_REQUEST['uid']))
94
	{
95
		$smcFunc['db_free_result']($request);
96
97
		$request = $smcFunc['db_query']('', '
98
			SELECT id_member, real_name, member_name, email_address, is_activated, validation_code, lngfile, secret_question
99
			FROM {db_prefix}members
100
			WHERE email_address = {string:email_address}
101
			LIMIT 1',
102
			$where_params
103
		);
104
		if ($smcFunc['db_num_rows']($request) == 0)
105
			fatal_lang_error('no_user_with_email', false);
106
	}
107
108
	$row = $smcFunc['db_fetch_assoc']($request);
109
	$smcFunc['db_free_result']($request);
110
111
	// If the user isn't activated/approved, give them some feedback on what to do next.
112
	if ($row['is_activated'] != 1)
113
	{
114
		// Awaiting approval...
115
		if (trim($row['validation_code']) == '')
116
			fatal_error(sprintf($txt['registration_not_approved'], $scripturl . '?action=activate;user=' . $_POST['user']), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
117
		else
118
			fatal_error(sprintf($txt['registration_not_activated'], $scripturl . '?action=activate;user=' . $_POST['user']), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
119
	}
120
121
	// You can't get emailed if you have no email address.
122
	$row['email_address'] = trim($row['email_address']);
123
	if ($row['email_address'] == '')
124
		fatal_error($txt['no_reminder_email'] . '<br>' . $txt['send_email'] . ' <a href="mailto:' . $webmaster_email . '">webmaster</a> ' . $txt['to_ask_password'] . '.');
125
126
	// If they have no secret question then they can only get emailed the item, or they are requesting the email, send them an email.
127
	if (empty($row['secret_question']) || (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'email'))
128
	{
129
		// Randomly generate a new password, with only alpha numeric characters that is a max length of 10 chars.
130
		require_once($sourcedir . '/Subs-Members.php');
131
		$password = generateValidationCode();
132
133
		require_once($sourcedir . '/Subs-Post.php');
134
		$replacements = array(
135
			'REALNAME' => $row['real_name'],
136
			'REMINDLINK' => $scripturl . '?action=reminder;sa=setpassword;u=' . $row['id_member'] . ';code=' . $password,
137
			'IP' => $user_info['ip'],
138
			'MEMBERNAME' => $row['member_name'],
139
		);
140
141
		$emaildata = loadEmailTemplate('forgot_password', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
142
		$context['description'] = $txt['reminder_sent'];
143
144
		sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'reminder', $emaildata['is_html'], 1);
0 ignored issues
show
Documentation introduced by
$row['email_address'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145
146
		// Set the password in the database.
147
		updateMemberData($row['id_member'], array('validation_code' => substr(md5($password), 0, 10)));
148
149
		// Set up the template.
150
		$context['sub_template'] = 'sent';
151
152
		// Don't really.
153
		return;
154
	}
155
	// Otherwise are ready to answer the question?
156
	elseif (isset($_POST['reminder_type']) && $_POST['reminder_type'] == 'secret')
157
	{
158
		return SecretAnswerInput();
159
	}
160
161
	// No we're here setup the context for template number 2!
162
	$context['sub_template'] = 'reminder_pick';
163
	$context['current_member'] = array(
164
		'id' => $row['id_member'],
165
		'name' => $row['member_name'],
166
	);
167
}
168
169
/**
170
 * Allows the user to set their new password
171
 */
172
function setPassword()
173
{
174
	global $txt, $context;
175
176
	loadLanguage('Login');
177
178
	// You need a code!
179
	if (!isset($_REQUEST['code']))
180
		fatal_lang_error('no_access', false);
181
182
	// Fill the context array.
183
	$context += array(
184
		'page_title' => $txt['reminder_set_password'],
185
		'sub_template' => 'set_password',
186
		'code' => $_REQUEST['code'],
187
		'memID' => (int) $_REQUEST['u']
188
	);
189
190
	loadJavaScriptFile('register.js', array('defer' => false, 'minimize' => true), 'smf_register');
191
192
	// Tokens!
193
	createToken('remind-sp');
194
}
195
196
/**
197
 * Actually sets the new password
198
 */
199
function setPassword2()
200
{
201
	global $context, $txt, $smcFunc, $sourcedir;
202
203
	checkSession();
204
	validateToken('remind-sp');
205
206
	if (empty($_POST['u']) || !isset($_POST['passwrd1']) || !isset($_POST['passwrd2']))
207
		fatal_lang_error('no_access', false);
208
209
	$_POST['u'] = (int) $_POST['u'];
210
211
	if ($_POST['passwrd1'] != $_POST['passwrd2'])
212
		fatal_lang_error('passwords_dont_match', false);
213
214
	if ($_POST['passwrd1'] == '')
215
		fatal_lang_error('no_password', false);
216
217
	loadLanguage('Login');
218
219
	// Get the code as it should be from the database.
220
	$request = $smcFunc['db_query']('', '
221
		SELECT validation_code, member_name, email_address, passwd_flood
222
		FROM {db_prefix}members
223
		WHERE id_member = {int:id_member}
224
			AND is_activated = {int:is_activated}
225
			AND validation_code != {string:blank_string}
226
		LIMIT 1',
227
		array(
228
			'id_member' => $_POST['u'],
229
			'is_activated' => 1,
230
			'blank_string' => '',
231
		)
232
	);
233
234
	// Does this user exist at all?
235
	if ($smcFunc['db_num_rows']($request) == 0)
236
		fatal_lang_error('invalid_userid', false);
237
238
	list ($realCode, $username, $email, $flood_value) = $smcFunc['db_fetch_row']($request);
239
	$smcFunc['db_free_result']($request);
240
241
	// Is the password actually valid?
242
	require_once($sourcedir . '/Subs-Auth.php');
243
	$passwordError = validatePassword($_POST['passwrd1'], $username, array($email));
244
245
	// What - it's not?
246
	if ($passwordError != null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $passwordError of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
247
		fatal_lang_error('profile_error_password_' . $passwordError, false);
248
249
	require_once($sourcedir . '/LogInOut.php');
250
251
	// Quit if this code is not right.
252
	if (empty($_POST['code']) || substr($realCode, 0, 10) !== substr(md5($_POST['code']), 0, 10))
253
	{
254
		// Stop brute force attacks like this.
255
		validatePasswordFlood($_POST['u'], $flood_value, false);
256
257
		fatal_error($txt['invalid_activation_code'], false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
258
	}
259
260
	// Just in case, flood control.
261
	validatePasswordFlood($_POST['u'], $flood_value, true);
262
263
	// User validated.  Update the database!
264
	updateMemberData($_POST['u'], array('validation_code' => '', 'passwd' => hash_password($username, $_POST['passwrd1'])));
265
266
	call_integration_hook('integrate_reset_pass', array($username, $username, $_POST['passwrd1']));
267
268
	loadTemplate('Login');
269
	$context += array(
270
		'page_title' => $txt['reminder_password_set'],
271
		'sub_template' => 'login',
272
		'default_username' => $username,
273
		'default_password' => $_POST['passwrd1'],
274
		'never_expire' => false,
275
		'description' => $txt['reminder_password_set']
276
	);
277
278
	createToken('login');
279
}
280
281
/**
282
 * Allows the user to enter their secret answer
283
 */
284
function SecretAnswerInput()
285
{
286
	global $context, $smcFunc;
287
288
	checkSession();
289
290
	// Strings for the register auto javascript clever stuffy wuffy.
291
	loadLanguage('Login');
292
293
	// Check they entered something...
294
	if (empty($_REQUEST['uid']))
295
		fatal_lang_error('username_no_exist', false);
296
297
	// Get the stuff....
298
	$request = $smcFunc['db_query']('', '
299
		SELECT id_member, real_name, member_name, secret_question
300
		FROM {db_prefix}members
301
		WHERE id_member = {int:id_member}
302
		LIMIT 1',
303
		array(
304
			'id_member' => (int) $_REQUEST['uid'],
305
		)
306
	);
307
	if ($smcFunc['db_num_rows']($request) == 0)
308
		fatal_lang_error('username_no_exist', false);
309
310
	$row = $smcFunc['db_fetch_assoc']($request);
311
	$smcFunc['db_free_result']($request);
312
313
	// If there is NO secret question - then throw an error.
314
	if (trim($row['secret_question']) == '')
315
		fatal_lang_error('registration_no_secret_question', false);
316
317
	// Ask for the answer...
318
	$context['remind_user'] = $row['id_member'];
319
	$context['remind_type'] = '';
320
	$context['secret_question'] = $row['secret_question'];
321
322
	$context['sub_template'] = 'ask';
323
	createToken('remind-sai');
324
	loadJavaScriptFile('register.js', array('defer' => false, 'minimize' => true), 'smf_register');
325
}
326
327
/**
328
 * Validates the secret answer input by the user
329
 */
330
function SecretAnswer2()
331
{
332
	global $txt, $context, $smcFunc, $sourcedir;
333
334
	checkSession();
335
	validateToken('remind-sai');
336
337
	// Hacker?  How did you get this far without an email or username?
338
	if (empty($_REQUEST['uid']))
339
		fatal_lang_error('username_no_exist', false);
340
341
	loadLanguage('Login');
342
343
	// Get the information from the database.
344
	$request = $smcFunc['db_query']('', '
345
		SELECT id_member, real_name, member_name, secret_answer, secret_question, email_address
346
		FROM {db_prefix}members
347
		WHERE id_member = {int:id_member}
348
		LIMIT 1',
349
		array(
350
			'id_member' => $_REQUEST['uid'],
351
		)
352
	);
353
	if ($smcFunc['db_num_rows']($request) == 0)
354
		fatal_lang_error('username_no_exist', false);
355
356
	$row = $smcFunc['db_fetch_assoc']($request);
357
	$smcFunc['db_free_result']($request);
358
359
	// Check if the secret answer is correct.
360
	if ($row['secret_question'] == '' || $row['secret_answer'] == '' || (!hash_verify_password($row['member_name'], $_POST['secret_answer'], $row['secret_answer']) && md5($_POST['secret_answer']) != $row['secret_answer']))
361
	{
362
		log_error(sprintf($txt['reminder_error'], $row['member_name']), 'user');
363
		fatal_lang_error('incorrect_answer', false);
364
	}
365
366
	// You can't use a blank one!
367
	if (strlen(trim($_POST['passwrd1'])) === 0)
368
		fatal_lang_error('no_password', false);
369
370
	// They have to be the same too.
371
	if ($_POST['passwrd1'] != $_POST['passwrd2'])
372
		fatal_lang_error('passwords_dont_match', false);
373
374
	// Make sure they have a strong enough password.
375
	require_once($sourcedir . '/Subs-Auth.php');
376
	$passwordError = validatePassword($_POST['passwrd1'], $row['member_name'], array($row['email_address']));
377
378
	// Invalid?
379
	if ($passwordError != null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $passwordError of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
380
		fatal_lang_error('profile_error_password_' . $passwordError, false);
381
382
	// Alright, so long as 'yer sure.
383
	updateMemberData($row['id_member'], array('passwd' => hash_password($row['member_name'], $_POST['passwrd1'])));
384
385
	call_integration_hook('integrate_reset_pass', array($row['member_name'], $row['member_name'], $_POST['passwrd1']));
386
387
	// Tell them it went fine.
388
	loadTemplate('Login');
389
	$context += array(
390
		'page_title' => $txt['reminder_password_set'],
391
		'sub_template' => 'login',
392
		'default_username' => $row['member_name'],
393
		'default_password' => $_POST['passwrd1'],
394
		'never_expire' => false,
395
		'description' => $txt['reminder_password_set']
396
	);
397
398
	createToken('login');
399
}
400
401
?>