Completed
Push — release-2.1 ( 6f6d35...abeae7 )
by Mathias
08:46
created

Profile-View.php ➔ showAlerts()   C

Complexity

Conditions 12
Paths 72

Size

Total Lines 76
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 29
nc 72
nop 1
dl 0
loc 76
rs 5.2846
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines http://www.simplemachines.org
8
 * @copyright 2017 Simple Machines and individual contributors
9
 * @license http://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 Beta 4
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
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']) || substr($context['member']['birth_date'], 0, 4) < 1002)
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 <= 1004 ? $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,
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', '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
					'post' => !empty($row['cannot_post']),
186
					'login' => !empty($row['cannot_login']),
187
				),
188
				'explanation' => $ban_explanation,
189
			);
190
		}
191
		$smcFunc['db_free_result']($request);
192
	}
193
	loadCustomFields($memID);
194
195
	$context['print_custom_fields'] = array();
196
197
	// Any custom profile fields?
198
	if (!empty($context['custom_fields']))
199
		foreach ($context['custom_fields'] as $custom)
200
			$context['print_custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
201
202
}
203
204
/**
205
 * Fetch the alerts a user currently has.
206
 *
207
 * @param int $memID The ID of the member
208
 * @param bool $all Whether to fetch all alerts or just unread ones
209
 * @param int $counter How many alerts to display (0 if displaying all or using pagination)
210
 * @param array $pagination An array containing info for handling pagination. Should have 'start' and 'maxIndex'
211
 * @return array An array of information about the fetched alerts
212
 */
213
function fetch_alerts($memID, $all = false, $counter = 0, $pagination = array())
214
{
215
	global $smcFunc, $txt, $scripturl, $memberContext;
216
217
	$alerts = array();
218
	$request = $smcFunc['db_query']('', '
219
		SELECT id_alert, alert_time, mem.id_member AS sender_id, COALESCE(mem.real_name, ua.member_name) AS sender_name,
220
			content_type, content_id, content_action, is_read, extra
221
		FROM {db_prefix}user_alerts AS ua
222
			LEFT JOIN {db_prefix}members AS mem ON (ua.id_member_started = mem.id_member)
223
		WHERE ua.id_member = {int:id_member}' . (!$all ? '
224
			AND is_read = 0' : '') . '
225
		ORDER BY id_alert DESC' . (!empty($counter) && empty($pagination) ? '
226
		LIMIT {int:counter}' : '') . (!empty($pagination) && empty($counter) ? '
227
		LIMIT {int:start}, {int:maxIndex}' : ''),
228
		array(
229
			'id_member' => $memID,
230
			'counter' => $counter,
231
			'start' => !empty($pagination['start']) ? $pagination['start'] : 0,
232
			'maxIndex' => !empty($pagination['maxIndex']) ? $pagination['maxIndex'] : 0,
233
		)
234
	);
235
236
	$senders = array();
237
	while ($row = $smcFunc['db_fetch_assoc']($request))
238
	{
239
		$id_alert = array_shift($row);
240
		$row['time'] = timeformat($row['alert_time']);
241
		$row['extra'] = !empty($row['extra']) ? $smcFunc['json_decode']($row['extra'], true) : array();
242
		$alerts[$id_alert] = $row;
243
244
		if (!empty($row['sender_id']))
245
			$senders[] = $row['sender_id'];
246
	}
247
	$smcFunc['db_free_result']($request);
248
249
	$senders = loadMemberData($senders);
250
	foreach ($senders as $member)
251
		loadMemberContext($member);
252
253
	// Now go through and actually make with the text.
254
	loadLanguage('Alerts');
255
256
	// Hooks might want to do something snazzy around their own content types - including enforcing permissions if appropriate.
257
	call_integration_hook('integrate_fetch_alerts', array(&$alerts));
258
259
	// For anything that wants us to check board or topic access, let's do that.
260
	$boards = array();
261
	$topics = array();
262
	$msgs = array();
263
	foreach ($alerts as $id_alert => $alert)
264
	{
265
		if (isset($alert['extra']['board']))
266
			$boards[$alert['extra']['board']] = $txt['board_na'];
267
		if (isset($alert['extra']['topic']))
268
			$topics[$alert['extra']['topic']] = $txt['topic_na'];
269
		if ($alert['content_type'] == 'msg')
270
			$msgs[$alert['content_id']] = $txt['topic_na'];
271
	}
272
273
	// 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.
274 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...
275
	{
276
		$request = $smcFunc['db_query']('', '
277
			SELECT id_board, name
278
			FROM {db_prefix}boards AS b
279
			WHERE {query_see_board}
280
				AND id_board IN ({array_int:boards})',
281
			array(
282
				'boards' => array_keys($boards),
283
			)
284
		);
285
		while ($row = $smcFunc['db_fetch_assoc']($request))
286
			$boards[$row['id_board']] = '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>';
287
	}
288 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...
289
	{
290
		$request = $smcFunc['db_query']('', '
291
			SELECT t.id_topic, m.subject
292
			FROM {db_prefix}topics AS t
293
				INNER JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
294
				INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
295
			WHERE {query_see_board}
296
				AND t.id_topic IN ({array_int:topics})',
297
			array(
298
				'topics' => array_keys($topics),
299
			)
300
		);
301
		while ($row = $smcFunc['db_fetch_assoc']($request))
302
			$topics[$row['id_topic']] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>';
303
	}
304
	if (!empty($msgs))
305
	{
306
		$request = $smcFunc['db_query']('', '
307
			SELECT m.id_msg, t.id_topic, m.subject
308
			FROM {db_prefix}messages AS m
309
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
310
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
311
			WHERE {query_see_board}
312
				AND m.id_msg IN ({array_int:msgs})',
313
			array(
314
				'msgs' => array_keys($msgs),
315
			)
316
		);
317 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...
318
			$msgs[$row['id_msg']] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
319
	}
320
321
	// 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)
322
	foreach ($alerts as $id_alert => $alert)
323
	{
324
		if (!empty($alert['text']))
325
			continue;
326 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...
327
			$alerts[$id_alert]['extra']['board_msg'] = $boards[$alert['extra']['board']];
328 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...
329
			$alerts[$id_alert]['extra']['topic_msg'] = $topics[$alert['extra']['topic']];
330
		if ($alert['content_type'] == 'msg')
331
			$alerts[$id_alert]['extra']['msg_msg'] = $msgs[$alert['content_id']];
332
		if ($alert['content_type'] == 'profile')
333
			$alerts[$id_alert]['extra']['profile_msg'] = '<a href="' . $scripturl . '?action=profile;u=' . $alerts[$id_alert]['content_id'] . '">' . $alerts[$id_alert]['extra']['user_name'] . '</a>';
334
335
		if (!empty($memberContext[$alert['sender_id']]))
336
			$alerts[$id_alert]['sender'] = &$memberContext[$alert['sender_id']];
337
338
		$string = 'alert_' . $alert['content_type'] . '_' . $alert['content_action'];
339
		if (isset($txt[$string]))
340
		{
341
			$extra = $alerts[$id_alert]['extra'];
342
			$search = array('{member_link}', '{scripturl}');
343
			$repl = array(!empty($alert['sender_id']) ? '<a href="' . $scripturl . '?action=profile;u=' . $alert['sender_id'] . '">' . $alert['sender_name'] . '</a>' : $alert['sender_name'], $scripturl);
344
			foreach ($extra as $k => $v)
345
			{
346
				$search[] = '{' . $k . '}';
347
				$repl[] = $v;
348
			}
349
			$alerts[$id_alert]['text'] = str_replace($search, $repl, $txt[$string]);
350
		}
351
	}
352
353
	return $alerts;
354
}
355
356
/**
357
 * Shows all alerts for this user
358
 *
359
 * @param int $memID The ID of the member
360
 */
361
function showAlerts($memID)
362
{
363
	global $context, $smcFunc, $txt, $sourcedir, $scripturl;
364
365
	require_once($sourcedir . '/Profile-Modify.php');
366
367
	// Prepare the pagination vars.
368
	$maxIndex = 10;
369
	$start = (int) isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
370
	$count = alert_count($memID);
371
372
	// Get the alerts.
373
	$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...
374
	$toMark = false;
375
	$action = '';
376
377
	// Create the pagination.
378
	$context['pagination'] = constructPageIndex($scripturl . '?action=profile;area=showalerts;u=' . $memID, $start, $count, $maxIndex, false);
379
380
	// Set some JavaScript for checking all alerts at once.
381
	addInlineJavaScript('
382
	$(function(){
383
		$(\'#select_all\').on(\'change\', function() {
384
			var checkboxes = $(\'ul.quickbuttons\').find(\':checkbox\');
385
			if($(this).prop(\'checked\')) {
386
				checkboxes.prop(\'checked\', true);
387
			}
388
			else {
389
				checkboxes.prop(\'checked\', false);
390
			}
391
		});
392
	});', true);
393
394
	// Set a nice message.
395
	if (!empty($_SESSION['update_message']))
396
	{
397
		$context['update_message'] = $txt['profile_updated_own'];
398
		unset($_SESSION['update_message']);
399
	}
400
401
	// Saving multiple changes?
402
	if (isset($_GET['save']) && !empty($_POST['mark']))
403
	{
404
		// Get the values.
405
		$toMark = array_map('intval', $_POST['mark']);
406
407
		// Which action?
408
		$action = !empty($_POST['mark_as']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_POST['mark_as'])) : '';
409
	}
410
411
	// A single change.
412
	if (!empty($_GET['do']) && !empty($_GET['aid']))
413
	{
414
		$toMark = (int) $_GET['aid'];
415
		$action = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_GET['do']));
416
	}
417
418
	// Save the changes.
419
	if (!empty($toMark) && !empty($action))
420
	{
421
		checkSession('request');
422
423
		// Call it!
424
		if ($action == 'remove')
425
			alert_delete($toMark, $memID);
426
427
		else
428
			alert_mark($memID, $toMark, $action == 'read' ? 1 : 0);
429
430
		// Set a nice update message.
431
		$_SESSION['update_message'] = true;
432
433
		// Redirect.
434
		redirectexit('action=profile;area=showalerts;u=' . $memID);
435
	}
436
}
437
438
/**
439
 * Show all posts by the current user
440
 * @todo This function needs to be split up properly.
441
 *
442
 * @param int $memID The ID of the member
443
 */
444
function showPosts($memID)
445
{
446
	global $txt, $user_info, $scripturl, $modSettings;
447
	global $context, $user_profile, $sourcedir, $smcFunc, $board;
448
449
	// Some initial context.
450
	$context['start'] = (int) $_REQUEST['start'];
451
	$context['current_member'] = $memID;
452
453
	// Create the tabs for the template.
454
	$context[$context['profile_menu_name']]['tab_data'] = array(
455
		'title' => $txt['showPosts'],
456
		'description' => $txt['showPosts_help'],
457
		'icon' => 'profile_hd.png',
458
		'tabs' => array(
459
			'messages' => array(
460
			),
461
			'topics' => array(
462
			),
463
			'unwatchedtopics' => array(
464
			),
465
			'attach' => array(
466
			),
467
		),
468
	);
469
470
	// Shortcut used to determine which $txt['show*'] string to use for the title, based on the SA
471
	$title = array(
472
		'attach' => 'Attachments',
473
		'unwatchedtopics' => 'Unwatched',
474
		'topics' => 'Topics'
475
	);
476
477
	// Set the page title
478
	if (isset($_GET['sa']) && array_key_exists($_GET['sa'], $title))
479
		$context['page_title'] = $txt['show' . $title[$_GET['sa']]];
480
	else
481
		$context['page_title'] = $txt['showPosts'];
482
483
	$context['page_title'] .= ' - ' . $user_profile[$memID]['real_name'];
484
485
	// Is the load average too high to allow searching just now?
486 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...
487
		fatal_lang_error('loadavg_show_posts_disabled', false);
488
489
	// If we're specifically dealing with attachments use that function!
490
	if (isset($_GET['sa']) && $_GET['sa'] == 'attach')
491
		return showAttachments($memID);
492
	// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
493
	elseif (isset($_GET['sa']) && $_GET['sa'] == 'unwatchedtopics')
494
		return showUnwatched($memID);
495
496
	// Are we just viewing topics?
497
	$context['is_topics'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? true : false;
498
499
	// If just deleting a message, do it and then redirect back.
500
	if (isset($_GET['delete']) && !$context['is_topics'])
501
	{
502
		checkSession('get');
503
504
		// We need msg info for logging.
505
		$request = $smcFunc['db_query']('', '
506
			SELECT subject, id_member, id_topic, id_board
507
			FROM {db_prefix}messages
508
			WHERE id_msg = {int:id_msg}',
509
			array(
510
				'id_msg' => (int) $_GET['delete'],
511
			)
512
		);
513
		$info = $smcFunc['db_fetch_row']($request);
514
		$smcFunc['db_free_result']($request);
515
516
		// Trying to remove a message that doesn't exist.
517
		if (empty($info))
518
			redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
519
520
		// We can be lazy, since removeMessage() will check the permissions for us.
521
		require_once($sourcedir . '/RemoveTopic.php');
522
		removeMessage((int) $_GET['delete']);
523
524
		// Add it to the mod log.
525 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...
526
			logAction('delete', array('topic' => $info[2], 'subject' => $info[0], 'member' => $info[1], 'board' => $info[3]));
527
528
		// Back to... where we are now ;).
529
		redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
530
	}
531
532
	// Default to 10.
533 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...
534
		$_REQUEST['viewscount'] = '10';
535
536
	if ($context['is_topics'])
537
		$request = $smcFunc['db_query']('', '
538
			SELECT COUNT(*)
539
			FROM {db_prefix}topics AS t' . ($user_info['query_see_board'] == '1=1' ? '' : '
540
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board})') . '
541
			WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
542
				AND t.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
543
				AND t.approved = {int:is_approved}'),
544
			array(
545
				'current_member' => $memID,
546
				'is_approved' => 1,
547
				'board' => $board,
548
			)
549
		);
550
	else
551
		$request = $smcFunc['db_query']('', '
552
			SELECT COUNT(*)
553
			FROM {db_prefix}messages AS m' . ($user_info['query_see_board'] == '1=1' ? '' : '
554
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . '
555
			WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
556
				AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
557
				AND m.approved = {int:is_approved}'),
558
			array(
559
				'current_member' => $memID,
560
				'is_approved' => 1,
561
				'board' => $board,
562
			)
563
		);
564
	list ($msgCount) = $smcFunc['db_fetch_row']($request);
565
	$smcFunc['db_free_result']($request);
566
567
	$request = $smcFunc['db_query']('', '
568
		SELECT MIN(id_msg), MAX(id_msg)
569
		FROM {db_prefix}messages AS m
570
		WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
571
			AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
572
			AND m.approved = {int:is_approved}'),
573
		array(
574
			'current_member' => $memID,
575
			'is_approved' => 1,
576
			'board' => $board,
577
		)
578
	);
579
	list ($min_msg_member, $max_msg_member) = $smcFunc['db_fetch_row']($request);
580
	$smcFunc['db_free_result']($request);
581
582
	$range_limit = '';
583
584
	if ($context['is_topics'])
585
		$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...
586
	else
587
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
588
589
	$maxIndex = $maxPerPage;
590
591
	// Make sure the starting place makes sense and construct our friend the page index.
592
	$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : '') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex);
