Completed
Push — release-2.1 ( 5876a2...f30e04 )
by Mert
06:50
created

Profile-View.php ➔ fetch_alerts()   F

Complexity

Conditions 32
Paths > 20000

Size

Total Lines 142
Code Lines 79

Duplication

Lines 36
Ratio 25.35 %
Metric Value
cc 32
eloc 79
nc 117000
nop 4
dl 36
loc 142
rs 2

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
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines http://www.simplemachines.org
8
 * @copyright 2016 Simple Machines and individual contributors
9
 * @license http://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 Beta 3
12
 */
13
14
if (!defined('SMF'))
15
	die('No direct access...');
16
17
/**
18
 * View a summary.
19
 * @param int $memID The ID of the member
20
 */
21
function summary($memID)
22
{
23
	global $context, $memberContext, $txt, $modSettings, $user_profile, $sourcedir, $scripturl, $smcFunc;
24
25
	// Attempt to load the member's profile data.
26
	if (!loadMemberContext($memID) || !isset($memberContext[$memID]))
27
		fatal_lang_error('not_a_user', false, 404);
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...
Documentation introduced by
404 is of type integer, 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...
28
29
	// Set up the stuff and load the user.
30
	$context += array(
31
		'page_title' => sprintf($txt['profile_of_username'], $memberContext[$memID]['name']),
32
		'can_send_pm' => allowedTo('pm_send'),
33
		'can_have_buddy' => allowedTo('profile_identity_own') && !empty($modSettings['enable_buddylist']),
34
		'can_issue_warning' => allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1,
35
		'can_view_warning' => (allowedTo('moderate_forum') || allowedTo('issue_warning') || allowedTo('view_warning_any') || ($context['user']['is_owner'] && allowedTo('view_warning_own')) && $modSettings['warning_settings'][0] === 1)
36
	);
37
	$context['member'] = &$memberContext[$memID];
38
39
	// Set a canonical URL for this page.
40
	$context['canonical_url'] = $scripturl . '?action=profile;u=' . $memID;
41
42
	// Are there things we don't show?
43
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
44
	// Menu tab
45
	$context[$context['profile_menu_name']]['tab_data'] = array(
46
		'title' => $txt['summary'],
47
		'icon' => 'profile_hd.png'
48
	);
49
50
	// See if they have broken any warning levels...
51
	list ($modSettings['warning_enable'], $modSettings['user_limit']) = explode(',', $modSettings['warning_settings']);
52
	if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $context['member']['warning'])
53
		$context['warning_status'] = $txt['profile_warning_is_muted'];
54 View Code Duplication
	elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $context['member']['warning'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
55
		$context['warning_status'] = $txt['profile_warning_is_moderation'];
56 View Code Duplication
	elseif (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $context['member']['warning'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
57
		$context['warning_status'] = $txt['profile_warning_is_watch'];
58
59
	// They haven't even been registered for a full day!?
60
	$days_registered = (int) ((time() - $user_profile[$memID]['date_registered']) / (3600 * 24));
61
	if (empty($user_profile[$memID]['date_registered']) || $days_registered < 1)
62
		$context['member']['posts_per_day'] = $txt['not_applicable'];
63
	else
64
		$context['member']['posts_per_day'] = comma_format($context['member']['real_posts'] / $days_registered, 3);
65
66
	// Set the age...
67
	if (empty($context['member']['birth_date']))
68
	{
69
		$context['member'] += array(
70
			'age' => $txt['not_applicable'],
71
			'today_is_birthday' => false
72
		);
73
	}
74
	else
75
	{
76
		list ($birth_year, $birth_month, $birth_day) = sscanf($context['member']['birth_date'], '%d-%d-%d');
77
		$datearray = getdate(forum_time());
78
		$context['member'] += array(
79
			'age' => $birth_year <= 4 ? $txt['not_applicable'] : $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] == $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1),
80
			'today_is_birthday' => $datearray['mon'] == $birth_month && $datearray['mday'] == $birth_day
81
		);
82
	}
83
84
	if (allowedTo('moderate_forum'))
85
	{
86
		// Make sure it's a valid ip address; otherwise, don't bother...
87
		if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $memberContext[$memID]['ip']) == 1 && empty($modSettings['disableHostnameLookup']))
88
			$context['member']['hostname'] = host_from_ip($memberContext[$memID]['ip']);
89
		else
90
			$context['member']['hostname'] = '';
91
92
		$context['can_see_ip'] = true;
93
	}
94
	else
95
		$context['can_see_ip'] = false;
96
97
	// Are they hidden?
98
	$context['member']['is_hidden'] = empty($user_profile[$memID]['show_online']);
99
	$context['member']['show_last_login'] = allowedTo('admin_forum') || !$context['member']['is_hidden'];
100
101
	if (!empty($modSettings['who_enabled']) && $context['member']['show_last_login'])
102
	{
103
		include_once($sourcedir . '/Who.php');
104
		$action = determineActions($user_profile[$memID]['url']);
105
106
		if ($action !== false)
107
			$context['member']['action'] = $action;
108
	}
109
110
	// If the user is awaiting activation, and the viewer has permission - setup some activation context messages.
111
	if ($context['member']['is_activated'] % 10 != 1 && allowedTo('moderate_forum'))
112
	{
113
		$context['activate_type'] = $context['member']['is_activated'];
114
		// What should the link text be?
115
		$context['activate_link_text'] = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15)) ? $txt['account_approve'] : $txt['account_activate'];
116
117
		// Should we show a custom message?
118
		$context['activate_message'] = isset($txt['account_activate_method_' . $context['member']['is_activated'] % 10]) ? $txt['account_activate_method_' . $context['member']['is_activated'] % 10] : $txt['account_not_activated'];
119
120
		// If they can be approved, we need to set up a token for them.
121
		$context['token_check'] = 'profile-aa' . $memID;
122
		createToken($context['token_check'], 'get');
123
124
		$context['activate_link'] = $scripturl . '?action=profile;save;area=activateaccount;u=' . $context['id_member'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';' . $context[$context['token_check'] . '_token_var'] . '=' . $context[$context['token_check'] . '_token'];
125
	}
126
127
	// Is the signature even enabled on this forum?
128
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
129
130
	// How about, are they banned?
131
	$context['member']['bans'] = array();
132
	if (allowedTo('moderate_forum'))
133
	{
134
		// Can they edit the ban?
135
		$context['can_edit_ban'] = allowedTo('manage_bans');
136
137
		$ban_query = array();
138
		$ban_query_vars = array(
139
			'time' => time(),
140
		);
141
		$ban_query[] = 'id_member = ' . $context['member']['id'];
142
		$ban_query[] = ' {inet:ip} BETWEEN bi.ip_low and bi.ip_high';
143
		$ban_query_vars['ip'] = $memberContext[$memID]['ip'];
144
		// Do we have a hostname already?
145 View Code Duplication
		if (!empty($context['member']['hostname']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
		{
147
			$ban_query[] = '({string:hostname} LIKE hostname)';
148
			$ban_query_vars['hostname'] = $context['member']['hostname'];
149
		}
150
		// Check their email as well...
151 View Code Duplication
		if (strlen($context['member']['email']) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
		{
153
			$ban_query[] = '({string:email} LIKE bi.email_address)';
154
			$ban_query_vars['email'] = $context['member']['email'];
155
		}
156
157
		// So... are they banned?  Dying to know!
158
		$request = $smcFunc['db_query']('', '
159
			SELECT bg.id_ban_group, bg.name, bg.cannot_access, bg.cannot_post, bg.cannot_register,
160
				bg.cannot_login, bg.reason
161
			FROM {db_prefix}ban_items AS bi
162
				INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:time}))
163
			WHERE (' . implode(' OR ', $ban_query) . ')',
164
			$ban_query_vars
165
		);
166
		while ($row = $smcFunc['db_fetch_assoc']($request))
167
		{
168
			// Work out what restrictions we actually have.
169
			$ban_restrictions = array();
170
			foreach (array('access', 'register', 'login', 'post') as $type)
171
				if ($row['cannot_' . $type])
172
					$ban_restrictions[] = $txt['ban_type_' . $type];
173
174
			// No actual ban in place?
175
			if (empty($ban_restrictions))
176
				continue;
177
178
			// Prepare the link for context.
179
			$ban_explanation = sprintf($txt['user_cannot_due_to'], implode(', ', $ban_restrictions), '<a href="' . $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $row['id_ban_group'] . '">' . $row['name'] . '</a>');
180
181
			$context['member']['bans'][$row['id_ban_group']] = array(
182
				'reason' => empty($row['reason']) ? '' : '<br><br><strong>' . $txt['ban_reason'] . ':</strong> ' . $row['reason'],
183
				'cannot' => array(
184
					'access' => !empty($row['cannot_access']),
185
					'register' => !empty($row['cannot_register']),
186
					'post' => !empty($row['cannot_post']),
187
					'login' => !empty($row['cannot_login']),
188
				),
189
				'explanation' => $ban_explanation,
190
			);
191
		}
192
		$smcFunc['db_free_result']($request);
193
	}
194
	loadCustomFields($memID);
195
196
	$context['print_custom_fields'] = array();
197
198
	// Any custom profile fields?
199
	if (!empty($context['custom_fields']))
200
		foreach ($context['custom_fields'] as $custom)
201
			$context['print_custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
202
203
}
204
205
/**
206
 * Fetch the alerts a user currently has.
207
 *
208
 * @param int $memID The ID of the member
209
 * @param bool $all Whether to fetch all alerts or just unread ones
210
 * @param int $counter How many alerts to display (0 if displaying all or using pagination)
211
 * @param array $pagination An array containing info for handling pagination. Should have 'start' and 'maxIndex'
212
 * @return array An array of information about the fetched alerts
213
 */