593
	$context['current_page'] = $context['start'] / $maxIndex;
594
595
	// Reverse the query if we're past 50% of the pages for better performance.
596
	$start = $context['start'];
597
	$reverse = $_REQUEST['start'] > $msgCount / 2;
598 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...
599
	{
600
		$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
601
		$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
602
	}
603
604
	// Guess the range of messages to be shown.
605
	if ($msgCount > 1000)
606
	{
607
		$margin = floor(($max_msg_member - $min_msg_member) * (($start + $maxPerPage) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
608
		// Make a bigger margin for topics only.
609
		if ($context['is_topics'])
610
		{
611
			$margin *= 5;
612
			$range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin);
613
		}
614
		else
615
			$range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin);
616
	}
617
618
	// Find this user's posts.  The left join on categories somehow makes this faster, weird as it looks.
619
	$looped = false;
620
	while (true)
621
	{
622
		if ($context['is_topics'])
623
		{
624
			$request = $smcFunc['db_query']('', '
625
				SELECT
626
					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,
627
					t.approved, m.body, m.smileys_enabled, m.subject, m.poster_time, m.id_topic, m.id_msg
628
				FROM {db_prefix}topics AS t
629
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
630
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
631
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
632
				WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
633
					AND t.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
634
					AND ' . $range_limit) . '
635
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
636
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
637
				ORDER BY t.id_first_msg ' . ($reverse ? 'ASC' : 'DESC') . '
638
				LIMIT {int:start}, {int:max}',
639
				array(
640
					'current_member' => $memID,
641
					'is_approved' => 1,
642
					'board' => $board,
643
					'start' => $start,
644
					'max' => $maxIndex,
645
				)
646
			);
647
		}
648
		else
649
		{
650
			$request = $smcFunc['db_query']('', '
651
				SELECT
652
					b.id_board, b.name AS bname, c.id_cat, c.name AS cname, m.id_topic, m.id_msg,
653
					t.id_member_started, t.id_first_msg, t.id_last_msg, m.body, m.smileys_enabled,
654
					m.subject, m.poster_time, m.approved
655
				FROM {db_prefix}messages AS m
656
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
657
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
658
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
659
				WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
660
					AND b.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
661
					AND ' . $range_limit) . '
662
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
663
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
664
				ORDER BY m.id_msg ' . ($reverse ? 'ASC' : 'DESC') . '
665
				LIMIT {int:start}, {int:max}',
666
				array(
667
					'current_member' => $memID,
668
					'is_approved' => 1,
669
					'board' => $board,
670
					'start' => $start,
671
					'max' => $maxIndex,
672
				)
673
			);
674
		}
675
676
		// Make sure we quit this loop.
677
		if ($smcFunc['db_num_rows']($request) === $maxIndex || $looped)
678
			break;
679
		$looped = true;
680
		$range_limit = '';
681
	}
682
683
	// Start counting at the number of the first message displayed.
684
	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
685
	$context['posts'] = array();
686
	$board_ids = array('own' => array(), 'any' => array());
687
	while ($row = $smcFunc['db_fetch_assoc']($request))
688
	{
689
		// Censor....
690
		censorText($row['body']);
691
		censorText($row['subject']);
692
693
		// Do the code.
694
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
695
696
		// And the array...
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
697
		$context['posts'][$counter += $reverse ? -1 : 1] = array(
698
			'body' => $row['body'],
699
			'counter' => $counter,
700
			'category' => array(
701
				'name' => $row['cname'],
702
				'id' => $row['id_cat']
703
			),
704
			'board' => array(
705
				'name' => $row['bname'],
706
				'id' => $row['id_board']
707
			),
708
			'topic' => $row['id_topic'],
709
			'subject' => $row['subject'],
710
			'start' => 'msg' . $row['id_msg'],
711
			'time' => timeformat($row['poster_time']),
712
			'timestamp' => forum_time(true, $row['poster_time']),
713
			'id' => $row['id_msg'],
714
			'can_reply' => false,
715
			'can_mark_notify' => !$context['user']['is_guest'],
716
			'can_delete' => false,
717
			'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()),
718
			'approved' => $row['approved'],
719
			'css_class' => $row['approved'] ? 'windowbg' : 'approvebg',
720
		);
721
722
		if ($user_info['id'] == $row['id_member_started'])
723
			$board_ids['own'][$row['id_board']][] = $counter;
724
		$board_ids['any'][$row['id_board']][] = $counter;
725
	}
726
	$smcFunc['db_free_result']($request);
727
728
	// All posts were retrieved in reverse order, get them right again.
729
	if ($reverse)
730
		$context['posts'] = array_reverse($context['posts'], true);
731
732
	// These are all the permissions that are different from board to board..
733
	if ($context['is_topics'])
734
		$permissions = array(
735
			'own' => array(
736
				'post_reply_own' => 'can_reply',
737
			),
738
			'any' => array(
739
				'post_reply_any' => 'can_reply',
740
			)
741
		);
742
	else
743
		$permissions = array(
744
			'own' => array(
745
				'post_reply_own' => 'can_reply',
746
				'delete_own' => 'can_delete',
747
			),
748
			'any' => array(
749
				'post_reply_any' => 'can_reply',
750
				'delete_any' => 'can_delete',
751
			)
752
		);
753
754
	// For every permission in the own/any lists...
755
	foreach ($permissions as $type => $list)
756
	{
757
		foreach ($list as $permission => $allowed)
758
		{
759
			// Get the boards they can do this on...
760
			$boards = boardsAllowedTo($permission);
761
762
			// Hmm, they can do it on all boards, can they?
763 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...
764
				$boards = array_keys($board_ids[$type]);
765
766
			// Now go through each board they can do the permission on.
767
			foreach ($boards as $board_id)
768
			{
769
				// There aren't any posts displayed from this board.
770
				if (!isset($board_ids[$type][$board_id]))
771
					continue;
772
773
				// Set the permission to true ;).
774
				foreach ($board_ids[$type][$board_id] as $counter)
775
					$context['posts'][$counter][$allowed] = true;
776
			}
777
		}
778
	}
779
780
	// Clean up after posts that cannot be deleted and quoted.
781
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
782 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...
783
	{
784
		$context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible'];
785
		$context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled;
786
	}
787
788
	// Allow last minute changes.
789
	call_integration_hook('integrate_profile_showPosts');
790
}
791
792
/**
793
 * Show all the attachments of a user.
794
 *
795
 * @param int $memID The ID of the member
796
 */
797
function showAttachments($memID)
798
{
799
	global $txt, $scripturl, $modSettings;
800
	global $sourcedir;
801
802
	// OBEY permissions!
803
	$boardsAllowed = boardsAllowedTo('view_attachments');
804
805
	// Make sure we can't actually see anything...
806
	if (empty($boardsAllowed))
807
		$boardsAllowed = array(-1);
808
809
	require_once($sourcedir . '/Subs-List.php');
810
811
	// This is all the information required to list attachments.
812
	$listOptions = array(
813
		'id' => 'attachments',
814
		'width' => '100%',
815
		'items_per_page' => $modSettings['defaultMaxListItems'],
816
		'no_items_label' => $txt['show_attachments_none'],
817
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=attach;u=' . $memID,
818
		'default_sort_col' => 'filename',
819
		'get_items' => array(
820
			'function' => 'list_getAttachments',
821
			'params' => array(
822
				$boardsAllowed,
823
				$memID,
824
			),
825
		),
826
		'get_count' => array(
827
			'function' => 'list_getNumAttachments',
828
			'params' => array(
829
				$boardsAllowed,
830
				$memID,
831
			),
832
		),
833
		'data_check' => array(
834
			'class' => function($data)
835
			{
836
				return $data['approved'] ? '' : 'approvebg';
837
			}
838
		),
839
		'columns' => array(
840
			'filename' => array(
841
				'header' => array(
842
					'value' => $txt['show_attach_filename'],
843
					'class' => 'lefttext',
844
					'style' => 'width: 25%;',
845
				),
846
				'data' => array(
847
					'sprintf' => array(
848
						'format' => '<a href="' . $scripturl . '?action=dlattach;topic=%1$d.0;attach=%2$d">%3$s</a>',
849
						'params' => array(
850
							'topic' => true,
851
							'id' => true,
852
							'filename' => false,
853
						),
854
					),
855
				),
856
				'sort' => array(
857
					'default' => 'a.filename',
858
					'reverse' => 'a.filename DESC',
859
				),
860
			),
861
			'downloads' => array(
862
				'header' => array(
863
					'value' => $txt['show_attach_downloads'],
864
					'style' => 'width: 12%;',
865
				),
866
				'data' => array(
867
					'db' => 'downloads',
868
					'comma_format' => true,
869
				),
870
				'sort' => array(
871
					'default' => 'a.downloads',
872
					'reverse' => 'a.downloads DESC',
873
				),
874
			),
875
			'subject' => array(
876
				'header' => array(
877
					'value' => $txt['message'],
878
					'class' => 'lefttext',
879
					'style' => 'width: 30%;',
880
				),
881
				'data' => array(
882
					'sprintf' => array(
883
						'format' => '<a href="' . $scripturl . '?msg=%1$d">%2$s</a>',
884
						'params' => array(
885
							'msg' => true,
886
							'subject' => false,
887
						),
888
					),
889
				),
890
				'sort' => array(
891
					'default' => 'm.subject',
892
					'reverse' => 'm.subject DESC',
893
				),
894
			),
895
			'posted' => array(
896
				'header' => array(
897
					'value' => $txt['show_attach_posted'],
898
					'class' => 'lefttext',
899
				),
900
				'data' => array(
901
					'db' => 'posted',
902
					'timeformat' => true,
903
				),
904
				'sort' => array(
905
					'default' => 'm.poster_time',
906
					'reverse' => 'm.poster_time DESC',
907
				),
908
			),
909
		),
910
	);
911
912
	// Create the request list.
913
	createList($listOptions);
914
}
915
916
/**
917
 * Get a list of attachments for this user. Callback for the list in showAttachments()
918
 *
919
 * @param int $start Which item to start with (for pagination purposes)
920
 * @param int $items_per_page How many items to show on each page
921
 * @param string $sort A string indicating how to sort the results
922
 * @param array $boardsAllowed An array containing the IDs of the boards they can see
923
 * @param int $memID The ID of the member
924
 * @return array An array of information about the attachments
925
 */
926
function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed, $memID)
927
{
928
	global $smcFunc, $board, $modSettings, $context;
929
930
	// Retrieve some attachments.
931
	$request = $smcFunc['db_query']('', '
932
		SELECT a.id_attach, a.id_msg, a.filename, a.downloads, a.approved, m.id_msg, m.id_topic,
933
			m.id_board, m.poster_time, m.subject, b.name
934
		FROM {db_prefix}attachments AS a
935
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
936
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
937
		WHERE a.attachment_type = {int:attachment_type}
938
			AND a.id_msg != {int:no_message}
939
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
940
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
941
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
942
			AND m.approved = {int:is_approved}') . '
943
		ORDER BY {raw:sort}
944
		LIMIT {int:offset}, {int:limit}',
945
		array(
946
			'boards_list' => $boardsAllowed,
947
			'attachment_type' => 0,
948
			'no_message' => 0,
949
			'current_member' => $memID,
950
			'is_approved' => 1,
951
			'board' => $board,
952
			'sort' => $sort,
953
			'offset' => $start,
954
			'limit' => $items_per_page,
955
		)
956
	);
957
	$attachments = array();
958
	while ($row = $smcFunc['db_fetch_assoc']($request))
959
		$attachments[] = array(
960
			'id' => $row['id_attach'],
961
			'filename' => $row['filename'],
962
			'downloads' => $row['downloads'],
963
			'subject' => censorText($row['subject']),
964
			'posted' => $row['poster_time'],
965
			'msg' => $row['id_msg'],
966
			'topic' => $row['id_topic'],
967
			'board' => $row['id_board'],
968
			'board_name' => $row['name'],
969
			'approved' => $row['approved'],
970
		);
971
972
	$smcFunc['db_free_result']($request);
973
974
	return $attachments;
975
}
976
977
/**
978
 * Gets the total number of attachments for the user
979
 *
980
 * @param array $boardsAllowed An array of the IDs of the boards they can see
981
 * @param int $memID The ID of the member
982
 * @return int The number of attachments
983
 */
984
function list_getNumAttachments($boardsAllowed, $memID)
985
{
986
	global $board, $smcFunc, $modSettings, $context;
987
988
	// Get the total number of attachments they have posted.
989
	$request = $smcFunc['db_query']('', '
990
		SELECT COUNT(*)
991
		FROM {db_prefix}attachments AS a
992
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
993
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
994
		WHERE a.attachment_type = {int:attachment_type}
995
			AND a.id_msg != {int:no_message}
996
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
997
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
998
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
999
			AND m.approved = {int:is_approved}'),
1000
		array(
1001
			'boards_list' => $boardsAllowed,
1002
			'attachment_type' => 0,
1003
			'no_message' => 0,
1004
			'current_member' => $memID,
1005
			'is_approved' => 1,
1006
			'board' => $board,
1007
		)
1008
	);
1009
	list ($attachCount) = $smcFunc['db_fetch_row']($request);
1010
	$smcFunc['db_free_result']($request);
1011
1012
	return $attachCount;
1013
}
1014
1015
/**
1016
 * Show all the unwatched topics.
1017
 *
1018
 * @param int $memID The ID of the member
1019
 */
1020
function showUnwatched($memID)
1021
{
1022
	global $txt, $user_info, $scripturl, $modSettings, $context, $sourcedir;
1023
1024
	// Only the owner can see the list (if the function is enabled of course)
1025
	if ($user_info['id'] != $memID)
1026
		return;
1027
1028
	require_once($sourcedir . '/Subs-List.php');
1029
1030
	// And here they are: the topics you don't like
1031
	$listOptions = array(
1032
		'id' => 'unwatched_topics',
1033
		'width' => '100%',
1034
		'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...
1035
		'no_items_label' => $txt['unwatched_topics_none'],
1036
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=unwatchedtopics;u=' . $memID,
1037
		'default_sort_col' => 'started_on',
1038
		'get_items' => array(
1039
			'function' => 'list_getUnwatched',
1040
			'params' => array(
1041
				$memID,
1042
			),
1043
		),
1044
		'get_count' => array(
1045
			'function' => 'list_getNumUnwatched',
1046
			'params' => array(
1047
				$memID,
1048
			),
1049
		),
1050
		'columns' => array(
1051
			'subject' => array(
1052
				'header' => array(
1053
					'value' => $txt['subject'],
1054
					'class' => 'lefttext',
1055
					'style' => 'width: 30%;',
1056
				),
1057
				'data' => array(
1058
					'sprintf' => array(
1059
						'format' => '<a href="' . $scripturl . '?topic=%1$d.0">%2$s</a>',
1060
						'params' => array(
1061
							'id_topic' => false,
1062
							'subject' => false,
1063
						),
1064
					),
1065
				),
1066
				'sort' => array(
1067
					'default' => 'm.subject',
1068
					'reverse' => 'm.subject DESC',
1069
				),
1070
			),
1071
			'started_by' => array(
1072
				'header' => array(
1073
					'value' => $txt['started_by'],
1074
					'style' => 'width: 15%;',
1075
				),
1076
				'data' => array(
1077
					'db' => 'started_by',
1078
				),
1079
				'sort' => array(
1080
					'default' => 'mem.real_name',
1081
					'reverse' => 'mem.real_name DESC',
1082
				),
1083
			),
1084
			'started_on' => array(
1085
				'header' => array(
1086
					'value' => $txt['on'],
1087
					'class' => 'lefttext',
1088
					'style' => 'width: 20%;',
1089
				),
1090
				'data' => array(
1091
					'db' => 'started_on',
1092
					'timeformat' => true,
1093
				),
1094
				'sort' => array(
1095
					'default' => 'm.poster_time',
1096
					'reverse' => 'm.poster_time DESC',
1097
				),
1098
			),
1099
			'last_post_by' => array(
1100
				'header' => array(
1101
					'value' => $txt['last_post'],
1102
					'style' => 'width: 15%;',
1103
				),
1104
				'data' => array(
1105
					'db' => 'last_post_by',
1106
				),
1107
				'sort' => array(
1108
					'default' => 'mem.real_name',
1109
					'reverse' => 'mem.real_name DESC',
1110
				),
1111
			),
1112
			'last_post_on' => array(
1113
				'header' => array(
1114
					'value' => $txt['on'],
1115
					'class' => 'lefttext',
1116
					'style' => 'width: 20%;',
1117
				),
1118
				'data' => array(
1119
					'db' => 'last_post_on',
1120
					'timeformat' => true,
1121
				),
1122
				'sort' => array(
1123
					'default' => 'm.poster_time',
1124
					'reverse' => 'm.poster_time DESC',
1125
				),
1126
			),
1127
		),
1128
	);
1129
1130
	// Create the request list.
1131
	createList($listOptions);
1132
1133
	$context['sub_template'] = 'show_list';
1134
	$context['default_list'] = 'unwatched_topics';
1135
}
1136
1137
/**
1138
 * Gets information about unwatched (disregarded) topics. Callback for the list in show_unwatched
1139
 *
1140
 * @param int $start The item to start with (for pagination purposes)
1141
 * @param int $items_per_page How many items to show on each page
1142
 * @param string $sort A string indicating how to sort the results
1143
 * @param int $memID The ID of the member
1144
 * @return array An array of information about the unwatched topics
1145
 */
1146
function list_getUnwatched($start, $items_per_page, $sort, $memID)
1147
{
1148
	global $smcFunc;
1149
1150
	// Get the list of topics we can see
1151
	$request = $smcFunc['db_query']('', '
1152
		SELECT lt.id_topic
1153
		FROM {db_prefix}log_topics as lt
1154
			LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1155
			LEFT JOIN {db_prefix}boards as b ON (t.id_board = b.id_board)
1156
			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')) ? '
1157
			LEFT JOIN {db_prefix}members as mem ON (m.id_member = mem.id_member)' : '') . '
1158
		WHERE lt.id_member = {int:current_member}
1159
			AND unwatched = 1
1160
			AND {query_see_board}
1161
		ORDER BY {raw:sort}
1162
		LIMIT {int:offset}, {int:limit}',
1163
		array(
1164
			'current_member' => $memID,
1165
			'sort' => $sort,
1166
			'offset' => $start,
1167
			'limit' => $items_per_page,
1168
		)
1169
	);
1170
1171
	$topics = array();
1172
	while ($row = $smcFunc['db_fetch_assoc']($request))
1173
		$topics[] = $row['id_topic'];
1174
1175
	$smcFunc['db_free_result']($request);
1176
1177
	// Any topics found?
1178
	$topicsInfo = array();
1179 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...
1180
	{
1181
		$request = $smcFunc['db_query']('', '
1182
			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
1183
			FROM {db_prefix}topics AS t
1184
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
1185
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1186
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
1187
				LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1188
			WHERE t.id_topic IN ({array_int:topics})',
1189
			array(
1190
				'topics' => $topics,
1191
			)
1192
		);
1193
		while ($row = $smcFunc['db_fetch_assoc']($request))
1194
			$topicsInfo[] = $row;
1195
		$smcFunc['db_free_result']($request);
1196
	}
1197
1198
	return $topicsInfo;
1199
}
1200
1201
/**
1202
 * Count the number of topics in the unwatched list
1203
 *
1204
 * @param int $memID The ID of the member
1205
 * @return int The number of unwatched topics
1206
 */
1207
function list_getNumUnwatched($memID)
1208
{
1209
	global $smcFunc;
1210
1211
	// Get the total number of attachments they have posted.
1212
	$request = $smcFunc['db_query']('', '
1213
		SELECT COUNT(*)
1214
		FROM {db_prefix}log_topics as lt
1215
		LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1216
		LEFT JOIN {db_prefix}boards as b ON (t.id_board = b.id_board)
1217
		WHERE id_member = {int:current_member}
1218
			AND unwatched = 1
1219
			AND {query_see_board}',
1220
		array(
1221
			'current_member' => $memID,
1222
		)
1223
	);
1224
	list ($unwatchedCount) = $smcFunc['db_fetch_row']($request);
1225
	$smcFunc['db_free_result']($request);
1226
1227
	return $unwatchedCount;
1228
}
1229
1230
/**
1231
 * Gets the user stats for display
1232
 *
1233
 * @param int $memID The ID of the member
1234
 */