214
function fetch_alerts($memID, $all = false, $counter = 0, $pagination = array())
215
{
216
	global $smcFunc, $txt, $scripturl, $memberContext;
217
218
	$alerts = array();
219
	$request = $smcFunc['db_query']('', '
220
		SELECT id_alert, alert_time, mem.id_member AS sender_id, COALESCE(mem.real_name, ua.member_name) AS sender_name,
221
			content_type, content_id, content_action, is_read, extra
222
		FROM {db_prefix}user_alerts AS ua
223
			LEFT JOIN {db_prefix}members AS mem ON (ua.id_member_started = mem.id_member)
224
		WHERE ua.id_member = {int:id_member}' . (!$all ? '
225
			AND is_read = 0' : '') . '
226
		ORDER BY id_alert DESC' . (!empty($counter) && empty($pagination) ? '
227
		LIMIT {int:counter}' : '') . (!empty($pagination) && empty($counter) ? '
228
		LIMIT {int:start}, {int:maxIndex}' : ''),
229
		array(
230
			'id_member' => $memID,
231
			'counter' => $counter,
232
			'start' => !empty($pagination['start']) ? $pagination['start'] : 0,
233
			'maxIndex' => !empty($pagination['maxIndex']) ? $pagination['maxIndex'] : 0,
234
		)
235
	);
236
237
	$senders = array();
238
	while ($row = $smcFunc['db_fetch_assoc']($request))
239
	{
240
		$id_alert = array_shift($row);
241
		$row['time'] = timeformat($row['alert_time']);
242
		$row['extra'] = !empty($row['extra']) ? @json_decode($row['extra'], true) : array();
243
		$alerts[$id_alert] = $row;
244
245
		if (!empty($row['sender_id']))
246
			$senders[] = $row['sender_id'];
247
	}
248
	$smcFunc['db_free_result']($request);
249
250
	$senders = loadMemberData($senders);
251
	foreach ($senders as $member)
252
		loadMemberContext($member);
253
254
	// Now go through and actually make with the text.
255
	loadLanguage('Alerts');
256
257
	// Hooks might want to do something snazzy around their own content types - including enforcing permissions if appropriate.
258
	call_integration_hook('integrate_fetch_alerts', array(&$alerts));
259
260
	// For anything that wants us to check board or topic access, let's do that.
261
	$boards = array();
262
	$topics = array();
263
	$msgs = array();
264
	foreach ($alerts as $id_alert => $alert)
265
	{
266
		if (isset($alert['extra']['board']))
267
			$boards[$alert['extra']['board']] = $txt['board_na'];
268
		if (isset($alert['extra']['topic']))
269
			$topics[$alert['extra']['topic']] = $txt['topic_na'];
270
		if ($alert['content_type'] == 'msg')
271
			$msgs[$alert['content_id']] = $txt['topic_na'];
272
	}
273
274
	// Having figured out what boards etc. there are, let's now get the names of them if we can see them. If not, there's already a fallback set up.
275 View Code Duplication
	if (!empty($boards))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
276
	{
277
		$request = $smcFunc['db_query']('', '
278
			SELECT id_board, name
279
			FROM {db_prefix}boards AS b
280
			WHERE {query_see_board}
281
				AND id_board IN ({array_int:boards})',
282
			array(
283
				'boards' => array_keys($boards),
284
			)
285
		);
286
		while ($row = $smcFunc['db_fetch_assoc']($request))
287
			$boards[$row['id_board']] = '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>';
288
	}
289 View Code Duplication
	if (!empty($topics))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
290
	{
291
		$request = $smcFunc['db_query']('', '
292
			SELECT t.id_topic, m.subject
293
			FROM {db_prefix}topics AS t
294
				INNER JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
295
				INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
296
			WHERE {query_see_board}
297
				AND t.id_topic IN ({array_int:topics})',
298
			array(
299
				'topics' => array_keys($topics),
300
			)
301
		);
302
		while ($row = $smcFunc['db_fetch_assoc']($request))
303
			$topics[$row['id_topic']] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>';
304
	}
305
	if (!empty($msgs))
306
	{
307
		$request = $smcFunc['db_query']('', '
308
			SELECT m.id_msg, t.id_topic, m.subject
309
			FROM {db_prefix}messages AS m
310
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
311
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
312
			WHERE {query_see_board}
313
				AND m.id_msg IN ({array_int:msgs})',
314
			array(
315
				'msgs' => array_keys($msgs),
316
			)
317
		);
318 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
319
			$msgs[$row['id_msg']] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
320
	}
321
322
	// Now to go back through the alerts, reattach this extra information and then try to build the string out of it (if a hook didn't already)
323
	foreach ($alerts as $id_alert => $alert)
324
	{
325
		if (!empty($alert['text']))
326
			continue;
327 View Code Duplication
		if (isset($alert['extra']['board']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
			$alerts[$id_alert]['extra']['board_msg'] = $boards[$alert['extra']['board']];
329 View Code Duplication
		if (isset($alert['extra']['topic']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
			$alerts[$id_alert]['extra']['topic_msg'] = $topics[$alert['extra']['topic']];
331
		if ($alert['content_type'] == 'msg')
332
			$alerts[$id_alert]['extra']['msg_msg'] = $msgs[$alert['content_id']];
333
		if ($alert['content_type'] == 'profile')
334
			$alerts[$id_alert]['extra']['profile_msg'] = '<a href="' . $scripturl . '?action=profile;u=' . $alerts[$id_alert]['content_id'] . '">' . $alerts[$id_alert]['extra']['user_name'] . '</a>';
335
336
		if (!empty($memberContext[$alert['sender_id']]))
337
			$alerts[$id_alert]['sender'] = &$memberContext[$alert['sender_id']];
338
339
		$string = 'alert_' . $alert['content_type'] . '_' . $alert['content_action'];
340
		if (isset($txt[$string]))
341
		{
342
			$extra = $alerts[$id_alert]['extra'];
343
			$search = array('{member_link}', '{scripturl}');
344
			$repl = array(!empty($alert['sender_id']) ? '<a href="' . $scripturl . '?action=profile;u=' . $alert['sender_id'] . '">' . $alert['sender_name'] . '</a>' : $alert['sender_name'], $scripturl);
345
			foreach ($extra as $k => $v)
346
			{
347
				$search[] = '{' . $k . '}';
348
				$repl[] = $v;
349
			}
350
			$alerts[$id_alert]['text'] = str_replace($search, $repl, $txt[$string]);
351
		}
352
	}
353
354
	return $alerts;
355
}
356
357
/**
358
 * Shows all alerts for this user
359
 *
360
 * @param int $memID The ID of the member
361
 */
362
function showAlerts($memID)
363
{
364
	global $context, $smcFunc, $txt, $sourcedir, $scripturl;
365
366
	require_once($sourcedir . '/Profile-Modify.php');
367
368
	// Prepare the pagination vars.
369
	$maxIndex = 10;
370
	$start = (int) isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
371
	$count =  alert_count($memID);
372
373
	// Get the alerts.
374
	$context['alerts'] = fetch_alerts($memID, true, false, array('start' => $start, 'maxIndex' => $maxIndex));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a integer.

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...
375
	$toMark = false;
376
	$action = '';
377
378
	// Create the pagination.
379
	$context['pagination'] = constructPageIndex($scripturl . '?action=profile;area=showalerts;u=' . $memID, $start, $count, $maxIndex, false);
380
381
	// Set some JavaScript for checking all alerts at once.
382
	addInlineJavascript('
383
	$(function(){
384
		$(\'#select_all\').on(\'change\', function() {
385
			var checkboxes = $(\'ul.quickbuttons\').find(\':checkbox\');
386
			if($(this).prop(\'checked\')) {
387
				checkboxes.prop(\'checked\', true);
388
			}
389
			else {
390
				checkboxes.prop(\'checked\', false);
391
			}
392
		});
393
	});', true);
394
395
	// Set a nice message.
396
	if (!empty($_SESSION['update_message']))
397
	{
398
		$context['update_message'] = $txt['profile_updated_own'];
399
		unset($_SESSION['update_message']);
400
	}
401
402
	// Saving multiple changes?
403
	if (isset($_GET['save']) && !empty($_POST['mark']))
404
	{
405
		// Get the values.
406
		$toMark = array_map('intval', $_POST['mark']);
407
408
		// Which action?
409
		$action = !empty($_POST['mark_as']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_POST['mark_as'])) : '';
410
	}
411
412
	// A single change.
413
	if (!empty($_GET['do']) && !empty($_GET['aid']))
414
	{
415
		$toMark = (int) $_GET['aid'];
416
		$action = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_GET['do']));
417
	}
418
419
	// Save the changes.
420
	if (!empty($toMark) && !empty($action))
421
	{
422
		checkSession('request');
423
424
		// Call it!
425
		if ($action == 'remove')
426
			alert_delete($toMark, $memID);
427
428
		else
429
			alert_mark($memID, $toMark, $action == 'read' ? 1 : 0);
430
431
		// Set a nice update message.
432
		$_SESSION['update_message'] = true;
433
434
		// Redirect.
435
		redirectexit('action=profile;area=showalerts;u=' . $memID);
436
	}
437
}
438
439
/**
440
 * Show all posts by the current user
441
 * @todo This function needs to be split up properly.
442
 *
443
 * @param int $memID The ID of the member
444
 */
445
function showPosts($memID)
446
{
447
	global $txt, $user_info, $scripturl, $modSettings;
448
	global $context, $user_profile, $sourcedir, $smcFunc, $board;
449
450
	// Some initial context.
451
	$context['start'] = (int) $_REQUEST['start'];
452
	$context['current_member'] = $memID;
453
454
	// Create the tabs for the template.
455
	$context[$context['profile_menu_name']]['tab_data'] = array(
456
		'title' => $txt['showPosts'],
457
		'description' => $txt['showPosts_help'],
458
		'icon' => 'profile_hd.png',
459
		'tabs' => array(
460
			'messages' => array(
461
			),
462
			'topics' => array(
463
			),
464
			'unwatchedtopics' => array(
465
			),
466
			'attach' => array(
467
			),
468
		),
469
	);
470
471
	// Shortcut used to determine which $txt['show*'] string to use for the title, based on the SA
472
	$title = array(
473
		'attach' => 'Attachments',
474
		'unwatchedtopics' => 'Unwatched',
475
		'topics' => 'Topics'
476
	);
477
478
	// Set the page title
479
	if (isset($_GET['sa']) && array_key_exists($_GET['sa'], $title))
480
		$context['page_title'] = $txt['show' . $title[$_GET['sa']]];
481
	else
482
		$context['page_title'] = $txt['showPosts'];
483
484
	$context['page_title'] .= ' - ' . $user_profile[$memID]['real_name'];
485
486
	// Is the load average too high to allow searching just now?
487 View Code Duplication
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_show_posts']) && $context['load_average'] >= $modSettings['loadavg_show_posts'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
488
		fatal_lang_error('loadavg_show_posts_disabled', 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...
489
490
	// If we're specifically dealing with attachments use that function!
491
	if (isset($_GET['sa']) && $_GET['sa'] == 'attach')
492
		return showAttachments($memID);
493
	// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
494
	elseif (isset($_GET['sa']) && $_GET['sa'] == 'unwatchedtopics')
495
		return showUnwatched($memID);
496
497
	// Are we just viewing topics?
498
	$context['is_topics'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? true : false;
499
500
	// If just deleting a message, do it and then redirect back.
501
	if (isset($_GET['delete']) && !$context['is_topics'])
502
	{
503
		checkSession('get');
504
505
		// We need msg info for logging.
506
		$request = $smcFunc['db_query']('', '
507
			SELECT subject, id_member, id_topic, id_board
508
			FROM {db_prefix}messages
509
			WHERE id_msg = {int:id_msg}',
510
			array(
511
				'id_msg' => (int) $_GET['delete'],
512
			)
513
		);
514
		$info = $smcFunc['db_fetch_row']($request);
515
		$smcFunc['db_free_result']($request);
516
517
		// Trying to remove a message that doesn't exist.
518
		if (empty($info))
519
			redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
520
521
		// We can be lazy, since removeMessage() will check the permissions for us.
522
		require_once($sourcedir . '/RemoveTopic.php');
523
		removeMessage((int) $_GET['delete']);
524
525
		// Add it to the mod log.
526 View Code Duplication
		if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
527
			logAction('delete', array('topic' => $info[2], 'subject' => $info[0], 'member' => $info[1], 'board' => $info[3]));
528
529
		// Back to... where we are now ;).
530
		redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
531
	}
532
533
	// Default to 10.
534 View Code Duplication
	if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
535
		$_REQUEST['viewscount'] = '10';
536
537
	if ($context['is_topics'])
538
		$request = $smcFunc['db_query']('', '
539
			SELECT COUNT(*)
540
			FROM {db_prefix}topics AS t' . ($user_info['query_see_board'] == '1=1' ? '' : '
541
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board})') . '
542
			WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
543
				AND t.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
544
				AND t.approved = {int:is_approved}'),
545
			array(
546
				'current_member' => $memID,
547
				'is_approved' => 1,
548
				'board' => $board,
549
			)
550
		);
551
	else
552
		$request = $smcFunc['db_query']('', '
553
			SELECT COUNT(*)
554
			FROM {db_prefix}messages AS m' . ($user_info['query_see_board'] == '1=1' ? '' : '
555
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . '
556
			WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
557
				AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
558
				AND m.approved = {int:is_approved}'),
559
			array(
560
				'current_member' => $memID,
561
				'is_approved' => 1,
562
				'board' => $board,
563
			)
564
		);
565
	list ($msgCount) = $smcFunc['db_fetch_row']($request);
566
	$smcFunc['db_free_result']($request);
567
568
	$request = $smcFunc['db_query']('', '
569
		SELECT MIN(id_msg), MAX(id_msg)
570
		FROM {db_prefix}messages AS m
571
		WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
572
			AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
573
			AND m.approved = {int:is_approved}'),
574
		array(
575
			'current_member' => $memID,
576
			'is_approved' => 1,
577
			'board' => $board,
578
		)
579
	);
580
	list ($min_msg_member, $max_msg_member) = $smcFunc['db_fetch_row']($request);
581
	$smcFunc['db_free_result']($request);
582
583
	$reverse = false;
0 ignored issues
show
Unused Code introduced by
$reverse is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
584
	$range_limit = '';
585
586
	if ($context['is_topics'])
587
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page']) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'];
0 ignored issues
show
Bug introduced by
The variable $options seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
588
	else
589
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
590
591
	$maxIndex = $maxPerPage;
592
593
	// Make sure the starting place makes sense and construct our friend the page index.
594
	$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : '') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex);
595
	$context['current_page'] = $context['start'] / $maxIndex;
596
597
	// Reverse the query if we're past 50% of the pages for better performance.
598
	$start = $context['start'];
599
	$reverse = $_REQUEST['start'] > $msgCount / 2;
600 View Code Duplication
	if ($reverse)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
601
	{
602
		$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
603
		$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
604
	}
605
606
	// Guess the range of messages to be shown.
607
	if ($msgCount > 1000)
608
	{
609
		$margin = floor(($max_msg_member - $min_msg_member) * (($start + $maxPerPage) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
610
		// Make a bigger margin for topics only.
611
		if ($context['is_topics'])
612
		{
613
			$margin *= 5;
614
			$range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin);
615
		}
616
		else
617
			$range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin);
618
	}
619
620
	// Find this user's posts.  The left join on categories somehow makes this faster, weird as it looks.
621
	$looped = false;
622
	while (true)
623
	{
624
		if ($context['is_topics'])
625
		{
626
			$request = $smcFunc['db_query']('', '
627
				SELECT
628
					b.id_board, b.name AS bname, c.id_cat, c.name AS cname, t.id_member_started, t.id_first_msg, t.id_last_msg,
629
					t.approved, m.body, m.smileys_enabled, m.subject, m.poster_time, m.id_topic, m.id_msg
630
				FROM {db_prefix}topics AS t
631
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
632
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
633
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
634
				WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
635
					AND t.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
636
					AND ' . $range_limit) . '
637
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
638
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
639
				ORDER BY t.id_first_msg ' . ($reverse ? 'ASC' : 'DESC') . '
640
				LIMIT {int:start}, {int:max}',
641
				array(
642
					'current_member' => $memID,
643
					'is_approved' => 1,
644
					'board' => $board,
645
					'start' => $start,
646
					'max' => $maxIndex,
647
				)
648
			);
649
		}
650
		else
651
		{
652
			$request = $smcFunc['db_query']('', '
653
				SELECT
654
					b.id_board, b.name AS bname, c.id_cat, c.name AS cname, m.id_topic, m.id_msg,
655
					t.id_member_started, t.id_first_msg, t.id_last_msg, m.body, m.smileys_enabled,
656
					m.subject, m.poster_time, m.approved
657
				FROM {db_prefix}messages AS m
658
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
659
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
660
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
661
				WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
662
					AND b.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
663
					AND ' . $range_limit) . '
664
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
665
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
666
				ORDER BY m.id_msg ' . ($reverse ? 'ASC' : 'DESC') . '
667
				LIMIT {int:start}, {int:max}',
668
				array(
669
					'current_member' => $memID,
670
					'is_approved' => 1,
671
					'board' => $board,
672
					'start' => $start,
673
					'max' => $maxIndex,
674
				)
675
			);
676
		}
677
678
		// Make sure we quit this loop.
679
		if ($smcFunc['db_num_rows']($request) === $maxIndex || $looped)
680
			break;
681
		$looped = true;
682
		$range_limit = '';
683
	}
684
685
	// Start counting at the number of the first message displayed.
686
	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
687
	$context['posts'] = array();
688
	$board_ids = array('own' => array(), 'any' => array());
689
	while ($row = $smcFunc['db_fetch_assoc']($request))
690
	{
691
		// Censor....
692
		censorText($row['body']);
693
		censorText($row['subject']);
694
695
		// Do the code.
696
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
697
698
		// And the array...
699
		$context['posts'][$counter += $reverse ? -1 : 1] = array(
700
			'body' => $row['body'],
701
			'counter' => $counter,
702
			'category' => array(
703
				'name' => $row['cname'],
704
				'id' => $row['id_cat']
705
			),
706
			'board' => array(
707
				'name' => $row['bname'],
708
				'id' => $row['id_board']
709
			),
710
			'topic' => $row['id_topic'],
711
			'subject' => $row['subject'],
712
			'start' => 'msg' . $row['id_msg'],
713
			'time' => timeformat($row['poster_time']),
714
			'timestamp' => forum_time(true, $row['poster_time']),
715
			'id' => $row['id_msg'],
716
			'can_reply' => false,
717
			'can_mark_notify' => !$context['user']['is_guest'],
718
			'can_delete' => false,
719
			'delete_possible' => ($row['id_first_msg'] != $row['id_msg'] || $row['id_last_msg'] == $row['id_msg']) && (empty($modSettings['edit_disable_time']) || $row['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()),
720
			'approved' => $row['approved'],
721
			'css_class' => $row['approved'] ? 'windowbg' : 'approvebg',
722
		);
723
724
		if ($user_info['id'] == $row['id_member_started'])
725
			$board_ids['own'][$row['id_board']][] = $counter;
726
		$board_ids['any'][$row['id_board']][] = $counter;
727
	}
728
	$smcFunc['db_free_result']($request);
729
730
	// All posts were retrieved in reverse order, get them right again.
731
	if ($reverse)
732
		$context['posts'] = array_reverse($context['posts'], true);
733
734
	// These are all the permissions that are different from board to board..
735
	if ($context['is_topics'])
736
		$permissions = array(
737
			'own' => array(
738
				'post_reply_own' => 'can_reply',
739
			),
740
			'any' => array(
741
				'post_reply_any' => 'can_reply',
742
			)
743
		);
744
	else
745
		$permissions = array(
746
			'own' => array(
747
				'post_reply_own' => 'can_reply',
748
				'delete_own' => 'can_delete',
749
			),
750
			'any' => array(
751
				'post_reply_any' => 'can_reply',
752
				'delete_any' => 'can_delete',
753
			)
754
		);
755
756
	// For every permission in the own/any lists...
757
	foreach ($permissions as $type => $list)
758
	{
759
		foreach ($list as $permission => $allowed)
760
		{
761
			// Get the boards they can do this on...
762
			$boards = boardsAllowedTo($permission);
763
764
			// Hmm, they can do it on all boards, can they?
765 View Code Duplication
			if (!empty($boards) && $boards[0] == 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
766
				$boards = array_keys($board_ids[$type]);
767
768
			// Now go through each board they can do the permission on.
769
			foreach ($boards as $board_id)
770
			{
771
				// There aren't any posts displayed from this board.
772
				if (!isset($board_ids[$type][$board_id]))
773
					continue;
774
775
				// Set the permission to true ;).
776
				foreach ($board_ids[$type][$board_id] as $counter)
777
					$context['posts'][$counter][$allowed] = true;
778
			}
779
		}
780
	}
781
782
	// Clean up after posts that cannot be deleted and quoted.
783
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
784 View Code Duplication
	foreach ($context['posts'] as $counter => $dummy)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
785
	{
786
		$context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible'];
787
		$context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled;
788
	}
789
790
	// Allow last minute changes.
791
	call_integration_hook('integrate_profile_showPosts');
792
}
793
794
/**
795
 * Show all the attachments of a user.
796
 *
797
 * @param int $memID The ID of the member
798
 */
799
function showAttachments($memID)
800
{
801
	global $txt, $scripturl, $modSettings;
802
	global $sourcedir;
803
804
	// OBEY permissions!
805
	$boardsAllowed = boardsAllowedTo('view_attachments');
806
807
	// Make sure we can't actually see anything...
808
	if (empty($boardsAllowed))
809
		$boardsAllowed = array(-1);
810
811
	require_once($sourcedir . '/Subs-List.php');
812
813
	// This is all the information required to list attachments.
814
	$listOptions = array(
815
		'id' => 'attachments',
816
		'width' => '100%',
817
		'items_per_page' => $modSettings['defaultMaxListItems'],
818
		'no_items_label' => $txt['show_attachments_none'],
819
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=attach;u=' . $memID,
820
		'default_sort_col' => 'filename',
821
		'get_items' => array(
822
			'function' => 'list_getAttachments',
823
			'params' => array(
824
				$boardsAllowed,
825
				$memID,
826
			),
827
		),
828
		'get_count' => array(
829
			'function' => 'list_getNumAttachments',
830
			'params' => array(
831
				$boardsAllowed,
832
				$memID,
833
			),
834
		),
835
		'data_check' => array(
836
			'class' => function ($data)
837
			{
838
				return $data['approved'] ? '' : 'approvebg';
839
			}
840
		),
841
		'columns' => array(
842
			'filename' => array(
843
				'header' => array(
844
					'value' => $txt['show_attach_filename'],
845
					'class' => 'lefttext',
846
					'style' => 'width: 25%;',
847
				),
848
				'data' => array(
849
					'sprintf' => array(
850
						'format' => '<a href="' . $scripturl . '?action=dlattach;topic=%1$d.0;attach=%2$d">%3$s</a>',
851
						'params' => array(
852
							'topic' => true,
853
							'id' => true,
854
							'filename' => false,
855
						),
856
					),
857
				),
858
				'sort' => array(
859
					'default' => 'a.filename',
860
					'reverse' => 'a.filename DESC',
861
				),
862
			),
863
			'downloads' => array(
864
				'header' => array(
865
					'value' => $txt['show_attach_downloads'],
866
					'style' => 'width: 12%;',
867
				),
868
				'data' => array(
869
					'db' => 'downloads',
870
					'comma_format' => true,
871
				),
872
				'sort' => array(
873
					'default' => 'a.downloads',
874
					'reverse' => 'a.downloads DESC',
875
				),
876
			),
877
			'subject' => array(
878
				'header' => array(
879
					'value' => $txt['message'],
880
					'class' => 'lefttext',
881
					'style' => 'width: 30%;',
882
				),
883
				'data' => array(
884
					'sprintf' => array(
885
						'format' => '<a href="' . $scripturl . '?msg=%1$d">%2$s</a>',
886
						'params' => array(
887
							'msg' => true,
888
							'subject' => false,
889
						),
890
					),
891
				),
892
				'sort' => array(
893
					'default' => 'm.subject',
894
					'reverse' => 'm.subject DESC',
895
				),
896
			),
897
			'posted' => array(
898
				'header' => array(
899
					'value' => $txt['show_attach_posted'],
900
					'class' => 'lefttext',
901
				),
902
				'data' => array(
903
					'db' => 'posted',
904
					'timeformat' => true,
905
				),
906
				'sort' => array(
907
					'default' => 'm.poster_time',
908
					'reverse' => 'm.poster_time DESC',
909
				),
910
			),
911
		),
912
	);
913
914
	// Create the request list.
915
	createList($listOptions);
916
}
917
918
/**
919
 * Get a list of attachments for this user. Callback for the list in showAttachments()
920
 *
921
 * @param int $start Which item to start with (for pagination purposes)
922
 * @param int $items_per_page How many items to show on each page
923
 * @param string $sort A string indicating how to sort the results
924
 * @param array $boardsAllowed An array containing the IDs of the boards they can see
925
 * @param int $memID The ID of the member
926
 * @return array An array of information about the attachments
927
 */
928
function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed, $memID)
929
{
930
	global $smcFunc, $board, $modSettings, $context;
931
932
	// Retrieve some attachments.
933
	$request = $smcFunc['db_query']('', '
934
		SELECT a.id_attach, a.id_msg, a.filename, a.downloads, a.approved, m.id_msg, m.id_topic,
935
			m.id_board, m.poster_time, m.subject, b.name
936
		FROM {db_prefix}attachments AS a
937
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
938
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
939
		WHERE a.attachment_type = {int:attachment_type}
940
			AND a.id_msg != {int:no_message}
941
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
942
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
943
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
944
			AND m.approved = {int:is_approved}') . '
945
		ORDER BY {raw:sort}
946
		LIMIT {int:offset}, {int:limit}',
947
		array(
948
			'boards_list' => $boardsAllowed,
949
			'attachment_type' => 0,
950
			'no_message' => 0,
951
			'current_member' => $memID,
952
			'is_approved' => 1,
953
			'board' => $board,
954
			'sort' => $sort,
955
			'offset' => $start,
956
			'limit' => $items_per_page,
957
		)
958
	);
959
	$attachments = array();
960
	while ($row = $smcFunc['db_fetch_assoc']($request))
961
		$attachments[] = array(
962
			'id' => $row['id_attach'],
963
			'filename' => $row['filename'],
964
			'downloads' => $row['downloads'],
965
			'subject' => censorText($row['subject']),
966
			'posted' => $row['poster_time'],
967
			'msg' => $row['id_msg'],
968
			'topic' => $row['id_topic'],
969
			'board' => $row['id_board'],
970
			'board_name' => $row['name'],
971
			'approved' => $row['approved'],
972
		);
973
974
	$smcFunc['db_free_result']($request);
975
976
	return $attachments;
977
}
978
979
/**
980
 * Gets the total number of attachments for the user
981
 *
982
 * @param array $boardsAllowed An array of the IDs of the boards they can see
983
 * @param int $memID The ID of the member
984
 * @return int The number of attachments
985
 */
986
function list_getNumAttachments($boardsAllowed, $memID)
987
{
988
	global $board, $smcFunc, $modSettings, $context;
989
990
	// Get the total number of attachments they have posted.
991
	$request = $smcFunc['db_query']('', '
992
		SELECT COUNT(*)
993
		FROM {db_prefix}attachments AS a
994
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
995
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
996
		WHERE a.attachment_type = {int:attachment_type}
997
			AND a.id_msg != {int:no_message}
998
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
999
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
1000
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1001
			AND m.approved = {int:is_approved}'),
1002
		array(
1003
			'boards_list' => $boardsAllowed,
1004
			'attachment_type' => 0,
1005
			'no_message' => 0,
1006
			'current_member' => $memID,
1007
			'is_approved' => 1,
1008
			'board' => $board,
1009
		)
1010
	);
1011
	list ($attachCount) = $smcFunc['db_fetch_row']($request);
1012
	$smcFunc['db_free_result']($request);
1013
1014
	return $attachCount;
1015
}
1016
1017
/**
1018
 * Show all the unwatched topics.
1019
 *
1020
 * @param int $memID The ID of the member
1021
 */
1022
function showUnwatched($memID)
1023
{
1024
	global $txt, $user_info, $scripturl, $modSettings, $context, $sourcedir;
1025
1026
	// Only the owner can see the list (if the function is enabled of course)
1027
	if ($user_info['id'] != $memID)
1028
		return;
1029
1030
	require_once($sourcedir . '/Subs-List.php');
1031
1032
	// And here they are: the topics you don't like
1033
	$listOptions = array(
1034
		'id' => 'unwatched_topics',
1035
		'width' => '100%',
1036
		'items_per_page' => (empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page'])) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'],
0 ignored issues
show
Bug introduced by
The variable $options seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1037
		'no_items_label' => $txt['unwatched_topics_none'],
1038
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=unwatchedtopics;u=' . $memID,
1039
		'default_sort_col' => 'started_on',
1040
		'get_items' => array(
1041
			'function' => 'list_getUnwatched',
1042
			'params' => array(
1043
				$memID,
1044
			),
1045
		),
1046
		'get_count' => array(
1047
			'function' => 'list_getNumUnwatched',
1048
			'params' => array(
1049
				$memID,
1050
			),
1051
		),
1052
		'columns' => array(
1053
			'subject' => array(
1054
				'header' => array(
1055
					'value' => $txt['subject'],
1056
					'class' => 'lefttext',
1057
					'style' => 'width: 30%;',
1058
				),
1059
				'data' => array(
1060
					'sprintf' => array(
1061
						'format' => '<a href="' . $scripturl . '?topic=%1$d.0">%2$s</a>',
1062
						'params' => array(
1063
							'id_topic' => false,
1064
							'subject' => false,
1065
						),
1066
					),
1067
				),
1068
				'sort' => array(
1069
					'default' => 'm.subject',
1070
					'reverse' => 'm.subject DESC',
1071
				),
1072
			),
1073
			'started_by' => array(
1074
				'header' => array(
1075
					'value' => $txt['started_by'],
1076
					'style' => 'width: 15%;',
1077
				),
1078
				'data' => array(
1079
					'db' => 'started_by',
1080
				),
1081
				'sort' => array(
1082
					'default' => 'mem.real_name',
1083
					'reverse' => 'mem.real_name DESC',
1084
				),
1085
			),
1086
			'started_on' => array(
1087
				'header' => array(
1088
					'value' => $txt['on'],
1089
					'class' => 'lefttext',
1090
					'style' => 'width: 20%;',
1091
				),
1092
				'data' => array(
1093
					'db' => 'started_on',
1094
					'timeformat' => true,
1095
				),
1096
				'sort' => array(
1097
					'default' => 'm.poster_time',
1098
					'reverse' => 'm.poster_time DESC',
1099
				),
1100
			),
1101
			'last_post_by' => array(
1102
				'header' => array(
1103
					'value' => $txt['last_post'],
1104
					'style' => 'width: 15%;',
1105
				),
1106
				'data' => array(
1107
					'db' => 'last_post_by',
1108
				),
1109
				'sort' => array(
1110
					'default' => 'mem.real_name',
1111
					'reverse' => 'mem.real_name DESC',
1112
				),
1113
			),
1114
			'last_post_on' => array(
1115
				'header' => array(
1116
					'value' => $txt['on'],
1117
					'class' => 'lefttext',
1118
					'style' => 'width: 20%;',
1119
				),
1120
				'data' => array(
1121
					'db' => 'last_post_on',
1122
					'timeformat' => true,
1123
				),
1124
				'sort' => array(
1125
					'default' => 'm.poster_time',
1126
					'reverse' => 'm.poster_time DESC',
1127
				),
1128
			),
1129
		),
1130
	);
1131
1132
	// Create the request list.
1133
	createList($listOptions);
1134
1135
	$context['sub_template'] = 'show_list';
1136
	$context['default_list'] = 'unwatched_topics';
1137
}
1138
1139
/**
1140
 * Gets information about unwatched (disregarded) topics. Callback for the list in show_unwatched
1141
 *
1142
 * @param int $start The item to start with (for pagination purposes)
1143
 * @param int $items_per_page How many items to show on each page
1144
 * @param string $sort A string indicating how to sort the results
1145
 * @param int $memID The ID of the member
1146
 * @return array An array of information about the unwatched topics
1147
 */
1148
function list_getUnwatched($start, $items_per_page, $sort, $memID)
1149
{
1150
	global $smcFunc;
1151
1152
	// Get the list of topics we can see
1153
	$request = $smcFunc['db_query']('', '
1154
		SELECT lt.id_topic
1155
		FROM {db_prefix}log_topics as lt
1156
			LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1157
			LEFT JOIN {db_prefix}boards as b ON (t.id_board = b.id_board)
1158
			LEFT JOIN {db_prefix}messages as m ON (t.id_first_msg = m.id_msg)' . (in_array($sort, array('mem.real_name', 'mem.real_name DESC', 'mem.poster_time', 'mem.poster_time DESC')) ? '
1159
			LEFT JOIN {db_prefix}members as mem ON (m.id_member = mem.id_member)' : '') . '
1160
		WHERE lt.id_member = {int:current_member}
1161
			AND unwatched = 1
1162
			AND {query_see_board}
1163
		ORDER BY {raw:sort}
1164
		LIMIT {int:offset}, {int:limit}',
1165
		array(
1166
			'current_member' => $memID,
1167
			'sort' => $sort,
1168
			'offset' => $start,
1169
			'limit' => $items_per_page,
1170
		)
1171
	);
1172
1173
	$topics = array();
1174
	while ($row = $smcFunc['db_fetch_assoc']($request))
1175
		$topics[] = $row['id_topic'];
1176
1177
	$smcFunc['db_free_result']($request);
1178
1179
	// Any topics found?
1180
	$topicsInfo = array();
1181
	if (!empty($topics))
1182
	{
1183
		$request = $smcFunc['db_query']('', '
1184
			SELECT mf.subject, mf.poster_time as started_on, COALESCE(memf.real_name, mf.poster_name) as started_by, ml.poster_time as last_post_on, COALESCE(meml.real_name, ml.poster_name) as last_post_by, t.id_topic
1185
			FROM {db_prefix}topics AS t
1186
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
1187
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1188
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
1189
				LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1190
			WHERE t.id_topic IN ({array_int:topics})',
1191
			array(
1192
				'topics' => $topics,
1193
			)
1194
		);
1195
		while ($row = $smcFunc['db_fetch_assoc']($request))
1196
			$topicsInfo[] = $row;
1197
		$smcFunc['db_free_result']($request);
1198
	}
1199
1200
	return $topicsInfo;
1201
}
1202
1203
/**
1204
 * Count the number of topics in the unwatched list
1205
 *
1206
 * @param int $memID The ID of the member
1207
 * @return int The number of unwatched topics
1208
 */
1209
function list_getNumUnwatched($memID)
1210
{
1211
	global $smcFunc;
1212
1213
	// Get the total number of attachments they have posted.
1214
	$request = $smcFunc['db_query']('', '
1215
		SELECT COUNT(*)
1216
		FROM {db_prefix}log_topics as lt
1217
		LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1218
		LEFT JOIN {db_prefix}boards as b ON (t.id_board = b.id_board)
1219
		WHERE id_member = {int:current_member}
1220
			AND unwatched = 1
1221
			AND {query_see_board}',
1222
		array(
1223
			'current_member' => $memID,
1224
		)
1225
	);
1226
	list ($unwatchedCount) = $smcFunc['db_fetch_row']($request);
1227
	$smcFunc['db_free_result']($request);
1228
1229
	return $unwatchedCount;
1230
}
1231
1232
/**
1233
 * Gets the user stats for display
1234
 *
1235
 * @param int $memID The ID of the member
1236
 */
1237
function statPanel($memID)
1238
{
1239
	global $txt, $scripturl, $context, $user_profile, $user_info, $modSettings, $smcFunc;
1240
1241
	$context['page_title'] = $txt['statPanel_showStats'] . ' ' . $user_profile[$memID]['real_name'];
1242
1243
	// Is the load average too high to allow searching just now?
1244 View Code Duplication
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_userstats']) && $context['load_average'] >= $modSettings['loadavg_userstats'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1245
		fatal_lang_error('loadavg_userstats_disabled', 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...
1246
1247
	// General user statistics.
1248
	$timeDays = floor($user_profile[$memID]['total_time_logged_in'] / 86400);
1249
	$timeHours = floor(($user_profile[$memID]['total_time_logged_in'] % 86400) / 3600);
1250
	$context['time_logged_in'] = ($timeDays > 0 ? $timeDays . $txt['totalTimeLogged2'] : '') . ($timeHours > 0 ? $timeHours . $txt['totalTimeLogged3'] : '') . floor(($user_profile[$memID]['total_time_logged_in'] % 3600) / 60) . $txt['totalTimeLogged4'];
1251
	$context['num_posts'] = comma_format($user_profile[$memID]['posts']);
1252
	// Menu tab
1253
	$context[$context['profile_menu_name']]['tab_data'] = array(
1254
		'title' => $txt['statPanel_generalStats'] . ' - ' . $context['member']['name'],
1255
		'icon' => 'stats_info_hd.png'
1256
	);
1257
1258
	// Number of topics started.
1259
	$result = $smcFunc['db_query']('', '
1260
		SELECT COUNT(*)
1261
		FROM {db_prefix}topics
1262
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1263
			AND id_board != {int:recycle_board}' : ''),
1264
		array(
1265
			'current_member' => $memID,
1266
			'recycle_board' => $modSettings['recycle_board'],
1267
		)
1268
	);
1269
	list ($context['num_topics']) = $smcFunc['db_fetch_row']($result);
1270
	$smcFunc['db_free_result']($result);
1271
1272
	// Number polls started.
1273
	$result = $smcFunc['db_query']('', '
1274
		SELECT COUNT(*)
1275
		FROM {db_prefix}topics
1276
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1277
			AND id_board != {int:recycle_board}' : '') . '
1278
			AND id_poll != {int:no_poll}',
1279
		array(
1280
			'current_member' => $memID,
1281
			'recycle_board' => $modSettings['recycle_board'],
1282
			'no_poll' => 0,
1283
		)
1284
	);
1285
	list ($context['num_polls']) = $smcFunc['db_fetch_row']($result);
1286
	$smcFunc['db_free_result']($result);
1287
1288
	// Number polls voted in.
1289
	$result = $smcFunc['db_query']('distinct_poll_votes', '
1290
		SELECT COUNT(DISTINCT id_poll)
1291
		FROM {db_prefix}log_polls
1292
		WHERE id_member = {int:current_member}',
1293
		array(
1294
			'current_member' => $memID,
1295
		)
1296
	);
1297
	list ($context['num_votes']) = $smcFunc['db_fetch_row']($result);
1298
	$smcFunc['db_free_result']($result);
1299
1300
	// Format the numbers...
1301
	$context['num_topics'] = comma_format($context['num_topics']);
1302
	$context['num_polls'] = comma_format($context['num_polls']);
1303
	$context['num_votes'] = comma_format($context['num_votes']);
1304
1305
	// Grab the board this member posted in most often.
1306
	$result = $smcFunc['db_query']('', '
1307
		SELECT
1308
			b.id_board, MAX(b.name) AS name, MAX(b.num_posts) AS num_posts, COUNT(*) AS message_count
1309
		FROM {db_prefix}messages AS m
1310
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1311
		WHERE m.id_member = {int:current_member}
1312
			AND b.count_posts = {int:count_enabled}
1313
			AND {query_see_board}
1314
		GROUP BY b.id_board
1315
		ORDER BY message_count DESC
1316
		LIMIT 10',
1317
		array(
1318
			'current_member' => $memID,
1319
			'count_enabled' => 0,
1320
		)
1321
	);
1322
	$context['popular_boards'] = array();
1323
	while ($row = $smcFunc['db_fetch_assoc']($result))
1324
	{
1325
		$context['popular_boards'][$row['id_board']] = array(
1326
			'id' => $row['id_board'],
1327
			'posts' => $row['message_count'],
1328
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1329
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1330
			'posts_percent' => $user_profile[$memID]['posts'] == 0 ? 0 : ($row['message_count'] * 100) / $user_profile[$memID]['posts'],
1331
			'total_posts' => $row['num_posts'],
1332
			'total_posts_member' => $user_profile[$memID]['posts'],
1333
		);
1334
	}
1335
	$smcFunc['db_free_result']($result);
1336
1337
	// Now get the 10 boards this user has most often participated in.
1338
	$result = $smcFunc['db_query']('profile_board_stats', '
1339
		SELECT
1340
			b.id_board, MAX(b.name) AS name, b.num_posts, COUNT(*) AS message_count,
1341
			CASE WHEN COUNT(*) > MAX(b.num_posts) THEN 1 ELSE COUNT(*) / MAX(b.num_posts) END * 100 AS percentage
1342
		FROM {db_prefix}messages AS m
1343
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1344
		WHERE m.id_member = {int:current_member}
1345
			AND {query_see_board}
1346
		GROUP BY b.id_board, b.num_posts
1347
		ORDER BY percentage DESC
1348
		LIMIT 10',
1349
		array(
1350
			'current_member' => $memID,
1351
		)
1352
	);
1353
	$context['board_activity'] = array();
1354
	while ($row = $smcFunc['db_fetch_assoc']($result))
1355
	{
1356
		$context['board_activity'][$row['id_board']] = array(
1357
			'id' => $row['id_board'],
1358
			'posts' => $row['message_count'],
1359
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1360
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1361
			'percent' => comma_format((float) $row['percentage'], 2),
1362
			'posts_percent' => (float) $row['percentage'],
1363
			'total_posts' => $row['num_posts'],
1364
		);
1365
	}
1366
	$smcFunc['db_free_result']($result);
1367
1368
	// Posting activity by time.
1369
	$result = $smcFunc['db_query']('user_activity_by_time', '
1370
		SELECT
1371
			HOUR(FROM_UNIXTIME(poster_time + {int:time_offset})) AS hour,
1372
			COUNT(*) AS post_count
1373
		FROM {db_prefix}messages
1374
		WHERE id_member = {int:current_member}' . ($modSettings['totalMessages'] > 100000 ? '
1375
			AND id_topic > {int:top_ten_thousand_topics}' : '') . '
1376
		GROUP BY hour',
1377
		array(
1378
			'current_member' => $memID,
1379
			'top_ten_thousand_topics' => $modSettings['totalTopics'] - 10000,
1380
			'time_offset' => (($user_info['time_offset'] + $modSettings['time_offset']) * 3600),
1381
		)
1382
	);
1383
	$maxPosts = $realPosts = 0;
1384
	$context['posts_by_time'] = array();
1385
	while ($row = $smcFunc['db_fetch_assoc']($result))
1386
	{
1387
		// Cast as an integer to remove the leading 0.
1388
		$row['hour'] = (int) $row['hour'];
1389
1390
		$maxPosts = max($row['post_count'], $maxPosts);
1391
		$realPosts += $row['post_count'];
1392
1393
		$context['posts_by_time'][$row['hour']] = array(
1394
			'hour' => $row['hour'],
1395
			'hour_format' => stripos($user_info['time_format'], '%p') === false ? $row['hour'] : date('g a', mktime($row['hour'])),
1396
			'posts' => $row['post_count'],
1397
			'posts_percent' => 0,
1398
			'is_last' => $row['hour'] == 23,
1399
		);
1400
	}
1401
	$smcFunc['db_free_result']($result);
1402
1403
	if ($maxPosts > 0)
1404
		for ($hour = 0; $hour < 24; $hour++)
1405
		{
1406
			if (!isset($context['posts_by_time'][$hour]))
1407
				$context['posts_by_time'][$hour] = array(
1408
					'hour' => $hour,
1409
					'hour_format' => stripos($user_info['time_format'], '%p') === false ? $hour : date('g a', mktime($hour)),
1410
					'posts' => 0,
1411
					'posts_percent' => 0,
1412
					'relative_percent' => 0,
1413
					'is_last' => $hour == 23,
1414
				);
1415
			else
1416
			{
1417
				$context['posts_by_time'][$hour]['posts_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $realPosts);
1418
				$context['posts_by_time'][$hour]['relative_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $maxPosts);
1419
			}
1420
		}
1421
1422
	// Put it in the right order.
1423
	ksort($context['posts_by_time']);
1424
1425
	// Custom stats (just add a template_layer to add it to the template!)
1426
 	call_integration_hook('integrate_profile_stats', array($memID));
1427
}
1428
1429
/**
1430
 * Loads up the information for the "track user" section of the profile
1431
 *
1432
 * @param int $memID The ID of the member
1433
 */
1434
function tracking($memID)
1435
{
1436
	global $context, $txt, $modSettings, $user_profile;
1437
1438
	$subActions = array(
1439
		'activity' => array('trackActivity', $txt['trackActivity'], 'moderate_forum'),
1440
		'ip' => array('TrackIP', $txt['trackIP'], 'moderate_forum'),
1441
		'edits' => array('trackEdits', $txt['trackEdits'], 'moderate_forum'),
1442
		'groupreq' => array('trackGroupReq', $txt['trackGroupRequests'], 'approve_group_requests'),
1443
		'logins' => array('TrackLogins', $txt['trackLogins'], 'moderate_forum'),
1444
	);
1445
1446
	foreach ($subActions as $sa => $action)
1447
	{
1448
		if (!allowedTo($action[2]))
1449
			unset($subActions[$sa]);
1450
	}
1451
1452
	// Create the tabs for the template.
1453
	$context[$context['profile_menu_name']]['tab_data'] = array(
1454
		'title' => $txt['tracking'],
1455
		'description' => $txt['tracking_description'],
1456
		'icon' => 'profile_hd.png',
1457
		'tabs' => array(
1458
			'activity' => array(),
1459
			'ip' => array(),
1460
			'edits' => array(),
1461
			'groupreq' => array(),
1462
			'logins' => array(),
1463
		),
1464
	);
1465
1466
	// Moderation must be on to track edits.
1467
	if (empty($modSettings['userlog_enabled']))
1468
		unset($context[$context['profile_menu_name']]['tab_data']['edits'], $subActions['edits']);
1469
1470
	// Group requests must be active to show it...
1471
	if (empty($modSettings['show_group_membership']))
1472
		unset($context[$context['profile_menu_name']]['tab_data']['groupreq'], $subActions['groupreq']);
1473
1474
	if (empty($subActions))
1475
		fatal_lang_error('no_access', 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...
1476
1477
	$keys = array_keys($subActions);
1478
	$default = array_shift($keys);
1479
	$context['tracking_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : $default;
1480
1481
	// Set a page title.
1482
	$context['page_title'] = $txt['trackUser'] . ' - ' . $subActions[$context['tracking_area']][1] . ' - ' . $user_profile[$memID]['real_name'];
1483
1484
	// Pass on to the actual function.
1485
	$context['sub_template'] = $subActions[$context['tracking_area']][0];
1486
	$call = call_helper($subActions[$context['tracking_area']][0], true);
1487
1488
	if (!empty($call))
1489
		call_user_func($call, $memID);
1490
}
1491
1492
/**
1493
 * Handles tracking a user's activity
1494
 *
1495
 * @param int $memID The ID of the member
1496
 */
1497
function trackActivity($memID)
1498
{
1499
	global $scripturl, $txt, $modSettings, $sourcedir;
1500
	global $user_profile, $context, $smcFunc;
1501
1502
	// Verify if the user has sufficient permissions.
1503
	isAllowedTo('moderate_forum');
1504
1505
	$context['last_ip'] = $user_profile[$memID]['member_ip'];
1506
	if ($context['last_ip'] != $user_profile[$memID]['member_ip2'])
1507
		$context['last_ip2'] = $user_profile[$memID]['member_ip2'];
1508
	$context['member']['name'] = $user_profile[$memID]['real_name'];
1509
1510
	// Set the options for the list component.
1511
	$listOptions = array(
1512
		'id' => 'track_user_list',
1513
		'title' => $txt['errors_by'] . ' ' . $context['member']['name'],
1514
		'items_per_page' => $modSettings['defaultMaxListItems'],
1515
		'no_items_label' => $txt['no_errors_from_user'],
1516
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=user;u=' . $memID,
1517
		'default_sort_col' => 'date',
1518
		'get_items' => array(
1519
			'function' => 'list_getUserErrors',
1520
			'params' => array(
1521
				'le.id_member = {int:current_member}',
1522
				array('current_member' => $memID),
1523
			),
1524
		),
1525
		'get_count' => array(
1526
			'function' => 'list_getUserErrorCount',
1527
			'params' => array(
1528
				'id_member = {int:current_member}',
1529
				array('current_member' => $memID),
1530
			),
1531
		),
1532
		'columns' => array(
1533
			'ip_address' => array(
1534
				'header' => array(
1535
					'value' => $txt['ip_address'],
1536
				),
1537
				'data' => array(
1538
					'sprintf' => array(
1539
						'format' => '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=%1$s;u=' . $memID. '">%1$s</a>',
1540
						'params' => array(
1541
							'ip' => false,
1542
						),
1543
					),
1544
				),
1545
				'sort' => array(
1546
					'default' => 'le.ip',
1547
					'reverse' => 'le.ip DESC',
1548
				),
1549
			),
1550
			'message' => array(
1551
				'header' => array(
1552
					'value' => $txt['message'],
1553
				),
1554
				'data' => array(
1555
					'sprintf' => array(
1556
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
1557
						'params' => array(
1558
							'message' => false,
1559
							'url' => false,
1560
						),
1561
					),
1562
				),
1563
			),
1564
			'date' => array(
1565
				'header' => array(
1566
					'value' => $txt['date'],
1567
				),
1568
				'data' => array(
1569
					'db' => 'time',
1570
				),
1571
				'sort' => array(
1572
					'default' => 'le.id_error DESC',
1573
					'reverse' => 'le.id_error',
1574
				),
1575
			),
1576
		),
1577
		'additional_rows' => array(
1578
			array(
1579
				'position' => 'after_title',
1580
				'value' => $txt['errors_desc'],
1581
				'class' => 'windowbg2',
1582
				'style' => 'padding: 1ex 2ex;',
1583
			),
1584
		),
1585
	);
1586
1587
	// Create the list for viewing.
1588
	require_once($sourcedir . '/Subs-List.php');
1589
	createList($listOptions);
1590
1591
	// @todo cache this
1592
	// If this is a big forum, or a large posting user, let's limit the search.
1593
	if ($modSettings['totalMessages'] > 50000 && $user_profile[$memID]['posts'] > 500)
1594
	{
1595
		$request = $smcFunc['db_query']('', '
1596
			SELECT MAX(id_msg)
1597
			FROM {db_prefix}messages AS m
1598
			WHERE m.id_member = {int:current_member}',
1599
			array(
1600
				'current_member' => $memID,
1601
			)
1602
		);
1603
		list ($max_msg_member) = $smcFunc['db_fetch_row']($request);
1604
		$smcFunc['db_free_result']($request);
1605
1606
		// There's no point worrying ourselves with messages made yonks ago, just get recent ones!
1607
		$min_msg_member = max(0, $max_msg_member - $user_profile[$memID]['posts'] * 3);
1608
	}
1609
1610
	// Default to at least the ones we know about.
1611
	$ips = array(
1612
		$user_profile[$memID]['member_ip'],
1613
		$user_profile[$memID]['member_ip2'],
1614
	);
1615
1616
	// @todo cache this
1617
	// Get all IP addresses this user has used for his messages.
1618
	$request = $smcFunc['db_query']('', '
1619
		SELECT poster_ip
1620
		FROM {db_prefix}messages
1621
		WHERE id_member = {int:current_member}
1622
		' . (isset($min_msg_member) ? '
1623
			AND id_msg >= {int:min_msg_member} AND id_msg <= {int:max_msg_member}' : '') . '
1624
		GROUP BY poster_ip',
1625
		array(
1626
			'current_member' => $memID,
1627
			'min_msg_member' => !empty($min_msg_member) ? $min_msg_member : 0,
1628
			'max_msg_member' => !empty($max_msg_member) ? $max_msg_member : 0,
1629
		)
1630
	);
1631
	$context['ips'] = array();
1632 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1633
	{
1634
		$context['ips'][] = '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=' . inet_dtop($row['poster_ip']) . ';u=' . $memID . '">' . inet_dtop($row['poster_ip']) . '</a>';
1635
		$ips[] = inet_dtop($row['poster_ip']);
1636
	}
1637
	$smcFunc['db_free_result']($request);
1638
1639
	// Now also get the IP addresses from the error messages.
1640
	$request = $smcFunc['db_query']('', '
1641
		SELECT COUNT(*) AS error_count, ip
1642
		FROM {db_prefix}log_errors
1643
		WHERE id_member = {int:current_member}
1644
		GROUP BY ip',
1645
		array(
1646
			'current_member' => $memID,
1647
		)
1648
	);
1649
	$context['error_ips'] = array();
1650 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1651
	{
1652
		$context['error_ips'][] = '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=' . $row['ip'] . ';u=' . $memID . '">' . $row['ip'] . '</a>';
1653
		$ips[] = inet_dtop($row['ip']);
1654
	}
1655
	$smcFunc['db_free_result']($request);
1656
1657
	// Find other users that might use the same IP.
1658
	$ips = array_unique($ips);
1659
	$context['members_in_range'] = array();
1660
	if (!empty($ips))
1661
	{
1662
		// Get member ID's which are in messages...
1663
		$request = $smcFunc['db_query']('', '
1664
			SELECT mem.id_member
1665
			FROM {db_prefix}messages AS m
1666
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1667
			WHERE m.poster_ip IN ({array_inet:ip_list})
1668
				AND mem.id_member != {int:current_member}',
1669
			array(
1670
				'current_member' => $memID,
1671
				'ip_list' => $ips,
1672
			)
1673
		);
1674
		$message_members = array();
1675
		while ($row = $smcFunc['db_fetch_assoc']($request))
1676
			$message_members[] = $row['id_member'];
1677
		$smcFunc['db_free_result']($request);
1678
1679
		// Fetch their names, cause of the GROUP BY doesn't like giving us that normally.
1680
		if (!empty($message_members))
1681
		{
1682
			$request = $smcFunc['db_query']('', '
1683
				SELECT id_member, real_name
1684
				FROM {db_prefix}members
1685
				WHERE id_member IN ({array_int:message_members})',
1686
				array(
1687
					'message_members' => $message_members,
1688
					'ip_list' => $ips,
1689
				)
1690
			);
1691
			while ($row = $smcFunc['db_fetch_assoc']($request))
1692
				$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
1693
			$smcFunc['db_free_result']($request);
1694
		}
1695
1696
		$request = $smcFunc['db_query']('', '
1697
			SELECT id_member, real_name
1698
			FROM {db_prefix}members
1699
			WHERE id_member != {int:current_member}
1700
				AND member_ip IN ({array_inet:ip_list})',
1701
			array(
1702
				'current_member' => $memID,
1703
				'ip_list' => $ips,
1704
			)
1705
		);
1706
		while ($row = $smcFunc['db_fetch_assoc']($request))
1707
			$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
1708
		$smcFunc['db_free_result']($request);
1709
	}
1710
}
1711
1712
/**
1713
 * Get the number of user errors
1714
 *
1715
 * @param string $where A query to limit which errors are counted
1716
 * @param array $where_vars The parameters for $where
1717
 * @return int Number of user errors
1718
 */
1719 View Code Duplication
function list_getUserErrorCount($where, $where_vars = array())
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1720
{
1721
	global $smcFunc;
1722
1723
	$request = $smcFunc['db_query']('', '
1724
		SELECT COUNT(*) AS error_count
1725
		FROM {db_prefix}log_errors
1726
		WHERE ' . $where,
1727
		$where_vars
1728
	);
1729
	list ($count) = $smcFunc['db_fetch_row']($request);
1730
	$smcFunc['db_free_result']($request);
1731
1732
	return (int) $count;
1733
}
1734
1735
/**
1736
 * Gets all of the errors generated by a user's actions. Callback for the list in track_activity
1737
 *
1738
 * @param int $start Which item to start with (for pagination purposes)
1739
 * @param int $items_per_page How many items to show on each page
1740
 * @param string $sort A string indicating how to sort the results
1741
 * @param string $where A query indicating how to filter the results (eg 'id_member={int:id_member}')
1742
 * @param array $where_vars An array of parameters for $where
1743
 * @return array An array of information about the error messages
1744
 */
1745
function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars = array())
1746
{
1747
	global $smcFunc, $txt, $scripturl;
1748
1749
	// Get a list of error messages from this ip (range).
1750
	$request = $smcFunc['db_query']('', '
1751
		SELECT
1752
			le.log_time, le.ip, le.url, le.message, COALESCE(mem.id_member, 0) AS id_member,
1753
			COALESCE(mem.real_name, {string:guest_title}) AS display_name, mem.member_name
1754
		FROM {db_prefix}log_errors AS le
1755
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = le.id_member)
1756
		WHERE ' . $where . '
1757
		ORDER BY {raw:sort}
1758
		LIMIT {int:start}, {int:max}',
1759
		array_merge($where_vars, array(
1760
			'guest_title' => $txt['guest_title'],
1761
			'sort' => $sort,
1762
			'start' => $start,
1763
			'max' => $items_per_page,
1764
		))
1765
	);
1766
	$error_messages = array();
1767
	while ($row = $smcFunc['db_fetch_assoc']($request))
1768
		$error_messages[] = array(
1769
			'ip' => inet_dtop($row['ip']),
1770
			'member_link' => $row['id_member'] > 0 ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>' : $row['display_name'],
1771
			'message' => strtr($row['message'], array('&lt;span class=&quot;remove&quot;&gt;' => '', '&lt;/span&gt;' => '')),
1772
			'url' => $row['url'],
1773
			'time' => timeformat($row['log_time']),
1774
			'timestamp' => forum_time(true, $row['log_time']),
1775
		);
1776
	$smcFunc['db_free_result']($request);
1777
1778
	return $error_messages;
1779
}
1780
1781
/**
1782
 * Gets the number of posts made from a particular IP
1783
 *
1784
 * @param string $where A query indicating which posts to count
1785
 * @param array $where_vars The parameters for $where
1786
 * @return int Count of messages matching the IP
1787
 */
1788 View Code Duplication
function list_getIPMessageCount($where, $where_vars = array())
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1789
{
1790
	global $smcFunc;
1791
1792
	$request = $smcFunc['db_query']('', '
1793
		SELECT COUNT(*) AS message_count
1794
		FROM {db_prefix}messages AS m
1795
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1796
		WHERE {query_see_board} AND ' . $where,
1797
		$where_vars
1798
	);
1799
	list ($count) = $smcFunc['db_fetch_row']($request);
1800
	$smcFunc['db_free_result']($request);
1801
1802
	return (int) $count;
1803
}
1804
1805
/**
1806
 * Gets all the posts made from a particular IP
1807
 *
1808
 * @param int $start Which item to start with (for pagination purposes)
1809
 * @param int $items_per_page How many items to show on each page
1810
 * @param string $sort A string indicating how to sort the results
1811
 * @param string $where A query to filter which posts are returned
1812
 * @param array $where_vars An array of parameters for $where
1813
 * @return array An array containing information about the posts
1814
 */
1815
function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars = array())
1816
{
1817
	global $smcFunc, $scripturl;
1818
1819
	// Get all the messages fitting this where clause.
1820
	// @todo SLOW This query is using a filesort.
1821
	$request = $smcFunc['db_query']('', '
1822
		SELECT
1823
			m.id_msg, m.poster_ip, COALESCE(mem.real_name, m.poster_name) AS display_name, mem.id_member,
1824
			m.subject, m.poster_time, m.id_topic, m.id_board
1825
		FROM {db_prefix}messages AS m
1826
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1827
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1828
		WHERE {query_see_board} AND ' . $where . '
1829
		ORDER BY {raw:sort}
1830
		LIMIT {int:start}, {int:max}',
1831
		array_merge($where_vars, array(
1832
			'sort' => $sort,
1833
			'start' => $start,
1834
			'max' => $items_per_page,
1835
		))
1836
	);
1837
	$messages = array();
1838
	while ($row = $smcFunc['db_fetch_assoc']($request))
1839
		$messages[] = array(
1840
			'ip' => inet_dtop($row['poster_ip']),
1841
			'member_link' => empty($row['id_member']) ? $row['display_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>',
1842
			'board' => array(
1843
				'id' => $row['id_board'],
1844
				'href' => $scripturl . '?board=' . $row['id_board']
1845
			),
1846
			'topic' => $row['id_topic'],
1847
			'id' => $row['id_msg'],
1848
			'subject' => $row['subject'],
1849
			'time' => timeformat($row['poster_time']),
1850
			'timestamp' => forum_time(true, $row['poster_time'])
1851
		);
1852
	$smcFunc['db_free_result']($request);
1853
1854
	return $messages;
1855
}
1856
1857
/**
1858
 * Handles tracking a particular IP address
1859
 *
1860
 * @param int $memID The ID of a member whose IP we want to track
1861
 */
1862
function TrackIP($memID = 0)
1863
{
1864
	global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir;
1865
	global $context, $smcFunc;
1866
1867
	// Can the user do this?
1868
	isAllowedTo('moderate_forum');
1869
1870
	if ($memID == 0)
1871
	{
1872
		$context['ip'] = $user_info['ip'];
1873
		loadTemplate('Profile');
1874
		loadLanguage('Profile');
1875
		$context['sub_template'] = 'trackIP';
1876
		$context['page_title'] = $txt['profile'];
1877
		$context['base_url'] = $scripturl . '?action=trackip';
1878
	}
1879
	else
1880
	{
1881
		$context['ip'] = $user_profile[$memID]['member_ip'];
1882
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
1883
	}
1884
1885
	// Searching?
1886
	if (isset($_REQUEST['searchip']))
1887
		$context['ip'] = trim($_REQUEST['searchip']);
1888
1889
	if (isValidIP($context['ip']) === false)
1890
		fatal_lang_error('invalid_tracking_ip', 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...
1891
1892
	//mysql didn't support like search with varbinary
1893
	//$ip_var = str_replace('*', '%', $context['ip']);
1894
	//$ip_string = strpos($ip_var, '%') === false ? '= {inet:ip_address}' : 'LIKE {string:ip_address}';
1895
	$ip_var = $context['ip'];
1896
	$ip_string = '= {inet:ip_address}';
1897
1898
	if (empty($context['tracking_area']))
1899
		$context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip'];
1900
1901
	$request = $smcFunc['db_query']('', '
1902
		SELECT id_member, real_name AS display_name, member_ip
1903
		FROM {db_prefix}members
1904
		WHERE member_ip ' . $ip_string,
1905
		array(
1906
			'ip_address' => $ip_var,
1907
		)
1908
	);
1909
	$context['ips'] = array();
1910
	while ($row = $smcFunc['db_fetch_assoc']($request))
1911
		$context['ips'][inet_dtop($row['member_ip'])][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
1912
	$smcFunc['db_free_result']($request);
1913
1914
	ksort($context['ips']);
1915
1916
	// For messages we use the "messages per page" option
1917
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
0 ignored issues
show
Bug introduced by
The variable $options seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1918
1919
	// Gonna want this for the list.
1920
	require_once($sourcedir . '/Subs-List.php');
1921
1922
	// Start with the user messages.
1923
	$listOptions = array(
1924
		'id' => 'track_message_list',
1925
		'title' => $txt['messages_from_ip'] . ' ' . $context['ip'],
1926
		'start_var_name' => 'messageStart',
1927
		'items_per_page' => $maxPerPage,
1928
		'no_items_label' => $txt['no_messages_from_ip'],
1929
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
1930
		'default_sort_col' => 'date',
1931
		'get_items' => array(
1932
			'function' => 'list_getIPMessages',
1933
			'params' => array(
1934
				'm.poster_ip ' . $ip_string,
1935
				array('ip_address' => $ip_var),
1936
			),
1937
		),
1938
		'get_count' => array(
1939
			'function' => 'list_getIPMessageCount',
1940
			'params' => array(
1941
				'm.poster_ip ' . $ip_string,
1942
				array('ip_address' => $ip_var),
1943
			),
1944
		),
1945
		'columns' => array(
1946
			'ip_address' => array(
1947
				'header' => array(
1948
					'value' => $txt['ip_address'],
1949
				),
1950
				'data' => array(
1951
					'sprintf' => array(
1952
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
1953
						'params' => array(
1954
							'ip' => false,
1955
						),
1956
					),
1957
				),
1958
				'sort' => array(
1959
					'default' => 'm.poster_ip',
1960
					'reverse' => 'm.poster_ip DESC',
1961
				),
1962
			),
1963
			'poster' => array(
1964
				'header' => array(
1965
					'value' => $txt['poster'],
1966
				),
1967
				'data' => array(
1968
					'db' => 'member_link',
1969
				),
1970
			),
1971
			'subject' => array(
1972
				'header' => array(
1973
					'value' => $txt['subject'],
1974
				),
1975
				'data' => array(
1976
					'sprintf' => array(
1977
						'format' => '<a href="' . $scripturl . '?topic=%1$s.msg%2$s#msg%2$s" rel="nofollow">%3$s</a>',
1978
						'params' => array(
1979
							'topic' => false,
1980
							'id' => false,
1981
							'subject' => false,
1982
						),
1983
					),
1984
				),
1985
			),
1986
			'date' => array(
1987
				'header' => array(
1988
					'value' => $txt['date'],
1989
				),
1990
				'data' => array(
1991
					'db' => 'time',
1992
				),
1993
				'sort' => array(
1994
					'default' => 'm.id_msg DESC',
1995
					'reverse' => 'm.id_msg',
1996
				),
1997
			),
1998
		),
1999
		'additional_rows' => array(
2000
			array(
2001
				'position' => 'after_title',
2002
				'value' => $txt['messages_from_ip_desc'],
2003
				'class' => 'windowbg2',
2004
				'style' => 'padding: 1ex 2ex;',
2005
			),
2006
		),
2007
	);
2008
2009
	// Create the messages list.
2010
	createList($listOptions);
2011
2012
	// Set the options for the error lists.
2013
	$listOptions = array(
2014
		'id' => 'track_user_list',
2015
		'title' => $txt['errors_from_ip'] . ' ' . $context['ip'],
2016
		'start_var_name' => 'errorStart',
2017
		'items_per_page' => $modSettings['defaultMaxListItems'],
2018
		'no_items_label' => $txt['no_errors_from_ip'],
2019
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2020
		'default_sort_col' => 'date2',
2021
		'get_items' => array(
2022
			'function' => 'list_getUserErrors',
2023
			'params' => array(
2024
				'le.ip ' . $ip_string,
2025
				array('ip_address' => $ip_var),
2026
			),
2027
		),
2028
		'get_count' => array(
2029
			'function' => 'list_getUserErrorCount',
2030
			'params' => array(
2031
				'ip ' . $ip_string,
2032
				array('ip_address' => $ip_var),
2033
			),
2034
		),
2035
		'columns' => array(
2036
			'ip_address2' => array(
2037
				'header' => array(
2038
					'value' => $txt['ip_address'],
2039
				),
2040
				'data' => array(
2041
					'sprintf' => array(
2042
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2043
						'params' => array(
2044
							'ip' => false,
2045
						),
2046
					),
2047
				),
2048
				'sort' => array(
2049
					'default' => 'le.ip',
2050
					'reverse' => 'le.ip DESC',
2051
				),
2052
			),
2053
			'display_name' => array(
2054
				'header' => array(
2055
					'value' => $txt['display_name'],
2056
				),
2057
				'data' => array(
2058
					'db' => 'member_link',
2059
				),
2060
			),
2061
			'message' => array(
2062
				'header' => array(
2063
					'value' => $txt['message'],
2064
				),
2065
				'data' => array(
2066
					'sprintf' => array(
2067
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
2068
						'params' => array(
2069
							'message' => false,
2070
							'url' => false,
2071
						),
2072
					),
2073
				),
2074
			),
2075
			'date2' => array(
2076
				'header' => array(
2077
					'value' => $txt['date'],
2078
				),
2079
				'data' => array(
2080
					'db' => 'time',
2081
				),
2082
				'sort' => array(
2083
					'default' => 'le.id_error DESC',
2084
					'reverse' => 'le.id_error',
2085
				),
2086
			),
2087
		),
2088
		'additional_rows' => array(
2089
			array(
2090
				'position' => 'after_title',
2091
				'value' => $txt['errors_from_ip_desc'],
2092
				'class' => 'windowbg2',
2093
				'style' => 'padding: 1ex 2ex;',
2094
			),
2095
		),
2096
	);
2097
2098
	// Create the error list.
2099
	createList($listOptions);
2100
2101
	// Allow 3rd party integrations to add in their own lists or whatever.
2102
	$context['additional_track_lists'] = array();
2103
	call_integration_hook('integrate_profile_trackip', array($ip_string, $ip_var));
2104
2105
	$context['single_ip'] = strpos($context['ip'], '*') === false;
2106
	if ($context['single_ip'])
2107
	{
2108
		$context['whois_servers'] = array(
2109
			'afrinic' => array(
2110
				'name' => $txt['whois_afrinic'],
2111
				'url' => 'http://www.afrinic.net/cgi-bin/whois?searchtext=' . $context['ip'],
2112
				'range' => array(41, 154, 196),
2113
			),
2114
			'apnic' => array(
2115
				'name' => $txt['whois_apnic'],
2116
				'url' => 'http://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'],
2117
				'range' => array(58, 59, 60, 61, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
2118
					125, 126, 133, 150, 153, 163, 171, 202, 203, 210, 211, 218, 219, 220, 221, 222),
2119
			),
2120
			'arin' => array(
2121
				'name' => $txt['whois_arin'],
2122
				'url' => 'http://whois.arin.net/rest/ip/' . $context['ip'],
2123
				'range' => array(7, 24, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 96, 97, 98, 99,
2124
					128, 129, 130, 131, 132, 134, 135, 136, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148, 149,
2125
					152, 155, 156, 157, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174,
2126
					192, 198, 199, 204, 205, 206, 207, 208, 209, 216),
2127
			),
2128
			'lacnic' => array(
2129
				'name' => $txt['whois_lacnic'],
2130
				'url' => 'http://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'],
2131
				'range' => array(186, 187, 189, 190, 191, 200, 201),
2132
			),
2133
			'ripe' => array(
2134
				'name' => $txt['whois_ripe'],
2135
				'url' => 'https://apps.db.ripe.net/search/query.html?searchtext=' . $context['ip'],
2136
				'range' => array(62, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2137
					141, 145, 151, 188, 193, 194, 195, 212, 213, 217),
2138
			),
2139
		);
2140
2141
		foreach ($context['whois_servers'] as $whois)
2142
		{
2143
			// Strip off the "decimal point" and anything following...
2144
			if (in_array((int) $context['ip'], $whois['range']))
2145
				$context['auto_whois_server'] = $whois;
2146
		}
2147
	}
2148
}
2149
2150
/**
2151
 * Tracks a user's logins.
2152
 *
2153
 * @param int $memID The ID of the member
2154
 */
2155
function TrackLogins($memID = 0)
2156
{
2157
	global $scripturl, $txt, $sourcedir, $context;
2158
2159
	// Gonna want this for the list.
2160
	require_once($sourcedir . '/Subs-List.php');
2161
2162
	if ($memID == 0)
2163
		$context['base_url'] = $scripturl . '?action=trackip';
2164
	else
2165
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2166
2167
	// Start with the user messages.
2168
	$listOptions = array(
2169
		'id' => 'track_logins_list',
2170
		'title' => $txt['trackLogins'],
2171
		'no_items_label' => $txt['trackLogins_none_found'],
2172
		'base_href' => $context['base_url'],
2173
		'get_items' => array(
2174
			'function' => 'list_getLogins',
2175
			'params' => array(
2176
				'id_member = {int:current_member}',
2177
				array('current_member' => $memID),
2178
			),
2179
		),
2180
		'get_count' => array(
2181
			'function' => 'list_getLoginCount',
2182
			'params' => array(
2183
				'id_member = {int:current_member}',
2184
				array('current_member' => $memID),
2185
			),
2186
		),
2187
		'columns' => array(
2188
			'time' => array(
2189
				'header' => array(
2190
					'value' => $txt['date'],
2191
				),
2192
				'data' => array(
2193
					'db' => 'time',
2194
				),
2195
			),
2196
			'ip' => array(
2197
				'header' => array(
2198
					'value' => $txt['ip_address'],
2199
				),
2200
				'data' => array(
2201
					'sprintf' => array(
2202
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a> (<a href="' . $context['base_url'] . ';searchip=%2$s">%2$s</a>) ',
2203
						'params' => array(
2204
							'ip' => false,
2205
							'ip2' => false
2206
						),
2207
					),
2208
				),
2209
			),
2210
		),
2211
		'additional_rows' => array(
2212
			array(
2213
				'position' => 'after_title',
2214
				'value' => $txt['trackLogins_desc'],
2215
				'class' => 'windowbg2',
2216
				'style' => 'padding: 1ex 2ex;',
2217
			),
2218
		),
2219
	);
2220
2221
	// Create the messages list.
2222
	createList($listOptions);
2223
2224
	$context['sub_template'] = 'show_list';
2225
	$context['default_list'] = 'track_logins_list';
2226
}
2227
2228
/**
2229
 * Finds the total number of tracked logins for a particular user
2230
 *
2231
 * @param string $where A query to limit which logins are counted
2232
 * @param array $where_vars An array of parameters for $where
2233
 * @return int count of messages matching the IP
2234
 */
2235 View Code Duplication
function list_getLoginCount($where, $where_vars = array())
0 ignored issues
show
Unused Code introduced by
The parameter $where is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2236
{
2237
	global $smcFunc;
2238
2239
	$request = $smcFunc['db_query']('', '
2240
		SELECT COUNT(*) AS message_count
2241
		FROM {db_prefix}member_logins
2242
		WHERE id_member = {int:id_member}',
2243
		array(
2244
			'id_member' => $where_vars['current_member'],
2245
		)
2246
	);
2247
	list ($count) = $smcFunc['db_fetch_row']($request);
2248
	$smcFunc['db_free_result']($request);
2249
2250
	return (int) $count;
2251
}
2252
2253
/**
2254
 * Callback for the list in trackLogins.
2255
 *
2256
 * @param int $start Which item to start with (not used here)
2257
 * @param int $items_per_page How many items to show on each page (not used here)
2258
 * @param string $sort A string indicating
2259
 * @param string $where A query to filter results (not used here)
2260
 * @param array $where_vars An array of parameters for $where. Only 'current_member' (the ID of the member) is used here
2261
 * @return array An array of information about user logins
2262
 */
2263
function list_getLogins($start, $items_per_page, $sort, $where, $where_vars = array())
0 ignored issues
show
Unused Code introduced by
The parameter $start is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $items_per_page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $sort is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $where is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2264
{
2265
	global $smcFunc;
2266
2267
	$request = $smcFunc['db_query']('', '
2268
		SELECT time, ip, ip2
2269
		FROM {db_prefix}member_logins
2270
		WHERE id_member = {int:id_member}
2271
		ORDER BY time DESC',
2272
		array(
2273
			'id_member' => $where_vars['current_member'],
2274
		)
2275
	);
2276
	$logins = array();
2277
	while ($row = $smcFunc['db_fetch_assoc']($request))
2278
		$logins[] = array(
2279
			'time' => timeformat($row['time']),
2280
			'ip' => inet_dtop($row['ip']),
2281
			'ip2' => inet_dtop($row['ip2']),
2282
		);
2283
	$smcFunc['db_free_result']($request);
2284
2285
	return $logins;
2286
}
2287
2288
/**
2289
 * Tracks a user's profile edits
2290
 *
2291
 * @param int $memID The ID of the member
2292
 */
2293
function trackEdits($memID)
2294
{
2295
	global $scripturl, $txt, $modSettings, $sourcedir, $context, $smcFunc;
2296
2297
	require_once($sourcedir . '/Subs-List.php');
2298
2299
	// Get the names of any custom fields.
2300
	$request = $smcFunc['db_query']('', '
2301
		SELECT col_name, field_name, bbc
2302
		FROM {db_prefix}custom_fields',
2303
		array(
2304
		)
2305
	);
2306
	$context['custom_field_titles'] = array();
2307
	while ($row = $smcFunc['db_fetch_assoc']($request))
2308
		$context['custom_field_titles']['customfield_' . $row['col_name']] = array(
2309
			'title' => $row['field_name'],
2310
			'parse_bbc' => $row['bbc'],
2311
		);
2312
	$smcFunc['db_free_result']($request);
2313
2314
	// Set the options for the error lists.
2315
	$listOptions = array(
2316
		'id' => 'edit_list',
2317
		'title' => $txt['trackEdits'],
2318
		'items_per_page' => $modSettings['defaultMaxListItems'],
2319
		'no_items_label' => $txt['trackEdit_no_edits'],
2320
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=edits;u=' . $memID,
2321
		'default_sort_col' => 'time',
2322
		'get_items' => array(
2323
			'function' => 'list_getProfileEdits',
2324
			'params' => array(
2325
				$memID,
2326
			),
2327
		),
2328
		'get_count' => array(
2329
			'function' => 'list_getProfileEditCount',
2330
			'params' => array(
2331
				$memID,
2332
			),
2333
		),
2334
		'columns' => array(
2335
			'action' => array(
2336
				'header' => array(
2337
					'value' => $txt['trackEdit_action'],
2338
				),
2339
				'data' => array(
2340
					'db' => 'action_text',
2341
				),
2342
			),
2343
			'before' => array(
2344
				'header' => array(
2345
					'value' => $txt['trackEdit_before'],
2346
				),
2347
				'data' => array(
2348
					'db' => 'before',
2349
				),
2350
			),
2351
			'after' => array(
2352
				'header' => array(
2353
					'value' => $txt['trackEdit_after'],
2354
				),
2355
				'data' => array(
2356
					'db' => 'after',
2357
				),
2358
			),
2359
			'time' => array(
2360
				'header' => array(
2361
					'value' => $txt['date'],
2362
				),
2363
				'data' => array(
2364
					'db' => 'time',
2365
				),
2366
				'sort' => array(
2367
					'default' => 'id_action DESC',
2368
					'reverse' => 'id_action',
2369
				),
2370
			),
2371
			'applicator' => array(
2372
				'header' => array(
2373
					'value' => $txt['trackEdit_applicator'],
2374
				),
2375
				'data' => array(
2376
					'db' => 'member_link',
2377
				),
2378
			),
2379
		),
2380
	);
2381
2382
	// Create the error list.
2383
	createList($listOptions);
2384
2385
	$context['sub_template'] = 'show_list';
2386
	$context['default_list'] = 'edit_list';
2387
}
2388
2389
/**
2390
 * How many edits?
2391
 *
2392
 * @param int $memID The ID of the member
2393
 * @return int The number of profile edits
2394
 */
2395 View Code Duplication
function list_getProfileEditCount($memID)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2396
{
2397
	global $smcFunc;
2398
2399
	$request = $smcFunc['db_query']('', '
2400
		SELECT COUNT(*) AS edit_count
2401
		FROM {db_prefix}log_actions
2402
		WHERE id_log = {int:log_type}
2403
			AND id_member = {int:owner}',
2404
		array(
2405
			'log_type' => 2,
2406
			'owner' => $memID,
2407
		)
2408
	);
2409
	list ($edit_count) = $smcFunc['db_fetch_row']($request);
2410
	$smcFunc['db_free_result']($request);
2411
2412
	return (int) $edit_count;
2413
}
2414
2415
/**
2416
 * Loads up information about a user's profile edits. Callback for the list in trackEdits()
2417
 *
2418
 * @param int $start Which item to start with (for pagination purposes)
2419
 * @param int $items_per_page How many items to show on each page
2420
 * @param string $sort A string indicating how to sort the results
2421
 * @param int $memID The ID of the member
2422
 * @return array An array of information about the profile edits
2423
 */
2424
function list_getProfileEdits($start, $items_per_page, $sort, $memID)
2425
{
2426
	global $smcFunc, $txt, $scripturl, $context;
2427
2428
	// Get a list of error messages from this ip (range).
2429
	$request = $smcFunc['db_query']('', '
2430
		SELECT
2431
			id_action, id_member, ip, log_time, action, extra
2432
		FROM {db_prefix}log_actions
2433
		WHERE id_log = {int:log_type}
2434
			AND id_member = {int:owner}
2435
		ORDER BY {raw:sort}
2436
		LIMIT {int:start}, {int:max}',
2437
		array(
2438
			'log_type' => 2,
2439
			'owner' => $memID,
2440
			'sort' => $sort,
2441
			'start' => $start,
2442
			'max' => $items_per_page,
2443
		)
2444
	);
2445
	$edits = array();
2446
	$members = array();
2447
	while ($row = $smcFunc['db_fetch_assoc']($request))
2448
	{
2449
		$extra = @json_decode($row['extra'], true);
2450
		if (!empty($extra['applicator']))
2451
			$members[] = $extra['applicator'];
2452
2453
		// Work out what the name of the action is.
2454
		if (isset($txt['trackEdit_action_' . $row['action']]))
2455
			$action_text = $txt['trackEdit_action_' . $row['action']];
2456
		elseif (isset($txt[$row['action']]))
2457
			$action_text = $txt[$row['action']];
2458
		// Custom field?
2459
		elseif (isset($context['custom_field_titles'][$row['action']]))
2460
			$action_text = $context['custom_field_titles'][$row['action']]['title'];
2461
		else
2462
			$action_text = $row['action'];
2463
2464
		// Parse BBC?
2465
		$parse_bbc = isset($context['custom_field_titles'][$row['action']]) && $context['custom_field_titles'][$row['action']]['parse_bbc'] ? true : false;
2466
2467
		$edits[] = array(
2468
			'id' => $row['id_action'],
2469
			'ip' => inet_dtop($row['ip']),
2470
			'id_member' => !empty($extra['applicator']) ? $extra['applicator'] : 0,
2471
			'member_link' => $txt['trackEdit_deleted_member'],
2472
			'action' => $row['action'],
2473
			'action_text' => $action_text,
2474
			'before' => !empty($extra['previous']) ? ($parse_bbc ? parse_bbc($extra['previous']) : $extra['previous']) : '',
2475
			'after' => !empty($extra['new']) ? ($parse_bbc ? parse_bbc($extra['new']) : $extra['new']) : '',
2476
			'time' => timeformat($row['log_time']),
2477
		);
2478
	}
2479
	$smcFunc['db_free_result']($request);
2480
2481
	// Get any member names.
2482
	if (!empty($members))
2483
	{
2484
		$request = $smcFunc['db_query']('', '
2485
			SELECT
2486
				id_member, real_name
2487
			FROM {db_prefix}members
2488
			WHERE id_member IN ({array_int:members})',
2489
			array(
2490
				'members' => $members,
2491
			)
2492
		);
2493
		$members = array();
2494
		while ($row = $smcFunc['db_fetch_assoc']($request))
2495
			$members[$row['id_member']] = $row['real_name'];
2496
		$smcFunc['db_free_result']($request);
2497
2498
		foreach ($edits as $key => $value)
2499
			if (isset($members[$value['id_member']]))
2500
				$edits[$key]['member_link'] = '<a href="' . $scripturl . '?action=profile;u=' . $value['id_member'] . '">' . $members[$value['id_member']] . '</a>';
2501
	}
2502
2503
	return $edits;
2504
}
2505
2506
/**
2507
 * Display the history of group requests made by the user whose profile we are viewing.
2508
 *
2509
 * @param int $memID The ID of the member
2510
 */
2511
function trackGroupReq($memID)
2512
{
2513
	global $scripturl, $txt, $modSettings, $sourcedir, $context;
2514
2515
	require_once($sourcedir . '/Subs-List.php');
2516
2517
	// Set the options for the error lists.
2518
	$listOptions = array(
2519
		'id' => 'request_list',
2520
		'title' => sprintf($txt['trackGroupRequests_title'], $context['member']['name']),
2521
		'items_per_page' => $modSettings['defaultMaxListItems'],
2522
		'no_items_label' => $txt['requested_none'],
2523
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=groupreq;u=' . $memID,
2524
		'default_sort_col' => 'time_applied',
2525
		'get_items' => array(
2526
			'function' => 'list_getGroupRequests',
2527
			'params' => array(
2528
				$memID,
2529
			),
2530
		),
2531
		'get_count' => array(
2532
			'function' => 'list_getGroupRequestsCount',
2533
			'params' => array(
2534
				$memID,
2535
			),
2536
		),
2537
		'columns' => array(
2538
			'group' => array(
2539
				'header' => array(
2540
					'value' => $txt['requested_group'],
2541
				),
2542
				'data' => array(
2543
					'db' => 'group_name',
2544
				),
2545
			),
2546
			'group_reason' => array(
2547
				'header' => array(
2548
					'value' => $txt['requested_group_reason'],
2549
				),
2550
				'data' => array(
2551
					'db' => 'group_reason',
2552
				),
2553
			),
2554
			'time_applied' => array(
2555
				'header' => array(
2556
					'value' => $txt['requested_group_time'],
2557
				),
2558
				'data' => array(
2559
					'db' => 'time_applied',
2560
					'timeformat' => true,
2561
				),
2562
				'sort' => array(
2563
					'default' => 'time_applied DESC',
2564
					'reverse' => 'time_applied',
2565
				),
2566
			),
2567
			'outcome' => array(
2568
				'header' => array(
2569
					'value' => $txt['requested_group_outcome'],
2570
				),
2571
				'data' => array(
2572
					'db' => 'outcome',
2573
				),
2574
			),
2575
		),
2576
	);
2577
2578
	// Create the error list.
2579
	createList($listOptions);
2580
2581
	$context['sub_template'] = 'show_list';
2582
	$context['default_list'] = 'request_list';
2583
}
2584
2585
/**
2586
 * How many edits?
2587
 *
2588
 * @param int $memID The ID of the member
2589
 * @return int The number of profile edits
2590
 */
2591
function list_getGroupRequestsCount($memID)
2592
{
2593
	global $smcFunc, $user_info;
2594
2595
	$request = $smcFunc['db_query']('', '
2596
		SELECT COUNT(*) AS req_count
2597
		FROM {db_prefix}log_group_requests AS lgr
2598
		WHERE id_member = {int:memID}
2599
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']),
2600
		array(
2601
			'memID' => $memID,
2602
		)
2603
	);
2604
	list ($report_count) = $smcFunc['db_fetch_row']($request);
2605
	$smcFunc['db_free_result']($request);
2606
2607
	return (int) $report_count;
2608
}
2609
2610
/**
2611
 * Loads up information about a user's group requests. Callback for the list in trackGroupReq()
2612
 *
2613
 * @param int $start Which item to start with (for pagination purposes)
2614
 * @param int $items_per_page How many items to show on each page
2615
 * @param string $sort A string indicating how to sort the results
2616
 * @param int $memID The ID of the member
2617
 * @return array An array of information about the user's group requests
2618
 */
2619
function list_getGroupRequests($start, $items_per_page, $sort, $memID)
0 ignored issues
show
Best Practice introduced by
The function list_getGroupRequests() has been defined more than once; this definition is ignored, only the first definition in Sources/Groups.php (L723-775) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
2620
{
2621
	global $smcFunc, $txt, $scripturl, $user_info;
2622
2623
	$groupreq = array();
2624
2625
	$request = $smcFunc['db_query']('', '
2626
		SELECT
2627
			lgr.id_group, mg.group_name, mg.online_color, lgr.time_applied, lgr.reason, lgr.status,
2628
			ma.id_member AS id_member_acted, COALESCE(ma.member_name, lgr.member_name_acted) AS act_name, lgr.time_acted, lgr.act_reason
2629
		FROM {db_prefix}log_group_requests AS lgr
2630
			LEFT JOIN {db_prefix}members AS ma ON (lgr.id_member_acted = ma.id_member)
2631
			INNER JOIN {db_prefix}membergroups AS mg ON (lgr.id_group = mg.id_group)
2632
		WHERE lgr.id_member = {int:memID}
2633
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']) . '
2634
		ORDER BY {raw:sort}
2635
		LIMIT {int:start}, {int:max}',
2636
		array(
2637
			'memID' => $memID,
2638
			'sort' => $sort,
2639
			'start' => $start,
2640
			'max' => $items_per_page,
2641
		)
2642
	);
2643
	while ($row = $smcFunc['db_fetch_assoc']($request))
2644
	{
2645
		$this_req = array(
2646
			'group_name' => empty($row['online_color']) ? $row['group_name'] : '<span style="color:' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
2647
			'group_reason' => $row['reason'],
2648
			'time_applied' => $row['time_applied'],
2649
		);
2650
		switch ($row['status'])
2651
		{
2652
			case 0:
2653
				$this_req['outcome'] = $txt['outcome_pending'];
2654
				break;
2655
			case 1:
2656
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2657
				$this_req['outcome'] = sprintf($txt['outcome_approved'], $member_link, timeformat($row['time_acted']));
2658
				break;
2659
			case 2:
2660
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2661
				$this_req['outcome'] = sprintf(!empty($row['act_reason']) ? $txt['outcome_refused_reason'] : $txt['outcome_refused'], $member_link, timeformat($row['time_acted']), $row['act_reason']);
2662
				break;
2663
		}
2664
2665
		$groupreq[] = $this_req;
2666
	}
2667
	$smcFunc['db_free_result']($request);
2668
2669
	return $groupreq;
2670
}
2671
2672
/**
2673
 * Shows which permissions a user has
2674
 *
2675
 * @param int $memID The ID of the member
2676
 */
2677
function showPermissions($memID)
2678
{
2679
	global $txt, $board;
2680
	global $user_profile, $context, $sourcedir, $smcFunc;
2681
2682
	// Verify if the user has sufficient permissions.
2683
	isAllowedTo('manage_permissions');
2684
2685
	loadLanguage('ManagePermissions');
2686
	loadLanguage('Admin');
2687
	loadTemplate('ManageMembers');
2688
2689
	// Load all the permission profiles.
2690
	require_once($sourcedir . '/ManagePermissions.php');
2691
	loadPermissionProfiles();
2692
2693
	$context['member']['id'] = $memID;
2694
	$context['member']['name'] = $user_profile[$memID]['real_name'];
2695
2696
	$context['page_title'] = $txt['showPermissions'];
2697
	$board = empty($board) ? 0 : (int) $board;
2698
	$context['board'] = $board;
2699
2700
	// Determine which groups this user is in.
2701
	if (empty($user_profile[$memID]['additional_groups']))
2702
		$curGroups = array();
2703
	else
2704
		$curGroups = explode(',', $user_profile[$memID]['additional_groups']);
2705
	$curGroups[] = $user_profile[$memID]['id_group'];
2706
	$curGroups[] = $user_profile[$memID]['id_post_group'];
2707
2708
	// Load a list of boards for the jump box - except the defaults.
2709
	$request = $smcFunc['db_query']('order_by_board_order', '
2710
		SELECT b.id_board, b.name, b.id_profile, b.member_groups, COALESCE(mods.id_member, modgs.id_group, 0) AS is_mod
2711
		FROM {db_prefix}boards AS b
2712
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
2713
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:current_groups}))
2714
		WHERE {query_see_board}',
2715
		array(
2716
			'current_member' => $memID,
2717
			'current_groups' => $curGroups,
2718
		)
2719
	);
2720
	$context['boards'] = array();
2721
	$context['no_access_boards'] = array();
2722
	while ($row = $smcFunc['db_fetch_assoc']($request))
2723
	{
2724
		if (count(array_intersect($curGroups, explode(',', $row['member_groups']))) === 0 && !$row['is_mod'])
2725
			$context['no_access_boards'][] = array(
2726
				'id' => $row['id_board'],
2727
				'name' => $row['name'],
2728
				'is_last' => false,
2729
			);
2730
		elseif ($row['id_profile'] != 1 || $row['is_mod'])
2731
			$context['boards'][$row['id_board']] = array(
2732
				'id' => $row['id_board'],
2733
				'name' => $row['name'],
2734
				'selected' => $board == $row['id_board'],
2735
				'profile' => $row['id_profile'],
2736
				'profile_name' => $context['profiles'][$row['id_profile']]['name'],
2737
			);
2738
	}
2739
	$smcFunc['db_free_result']($request);
2740
2741
	require_once($sourcedir . '/Subs-Boards.php');
2742
	sortBoards($context['boards']);
2743
2744 View Code Duplication
	if (!empty($context['no_access_boards']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2745
		$context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true;
2746
2747
	$context['member']['permissions'] = array(
2748
		'general' => array(),
2749
		'board' => array()
2750
	);
2751
2752
	// If you're an admin we know you can do everything, we might as well leave.
2753
	$context['member']['has_all_permissions'] = in_array(1, $curGroups);
2754
	if ($context['member']['has_all_permissions'])
2755
		return;
2756
2757
	$denied = array();
2758
2759
	// Get all general permissions.
2760
	$result = $smcFunc['db_query']('', '
2761
		SELECT p.permission, p.add_deny, mg.group_name, p.id_group
2762
		FROM {db_prefix}permissions AS p
2763
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = p.id_group)
2764
		WHERE p.id_group IN ({array_int:group_list})
2765
		ORDER BY p.add_deny DESC, p.permission, mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name',
2766
		array(
2767
			'group_list' => $curGroups,
2768
			'newbie_group' => 4,
2769
		)
2770
	);
2771
	while ($row = $smcFunc['db_fetch_assoc']($result))
2772
	{
2773
		// We don't know about this permission, it doesn't exist :P.
2774
		if (!isset($txt['permissionname_' . $row['permission']]))
2775
			continue;
2776
2777
		if (empty($row['add_deny']))
2778
			$denied[] = $row['permission'];
2779
2780
		// Permissions that end with _own or _any consist of two parts.
2781 View Code Duplication
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2782
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
2783
		else
2784
			$name = $txt['permissionname_' . $row['permission']];
2785
2786
		// Add this permission if it doesn't exist yet.
2787 View Code Duplication
		if (!isset($context['member']['permissions']['general'][$row['permission']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2788
			$context['member']['permissions']['general'][$row['permission']] = array(
2789
				'id' => $row['permission'],
2790
				'groups' => array(
2791
					'allowed' => array(),
2792
					'denied' => array()
2793
				),
2794
				'name' => $name,
2795
				'is_denied' => false,
2796
				'is_global' => true,
2797
			);
2798
2799
		// Add the membergroup to either the denied or the allowed groups.
2800
		$context['member']['permissions']['general'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name'];
2801
2802
		// Once denied is always denied.
2803
		$context['member']['permissions']['general'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
2804
	}
2805
	$smcFunc['db_free_result']($result);
2806
2807
	$request = $smcFunc['db_query']('', '
2808
		SELECT
2809
			bp.add_deny, bp.permission, bp.id_group, mg.group_name' . (empty($board) ? '' : ',
2810
			b.id_profile, CASE WHEN (mods.id_member IS NULL AND modgs.id_group IS NULL) THEN 0 ELSE 1 END AS is_moderator') . '
2811
		FROM {db_prefix}board_permissions AS bp' . (empty($board) ? '' : '
2812
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = {int:current_board})
2813
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
2814
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list}))') . '
2815
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = bp.id_group)
2816
		WHERE bp.id_profile = {raw:current_profile}
2817
			AND bp.id_group IN ({array_int:group_list}' . (empty($board) ? ')' : ', {int:moderator_group})
2818
			AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group})'),
2819
		array(
2820
			'current_board' => $board,
2821
			'group_list' => $curGroups,
2822
			'current_member' => $memID,
2823
			'current_profile' => empty($board) ? '1' : 'b.id_profile',
2824
			'moderator_group' => 3,
2825
		)
2826
	);
2827
2828
	while ($row = $smcFunc['db_fetch_assoc']($request))
2829
	{
2830
		// We don't know about this permission, it doesn't exist :P.
2831
		if (!isset($txt['permissionname_' . $row['permission']]))
2832
			continue;
2833
2834
		// The name of the permission using the format 'permission name' - 'own/any topic/event/etc.'.
2835 View Code Duplication
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2836
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
2837
		else
2838
			$name = $txt['permissionname_' . $row['permission']];
2839
2840
		// Create the structure for this permission.
2841 View Code Duplication
		if (!isset($context['member']['permissions']['board'][$row['permission']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2842
			$context['member']['permissions']['board'][$row['permission']] = array(
2843
				'id' => $row['permission'],
2844
				'groups' => array(
2845
					'allowed' => array(),
2846
					'denied' => array()
2847
				),
2848
				'name' => $name,
2849
				'is_denied' => false,
2850
				'is_global' => empty($board),
2851
			);
2852
2853
		$context['member']['permissions']['board'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][$row['id_group']] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name'];
2854
2855
		$context['member']['permissions']['board'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
2856
	}
2857
	$smcFunc['db_free_result']($request);
2858
}
2859
2860
/**
2861
 * View a member's warnings
2862
 *
2863
 * @param int $memID The ID of the member
2864
 */
2865
function viewWarning($memID)
2866
{
2867
	global $modSettings, $context, $sourcedir, $txt, $scripturl;
2868
2869
	// Firstly, can we actually even be here?
2870
	if (!($context['user']['is_owner'] && allowedTo('view_warning_own')) && !allowedTo('view_warning_any') && !allowedTo('issue_warning') && !allowedTo('moderate_forum'))
2871
		fatal_lang_error('no_access', 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...
2872
2873
	// Make sure things which are disabled stay disabled.
2874
	$modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110;
2875
	$modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110;
2876
	$modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110;
2877
2878
	// Let's use a generic list to get all the current warnings, and use the issue warnings grab-a-granny thing.
2879
	require_once($sourcedir . '/Subs-List.php');
2880
	require_once($sourcedir . '/Profile-Actions.php');
2881
2882
	$listOptions = array(
2883
		'id' => 'view_warnings',
2884
		'title' => $txt['profile_viewwarning_previous_warnings'],
2885
		'items_per_page' => $modSettings['defaultMaxListItems'],
2886
		'no_items_label' => $txt['profile_viewwarning_no_warnings'],
2887
		'base_href' => $scripturl . '?action=profile;area=viewwarning;sa=user;u=' . $memID,
2888
		'default_sort_col' => 'log_time',
2889
		'get_items' => array(
2890
			'function' => 'list_getUserWarnings',
2891
			'params' => array(
2892
				$memID,
2893
			),
2894
		),
2895
		'get_count' => array(
2896
			'function' => 'list_getUserWarningCount',
2897
			'params' => array(
2898
				$memID,
2899
			),
2900
		),
2901
		'columns' => array(
2902
			'log_time' => array(
2903
				'header' => array(
2904
					'value' => $txt['profile_warning_previous_time'],
2905
				),
2906
				'data' => array(
2907
					'db' => 'time',
2908
				),
2909
				'sort' => array(
2910
					'default' => 'lc.log_time DESC',
2911
					'reverse' => 'lc.log_time',
2912
				),
2913
			),
2914
			'reason' => array(
2915
				'header' => array(
2916
					'value' => $txt['profile_warning_previous_reason'],
2917
					'style' => 'width: 50%;',
2918
				),
2919
				'data' => array(
2920
					'db' => 'reason',
2921
				),
2922
			),
2923
			'level' => array(
2924
				'header' => array(
2925
					'value' => $txt['profile_warning_previous_level'],
2926
				),
2927
				'data' => array(
2928
					'db' => 'counter',
2929
				),
2930
				'sort' => array(
2931
					'default' => 'lc.counter DESC',
2932
					'reverse' => 'lc.counter',
2933
				),
2934
			),
2935
		),
2936
		'additional_rows' => array(
2937
			array(
2938
				'position' => 'after_title',
2939
				'value' => $txt['profile_viewwarning_desc'],
2940
				'class' => 'smalltext',
2941
				'style' => 'padding: 2ex;',
2942
			),
2943
		),
2944
	);
2945
2946
	// Create the list for viewing.
2947
	require_once($sourcedir . '/Subs-List.php');
2948
	createList($listOptions);
2949
2950
	// Create some common text bits for the template.
2951
	$context['level_effects'] = array(
2952
		0 => '',
2953
		$modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'],
2954
		$modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'],
2955
		$modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'],
2956
	);
2957
	$context['current_level'] = 0;
2958 View Code Duplication
	foreach ($context['level_effects'] as $limit => $dummy)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2959
		if ($context['member']['warning'] >= $limit)
2960
			$context['current_level'] = $limit;
2961
}
2962
2963
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...