1235
function statPanel($memID)
1236
{
1237
	global $txt, $scripturl, $context, $user_profile, $user_info, $modSettings, $smcFunc;
1238
1239
	$context['page_title'] = $txt['statPanel_showStats'] . ' ' . $user_profile[$memID]['real_name'];
1240
1241
	// Is the load average too high to allow searching just now?
1242 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...
1243
		fatal_lang_error('loadavg_userstats_disabled', false);
1244
1245
	// General user statistics.
1246
	$timeDays = floor($user_profile[$memID]['total_time_logged_in'] / 86400);
1247
	$timeHours = floor(($user_profile[$memID]['total_time_logged_in'] % 86400) / 3600);
1248
	$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'];
1249
	$context['num_posts'] = comma_format($user_profile[$memID]['posts']);
1250
	// Menu tab
1251
	$context[$context['profile_menu_name']]['tab_data'] = array(
1252
		'title' => $txt['statPanel_generalStats'] . ' - ' . $context['member']['name'],
1253
		'icon' => 'stats_info.png'
1254
	);
1255
1256
	// Number of topics started.
1257
	$result = $smcFunc['db_query']('', '
1258
		SELECT COUNT(*)
1259
		FROM {db_prefix}topics
1260
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1261
			AND id_board != {int:recycle_board}' : ''),
1262
		array(
1263
			'current_member' => $memID,
1264
			'recycle_board' => $modSettings['recycle_board'],
1265
		)
1266
	);
1267
	list ($context['num_topics']) = $smcFunc['db_fetch_row']($result);
1268
	$smcFunc['db_free_result']($result);
1269
1270
	// Number polls started.
1271
	$result = $smcFunc['db_query']('', '
1272
		SELECT COUNT(*)
1273
		FROM {db_prefix}topics
1274
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1275
			AND id_board != {int:recycle_board}' : '') . '
1276
			AND id_poll != {int:no_poll}',
1277
		array(
1278
			'current_member' => $memID,
1279
			'recycle_board' => $modSettings['recycle_board'],
1280
			'no_poll' => 0,
1281
		)
1282
	);
1283
	list ($context['num_polls']) = $smcFunc['db_fetch_row']($result);
1284
	$smcFunc['db_free_result']($result);
1285
1286
	// Number polls voted in.
1287
	$result = $smcFunc['db_query']('distinct_poll_votes', '
1288
		SELECT COUNT(DISTINCT id_poll)
1289
		FROM {db_prefix}log_polls
1290
		WHERE id_member = {int:current_member}',
1291
		array(
1292
			'current_member' => $memID,
1293
		)
1294
	);
1295
	list ($context['num_votes']) = $smcFunc['db_fetch_row']($result);
1296
	$smcFunc['db_free_result']($result);
1297
1298
	// Format the numbers...
1299
	$context['num_topics'] = comma_format($context['num_topics']);
1300
	$context['num_polls'] = comma_format($context['num_polls']);
1301
	$context['num_votes'] = comma_format($context['num_votes']);
1302
1303
	// Grab the board this member posted in most often.
1304
	$result = $smcFunc['db_query']('', '
1305
		SELECT
1306
			b.id_board, MAX(b.name) AS name, MAX(b.num_posts) AS num_posts, COUNT(*) AS message_count
1307
		FROM {db_prefix}messages AS m
1308
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1309
		WHERE m.id_member = {int:current_member}
1310
			AND b.count_posts = {int:count_enabled}
1311
			AND {query_see_board}
1312
		GROUP BY b.id_board
1313
		ORDER BY message_count DESC
1314
		LIMIT 10',
1315
		array(
1316
			'current_member' => $memID,
1317
			'count_enabled' => 0,
1318
		)
1319
	);
1320
	$context['popular_boards'] = array();
1321
	while ($row = $smcFunc['db_fetch_assoc']($result))
1322
	{
1323
		$context['popular_boards'][$row['id_board']] = array(
1324
			'id' => $row['id_board'],
1325
			'posts' => $row['message_count'],
1326
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1327
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1328
			'posts_percent' => $user_profile[$memID]['posts'] == 0 ? 0 : ($row['message_count'] * 100) / $user_profile[$memID]['posts'],
1329
			'total_posts' => $row['num_posts'],
1330
			'total_posts_member' => $user_profile[$memID]['posts'],
1331
		);
1332
	}
1333
	$smcFunc['db_free_result']($result);
1334
1335
	// Now get the 10 boards this user has most often participated in.
1336
	$result = $smcFunc['db_query']('profile_board_stats', '
1337
		SELECT
1338
			b.id_board, MAX(b.name) AS name, b.num_posts, COUNT(*) AS message_count,
1339
			CASE WHEN COUNT(*) > MAX(b.num_posts) THEN 1 ELSE COUNT(*) / MAX(b.num_posts) END * 100 AS percentage
1340
		FROM {db_prefix}messages AS m
1341
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1342
		WHERE m.id_member = {int:current_member}
1343
			AND {query_see_board}
1344
		GROUP BY b.id_board, b.num_posts
1345
		ORDER BY percentage DESC
1346
		LIMIT 10',
1347
		array(
1348
			'current_member' => $memID,
1349
		)
1350
	);
1351
	$context['board_activity'] = array();
1352
	while ($row = $smcFunc['db_fetch_assoc']($result))
1353
	{
1354
		$context['board_activity'][$row['id_board']] = array(
1355
			'id' => $row['id_board'],
1356
			'posts' => $row['message_count'],
1357
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1358
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1359
			'percent' => comma_format((float) $row['percentage'], 2),
1360
			'posts_percent' => (float) $row['percentage'],
1361
			'total_posts' => $row['num_posts'],
1362
		);
1363
	}
1364
	$smcFunc['db_free_result']($result);
1365
1366
	// Posting activity by time.
1367
	$result = $smcFunc['db_query']('user_activity_by_time', '
1368
		SELECT
1369
			HOUR(FROM_UNIXTIME(poster_time + {int:time_offset})) AS hour,
1370
			COUNT(*) AS post_count
1371
		FROM {db_prefix}messages
1372
		WHERE id_member = {int:current_member}' . ($modSettings['totalMessages'] > 100000 ? '
1373
			AND id_topic > {int:top_ten_thousand_topics}' : '') . '
1374
		GROUP BY hour',
1375
		array(
1376
			'current_member' => $memID,
1377
			'top_ten_thousand_topics' => $modSettings['totalTopics'] - 10000,
1378
			'time_offset' => (($user_info['time_offset'] + $modSettings['time_offset']) * 3600),
1379
		)
1380
	);
1381
	$maxPosts = $realPosts = 0;
1382
	$context['posts_by_time'] = array();
1383
	while ($row = $smcFunc['db_fetch_assoc']($result))
1384
	{
1385
		// Cast as an integer to remove the leading 0.
1386
		$row['hour'] = (int) $row['hour'];
1387
1388
		$maxPosts = max($row['post_count'], $maxPosts);
1389
		$realPosts += $row['post_count'];
1390
1391
		$context['posts_by_time'][$row['hour']] = array(
1392
			'hour' => $row['hour'],
1393
			'hour_format' => stripos($user_info['time_format'], '%p') === false ? $row['hour'] : date('g a', mktime($row['hour'])),
1394
			'posts' => $row['post_count'],
1395
			'posts_percent' => 0,
1396
			'is_last' => $row['hour'] == 23,
1397
		);
1398
	}
1399
	$smcFunc['db_free_result']($result);
1400
1401
	if ($maxPosts > 0)
1402
		for ($hour = 0; $hour < 24; $hour++)
1403
		{
1404
			if (!isset($context['posts_by_time'][$hour]))
1405
				$context['posts_by_time'][$hour] = array(
1406
					'hour' => $hour,
1407
					'hour_format' => stripos($user_info['time_format'], '%p') === false ? $hour : date('g a', mktime($hour)),
1408
					'posts' => 0,
1409
					'posts_percent' => 0,
1410
					'relative_percent' => 0,
1411
					'is_last' => $hour == 23,
1412
				);
1413
			else
1414
			{
1415
				$context['posts_by_time'][$hour]['posts_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $realPosts);
1416
				$context['posts_by_time'][$hour]['relative_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $maxPosts);
1417
			}
1418
		}
1419
1420
	// Put it in the right order.
1421
	ksort($context['posts_by_time']);
1422
1423
	// Custom stats (just add a template_layer to add it to the template!)
1424
 	call_integration_hook('integrate_profile_stats', array($memID));
1425
}
1426
1427
/**
1428
 * Loads up the information for the "track user" section of the profile
1429
 *
1430
 * @param int $memID The ID of the member
1431
 */
1432
function tracking($memID)
1433
{
1434
	global $context, $txt, $modSettings, $user_profile;
1435
1436
	$subActions = array(
1437
		'activity' => array('trackActivity', $txt['trackActivity'], 'moderate_forum'),
1438
		'ip' => array('TrackIP', $txt['trackIP'], 'moderate_forum'),
1439
		'edits' => array('trackEdits', $txt['trackEdits'], 'moderate_forum'),
1440
		'groupreq' => array('trackGroupReq', $txt['trackGroupRequests'], 'approve_group_requests'),
1441
		'logins' => array('TrackLogins', $txt['trackLogins'], 'moderate_forum'),
1442
	);
1443
1444
	foreach ($subActions as $sa => $action)
1445
	{
1446
		if (!allowedTo($action[2]))
1447
			unset($subActions[$sa]);
1448
	}
1449
1450
	// Create the tabs for the template.
1451
	$context[$context['profile_menu_name']]['tab_data'] = array(
1452
		'title' => $txt['tracking'],
1453
		'description' => $txt['tracking_description'],
1454
		'icon' => 'profile_hd.png',
1455
		'tabs' => array(
1456
			'activity' => array(),
1457
			'ip' => array(),
1458
			'edits' => array(),
1459
			'groupreq' => array(),
1460
			'logins' => array(),
1461
		),
1462
	);
1463
1464
	// Moderation must be on to track edits.
1465
	if (empty($modSettings['userlog_enabled']))
1466
		unset($context[$context['profile_menu_name']]['tab_data']['edits'], $subActions['edits']);
1467
1468
	// Group requests must be active to show it...
1469
	if (empty($modSettings['show_group_membership']))
1470
		unset($context[$context['profile_menu_name']]['tab_data']['groupreq'], $subActions['groupreq']);
1471
1472
	if (empty($subActions))
1473
		fatal_lang_error('no_access', false);
1474
1475
	$keys = array_keys($subActions);
1476
	$default = array_shift($keys);
1477
	$context['tracking_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : $default;
1478
1479
	// Set a page title.
1480
	$context['page_title'] = $txt['trackUser'] . ' - ' . $subActions[$context['tracking_area']][1] . ' - ' . $user_profile[$memID]['real_name'];
1481
1482
	// Pass on to the actual function.
1483
	$context['sub_template'] = $subActions[$context['tracking_area']][0];
1484
	$call = call_helper($subActions[$context['tracking_area']][0], true);
1485
1486
	if (!empty($call))
1487
		call_user_func($call, $memID);
1488
}
1489
1490
/**
1491
 * Handles tracking a user's activity
1492
 *
1493
 * @param int $memID The ID of the member
1494
 */
1495
function trackActivity($memID)
1496
{
1497
	global $scripturl, $txt, $modSettings, $sourcedir;
1498
	global $user_profile, $context, $smcFunc;
1499
1500
	// Verify if the user has sufficient permissions.
1501
	isAllowedTo('moderate_forum');
1502
1503
	$context['last_ip'] = $user_profile[$memID]['member_ip'];
1504
	if ($context['last_ip'] != $user_profile[$memID]['member_ip2'])
1505
		$context['last_ip2'] = $user_profile[$memID]['member_ip2'];
1506
	$context['member']['name'] = $user_profile[$memID]['real_name'];
1507
1508
	// Set the options for the list component.
1509
	$listOptions = array(
1510
		'id' => 'track_user_list',
1511
		'title' => $txt['errors_by'] . ' ' . $context['member']['name'],
1512
		'items_per_page' => $modSettings['defaultMaxListItems'],
1513
		'no_items_label' => $txt['no_errors_from_user'],
1514
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=user;u=' . $memID,
1515
		'default_sort_col' => 'date',
1516
		'get_items' => array(
1517
			'function' => 'list_getUserErrors',
1518
			'params' => array(
1519
				'le.id_member = {int:current_member}',
1520
				array('current_member' => $memID),
1521
			),
1522
		),
1523
		'get_count' => array(
1524
			'function' => 'list_getUserErrorCount',
1525
			'params' => array(
1526
				'id_member = {int:current_member}',
1527
				array('current_member' => $memID),
1528
			),
1529
		),
1530
		'columns' => array(
1531
			'ip_address' => array(
1532
				'header' => array(
1533
					'value' => $txt['ip_address'],
1534
				),
1535
				'data' => array(
1536
					'sprintf' => array(
1537
						'format' => '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=%1$s;u=' . $memID . '">%1$s</a>',
1538
						'params' => array(
1539
							'ip' => false,
1540
						),
1541
					),
1542
				),
1543
				'sort' => array(
1544
					'default' => 'le.ip',
1545
					'reverse' => 'le.ip DESC',
1546
				),
1547
			),
1548
			'message' => array(
1549
				'header' => array(
1550
					'value' => $txt['message'],
1551
				),
1552
				'data' => array(
1553
					'sprintf' => array(
1554
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
1555
						'params' => array(
1556
							'message' => false,
1557
							'url' => false,
1558
						),
1559
					),
1560
				),
1561
			),
1562
			'date' => array(
1563
				'header' => array(
1564
					'value' => $txt['date'],
1565
				),
1566
				'data' => array(
1567
					'db' => 'time',
1568
				),
1569
				'sort' => array(
1570
					'default' => 'le.id_error DESC',
1571
					'reverse' => 'le.id_error',
1572
				),
1573
			),
1574
		),
1575
		'additional_rows' => array(
1576
			array(
1577
				'position' => 'after_title',
1578
				'value' => $txt['errors_desc'],
1579
			),
1580
		),
1581
	);
1582
1583
	// Create the list for viewing.
1584
	require_once($sourcedir . '/Subs-List.php');
1585
	createList($listOptions);
1586
1587
	// @todo cache this
1588
	// If this is a big forum, or a large posting user, let's limit the search.
1589
	if ($modSettings['totalMessages'] > 50000 && $user_profile[$memID]['posts'] > 500)
1590
	{
1591
		$request = $smcFunc['db_query']('', '
1592
			SELECT MAX(id_msg)
1593
			FROM {db_prefix}messages AS m
1594
			WHERE m.id_member = {int:current_member}',
1595
			array(
1596
				'current_member' => $memID,
1597
			)
1598
		);
1599
		list ($max_msg_member) = $smcFunc['db_fetch_row']($request);
1600
		$smcFunc['db_free_result']($request);
1601
1602
		// There's no point worrying ourselves with messages made yonks ago, just get recent ones!
1603
		$min_msg_member = max(0, $max_msg_member - $user_profile[$memID]['posts'] * 3);
1604
	}
1605
1606
	// Default to at least the ones we know about.
1607
	$ips = array(
1608
		$user_profile[$memID]['member_ip'],
1609
		$user_profile[$memID]['member_ip2'],
1610
	);
1611
1612
	// @todo cache this
1613
	// Get all IP addresses this user has used for his messages.
1614
	$request = $smcFunc['db_query']('', '
1615
		SELECT poster_ip
1616
		FROM {db_prefix}messages
1617
		WHERE id_member = {int:current_member}
1618
		' . (isset($min_msg_member) ? '
1619
			AND id_msg >= {int:min_msg_member} AND id_msg <= {int:max_msg_member}' : '') . '
1620
		GROUP BY poster_ip',
1621
		array(
1622
			'current_member' => $memID,
1623
			'min_msg_member' => !empty($min_msg_member) ? $min_msg_member : 0,
1624
			'max_msg_member' => !empty($max_msg_member) ? $max_msg_member : 0,
1625
		)
1626
	);
1627
	$context['ips'] = array();
1628 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...
1629
	{
1630
		$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>';
1631
		$ips[] = inet_dtop($row['poster_ip']);
1632
	}
1633
	$smcFunc['db_free_result']($request);
1634
1635
	// Now also get the IP addresses from the error messages.
1636
	$request = $smcFunc['db_query']('', '
1637
		SELECT COUNT(*) AS error_count, ip
1638
		FROM {db_prefix}log_errors
1639
		WHERE id_member = {int:current_member}
1640
		GROUP BY ip',
1641
		array(
1642
			'current_member' => $memID,
1643
		)
1644
	);
1645
	$context['error_ips'] = array();
1646 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...
1647
	{
1648
		$context['error_ips'][] = '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=' . $row['ip'] . ';u=' . $memID . '">' . $row['ip'] . '</a>';
1649
		$ips[] = inet_dtop($row['ip']);
1650
	}
1651
	$smcFunc['db_free_result']($request);
1652
1653
	// Find other users that might use the same IP.
1654
	$ips = array_unique($ips);
1655
	$context['members_in_range'] = array();
1656
	if (!empty($ips))
1657
	{
1658
		// Get member ID's which are in messages...
1659
		$request = $smcFunc['db_query']('', '
1660
			SELECT mem.id_member
1661
			FROM {db_prefix}messages AS m
1662
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1663
			WHERE m.poster_ip IN ({array_inet:ip_list})
1664
				AND mem.id_member != {int:current_member}',
1665
			array(
1666
				'current_member' => $memID,
1667
				'ip_list' => $ips,
1668
			)
1669
		);
1670
		$message_members = array();
1671
		while ($row = $smcFunc['db_fetch_assoc']($request))
1672
			$message_members[] = $row['id_member'];
1673
		$smcFunc['db_free_result']($request);
1674
1675
		// Fetch their names, cause of the GROUP BY doesn't like giving us that normally.
1676
		if (!empty($message_members))
1677
		{
1678
			$request = $smcFunc['db_query']('', '
1679
				SELECT id_member, real_name
1680
				FROM {db_prefix}members
1681
				WHERE id_member IN ({array_int:message_members})',
1682
				array(
1683
					'message_members' => $message_members,
1684
					'ip_list' => $ips,
1685
				)
1686
			);
1687
			while ($row = $smcFunc['db_fetch_assoc']($request))
1688
				$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
1689
			$smcFunc['db_free_result']($request);
1690
		}
1691
1692
		$request = $smcFunc['db_query']('', '
1693
			SELECT id_member, real_name
1694
			FROM {db_prefix}members
1695
			WHERE id_member != {int:current_member}
1696
				AND member_ip IN ({array_inet:ip_list})',
1697
			array(
1698
				'current_member' => $memID,
1699
				'ip_list' => $ips,
1700
			)
1701
		);
1702
		while ($row = $smcFunc['db_fetch_assoc']($request))
1703
			$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
1704
		$smcFunc['db_free_result']($request);
1705
	}
1706
}
1707
1708
/**
1709
 * Get the number of user errors
1710
 *
1711
 * @param string $where A query to limit which errors are counted
1712
 * @param array $where_vars The parameters for $where
1713
 * @return int Number of user errors
1714
 */
1715 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...
1716
{
1717
	global $smcFunc;
1718
1719
	$request = $smcFunc['db_query']('', '
1720
		SELECT COUNT(*) AS error_count
1721
		FROM {db_prefix}log_errors
1722
		WHERE ' . $where,
1723
		$where_vars
1724
	);
1725
	list ($count) = $smcFunc['db_fetch_row']($request);
1726
	$smcFunc['db_free_result']($request);
1727
1728
	return (int) $count;
1729
}
1730
1731
/**
1732
 * Gets all of the errors generated by a user's actions. Callback for the list in track_activity
1733
 *
1734
 * @param int $start Which item to start with (for pagination purposes)
1735
 * @param int $items_per_page How many items to show on each page
1736
 * @param string $sort A string indicating how to sort the results
1737
 * @param string $where A query indicating how to filter the results (eg 'id_member={int:id_member}')
1738
 * @param array $where_vars An array of parameters for $where
1739
 * @return array An array of information about the error messages
1740
 */
1741
function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars = array())
1742
{
1743
	global $smcFunc, $txt, $scripturl;
1744
1745
	// Get a list of error messages from this ip (range).
1746
	$request = $smcFunc['db_query']('', '
1747
		SELECT
1748
			le.log_time, le.ip, le.url, le.message, COALESCE(mem.id_member, 0) AS id_member,
1749
			COALESCE(mem.real_name, {string:guest_title}) AS display_name, mem.member_name
1750
		FROM {db_prefix}log_errors AS le
1751
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = le.id_member)
1752
		WHERE ' . $where . '
1753
		ORDER BY {raw:sort}
1754
		LIMIT {int:start}, {int:max}',
1755
		array_merge($where_vars, array(
1756
			'guest_title' => $txt['guest_title'],
1757
			'sort' => $sort,
1758
			'start' => $start,
1759
			'max' => $items_per_page,
1760
		))
1761
	);
1762
	$error_messages = array();
1763
	while ($row = $smcFunc['db_fetch_assoc']($request))
1764
		$error_messages[] = array(
1765
			'ip' => inet_dtop($row['ip']),
1766
			'member_link' => $row['id_member'] > 0 ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>' : $row['display_name'],
1767
			'message' => strtr($row['message'], array('&lt;span class=&quot;remove&quot;&gt;' => '', '&lt;/span&gt;' => '')),
1768
			'url' => $row['url'],
1769
			'time' => timeformat($row['log_time']),
1770
			'timestamp' => forum_time(true, $row['log_time']),
1771
		);
1772
	$smcFunc['db_free_result']($request);
1773
1774
	return $error_messages;
1775
}
1776
1777
/**
1778
 * Gets the number of posts made from a particular IP
1779
 *
1780
 * @param string $where A query indicating which posts to count
1781
 * @param array $where_vars The parameters for $where
1782
 * @return int Count of messages matching the IP
1783
 */
1784 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...
1785
{
1786
	global $smcFunc;
1787
1788
	$request = $smcFunc['db_query']('', '
1789
		SELECT COUNT(*) AS message_count
1790
		FROM {db_prefix}messages AS m
1791
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1792
		WHERE {query_see_board} AND ' . $where,
1793
		$where_vars
1794
	);
1795
	list ($count) = $smcFunc['db_fetch_row']($request);
1796
	$smcFunc['db_free_result']($request);
1797
1798
	return (int) $count;
1799
}
1800
1801
/**
1802
 * Gets all the posts made from a particular IP
1803
 *
1804
 * @param int $start Which item to start with (for pagination purposes)
1805
 * @param int $items_per_page How many items to show on each page
1806
 * @param string $sort A string indicating how to sort the results
1807
 * @param string $where A query to filter which posts are returned
1808
 * @param array $where_vars An array of parameters for $where
1809
 * @return array An array containing information about the posts
1810
 */
1811
function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars = array())
1812
{
1813
	global $smcFunc, $scripturl;
1814
1815
	// Get all the messages fitting this where clause.
1816
	// @todo SLOW This query is using a filesort.
1817
	$request = $smcFunc['db_query']('', '
1818
		SELECT
1819
			m.id_msg, m.poster_ip, COALESCE(mem.real_name, m.poster_name) AS display_name, mem.id_member,
1820
			m.subject, m.poster_time, m.id_topic, m.id_board
1821
		FROM {db_prefix}messages AS m
1822
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1823
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1824
		WHERE {query_see_board} AND ' . $where . '
1825
		ORDER BY {raw:sort}
1826
		LIMIT {int:start}, {int:max}',
1827
		array_merge($where_vars, array(
1828
			'sort' => $sort,
1829
			'start' => $start,
1830
			'max' => $items_per_page,
1831
		))
1832
	);
1833
	$messages = array();
1834
	while ($row = $smcFunc['db_fetch_assoc']($request))
1835
		$messages[] = array(
1836
			'ip' => inet_dtop($row['poster_ip']),
1837
			'member_link' => empty($row['id_member']) ? $row['display_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>',
1838
			'board' => array(
1839
				'id' => $row['id_board'],
1840
				'href' => $scripturl . '?board=' . $row['id_board']
1841
			),
1842
			'topic' => $row['id_topic'],
1843
			'id' => $row['id_msg'],
1844
			'subject' => $row['subject'],
1845
			'time' => timeformat($row['poster_time']),
1846
			'timestamp' => forum_time(true, $row['poster_time'])
1847
		);
1848
	$smcFunc['db_free_result']($request);
1849
1850
	return $messages;
1851
}
1852
1853
/**
1854
 * Handles tracking a particular IP address
1855
 *
1856
 * @param int $memID The ID of a member whose IP we want to track
1857
 */
1858
function TrackIP($memID = 0)
1859
{
1860
	global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir;
1861
	global $context, $smcFunc;
1862
1863
	// Can the user do this?
1864
	isAllowedTo('moderate_forum');
1865
1866
	if ($memID == 0)
1867
	{
1868
		$context['ip'] = $user_info['ip'];
1869
		loadTemplate('Profile');
1870
		loadLanguage('Profile');
1871
		$context['sub_template'] = 'trackIP';
1872
		$context['page_title'] = $txt['profile'];
1873
		$context['base_url'] = $scripturl . '?action=trackip';
1874
	}
1875
	else
1876
	{
1877
		$context['ip'] = $user_profile[$memID]['member_ip'];
1878
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
1879
	}
1880
1881
	// Searching?
1882
	if (isset($_REQUEST['searchip']))
1883
		$context['ip'] = trim($_REQUEST['searchip']);
1884
1885
	if (isValidIP($context['ip']) === false)
1886
		fatal_lang_error('invalid_tracking_ip', false);
1887
1888
	//mysql didn't support like search with varbinary
1889
	//$ip_var = str_replace('*', '%', $context['ip']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1890
	//$ip_string = strpos($ip_var, '%') === false ? '= {inet:ip_address}' : 'LIKE {string:ip_address}';
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1891
	$ip_var = $context['ip'];
1892
	$ip_string = '= {inet:ip_address}';
1893
1894
	if (empty($context['tracking_area']))
1895
		$context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip'];
1896
1897
	$request = $smcFunc['db_query']('', '
1898
		SELECT id_member, real_name AS display_name, member_ip
1899
		FROM {db_prefix}members
1900
		WHERE member_ip ' . $ip_string,
1901
		array(
1902
			'ip_address' => $ip_var,
1903
		)
1904
	);
1905
	$context['ips'] = array();
1906
	while ($row = $smcFunc['db_fetch_assoc']($request))
1907
		$context['ips'][inet_dtop($row['member_ip'])][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
1908
	$smcFunc['db_free_result']($request);
1909
1910
	ksort($context['ips']);
1911
1912
	// For messages we use the "messages per page" option
1913
	$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...
1914
1915
	// Gonna want this for the list.
1916
	require_once($sourcedir . '/Subs-List.php');
1917
1918
	// Start with the user messages.
1919
	$listOptions = array(
1920
		'id' => 'track_message_list',
1921
		'title' => $txt['messages_from_ip'] . ' ' . $context['ip'],
1922
		'start_var_name' => 'messageStart',
1923
		'items_per_page' => $maxPerPage,
1924
		'no_items_label' => $txt['no_messages_from_ip'],
1925
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
1926
		'default_sort_col' => 'date',
1927
		'get_items' => array(
1928
			'function' => 'list_getIPMessages',
1929
			'params' => array(
1930
				'm.poster_ip ' . $ip_string,
1931
				array('ip_address' => $ip_var),
1932
			),
1933
		),
1934
		'get_count' => array(
1935
			'function' => 'list_getIPMessageCount',
1936
			'params' => array(
1937
				'm.poster_ip ' . $ip_string,
1938
				array('ip_address' => $ip_var),
1939
			),
1940
		),
1941
		'columns' => array(
1942
			'ip_address' => array(
1943
				'header' => array(
1944
					'value' => $txt['ip_address'],
1945
				),
1946
				'data' => array(
1947
					'sprintf' => array(
1948
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
1949
						'params' => array(
1950
							'ip' => false,
1951
						),
1952
					),
1953
				),
1954
				'sort' => array(
1955
					'default' => 'm.poster_ip',
1956
					'reverse' => 'm.poster_ip DESC',
1957
				),
1958
			),
1959
			'poster' => array(
1960
				'header' => array(
1961
					'value' => $txt['poster'],
1962
				),
1963
				'data' => array(
1964
					'db' => 'member_link',
1965
				),
1966
			),
1967
			'subject' => array(
1968
				'header' => array(
1969
					'value' => $txt['subject'],
1970
				),
1971
				'data' => array(
1972
					'sprintf' => array(
1973
						'format' => '<a href="' . $scripturl . '?topic=%1$s.msg%2$s#msg%2$s" rel="nofollow">%3$s</a>',
1974
						'params' => array(
1975
							'topic' => false,
1976
							'id' => false,
1977
							'subject' => false,
1978
						),
1979
					),
1980
				),
1981
			),
1982
			'date' => array(
1983
				'header' => array(
1984
					'value' => $txt['date'],
1985
				),
1986
				'data' => array(
1987
					'db' => 'time',
1988
				),
1989
				'sort' => array(
1990
					'default' => 'm.id_msg DESC',
1991
					'reverse' => 'm.id_msg',
1992
				),
1993
			),
1994
		),
1995
		'additional_rows' => array(
1996
			array(
1997
				'position' => 'after_title',
1998
				'value' => $txt['messages_from_ip_desc'],
1999
			),
2000
		),
2001
	);
2002
2003
	// Create the messages list.
2004
	createList($listOptions);
2005
2006
	// Set the options for the error lists.
2007
	$listOptions = array(
2008
		'id' => 'track_user_list',
2009
		'title' => $txt['errors_from_ip'] . ' ' . $context['ip'],
2010
		'start_var_name' => 'errorStart',
2011
		'items_per_page' => $modSettings['defaultMaxListItems'],
2012
		'no_items_label' => $txt['no_errors_from_ip'],
2013
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2014
		'default_sort_col' => 'date2',
2015
		'get_items' => array(
2016
			'function' => 'list_getUserErrors',
2017
			'params' => array(
2018
				'le.ip ' . $ip_string,
2019
				array('ip_address' => $ip_var),
2020
			),
2021
		),
2022
		'get_count' => array(
2023
			'function' => 'list_getUserErrorCount',
2024
			'params' => array(
2025
				'ip ' . $ip_string,
2026
				array('ip_address' => $ip_var),
2027
			),
2028
		),
2029
		'columns' => array(
2030
			'ip_address2' => array(
2031
				'header' => array(
2032
					'value' => $txt['ip_address'],
2033
				),
2034
				'data' => array(
2035
					'sprintf' => array(
2036
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2037
						'params' => array(
2038
							'ip' => false,
2039
						),
2040
					),
2041
				),
2042
				'sort' => array(
2043
					'default' => 'le.ip',
2044
					'reverse' => 'le.ip DESC',
2045
				),
2046
			),
2047
			'display_name' => array(
2048
				'header' => array(
2049
					'value' => $txt['display_name'],
2050
				),
2051
				'data' => array(
2052
					'db' => 'member_link',
2053
				),
2054
			),
2055
			'message' => array(
2056
				'header' => array(
2057
					'value' => $txt['message'],
2058
				),
2059
				'data' => array(
2060
					'sprintf' => array(
2061
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
2062
						'params' => array(
2063
							'message' => false,
2064
							'url' => false,
2065
						),
2066
					),
2067
				),
2068
			),
2069
			'date2' => array(
2070
				'header' => array(
2071
					'value' => $txt['date'],
2072
				),
2073
				'data' => array(
2074
					'db' => 'time',
2075
				),
2076
				'sort' => array(
2077
					'default' => 'le.id_error DESC',
2078
					'reverse' => 'le.id_error',
2079
				),
2080
			),
2081
		),
2082
		'additional_rows' => array(
2083
			array(
2084
				'position' => 'after_title',
2085
				'value' => $txt['errors_from_ip_desc'],
2086
			),
2087
		),
2088
	);
2089
2090
	// Create the error list.
2091
	createList($listOptions);
2092
2093
	// Allow 3rd party integrations to add in their own lists or whatever.
2094
	$context['additional_track_lists'] = array();
2095
	call_integration_hook('integrate_profile_trackip', array($ip_string, $ip_var));
2096
2097
	$context['single_ip'] = strpos($context['ip'], '*') === false;
2098
	if ($context['single_ip'])
2099
	{
2100
		$context['whois_servers'] = array(
2101
			'afrinic' => array(
2102
				'name' => $txt['whois_afrinic'],
2103
				'url' => 'https://www.afrinic.net/cgi-bin/whois?searchtext=' . $context['ip'],
2104
				'range' => array(41, 154, 196),
2105
			),
2106
			'apnic' => array(
2107
				'name' => $txt['whois_apnic'],
2108
				'url' => 'https://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'],
2109
				'range' => array(58, 59, 60, 61, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
2110
					125, 126, 133, 150, 153, 163, 171, 202, 203, 210, 211, 218, 219, 220, 221, 222),
2111
			),
2112
			'arin' => array(
2113
				'name' => $txt['whois_arin'],
2114
				'url' => 'https://whois.arin.net/rest/ip/' . $context['ip'],
2115
				'range' => array(7, 24, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 96, 97, 98, 99,
2116
					128, 129, 130, 131, 132, 134, 135, 136, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148, 149,
2117
					152, 155, 156, 157, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174,
2118
					192, 198, 199, 204, 205, 206, 207, 208, 209, 216),
2119
			),
2120
			'lacnic' => array(
2121
				'name' => $txt['whois_lacnic'],
2122
				'url' => 'https://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'],
2123
				'range' => array(186, 187, 189, 190, 191, 200, 201),
2124
			),
2125
			'ripe' => array(
2126
				'name' => $txt['whois_ripe'],
2127
				'url' => 'https://apps.db.ripe.net/search/query.html?searchtext=' . $context['ip'],
2128
				'range' => array(62, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
2129
					141, 145, 151, 188, 193, 194, 195, 212, 213, 217),
2130
			),
2131
		);
2132
2133
		foreach ($context['whois_servers'] as $whois)
2134
		{
2135
			// Strip off the "decimal point" and anything following...
2136
			if (in_array((int) $context['ip'], $whois['range']))
2137
				$context['auto_whois_server'] = $whois;
2138
		}
2139
	}
2140
}
2141
2142
/**
2143
 * Tracks a user's logins.
2144
 *
2145
 * @param int $memID The ID of the member
2146
 */
2147
function TrackLogins($memID = 0)
2148
{
2149
	global $scripturl, $txt, $sourcedir, $context;
2150
2151
	// Gonna want this for the list.
2152
	require_once($sourcedir . '/Subs-List.php');
2153
2154
	if ($memID == 0)
2155
		$context['base_url'] = $scripturl . '?action=trackip';
2156
	else
2157
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2158
2159
	// Start with the user messages.
2160
	$listOptions = array(
2161
		'id' => 'track_logins_list',
2162
		'title' => $txt['trackLogins'],
2163
		'no_items_label' => $txt['trackLogins_none_found'],
2164
		'base_href' => $context['base_url'],
2165
		'get_items' => array(
2166
			'function' => 'list_getLogins',
2167
			'params' => array(
2168
				'id_member = {int:current_member}',
2169
				array('current_member' => $memID),
2170
			),
2171
		),
2172
		'get_count' => array(
2173
			'function' => 'list_getLoginCount',
2174
			'params' => array(
2175
				'id_member = {int:current_member}',
2176
				array('current_member' => $memID),
2177
			),
2178
		),
2179
		'columns' => array(
2180
			'time' => array(
2181
				'header' => array(
2182
					'value' => $txt['date'],
2183
				),
2184
				'data' => array(
2185
					'db' => 'time',
2186
				),
2187
			),
2188
			'ip' => array(
2189
				'header' => array(
2190
					'value' => $txt['ip_address'],
2191
				),
2192
				'data' => array(
2193
					'sprintf' => array(
2194
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a> (<a href="' . $context['base_url'] . ';searchip=%2$s">%2$s</a>) ',
2195
						'params' => array(
2196
							'ip' => false,
2197
							'ip2' => false
2198
						),
2199
					),
2200
				),
2201
			),
2202
		),
2203
		'additional_rows' => array(
2204
			array(
2205
				'position' => 'after_title',
2206
				'value' => $txt['trackLogins_desc'],
2207
			),
2208
		),
2209
	);
2210
2211
	// Create the messages list.
2212
	createList($listOptions);
2213
2214
	$context['sub_template'] = 'show_list';
2215
	$context['default_list'] = 'track_logins_list';
2216
}
2217
2218
/**
2219
 * Finds the total number of tracked logins for a particular user
2220
 *
2221
 * @param string $where A query to limit which logins are counted
2222
 * @param array $where_vars An array of parameters for $where
2223
 * @return int count of messages matching the IP
2224
 */
2225 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...
2226
{
2227
	global $smcFunc;
2228
2229
	$request = $smcFunc['db_query']('', '
2230
		SELECT COUNT(*) AS message_count
2231
		FROM {db_prefix}member_logins
2232
		WHERE id_member = {int:id_member}',
2233
		array(
2234
			'id_member' => $where_vars['current_member'],
2235
		)
2236
	);
2237
	list ($count) = $smcFunc['db_fetch_row']($request);
2238
	$smcFunc['db_free_result']($request);
2239
2240
	return (int) $count;
2241
}
2242
2243
/**
2244
 * Callback for the list in trackLogins.
2245
 *
2246
 * @param int $start Which item to start with (not used here)
2247
 * @param int $items_per_page How many items to show on each page (not used here)
2248
 * @param string $sort A string indicating
2249
 * @param string $where A query to filter results (not used here)
2250
 * @param array $where_vars An array of parameters for $where. Only 'current_member' (the ID of the member) is used here
2251
 * @return array An array of information about user logins
2252
 */
2253
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...
2254
{
2255
	global $smcFunc;
2256
2257
	$request = $smcFunc['db_query']('', '
2258
		SELECT time, ip, ip2
2259
		FROM {db_prefix}member_logins
2260
		WHERE id_member = {int:id_member}
2261
		ORDER BY time DESC',
2262
		array(
2263
			'id_member' => $where_vars['current_member'],
2264
		)
2265
	);
2266
	$logins = array();
2267
	while ($row = $smcFunc['db_fetch_assoc']($request))
2268
		$logins[] = array(
2269
			'time' => timeformat($row['time']),
2270
			'ip' => inet_dtop($row['ip']),
2271
			'ip2' => inet_dtop($row['ip2']),
2272
		);
2273
	$smcFunc['db_free_result']($request);
2274
2275
	return $logins;
2276
}
2277
2278
/**
2279
 * Tracks a user's profile edits
2280
 *
2281
 * @param int $memID The ID of the member
2282
 */
2283
function trackEdits($memID)
2284
{
2285
	global $scripturl, $txt, $modSettings, $sourcedir, $context, $smcFunc;
2286
2287
	require_once($sourcedir . '/Subs-List.php');
2288
2289
	// Get the names of any custom fields.
2290
	$request = $smcFunc['db_query']('', '
2291
		SELECT col_name, field_name, bbc
2292
		FROM {db_prefix}custom_fields',
2293
		array(
2294
		)
2295
	);
2296
	$context['custom_field_titles'] = array();
2297
	while ($row = $smcFunc['db_fetch_assoc']($request))
2298
		$context['custom_field_titles']['customfield_' . $row['col_name']] = array(
2299
			'title' => $row['field_name'],
2300
			'parse_bbc' => $row['bbc'],
2301
		);
2302
	$smcFunc['db_free_result']($request);
2303
2304
	// Set the options for the error lists.
2305
	$listOptions = array(
2306
		'id' => 'edit_list',
2307
		'title' => $txt['trackEdits'],
2308
		'items_per_page' => $modSettings['defaultMaxListItems'],
2309
		'no_items_label' => $txt['trackEdit_no_edits'],
2310
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=edits;u=' . $memID,
2311
		'default_sort_col' => 'time',
2312
		'get_items' => array(
2313
			'function' => 'list_getProfileEdits',
2314
			'params' => array(
2315
				$memID,
2316
			),
2317
		),
2318
		'get_count' => array(
2319
			'function' => 'list_getProfileEditCount',
2320
			'params' => array(
2321
				$memID,
2322
			),
2323
		),
2324
		'columns' => array(
2325
			'action' => array(
2326
				'header' => array(
2327
					'value' => $txt['trackEdit_action'],
2328
				),
2329
				'data' => array(
2330
					'db' => 'action_text',
2331
				),
2332
			),
2333
			'before' => array(
2334
				'header' => array(
2335
					'value' => $txt['trackEdit_before'],
2336
				),
2337
				'data' => array(
2338
					'db' => 'before',
2339
				),
2340
			),
2341
			'after' => array(
2342
				'header' => array(
2343
					'value' => $txt['trackEdit_after'],
2344
				),
2345
				'data' => array(
2346
					'db' => 'after',
2347
				),
2348
			),
2349
			'time' => array(
2350
				'header' => array(
2351
					'value' => $txt['date'],
2352
				),
2353
				'data' => array(
2354
					'db' => 'time',
2355
				),
2356
				'sort' => array(
2357
					'default' => 'id_action DESC',
2358
					'reverse' => 'id_action',
2359
				),
2360
			),
2361
			'applicator' => array(
2362
				'header' => array(
2363
					'value' => $txt['trackEdit_applicator'],
2364
				),
2365
				'data' => array(
2366
					'db' => 'member_link',
2367
				),
2368
			),
2369
		),
2370
	);
2371
2372
	// Create the error list.
2373
	createList($listOptions);
2374
2375
	$context['sub_template'] = 'show_list';
2376
	$context['default_list'] = 'edit_list';
2377
}
2378
2379
/**
2380
 * How many edits?
2381
 *
2382
 * @param int $memID The ID of the member
2383
 * @return int The number of profile edits
2384
 */
2385 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...
2386
{
2387
	global $smcFunc;
2388
2389
	$request = $smcFunc['db_query']('', '
2390
		SELECT COUNT(*) AS edit_count
2391
		FROM {db_prefix}log_actions
2392
		WHERE id_log = {int:log_type}
2393
			AND id_member = {int:owner}',
2394
		array(
2395
			'log_type' => 2,
2396
			'owner' => $memID,
2397
		)
2398
	);
2399
	list ($edit_count) = $smcFunc['db_fetch_row']($request);
2400
	$smcFunc['db_free_result']($request);
2401
2402
	return (int) $edit_count;
2403
}
2404
2405
/**
2406
 * Loads up information about a user's profile edits. Callback for the list in trackEdits()
2407
 *
2408
 * @param int $start Which item to start with (for pagination purposes)
2409
 * @param int $items_per_page How many items to show on each page
2410
 * @param string $sort A string indicating how to sort the results
2411
 * @param int $memID The ID of the member
2412
 * @return array An array of information about the profile edits
2413
 */
2414
function list_getProfileEdits($start, $items_per_page, $sort, $memID)
2415
{
2416
	global $smcFunc, $txt, $scripturl, $context;
2417
2418
	// Get a list of error messages from this ip (range).
2419
	$request = $smcFunc['db_query']('', '
2420
		SELECT
2421
			id_action, id_member, ip, log_time, action, extra
2422
		FROM {db_prefix}log_actions
2423
		WHERE id_log = {int:log_type}
2424
			AND id_member = {int:owner}
2425
		ORDER BY {raw:sort}
2426
		LIMIT {int:start}, {int:max}',
2427
		array(
2428
			'log_type' => 2,
2429
			'owner' => $memID,
2430
			'sort' => $sort,
2431
			'start' => $start,
2432
			'max' => $items_per_page,
2433
		)
2434
	);
2435
	$edits = array();
2436
	$members = array();
2437
	while ($row = $smcFunc['db_fetch_assoc']($request))
2438
	{
2439
		$extra = $smcFunc['json_decode']($row['extra'], true);
2440
		if (!empty($extra['applicator']))
2441
			$members[] = $extra['applicator'];
2442
2443
		// Work out what the name of the action is.
2444
		if (isset($txt['trackEdit_action_' . $row['action']]))
2445
			$action_text = $txt['trackEdit_action_' . $row['action']];
2446
		elseif (isset($txt[$row['action']]))
2447
			$action_text = $txt[$row['action']];
2448
		// Custom field?
2449
		elseif (isset($context['custom_field_titles'][$row['action']]))
2450
			$action_text = $context['custom_field_titles'][$row['action']]['title'];
2451
		else
2452
			$action_text = $row['action'];
2453
2454
		// Parse BBC?
2455
		$parse_bbc = isset($context['custom_field_titles'][$row['action']]) && $context['custom_field_titles'][$row['action']]['parse_bbc'] ? true : false;
2456
2457
		$edits[] = array(
2458
			'id' => $row['id_action'],
2459
			'ip' => inet_dtop($row['ip']),
2460
			'id_member' => !empty($extra['applicator']) ? $extra['applicator'] : 0,
2461
			'member_link' => $txt['trackEdit_deleted_member'],
2462
			'action' => $row['action'],
2463
			'action_text' => $action_text,
2464
			'before' => !empty($extra['previous']) ? ($parse_bbc ? parse_bbc($extra['previous']) : $extra['previous']) : '',
2465
			'after' => !empty($extra['new']) ? ($parse_bbc ? parse_bbc($extra['new']) : $extra['new']) : '',
2466
			'time' => timeformat($row['log_time']),
2467
		);
2468
	}
2469
	$smcFunc['db_free_result']($request);
2470
2471
	// Get any member names.
2472
	if (!empty($members))
2473
	{
2474
		$request = $smcFunc['db_query']('', '
2475
			SELECT
2476
				id_member, real_name
2477
			FROM {db_prefix}members
2478
			WHERE id_member IN ({array_int:members})',
2479
			array(
2480
				'members' => $members,
2481
			)
2482
		);
2483
		$members = array();
2484
		while ($row = $smcFunc['db_fetch_assoc']($request))
2485
			$members[$row['id_member']] = $row['real_name'];
2486
		$smcFunc['db_free_result']($request);
2487
2488
		foreach ($edits as $key => $value)
2489
			if (isset($members[$value['id_member']]))
2490
				$edits[$key]['member_link'] = '<a href="' . $scripturl . '?action=profile;u=' . $value['id_member'] . '">' . $members[$value['id_member']] . '</a>';
2491
	}
2492
2493
	return $edits;
2494
}
2495
2496
/**
2497
 * Display the history of group requests made by the user whose profile we are viewing.
2498
 *
2499
 * @param int $memID The ID of the member
2500
 */
2501
function trackGroupReq($memID)
2502
{
2503
	global $scripturl, $txt, $modSettings, $sourcedir, $context;
2504
2505
	require_once($sourcedir . '/Subs-List.php');
2506
2507
	// Set the options for the error lists.
2508
	$listOptions = array(
2509
		'id' => 'request_list',
2510
		'title' => sprintf($txt['trackGroupRequests_title'], $context['member']['name']),
2511
		'items_per_page' => $modSettings['defaultMaxListItems'],
2512
		'no_items_label' => $txt['requested_none'],
2513
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=groupreq;u=' . $memID,
2514
		'default_sort_col' => 'time_applied',
2515
		'get_items' => array(
2516
			'function' => 'list_getGroupRequests',
2517
			'params' => array(
2518
				$memID,
2519
			),
2520
		),
2521
		'get_count' => array(
2522
			'function' => 'list_getGroupRequestsCount',
2523
			'params' => array(
2524
				$memID,
2525
			),
2526
		),
2527
		'columns' => array(
2528
			'group' => array(
2529
				'header' => array(
2530
					'value' => $txt['requested_group'],
2531
				),
2532
				'data' => array(
2533
					'db' => 'group_name',
2534
				),
2535
			),
2536
			'group_reason' => array(
2537
				'header' => array(
2538
					'value' => $txt['requested_group_reason'],
2539
				),
2540
				'data' => array(
2541
					'db' => 'group_reason',
2542
				),
2543
			),
2544
			'time_applied' => array(
2545
				'header' => array(
2546
					'value' => $txt['requested_group_time'],
2547
				),
2548
				'data' => array(
2549
					'db' => 'time_applied',
2550
					'timeformat' => true,
2551
				),
2552
				'sort' => array(
2553
					'default' => 'time_applied DESC',
2554
					'reverse' => 'time_applied',
2555
				),
2556
			),
2557
			'outcome' => array(
2558
				'header' => array(
2559
					'value' => $txt['requested_group_outcome'],
2560
				),
2561
				'data' => array(
2562
					'db' => 'outcome',
2563
				),
2564
			),
2565
		),
2566
	);
2567
2568
	// Create the error list.
2569
	createList($listOptions);
2570
2571
	$context['sub_template'] = 'show_list';
2572
	$context['default_list'] = 'request_list';
2573
}
2574
2575
/**
2576
 * How many edits?
2577
 *
2578
 * @param int $memID The ID of the member
2579
 * @return int The number of profile edits
2580
 */
2581
function list_getGroupRequestsCount($memID)
2582
{
2583
	global $smcFunc, $user_info;
2584
2585
	$request = $smcFunc['db_query']('', '
2586
		SELECT COUNT(*) AS req_count
2587
		FROM {db_prefix}log_group_requests AS lgr
2588
		WHERE id_member = {int:memID}
2589
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']),
2590
		array(
2591
			'memID' => $memID,
2592
		)
2593
	);
2594
	list ($report_count) = $smcFunc['db_fetch_row']($request);
2595
	$smcFunc['db_free_result']($request);
2596
2597
	return (int) $report_count;
2598
}
2599
2600
/**
2601
 * Loads up information about a user's group requests. Callback for the list in trackGroupReq()
2602
 *
2603
 * @param int $start Which item to start with (for pagination purposes)
2604
 * @param int $items_per_page How many items to show on each page
2605
 * @param string $sort A string indicating how to sort the results
2606
 * @param int $memID The ID of the member
2607
 * @return array An array of information about the user's group requests
2608
 */
2609
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...
2610
{
2611
	global $smcFunc, $txt, $scripturl, $user_info;
2612
2613
	$groupreq = array();
2614
2615
	$request = $smcFunc['db_query']('', '
2616
		SELECT
2617
			lgr.id_group, mg.group_name, mg.online_color, lgr.time_applied, lgr.reason, lgr.status,
2618
			ma.id_member AS id_member_acted, COALESCE(ma.member_name, lgr.member_name_acted) AS act_name, lgr.time_acted, lgr.act_reason
2619
		FROM {db_prefix}log_group_requests AS lgr
2620
			LEFT JOIN {db_prefix}members AS ma ON (lgr.id_member_acted = ma.id_member)
2621
			INNER JOIN {db_prefix}membergroups AS mg ON (lgr.id_group = mg.id_group)
2622
		WHERE lgr.id_member = {int:memID}
2623
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']) . '
2624
		ORDER BY {raw:sort}
2625
		LIMIT {int:start}, {int:max}',
2626
		array(
2627
			'memID' => $memID,
2628
			'sort' => $sort,
2629
			'start' => $start,
2630
			'max' => $items_per_page,
2631
		)
2632
	);
2633
	while ($row = $smcFunc['db_fetch_assoc']($request))
2634
	{
2635
		$this_req = array(
2636
			'group_name' => empty($row['online_color']) ? $row['group_name'] : '<span style="color:' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
2637
			'group_reason' => $row['reason'],
2638
			'time_applied' => $row['time_applied'],
2639
		);
2640
		switch ($row['status'])
2641
		{
2642
			case 0:
2643
				$this_req['outcome'] = $txt['outcome_pending'];
2644
				break;
2645
			case 1:
2646
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2647
				$this_req['outcome'] = sprintf($txt['outcome_approved'], $member_link, timeformat($row['time_acted']));
2648
				break;
2649
			case 2:
2650
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2651
				$this_req['outcome'] = sprintf(!empty($row['act_reason']) ? $txt['outcome_refused_reason'] : $txt['outcome_refused'], $member_link, timeformat($row['time_acted']), $row['act_reason']);
2652
				break;
2653
		}
2654
2655
		$groupreq[] = $this_req;
2656
	}
2657
	$smcFunc['db_free_result']($request);
2658
2659
	return $groupreq;
2660
}
2661
2662
/**
2663
 * Shows which permissions a user has
2664
 *
2665
 * @param int $memID The ID of the member
2666
 */
2667
function showPermissions($memID)
2668
{
2669
	global $txt, $board;
2670
	global $user_profile, $context, $sourcedir, $smcFunc;
2671
2672
	// Verify if the user has sufficient permissions.
2673
	isAllowedTo('manage_permissions');
2674
2675
	loadLanguage('ManagePermissions');
2676
	loadLanguage('Admin');
2677
	loadTemplate('ManageMembers');
2678
2679
	// Load all the permission profiles.
2680
	require_once($sourcedir . '/ManagePermissions.php');
2681
	loadPermissionProfiles();
2682
2683
	$context['member']['id'] = $memID;
2684
	$context['member']['name'] = $user_profile[$memID]['real_name'];
2685
2686
	$context['page_title'] = $txt['showPermissions'];
2687
	$board = empty($board) ? 0 : (int) $board;
2688
	$context['board'] = $board;
2689
2690
	// Determine which groups this user is in.
2691
	if (empty($user_profile[$memID]['additional_groups']))
2692
		$curGroups = array();
2693
	else
2694
		$curGroups = explode(',', $user_profile[$memID]['additional_groups']);
2695
	$curGroups[] = $user_profile[$memID]['id_group'];
2696
	$curGroups[] = $user_profile[$memID]['id_post_group'];
2697
2698
	// Load a list of boards for the jump box - except the defaults.
2699
	$request = $smcFunc['db_query']('order_by_board_order', '
2700
		SELECT b.id_board, b.name, b.id_profile, b.member_groups, COALESCE(mods.id_member, modgs.id_group, 0) AS is_mod
2701
		FROM {db_prefix}boards AS b
2702
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
2703
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:current_groups}))
2704
		WHERE {query_see_board}',
2705
		array(
2706
			'current_member' => $memID,
2707
			'current_groups' => $curGroups,
2708
		)
2709
	);
2710
	$context['boards'] = array();
2711
	$context['no_access_boards'] = array();
2712
	while ($row = $smcFunc['db_fetch_assoc']($request))
2713
	{
2714
		if (count(array_intersect($curGroups, explode(',', $row['member_groups']))) === 0 && !$row['is_mod'])
2715
			$context['no_access_boards'][] = array(
2716
				'id' => $row['id_board'],
2717
				'name' => $row['name'],
2718
				'is_last' => false,
2719
			);
2720
		elseif ($row['id_profile'] != 1 || $row['is_mod'])
2721
			$context['boards'][$row['id_board']] = array(
2722
				'id' => $row['id_board'],
2723
				'name' => $row['name'],
2724
				'selected' => $board == $row['id_board'],
2725
				'profile' => $row['id_profile'],
2726
				'profile_name' => $context['profiles'][$row['id_profile']]['name'],
2727
			);
2728
	}
2729
	$smcFunc['db_free_result']($request);
2730
2731
	require_once($sourcedir . '/Subs-Boards.php');
2732
	sortBoards($context['boards']);
2733
2734 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...
2735
		$context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true;
2736
2737
	$context['member']['permissions'] = array(
2738
		'general' => array(),
2739
		'board' => array()
2740
	);
2741
2742
	// If you're an admin we know you can do everything, we might as well leave.
2743
	$context['member']['has_all_permissions'] = in_array(1, $curGroups);
2744
	if ($context['member']['has_all_permissions'])
2745
		return;
2746
2747
	$denied = array();
2748
2749
	// Get all general permissions.
2750
	$result = $smcFunc['db_query']('', '
2751
		SELECT p.permission, p.add_deny, mg.group_name, p.id_group
2752
		FROM {db_prefix}permissions AS p
2753
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = p.id_group)
2754
		WHERE p.id_group IN ({array_int:group_list})
2755
		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',
2756
		array(
2757
			'group_list' => $curGroups,
2758
			'newbie_group' => 4,
2759
		)
2760
	);
2761
	while ($row = $smcFunc['db_fetch_assoc']($result))
2762
	{
2763
		// We don't know about this permission, it doesn't exist :P.
2764
		if (!isset($txt['permissionname_' . $row['permission']]))
2765
			continue;
2766
2767
		if (empty($row['add_deny']))
2768
			$denied[] = $row['permission'];
2769
2770
		// Permissions that end with _own or _any consist of two parts.
2771 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...
2772
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
2773
		else
2774
			$name = $txt['permissionname_' . $row['permission']];
2775
2776
		// Add this permission if it doesn't exist yet.
2777 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...
2778
			$context['member']['permissions']['general'][$row['permission']] = array(
2779
				'id' => $row['permission'],
2780
				'groups' => array(
2781
					'allowed' => array(),
2782
					'denied' => array()
2783
				),
2784
				'name' => $name,
2785
				'is_denied' => false,
2786
				'is_global' => true,
2787
			);
2788
2789
		// Add the membergroup to either the denied or the allowed groups.
2790
		$context['member']['permissions']['general'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name'];
2791
2792
		// Once denied is always denied.
2793
		$context['member']['permissions']['general'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
2794
	}
2795
	$smcFunc['db_free_result']($result);
2796
2797
	$request = $smcFunc['db_query']('', '
2798
		SELECT
2799
			bp.add_deny, bp.permission, bp.id_group, mg.group_name' . (empty($board) ? '' : ',
2800
			b.id_profile, CASE WHEN (mods.id_member IS NULL AND modgs.id_group IS NULL) THEN 0 ELSE 1 END AS is_moderator') . '
2801
		FROM {db_prefix}board_permissions AS bp' . (empty($board) ? '' : '
2802
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = {int:current_board})
2803
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
2804
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list}))') . '
2805
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = bp.id_group)
2806
		WHERE bp.id_profile = {raw:current_profile}
2807
			AND bp.id_group IN ({array_int:group_list}' . (empty($board) ? ')' : ', {int:moderator_group})
2808
			AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group})'),
2809
		array(
2810
			'current_board' => $board,
2811
			'group_list' => $curGroups,
2812
			'current_member' => $memID,
2813
			'current_profile' => empty($board) ? '1' : 'b.id_profile',
2814
			'moderator_group' => 3,
2815
		)
2816
	);
2817
2818
	while ($row = $smcFunc['db_fetch_assoc']($request))
2819
	{
2820
		// We don't know about this permission, it doesn't exist :P.
2821
		if (!isset($txt['permissionname_' . $row['permission']]))
2822
			continue;
2823
2824
		// The name of the permission using the format 'permission name' - 'own/any topic/event/etc.'.
2825 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...
2826
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
2827
		else
2828
			$name = $txt['permissionname_' . $row['permission']];
2829
2830
		// Create the structure for this permission.
2831 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...
2832
			$context['member']['permissions']['board'][$row['permission']] = array(
2833
				'id' => $row['permission'],
2834
				'groups' => array(
2835
					'allowed' => array(),
2836
					'denied' => array()
2837
				),
2838
				'name' => $name,
2839
				'is_denied' => false,
2840
				'is_global' => empty($board),
2841
			);
2842
2843
		$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'];
2844
2845
		$context['member']['permissions']['board'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
2846
	}
2847
	$smcFunc['db_free_result']($request);
2848
}
2849
2850
/**
2851
 * View a member's warnings
2852
 *
2853
 * @param int $memID The ID of the member
2854
 */
2855
function viewWarning($memID)
2856
{
2857
	global $modSettings, $context, $sourcedir, $txt, $scripturl;
2858
2859
	// Firstly, can we actually even be here?
2860
	if (!($context['user']['is_owner'] && allowedTo('view_warning_own')) && !allowedTo('view_warning_any') && !allowedTo('issue_warning') && !allowedTo('moderate_forum'))
2861
		fatal_lang_error('no_access', false);
2862
2863
	// Make sure things which are disabled stay disabled.
2864
	$modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110;
2865
	$modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110;
2866
	$modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110;
2867
2868
	// Let's use a generic list to get all the current warnings, and use the issue warnings grab-a-granny thing.
2869
	require_once($sourcedir . '/Subs-List.php');
2870
	require_once($sourcedir . '/Profile-Actions.php');
2871
2872
	$listOptions = array(
2873
		'id' => 'view_warnings',
2874
		'title' => $txt['profile_viewwarning_previous_warnings'],
2875
		'items_per_page' => $modSettings['defaultMaxListItems'],
2876
		'no_items_label' => $txt['profile_viewwarning_no_warnings'],
2877
		'base_href' => $scripturl . '?action=profile;area=viewwarning;sa=user;u=' . $memID,
2878
		'default_sort_col' => 'log_time',
2879
		'get_items' => array(
2880
			'function' => 'list_getUserWarnings',
2881
			'params' => array(
2882
				$memID,
2883
			),
2884
		),
2885
		'get_count' => array(
2886
			'function' => 'list_getUserWarningCount',
2887
			'params' => array(
2888
				$memID,
2889
			),
2890
		),
2891
		'columns' => array(
2892
			'log_time' => array(
2893
				'header' => array(
2894
					'value' => $txt['profile_warning_previous_time'],
2895
				),
2896
				'data' => array(
2897
					'db' => 'time',
2898
				),
2899
				'sort' => array(
2900
					'default' => 'lc.log_time DESC',
2901
					'reverse' => 'lc.log_time',
2902
				),
2903
			),
2904
			'reason' => array(
2905
				'header' => array(
2906
					'value' => $txt['profile_warning_previous_reason'],
2907
					'style' => 'width: 50%;',
2908
				),
2909
				'data' => array(
2910
					'db' => 'reason',
2911
				),
2912
			),
2913
			'level' => array(
2914
				'header' => array(
2915
					'value' => $txt['profile_warning_previous_level'],
2916
				),
2917
				'data' => array(
2918
					'db' => 'counter',
2919
				),
2920
				'sort' => array(
2921
					'default' => 'lc.counter DESC',
2922
					'reverse' => 'lc.counter',
2923
				),
2924
			),
2925
		),
2926
		'additional_rows' => array(
2927
			array(
2928
				'position' => 'after_title',
2929
				'value' => $txt['profile_viewwarning_desc'],
2930
				'class' => 'smalltext',
2931
				'style' => 'padding: 2ex;',
2932
			),
2933
		),
2934
	);
2935
2936
	// Create the list for viewing.
2937
	require_once($sourcedir . '/Subs-List.php');
2938
	createList($listOptions);
2939
2940
	// Create some common text bits for the template.
2941
	$context['level_effects'] = array(
2942
		0 => '',
2943
		$modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'],
2944
		$modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'],
2945
		$modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'],
2946
	);
2947
	$context['current_level'] = 0;
2948 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...
2949
		if ($context['member']['warning'] >= $limit)
2950
			$context['current_level'] = $limit;
2951
}
2952
2953
?>
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...