list_getUserErrorCount()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
c 0
b 0
f 0
nop 2
dl 0
loc 14
rs 10
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines http://www.simplemachines.org
8
 * @copyright 2019 Simple Machines and individual contributors
9
 * @license http://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC2
12
 */
13
14
if (!defined('SMF'))
15
	die('No direct access...');
16
17
/**
18
 * View a summary.
19
 *
20
 * @param int $memID The ID of the member
21
 */
22
function summary($memID)
23
{
24
	global $context, $memberContext, $txt, $modSettings, $user_profile, $sourcedir, $scripturl, $smcFunc;
25
26
	// Attempt to load the member's profile data.
27
	if (!loadMemberContext($memID) || !isset($memberContext[$memID]))
28
		fatal_lang_error('not_a_user', false, 404);
0 ignored issues
show
Bug introduced by
404 of type integer is incompatible with the type array expected by parameter $sprintf of fatal_lang_error(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

28
		fatal_lang_error('not_a_user', false, /** @scrutinizer ignore-type */ 404);
Loading history...
29
30
	// Set up the stuff and load the user.
31
	$context += array(
32
		'page_title' => sprintf($txt['profile_of_username'], $memberContext[$memID]['name']),
33
		'can_send_pm' => allowedTo('pm_send'),
34
		'can_have_buddy' => allowedTo('profile_extra_own') && !empty($modSettings['enable_buddylist']),
35
		'can_issue_warning' => allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1,
36
		'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)
37
	);
38
	$context['member'] = &$memberContext[$memID];
39
40
	// Set a canonical URL for this page.
41
	$context['canonical_url'] = $scripturl . '?action=profile;u=' . $memID;
42
43
	// Are there things we don't show?
44
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
45
	// Menu tab
46
	$context[$context['profile_menu_name']]['tab_data'] = array(
47
		'title' => $txt['summary'],
48
		'icon' => 'profile_hd.png'
49
	);
50
51
	// See if they have broken any warning levels...
52
	list ($modSettings['warning_enable'], $modSettings['user_limit']) = explode(',', $modSettings['warning_settings']);
53
	if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $context['member']['warning'])
54
		$context['warning_status'] = $txt['profile_warning_is_muted'];
55
	elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $context['member']['warning'])
56
		$context['warning_status'] = $txt['profile_warning_is_moderation'];
57
	elseif (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $context['member']['warning'])
58
		$context['warning_status'] = $txt['profile_warning_is_watch'];
59
60
	// They haven't even been registered for a full day!?
61
	$days_registered = (int) ((time() - $user_profile[$memID]['date_registered']) / (3600 * 24));
62
	if (empty($user_profile[$memID]['date_registered']) || $days_registered < 1)
63
		$context['member']['posts_per_day'] = $txt['not_applicable'];
64
	else
65
		$context['member']['posts_per_day'] = comma_format($context['member']['real_posts'] / $days_registered, 3);
66
67
	// Set the age...
68
	if (empty($context['member']['birth_date']) || substr($context['member']['birth_date'], 0, 4) < 1002)
69
	{
70
		$context['member'] += array(
71
			'age' => $txt['not_applicable'],
72
			'today_is_birthday' => false
73
		);
74
	}
75
	else
76
	{
77
		list ($birth_year, $birth_month, $birth_day) = sscanf($context['member']['birth_date'], '%d-%d-%d');
78
		$datearray = getdate(forum_time());
79
		$context['member'] += array(
80
			'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),
81
			'today_is_birthday' => $datearray['mon'] == $birth_month && $datearray['mday'] == $birth_day
82
		);
83
	}
84
85
	if (allowedTo('moderate_forum'))
86
	{
87
		// Make sure it's a valid ip address; otherwise, don't bother...
88
		if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $memberContext[$memID]['ip']) == 1 && empty($modSettings['disableHostnameLookup']))
89
			$context['member']['hostname'] = host_from_ip($memberContext[$memID]['ip']);
90
		else
91
			$context['member']['hostname'] = '';
92
93
		$context['can_see_ip'] = true;
94
	}
95
	else
96
		$context['can_see_ip'] = false;
97
98
	// Are they hidden?
99
	$context['member']['is_hidden'] = empty($user_profile[$memID]['show_online']);
100
	$context['member']['show_last_login'] = allowedTo('admin_forum') || !$context['member']['is_hidden'];
101
102
	if (!empty($modSettings['who_enabled']) && $context['member']['show_last_login'])
103
	{
104
		include_once($sourcedir . '/Who.php');
105
		$action = determineActions($user_profile[$memID]['url']);
106
107
		if ($action !== false)
0 ignored issues
show
introduced by
The condition $action !== false is always true.
Loading history...
108
			$context['member']['action'] = $action;
109
	}
110
111
	// If the user is awaiting activation, and the viewer has permission - setup some activation context messages.
112
	if ($context['member']['is_activated'] % 10 != 1 && allowedTo('moderate_forum'))
113
	{
114
		$context['activate_type'] = $context['member']['is_activated'];
115
		// What should the link text be?
116
		$context['activate_link_text'] = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15)) ? $txt['account_approve'] : $txt['account_activate'];
117
118
		// Should we show a custom message?
119
		$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'];
120
121
		// If they can be approved, we need to set up a token for them.
122
		$context['token_check'] = 'profile-aa' . $memID;
123
		createToken($context['token_check'], 'get');
124
125
		// Puerile comment
126
		$context['activate_link'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve;' . $context['session_var'] . '=' . $context['session_id'] . ';' . $context[$context['token_check'] . '_token_var'] . '=' . $context[$context['token_check'] . '_token'];
127
	}
128
129
	// Is the signature even enabled on this forum?
130
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
131
132
	// Prevent signature images from going outside the box.
133
	if ($context['signature_enabled'])
134
	{
135
		list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
136
		$sig_limits = explode(',', $sig_limits);
137
138
		if (!empty($sig_limits[5]) || !empty($sig_limits[6]))
139
			addInlineCss('
140
	.signature img { ' . (!empty($sig_limits[5]) ? 'max-width: ' . (int) $sig_limits[5] . 'px; ' : '') . (!empty($sig_limits[6]) ? 'max-height: ' . (int) $sig_limits[6] . 'px; ' : '') . '}');
141
	}
142
143
	// How about, are they banned?
144
	$context['member']['bans'] = array();
145
	if (allowedTo('moderate_forum'))
146
	{
147
		// Can they edit the ban?
148
		$context['can_edit_ban'] = allowedTo('manage_bans');
149
150
		$ban_query = array();
151
		$ban_query_vars = array(
152
			'time' => time(),
153
		);
154
		$ban_query[] = 'id_member = ' . $context['member']['id'];
155
		$ban_query[] = ' {inet:ip} BETWEEN bi.ip_low and bi.ip_high';
156
		$ban_query_vars['ip'] = $memberContext[$memID]['ip'];
157
		// Do we have a hostname already?
158
		if (!empty($context['member']['hostname']))
159
		{
160
			$ban_query[] = '({string:hostname} LIKE hostname)';
161
			$ban_query_vars['hostname'] = $context['member']['hostname'];
162
		}
163
		// Check their email as well...
164
		if (strlen($context['member']['email']) != 0)
165
		{
166
			$ban_query[] = '({string:email} LIKE bi.email_address)';
167
			$ban_query_vars['email'] = $context['member']['email'];
168
		}
169
170
		// So... are they banned?  Dying to know!
171
		$request = $smcFunc['db_query']('', '
172
			SELECT bg.id_ban_group, bg.name, bg.cannot_access, bg.cannot_post,
173
				bg.cannot_login, bg.reason
174
			FROM {db_prefix}ban_items AS bi
175
				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}))
176
			WHERE (' . implode(' OR ', $ban_query) . ')',
177
			$ban_query_vars
178
		);
179
		while ($row = $smcFunc['db_fetch_assoc']($request))
180
		{
181
			// Work out what restrictions we actually have.
182
			$ban_restrictions = array();
183
			foreach (array('access', 'login', 'post') as $type)
184
				if ($row['cannot_' . $type])
185
					$ban_restrictions[] = $txt['ban_type_' . $type];
186
187
			// No actual ban in place?
188
			if (empty($ban_restrictions))
189
				continue;
190
191
			// Prepare the link for context.
192
			$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>');
193
194
			$context['member']['bans'][$row['id_ban_group']] = array(
195
				'reason' => empty($row['reason']) ? '' : '<br><br><strong>' . $txt['ban_reason'] . ':</strong> ' . $row['reason'],
196
				'cannot' => array(
197
					'access' => !empty($row['cannot_access']),
198
					'post' => !empty($row['cannot_post']),
199
					'login' => !empty($row['cannot_login']),
200
				),
201
				'explanation' => $ban_explanation,
202
			);
203
		}
204
		$smcFunc['db_free_result']($request);
205
	}
206
	loadCustomFields($memID);
207
208
	$context['print_custom_fields'] = array();
209
210
	// Any custom profile fields?
211
	if (!empty($context['custom_fields']))
212
		foreach ($context['custom_fields'] as $custom)
213
			$context['print_custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
214
215
}
216
217
/**
218
 * Fetch the alerts a member currently has.
219
 *
220
 * @param int $memID The ID of the member.
221
 * @param mixed $to_fetch Alerts to fetch: true/false for all/unread, or a list of one or more IDs.
222
 * @param array $limit Maximum number of alerts to fetch (0 for no limit).
223
 * @param array $offset Number of alerts to skip for pagination. Ignored if $to_fetch is a list of IDs.
224
 * @param bool $with_avatar Whether to load the avatar of the alert sender.
225
 * @param bool $show_links Whether to show links in the constituent parts of the alert meessage.
226
 * @return array An array of information about the fetched alerts.
227
 */
228
function fetch_alerts($memID, $to_fetch = false, $limit = 0, $offset = 0, $with_avatar = false, $show_links = false)
229
{
230
	global $smcFunc, $txt, $scripturl, $user_info, $user_profile, $modSettings, $context;
231
232
	// Are we being asked for some specific alerts?
233
	$alertIDs = is_bool($to_fetch) ? array() : array_filter(array_map('intval', (array) $to_fetch));
234
235
	// Basic sanitation.
236
	$memID = (int) $memID;
237
	$unread = $to_fetch === false;
238
	$limit = max(0, (int) $limit);
239
	$offset = !empty($alertIDs) ? 0 : max(0, (int) $offset);
240
	$with_avatar = !empty($with_avatar);
241
	$show_links = !empty($show_links);
242
243
	// Arrays we'll need.
244
	$alerts = array();
245
	$senders = array();
246
	$profiles = array();
247
	$profile_alerts = array();
248
	$possible_msgs = array();
249
	$possible_topics = array();
250
251
	// Get the basic alert info.
252
	$request = $smcFunc['db_query']('', '
253
		SELECT a.id_alert, a.alert_time, a.is_read, a.extra,
254
			a.content_type, a.content_id, a.content_action,
255
			mem.id_member AS sender_id, COALESCE(mem.real_name, a.member_name) AS sender_name' . ($with_avatar ? ',
256
			mem.email_address AS sender_email, mem.avatar AS sender_avatar, f.filename AS sender_filename' : '') . '
257
		FROM {db_prefix}user_alerts AS a
258
			LEFT JOIN {db_prefix}members AS mem ON (a.id_member_started = mem.id_member)' . ($with_avatar ? '
259
			LEFT JOIN {db_prefix}attachments AS f ON (mem.id_member = f.id_member)' : '') . '
260
		WHERE a.id_member = {int:id_member}' . ($unread ? '
261
			AND a.is_read = 0' : '') . (!empty($alertIDs) ? '
262
			AND a.id_alert IN ({array_int:alertIDs})' : '') . '
263
		ORDER BY id_alert DESC' . (!empty($limit) ? '
264
		LIMIT {int:limit}' : '') . (!empty($offset) ?'
265
		OFFSET {int:offset}' : ''),
266
		array(
267
			'id_member' => $memID,
268
			'alertIDs' => $alertIDs,
269
			'limit' => $limit,
270
			'offset' => $offset,
271
		)
272
	);
273
	while ($row = $smcFunc['db_fetch_assoc']($request))
274
	{
275
		$id_alert = array_shift($row);
276
		$row['time'] = timeformat($row['alert_time']);
277
		$row['extra'] = !empty($row['extra']) ? $smcFunc['json_decode']($row['extra'], true) : array();
278
		$alerts[$id_alert] = $row;
279
280
		if (!empty($row['sender_email']))
281
		{
282
			$senders[$row['sender_id']] = array(
283
				'email' => $row['sender_email'],
284
				'avatar' => $row['sender_avatar'],
285
				'filename' => $row['sender_filename'],
286
			);
287
		}
288
289
		if ($row['content_type'] == 'profile')
290
		{
291
			$profiles[] = $row['content_id'];
292
			$profile_alerts[] = $id_alert;
293
		}
294
295
		// For these types, we need to check whether they can actually see the content.
296
		if ($row['content_type'] == 'msg')
297
		{
298
			$alerts[$id_alert]['visible'] = false;
299
			$possible_msgs[$id_alert] = $row['content_id'];
300
		}
301
		elseif (in_array($row['content_type'], array('topic', 'board')))
302
		{
303
			$alerts[$id_alert]['visible'] = false;
304
			$possible_topics[$id_alert] = $row['content_id'];
305
		}
306
		// For the rest, they can always see it.
307
		else
308
			$alerts[$id_alert]['visible'] = true;
309
310
		// Are we showing multiple links or one big main link ?
311
		$alerts[$id_alert]['show_links'] = $show_links || (isset($row['extra']['show_links']) && $row['extra']['show_links']);
312
313
		// Set an appropriate icon.
314
		$alerts[$id_alert]['icon'] = set_alert_icon($alerts[$id_alert]);
315
	}
316
	$smcFunc['db_free_result']($request);
317
318
	// Look up member info of anyone we need it for.
319
	if (!empty($profiles))
320
		loadMemberData($profiles, 'minimal');
0 ignored issues
show
Bug introduced by
'minimal' of type string is incompatible with the type boolean expected by parameter $is_name of loadMemberData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

320
		loadMemberData($profiles, /** @scrutinizer ignore-type */ 'minimal');
Loading history...
321
322
	// Get the senders' avatars.
323
	if ($with_avatar)
324
	{
325
		foreach ($senders as $sender_id => $sender)
326
			$senders[$sender_id]['avatar'] = set_avatar_data($sender);
327
328
		$context['avatar_url'] = $modSettings['avatar_url'];
329
	}
330
331
	// Now go through and actually make with the text.
332
	loadLanguage('Alerts');
333
334
	// Some sprintf formats for generating links/strings.
335
	// 'required' is an array of keys in $alert['extra'] that should be used to generate the message, ordered to match the sprintf formats.
336
	// 'link' and 'text' are the sprintf formats that will be used when $alert['show_links'] is true or false, respectively.
337
	$formats['msg_msg'] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$formats was never initialized. Although not strictly required by PHP, it is generally a good practice to add $formats = array(); before regardless.
Loading history...
338
		'required' => array('content_subject', 'topic', 'msg'),
339
		'link' => '<a href="{scripturl}?topic=%2$d.msg%3$d#msg%3$d">%1$s</a>',
340
		'text' => '<strong>%1$s</strong>',
341
	);
342
	$formats['topic_msg'] = array(
343
		'required' => array('content_subject', 'topic', 'topic_suffix'),
344
		'link' => '<a href="{scripturl}?topic=%2$d.%3$s">%1$s</a>',
345
		'text' => '<strong>%1$s</strong>',
346
	);
347
	$formats['board_msg'] = array(
348
		'required' => array('board_name', 'board'),
349
		'link' => '<a href="{scripturl}?board=%2$d.0">%1$s</a>',
350
		'text' => '<strong>%1$s</strong>',
351
	);
352
	$formats['profile_msg'] = array(
353
		'required' => array('user_name', 'user_id'),
354
		'link' => '<a href="{scripturl}?action=profile;u=%2$d">%1$s</a>',
355
		'text' => '<strong>%1$s</strong>',
356
	);
357
358
	// Hooks might want to do something snazzy around their own content types - including enforcing permissions if appropriate.
359
	call_integration_hook('integrate_fetch_alerts', array(&$alerts, &$formats));
360
361
	// Substitute $scripturl into the link formats. (Done here to make life easier for hooked mods.)
362
	foreach ($formats as &$format_type)
363
		$format_type = str_replace('{scripturl}', $scripturl, $format_type);
364
365
	// If we need to check board access, use the correct board access filter for the member in question.
366
	if ((!isset($user_info['query_see_board']) || $user_info['id'] != $memID) && (!empty($possible_msgs) || !empty($possible_topics)))
367
		$qb = build_query_board($memID);
368
	else
369
		$qb['query_see_board'] = '{query_see_board}';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$qb was never initialized. Although not strictly required by PHP, it is generally a good practice to add $qb = array(); before regardless.
Loading history...
370
371
	// For anything that needs more info and/or wants us to check board or topic access, let's do that.
372
	if (!empty($possible_msgs))
373
	{
374
		$flipped_msgs = array();
375
		foreach ($possible_msgs as $id_alert => $id_msg)
376
		{
377
			if (!isset($flipped_msgs[$id_msg]))
378
				$flipped_msgs[$id_msg] = array();
379
380
			$flipped_msgs[$id_msg][] = $id_alert;
381
		}
382
383
		$request = $smcFunc['db_query']('', '
384
			SELECT m.id_msg, m.id_topic, m.subject, b.id_board, b.name AS board_name
385
			FROM {db_prefix}messages AS m
386
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
387
			WHERE ' . $qb['query_see_board'] . '
388
				AND m.id_msg IN ({array_int:msgs})
389
			ORDER BY m.id_msg',
390
			array(
391
				'msgs' => $possible_msgs,
392
			)
393
		);
394
		while ($row = $smcFunc['db_fetch_assoc']($request))
395
		{
396
			foreach ($flipped_msgs[$row['id_msg']] as $id_alert)
397
			{
398
				$alerts[$id_alert]['content_data'] = $row;
399
				$alerts[$id_alert]['visible'] = true;
400
			}
401
		}
402
		$smcFunc['db_free_result']($request);
403
	}
404
	if (!empty($possible_topics))
405
	{
406
		$flipped_topics = array();
407
		foreach ($possible_topics as $id_alert => $id_topic)
408
		{
409
			if (!isset($flipped_topics[$id_topic]))
410
				$flipped_topics[$id_topic] = array();
411
412
			$flipped_topics[$id_topic][] = $id_alert;
413
		}
414
415
		$request = $smcFunc['db_query']('', '
416
			SELECT m.id_msg, t.id_topic, m.subject, b.id_board, b.name AS board_name
417
			FROM {db_prefix}topics AS t
418
				INNER JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
419
				INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
420
			WHERE ' . $qb['query_see_board'] . '
421
				AND t.id_topic IN ({array_int:topics})',
422
			array(
423
				'topics' => $possible_topics,
424
			)
425
		);
426
		while ($row = $smcFunc['db_fetch_assoc']($request))
427
		{
428
			foreach ($flipped_topics[$row['id_topic']] as $id_alert)
429
			{
430
				$alerts[$id_alert]['content_data'] = $row;
431
				$alerts[$id_alert]['visible'] = true;
432
			}
433
		}
434
		$smcFunc['db_free_result']($request);
435
	}
436
437
	// 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)
438
	foreach ($alerts as $id_alert => $dummy)
439
	{
440
		// There's no point showing alerts for inaccessible content.
441
		if (!$alerts[$id_alert]['visible'])
442
		{
443
			unset($alerts[$id_alert]);
444
			continue;
445
		}
446
		else
447
			unset($alerts[$id_alert]['visible']);
448
449
		// Did a mod already take care of this one?
450
		if (!empty($alerts[$id_alert]['text']))
451
			continue;
452
453
		// For developer convenience.
454
		$alert = &$alerts[$id_alert];
455
456
		// The info in extra might outdated if the topic was moved, the message's subject was changed, etc.
457
		if (!empty($alert['content_data']))
458
		{
459
			$data = $alert['content_data'];
460
461
			// Make sure msg, topic, and board info are correct.
462
			$patterns = array();
463
			$replacements = array();
464
			foreach (array('msg', 'topic', 'board') as $item)
465
			{
466
				if (isset($data['id_' . $item]))
467
				{
468
					$separator = $item == 'msg' ? '=?' : '=';
469
470
					if (isset($alert['extra']['content_link']) && strpos($alert['extra']['content_link'], $item . $separator) !== false && strpos($alert['extra']['content_link'], $item . $separator . $data['id_' . $item]) === false)
471
					{
472
						$patterns[] = '/\b' . $item . $separator . '\d+/';
473
						$replacements[] = $item . $separator . $data['id_' . $item];
474
					}
475
476
					$alert['extra'][$item] = $data['id_' . $item];
477
				}
478
			}
479
			if (!empty($patterns))
480
				$alert['extra']['content_link'] = preg_replace($patterns, $replacements, $alert['extra']['content_link']);
481
482
			// Make sure the subject is correct.
483
			if (isset($data['subject']))
484
				$alert['extra']['content_subject'] = $data['subject'];
485
486
			// Keep track of this so we can use it below.
487
			if (isset($data['board_name']))
488
				$alert['extra']['board_name'] = $data['board_name'];
489
490
			unset($alert['content_data']);
491
		}
492
493
		// Do we want to link to the topic in general or the new messages specifically?
494
		if (isset($possible_topics[$id_alert]))
495
			$alert['extra']['topic_suffix'] = 'new#new';
496
		elseif (isset($alert['extra']['topic']))
497
			$alert['extra']['topic_suffix'] = '0';
498
499
		// Make sure profile alerts have what they need.
500
		if (in_array($id_alert, $profile_alerts))
501
		{
502
			if (empty($alert['extra']['user_id']))
503
				$alert['extra']['user_id'] = $alert['content_id'];
504
505
			if (isset($user_profile[$alert['extra']['user_id']]))
506
				$alert['extra']['user_name'] = $user_profile[$alert['extra']['user_id']]['real_name'];
507
		}
508
509
		// If we loaded the sender's profile, we may as well use it.
510
		$sender_id = !empty($alert['sender_id']) ? $alert['sender_id'] : 0;
511
		if (isset($user_profile[$sender_id]))
512
			$alert['sender_name'] = $user_profile[$sender_id]['real_name'];
513
514
		// If requested, include the sender's avatar data.
515
		if ($with_avatar && !empty($senders[$sender_id]))
516
			$alert['sender'] = $senders[$sender_id];
517
518
		// Next, build the message strings.
519
		foreach ($formats as $msg_type => $format_info)
520
		{
521
			// Get the values to use in the formatted string, in the right order.
522
			$msg_values = array_replace(
523
				array_fill_keys($format_info['required'], ''),
524
				array_intersect_key($alert['extra'], array_flip($format_info['required']))
525
			);
526
527
			// Assuming all required values are present, build the message.
528
			if (!in_array('', $msg_values))
529
				$alert['extra'][$msg_type] = vsprintf($formats[$msg_type][$alert['show_links'] ? 'link' : 'text'], $msg_values);
530
531
			elseif (in_array($msg_type, array('msg_msg', 'topic_msg', 'board_msg')))
532
				$alert['extra'][$msg_type] = $txt[$msg_type == 'board_msg' ? 'board_na' : 'topic_na'];
533
			else
534
				$alert['extra'][$msg_type] = '(' . $txt['not_applicable'] . ')';
535
		}
536
537
		// Show the formatted time in alerts about subscriptions.
538
		if ($alert['content_type'] == 'paidsubs' && isset($alert['extra']['end_time']))
539
		{
540
			// If the subscription already expired, say so.
541
			if ($alert['extra']['end_time'] < time())
542
				$alert['content_action'] = 'expired';
543
544
			// Present a nicely formatted date.
545
			$alert['extra']['end_time'] = timeformat($alert['extra']['end_time']);
546
		}
547
548
		// Finally, set this alert's text string.
549
		$string = 'alert_' . $alert['content_type'] . '_' . $alert['content_action'];
550
551
		// This kludge exists because the alert content_types prior to 2.1 RC3 were a bit haphazard.
552
		// This can be removed once all the translated language files have been updated.
553
		if (!isset($txt[$string]))
554
		{
555
			if (strpos($alert['content_action'], 'unapproved_') === 0)
556
				$string = 'alert_' . $alert['content_action'];
557
558
			if ($alert['content_type'] === 'member' && in_array($alert['content_action'], array('report', 'report_reply')))
559
				$string = 'alert_profile_' . $alert['content_action'];
560
561
			if ($alert['content_type'] === 'member' && $alert['content_action'] === 'buddy_request')
562
				$string = 'alert_buddy_' . $alert['content_action'];
563
		}
564
565
		if (isset($txt[$string]))
566
		{
567
			$substitutions = array(
568
				'{scripturl}' => $scripturl,
569
				'{member_link}' => !empty($sender_id) && $alert['show_links'] ? '<a href="' . $scripturl . '?action=profile;u=' . $sender_id . '">' . $alert['sender_name'] . '</a>' : '<strong>' . $alert['sender_name'] . '</strong>',
570
			);
571
572
			if (is_array($alert['extra']))
573
			{
574
				foreach ($alert['extra'] as $k => $v)
575
					$substitutions['{' . $k . '}'] = $v;
576
			}
577
578
			$alert['text'] = strtr($txt[$string], $substitutions);
579
		}
580
581
		// Unset the reference variable to avoid any surprises in subsequent loops.
582
		unset($alert);
583
	}
584
585
	return $alerts;
586
}
587
588
/**
589
 * Shows all alerts for a member
590
 *
591
 * @param int $memID The ID of the member
592
 */
593
function showAlerts($memID)
594
{
595
	global $context, $smcFunc, $txt, $sourcedir, $scripturl, $options;
596
597
	require_once($sourcedir . '/Profile-Modify.php');
598
599
	// Are we opening a specific alert ?
600
	if (!empty($_REQUEST['alert']))
601
	{
602
		$alert_id = (int) $_REQUEST['alert'];
603
		$alerts = fetch_alerts($memID, $alert_id);
604
		$alert = array_pop($alerts);
605
606
		if (empty($alert))
607
			redirectexit('action=profile;area=showalerts');
608
609
		// Determine where the alert takes
610
		$link = '';
611
		// Priority goes to explicitly specified links
612
		if (isset($alert['extra']['content_link']))
613
			$link = $alert['extra']['content_link'];
614
		elseif (isset($alert['extra']['report_link']))
615
			$link = $scripturl . $alert['extra']['report_link'];
616
		elseif (isset($alert['content_action']) && $alert['content_action'] === 'register_approval')
617
			$link = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve';
618
		elseif (isset($alert['content_action']) && $alert['content_action'] === 'group_request')
619
			$link = $scripturl . '?action=moderate;area=groups;sa=requests';
620
		elseif (isset($alert['content_action'], $alert['id_member_started']) && ($alert['content_type'] === 'member' || $alert['content_action'] === 'buddy_request'))
621
			$link = $scripturl . '?action=profile;u=' . $alert['id_member_started'];
622
		elseif (isset($alert['content_type'], $alert['extra']['event_id']) && $alert['content_type'] === 'event')
623
			$link = $scripturl . '?action=calendar;event=' . $alert['extra']['event_id'];
624
		elseif (isset($alert['content_type'], $alert['content_id']) && $alert['content_type'] === 'msg')
625
			$link = $scripturl . '?msg=' . $alert['content_id'];
626
627
		call_integration_hook('integrate_show_alert', array(&$alert, &$link));
628
629
		// Mark the alert as read while we're at it.
630
		alert_mark($memID, $alert_id, 1);
631
632
		// Take the user to the content
633
		if (!empty($link))
634
			redirectexit($link);
635
		// In case it failed to determine this alert's link
636
		else
637
			redirectexit('action=profile;area=showalerts');
638
	}
639
640
	// Prepare the pagination vars.
641
	$maxIndex = 10;
642
	$context['start'] = (int) isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
643
	$count = alert_count($memID);
644
645
	// Fix invalid 'start' offsets.
646
	if ($context['start'] > $count)
647
		$context['start'] = $count - ($count % $maxIndex);
648
	else
649
		$context['start'] = $context['start'] - ($context['start'] % $maxIndex);
650
651
	// Get the alerts.
652
	$context['alerts'] = fetch_alerts($memID, true, $maxIndex, $context['start'], true, true);
0 ignored issues
show
Bug introduced by
$maxIndex of type integer is incompatible with the type array expected by parameter $limit of fetch_alerts(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

652
	$context['alerts'] = fetch_alerts($memID, true, /** @scrutinizer ignore-type */ $maxIndex, $context['start'], true, true);
Loading history...
653
	$toMark = false;
654
	$action = '';
655
656
	//  Are we using checkboxes?
657
	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1;
658
659
	// Create the pagination.
660
	$context['pagination'] = constructPageIndex($scripturl . '?action=profile;area=showalerts;u=' . $memID, $context['start'], $count, $maxIndex, false);
661
662
	// Set some JavaScript for checking all alerts at once.
663
	if ($context['showCheckboxes'])
664
		addInlineJavaScript('
665
		$(function(){
666
			$(\'#select_all\').on(\'change\', function() {
667
				var checkboxes = $(\'ul.quickbuttons\').find(\':checkbox\');
668
				if($(this).prop(\'checked\')) {
669
					checkboxes.prop(\'checked\', true);
670
				}
671
				else {
672
					checkboxes.prop(\'checked\', false);
673
				}
674
			});
675
		});', true);
676
677
	// The quickbuttons
678
	foreach ($context['alerts'] as $id => $alert)
679
	{
680
		$context['alerts'][$id]['quickbuttons'] = array(
681
			'delete' => array(
682
				'label' => $txt['delete'],
683
				'href' => $scripturl . '?action=profile;u=' . $context['id_member'] . ';area=showalerts;do=remove;aid=' . $id . ';' . $context['session_var'] . '=' . $context['session_id'] . (!empty($context['start']) ? ';start=' . $context['start'] : ''),
684
				'javascript' => 'class="you_sure"',
685
				'icon' => 'remove_button'
686
			),
687
			'mark' => array(
688
				'label' => $alert['is_read'] != 0 ? $txt['mark_unread'] : $txt['mark_read_short'],
689
				'href' => $scripturl . '?action=profile;u=' . $context['id_member'] . ';area=showalerts;do=' . ($alert['is_read'] != 0 ? 'unread' : 'read') . ';aid=' . $id . ';' . $context['session_var'] . '=' . $context['session_id'] . (!empty($context['start']) ? ';start=' . $context['start'] : ''),
690
				'icon' => $alert['is_read'] != 0 ? 'unread_button' : 'read_button',
691
			),
692
			'view' => array(
693
				'label' => $txt['view'],
694
				'href' => $scripturl . '?action=profile;area=showalerts;alert=' . $id . ';',
695
				'icon' => 'move',
696
			),
697
			'quickmod' => array(
698
				'content' => '<input type="checkbox" name="mark[' . $id . ']" value="' . $id . '">',
699
				'show' => $context['showCheckboxes']
700
			)
701
		);
702
	}
703
704
	// Set a nice message.
705
	if (!empty($_SESSION['update_message']))
706
	{
707
		$context['update_message'] = $txt['profile_updated_own'];
708
		unset($_SESSION['update_message']);
709
	}
710
711
	// Saving multiple changes?
712
	if (isset($_GET['save']) && !empty($_POST['mark']))
713
	{
714
		// Get the values.
715
		$toMark = array_map('intval', (array) $_POST['mark']);
716
717
		// Which action?
718
		$action = !empty($_POST['mark_as']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_POST['mark_as'])) : '';
719
	}
720
721
	// A single change.
722
	if (!empty($_GET['do']) && !empty($_GET['aid']))
723
	{
724
		$toMark = (int) $_GET['aid'];
725
		$action = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_GET['do']));
726
	}
727
728
	// Save the changes.
729
	if (!empty($toMark) && !empty($action))
730
	{
731
		checkSession('request');
732
733
		// Call it!
734
		if ($action == 'remove')
735
			alert_delete($toMark, $memID);
736
737
		else
738
			alert_mark($memID, $toMark, $action == 'read' ? 1 : 0);
739
740
		// Set a nice update message.
741
		$_SESSION['update_message'] = true;
742
743
		// Redirect.
744
		redirectexit('action=profile;area=showalerts;u=' . $memID . (!empty($context['start']) ? ';start=' . $context['start'] : ''));
745
	}
746
}
747
748
/**
749
 * Show all posts by a member
750
 *
751
 * @todo This function needs to be split up properly.
752
 *
753
 * @param int $memID The ID of the member
754
 */
755
function showPosts($memID)
756
{
757
	global $txt, $user_info, $scripturl, $modSettings, $options;
758
	global $context, $user_profile, $sourcedir, $smcFunc, $board;
759
760
	// Some initial context.
761
	$context['start'] = (int) $_REQUEST['start'];
762
	$context['current_member'] = $memID;
763
764
	// Create the tabs for the template.
765
	$context[$context['profile_menu_name']]['tab_data'] = array(
766
		'title' => $txt['showPosts'],
767
		'description' => $txt['showPosts_help'],
768
		'icon' => 'profile_hd.png',
769
		'tabs' => array(
770
			'messages' => array(
771
			),
772
			'topics' => array(
773
			),
774
			'unwatchedtopics' => array(
775
			),
776
			'attach' => array(
777
			),
778
		),
779
	);
780
781
	// Shortcut used to determine which $txt['show*'] string to use for the title, based on the SA
782
	$title = array(
783
		'attach' => 'Attachments',
784
		'unwatchedtopics' => 'Unwatched',
785
		'topics' => 'Topics'
786
	);
787
788
	// Set the page title
789
	if (isset($_GET['sa']) && array_key_exists($_GET['sa'], $title))
790
		$context['page_title'] = $txt['show' . $title[$_GET['sa']]];
791
	else
792
		$context['page_title'] = $txt['showPosts'];
793
794
	$context['page_title'] .= ' - ' . $user_profile[$memID]['real_name'];
795
796
	// Is the load average too high to allow searching just now?
797
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_show_posts']) && $context['load_average'] >= $modSettings['loadavg_show_posts'])
798
		fatal_lang_error('loadavg_show_posts_disabled', false);
799
800
	// If we're specifically dealing with attachments use that function!
801
	if (isset($_GET['sa']) && $_GET['sa'] == 'attach')
802
		return showAttachments($memID);
803
	// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
804
	elseif (isset($_GET['sa']) && $_GET['sa'] == 'unwatchedtopics')
805
		return showUnwatched($memID);
0 ignored issues
show
Bug introduced by
Are you sure the usage of showUnwatched($memID) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
806
807
	// Are we just viewing topics?
808
	$context['is_topics'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? true : false;
809
810
	// If just deleting a message, do it and then redirect back.
811
	if (isset($_GET['delete']) && !$context['is_topics'])
812
	{
813
		checkSession('get');
814
815
		// We need msg info for logging.
816
		$request = $smcFunc['db_query']('', '
817
			SELECT subject, id_member, id_topic, id_board
818
			FROM {db_prefix}messages
819
			WHERE id_msg = {int:id_msg}',
820
			array(
821
				'id_msg' => (int) $_GET['delete'],
822
			)
823
		);
824
		$info = $smcFunc['db_fetch_row']($request);
825
		$smcFunc['db_free_result']($request);
826
827
		// Trying to remove a message that doesn't exist.
828
		if (empty($info))
829
			redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
830
831
		// We can be lazy, since removeMessage() will check the permissions for us.
832
		require_once($sourcedir . '/RemoveTopic.php');
833
		removeMessage((int) $_GET['delete']);
834
835
		// Add it to the mod log.
836
		if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
837
			logAction('delete', array('topic' => $info[2], 'subject' => $info[0], 'member' => $info[1], 'board' => $info[3]));
838
839
		// Back to... where we are now ;).
840
		redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
841
	}
842
843
	// Default to 10.
844
	if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
845
		$_REQUEST['viewscount'] = '10';
846
847
	if ($context['is_topics'])
848
		$request = $smcFunc['db_query']('', '
849
			SELECT COUNT(*)
850
			FROM {db_prefix}topics AS t' . '
851
			WHERE {query_see_topic_board}
852
				AND t.id_member_started = {int:current_member}' . (!empty($board) ? '
853
				AND t.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
854
				AND t.approved = {int:is_approved}'),
855
			array(
856
				'current_member' => $memID,
857
				'is_approved' => 1,
858
				'board' => $board,
859
			)
860
		);
861
	else
862
		$request = $smcFunc['db_query']('', '
863
			SELECT COUNT(id_msg)
864
			FROM {db_prefix}messages AS m
865
			WHERE {query_see_message_board} AND m.id_member = {int:current_member}' . (!empty($board) ? '
866
				AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
867
				AND m.approved = {int:is_approved}'),
868
			array(
869
				'current_member' => $memID,
870
				'is_approved' => 1,
871
				'board' => $board,
872
			)
873
		);
874
	list ($msgCount) = $smcFunc['db_fetch_row']($request);
875
	$smcFunc['db_free_result']($request);
876
877
	$request = $smcFunc['db_query']('', '
878
		SELECT MIN(id_msg), MAX(id_msg)
879
		FROM {db_prefix}messages AS m
880
		WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
881
			AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
882
			AND m.approved = {int:is_approved}'),
883
		array(
884
			'current_member' => $memID,
885
			'is_approved' => 1,
886
			'board' => $board,
887
		)
888
	);
889
	list ($min_msg_member, $max_msg_member) = $smcFunc['db_fetch_row']($request);
890
	$smcFunc['db_free_result']($request);
891
892
	$range_limit = '';
893
894
	if ($context['is_topics'])
895
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page']) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'];
896
	else
897
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
898
899
	$maxIndex = $maxPerPage;
900
901
	// Make sure the starting place makes sense and construct our friend the page index.
902
	$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : '') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex);
903
	$context['current_page'] = $context['start'] / $maxIndex;
904
905
	// Reverse the query if we're past 50% of the pages for better performance.
906
	$start = $context['start'];
907
	$reverse = $_REQUEST['start'] > $msgCount / 2;
908
	if ($reverse)
909
	{
910
		$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
911
		$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
912
	}
913
914
	// Guess the range of messages to be shown.
915
	if ($msgCount > 1000)
916
	{
917
		$margin = floor(($max_msg_member - $min_msg_member) * (($start + $maxPerPage) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
918
		// Make a bigger margin for topics only.
919
		if ($context['is_topics'])
920
		{
921
			$margin *= 5;
922
			$range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin);
923
		}
924
		else
925
			$range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin);
926
	}
927
928
	// Find this user's posts.  The left join on categories somehow makes this faster, weird as it looks.
929
	$looped = false;
930
	while (true)
931
	{
932
		if ($context['is_topics'])
933
		{
934
			$request = $smcFunc['db_query']('', '
935
				SELECT
936
					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,
937
					t.approved, m.body, m.smileys_enabled, m.subject, m.poster_time, m.id_topic, m.id_msg
938
				FROM {db_prefix}topics AS t
939
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
940
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
941
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
942
				WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
943
					AND t.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
944
					AND ' . $range_limit) . '
945
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
946
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
947
				ORDER BY t.id_first_msg ' . ($reverse ? 'ASC' : 'DESC') . '
948
				LIMIT {int:start}, {int:max}',
949
				array(
950
					'current_member' => $memID,
951
					'is_approved' => 1,
952
					'board' => $board,
953
					'start' => $start,
954
					'max' => $maxIndex,
955
				)
956
			);
957
		}
958
		else
959
		{
960
			$request = $smcFunc['db_query']('', '
961
				SELECT
962
					b.id_board, b.name AS bname, c.id_cat, c.name AS cname, m.id_topic, m.id_msg,
963
					t.id_member_started, t.id_first_msg, t.id_last_msg, m.body, m.smileys_enabled,
964
					m.subject, m.poster_time, m.approved
965
				FROM {db_prefix}messages AS m
966
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
967
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
968
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
969
				WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
970
					AND b.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
971
					AND ' . $range_limit) . '
972
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
973
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
974
				ORDER BY m.id_msg ' . ($reverse ? 'ASC' : 'DESC') . '
975
				LIMIT {int:start}, {int:max}',
976
				array(
977
					'current_member' => $memID,
978
					'is_approved' => 1,
979
					'board' => $board,
980
					'start' => $start,
981
					'max' => $maxIndex,
982
				)
983
			);
984
		}
985
986
		// Make sure we quit this loop.
987
		if ($smcFunc['db_num_rows']($request) === $maxIndex || $looped || $range_limit == '')
988
			break;
989
		$looped = true;
990
		$range_limit = '';
991
	}
992
993
	// Start counting at the number of the first message displayed.
994
	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
995
	$context['posts'] = array();
996
	$board_ids = array('own' => array(), 'any' => array());
997
	while ($row = $smcFunc['db_fetch_assoc']($request))
998
	{
999
		// Censor....
1000
		censorText($row['body']);
1001
		censorText($row['subject']);
1002
1003
		// Do the code.
1004
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
1005
1006
		// And the array...
1007
		$context['posts'][$counter += $reverse ? -1 : 1] = array(
1008
			'body' => $row['body'],
1009
			'counter' => $counter,
1010
			'category' => array(
1011
				'name' => $row['cname'],
1012
				'id' => $row['id_cat']
1013
			),
1014
			'board' => array(
1015
				'name' => $row['bname'],
1016
				'id' => $row['id_board']
1017
			),
1018
			'topic' => $row['id_topic'],
1019
			'subject' => $row['subject'],
1020
			'start' => 'msg' . $row['id_msg'],
1021
			'time' => timeformat($row['poster_time']),
1022
			'timestamp' => forum_time(true, $row['poster_time']),
1023
			'id' => $row['id_msg'],
1024
			'can_reply' => false,
1025
			'can_mark_notify' => !$context['user']['is_guest'],
1026
			'can_delete' => false,
1027
			'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()),
1028
			'approved' => $row['approved'],
1029
			'css_class' => $row['approved'] ? 'windowbg' : 'approvebg',
1030
		);
1031
1032
		if ($user_info['id'] == $row['id_member_started'])
1033
			$board_ids['own'][$row['id_board']][] = $counter;
1034
		$board_ids['any'][$row['id_board']][] = $counter;
1035
	}
1036
	$smcFunc['db_free_result']($request);
1037
1038
	// All posts were retrieved in reverse order, get them right again.
1039
	if ($reverse)
1040
		$context['posts'] = array_reverse($context['posts'], true);
1041
1042
	// These are all the permissions that are different from board to board..
1043
	if ($context['is_topics'])
1044
		$permissions = array(
1045
			'own' => array(
1046
				'post_reply_own' => 'can_reply',
1047
			),
1048
			'any' => array(
1049
				'post_reply_any' => 'can_reply',
1050
			)
1051
		);
1052
	else
1053
		$permissions = array(
1054
			'own' => array(
1055
				'post_reply_own' => 'can_reply',
1056
				'delete_own' => 'can_delete',
1057
			),
1058
			'any' => array(
1059
				'post_reply_any' => 'can_reply',
1060
				'delete_any' => 'can_delete',
1061
			)
1062
		);
1063
1064
	// Create an array for the permissions.
1065
	$boards_can = boardsAllowedTo(array_keys(iterator_to_array(
1066
		new RecursiveIteratorIterator(new RecursiveArrayIterator($permissions)))
1067
	), true, false);
1068
1069
	// For every permission in the own/any lists...
1070
	foreach ($permissions as $type => $list)
1071
	{
1072
		foreach ($list as $permission => $allowed)
1073
		{
1074
			// Get the boards they can do this on...
1075
			$boards = $boards_can[$permission];
1076
1077
			// Hmm, they can do it on all boards, can they?
1078
			if (!empty($boards) && $boards[0] == 0)
1079
				$boards = array_keys($board_ids[$type]);
1080
1081
			// Now go through each board they can do the permission on.
1082
			foreach ($boards as $board_id)
1083
			{
1084
				// There aren't any posts displayed from this board.
1085
				if (!isset($board_ids[$type][$board_id]))
1086
					continue;
1087
1088
				// Set the permission to true ;).
1089
				foreach ($board_ids[$type][$board_id] as $counter)
1090
					$context['posts'][$counter][$allowed] = true;
1091
			}
1092
		}
1093
	}
1094
1095
	// Clean up after posts that cannot be deleted and quoted.
1096
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
1097
	foreach ($context['posts'] as $counter => $dummy)
1098
	{
1099
		$context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible'];
1100
		$context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled;
1101
	}
1102
1103
	// Allow last minute changes.
1104
	call_integration_hook('integrate_profile_showPosts');
1105
1106
	foreach ($context['posts'] as $key => $post)
1107
	{
1108
		$context['posts'][$key]['quickbuttons'] = array(
1109
			'reply' => array(
1110
				'label' => $txt['reply'],
1111
				'href' => $scripturl.'?action=post;topic='.$post['topic'].'.'.$post['start'],
1112
				'icon' => 'reply_button',
1113
				'show' => $post['can_reply']
1114
			),
1115
			'quote' => array(
1116
				'label' => $txt['quote_action'],
1117
				'href' => $scripturl.'?action=post;topic='.$post['topic'].'.'.$post['start'].';quote='.$post['id'],
1118
				'icon' => 'quote',
1119
				'show' => $post['can_quote']
1120
			),
1121
			'remove' => array(
1122
				'label' => $txt['remove'],
1123
				'href' => $scripturl.'?action=deletemsg;msg='.$post['id'].';topic='.$post['topic'].';profile;u='.$context['member']['id'].';start='.$context['start'].';'.$context['session_var'].'='.$context['session_id'],
1124
				'javascript' => 'data-confirm="'.$txt['remove_message'].'" class="you_sure"',
1125
				'icon' => 'remove_button',
1126
				'show' => $post['can_delete']
1127
			)
1128
		);
1129
	}
1130
}
1131
1132
/**
1133
 * Show all the attachments belonging to a member.
1134
 *
1135
 * @param int $memID The ID of the member
1136
 */
1137
function showAttachments($memID)
1138
{
1139
	global $txt, $scripturl, $modSettings;
1140
	global $sourcedir;
1141
1142
	// OBEY permissions!
1143
	$boardsAllowed = boardsAllowedTo('view_attachments');
1144
1145
	// Make sure we can't actually see anything...
1146
	if (empty($boardsAllowed))
1147
		$boardsAllowed = array(-1);
1148
1149
	require_once($sourcedir . '/Subs-List.php');
1150
1151
	// This is all the information required to list attachments.
1152
	$listOptions = array(
1153
		'id' => 'attachments',
1154
		'width' => '100%',
1155
		'items_per_page' => $modSettings['defaultMaxListItems'],
1156
		'no_items_label' => $txt['show_attachments_none'],
1157
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=attach;u=' . $memID,
1158
		'default_sort_col' => 'filename',
1159
		'get_items' => array(
1160
			'function' => 'list_getAttachments',
1161
			'params' => array(
1162
				$boardsAllowed,
1163
				$memID,
1164
			),
1165
		),
1166
		'get_count' => array(
1167
			'function' => 'list_getNumAttachments',
1168
			'params' => array(
1169
				$boardsAllowed,
1170
				$memID,
1171
			),
1172
		),
1173
		'data_check' => array(
1174
			'class' => function($data)
1175
			{
1176
				return $data['approved'] ? '' : 'approvebg';
1177
			}
1178
		),
1179
		'columns' => array(
1180
			'filename' => array(
1181
				'header' => array(
1182
					'value' => $txt['show_attach_filename'],
1183
					'class' => 'lefttext',
1184
					'style' => 'width: 25%;',
1185
				),
1186
				'data' => array(
1187
					'sprintf' => array(
1188
						'format' => '<a href="' . $scripturl . '?action=dlattach;topic=%1$d.0;attach=%2$d">%3$s</a>',
1189
						'params' => array(
1190
							'topic' => true,
1191
							'id' => true,
1192
							'filename' => false,
1193
						),
1194
					),
1195
				),
1196
				'sort' => array(
1197
					'default' => 'a.filename',
1198
					'reverse' => 'a.filename DESC',
1199
				),
1200
			),
1201
			'downloads' => array(
1202
				'header' => array(
1203
					'value' => $txt['show_attach_downloads'],
1204
					'style' => 'width: 12%;',
1205
				),
1206
				'data' => array(
1207
					'db' => 'downloads',
1208
					'comma_format' => true,
1209
				),
1210
				'sort' => array(
1211
					'default' => 'a.downloads',
1212
					'reverse' => 'a.downloads DESC',
1213
				),
1214
			),
1215
			'subject' => array(
1216
				'header' => array(
1217
					'value' => $txt['message'],
1218
					'class' => 'lefttext',
1219
					'style' => 'width: 30%;',
1220
				),
1221
				'data' => array(
1222
					'sprintf' => array(
1223
						'format' => '<a href="' . $scripturl . '?msg=%1$d">%2$s</a>',
1224
						'params' => array(
1225
							'msg' => true,
1226
							'subject' => false,
1227
						),
1228
					),
1229
				),
1230
				'sort' => array(
1231
					'default' => 'm.subject',
1232
					'reverse' => 'm.subject DESC',
1233
				),
1234
			),
1235
			'posted' => array(
1236
				'header' => array(
1237
					'value' => $txt['show_attach_posted'],
1238
					'class' => 'lefttext',
1239
				),
1240
				'data' => array(
1241
					'db' => 'posted',
1242
					'timeformat' => true,
1243
				),
1244
				'sort' => array(
1245
					'default' => 'm.poster_time',
1246
					'reverse' => 'm.poster_time DESC',
1247
				),
1248
			),
1249
		),
1250
	);
1251
1252
	// Create the request list.
1253
	createList($listOptions);
1254
}
1255
1256
/**
1257
 * Get a list of attachments for a member. Callback for the list in showAttachments()
1258
 *
1259
 * @param int $start Which item to start with (for pagination purposes)
1260
 * @param int $items_per_page How many items to show on each page
1261
 * @param string $sort A string indicating how to sort the results
1262
 * @param array $boardsAllowed An array containing the IDs of the boards they can see
1263
 * @param int $memID The ID of the member
1264
 * @return array An array of information about the attachments
1265
 */
1266
function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed, $memID)
1267
{
1268
	global $smcFunc, $board, $modSettings, $context;
1269
1270
	// Retrieve some attachments.
1271
	$request = $smcFunc['db_query']('', '
1272
		SELECT a.id_attach, a.id_msg, a.filename, a.downloads, a.approved, m.id_msg, m.id_topic,
1273
			m.id_board, m.poster_time, m.subject, b.name
1274
		FROM {db_prefix}attachments AS a
1275
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1276
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1277
		WHERE a.attachment_type = {int:attachment_type}
1278
			AND a.id_msg != {int:no_message}
1279
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
1280
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
1281
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1282
			AND m.approved = {int:is_approved}') . '
1283
		ORDER BY {raw:sort}
1284
		LIMIT {int:offset}, {int:limit}',
1285
		array(
1286
			'boards_list' => $boardsAllowed,
1287
			'attachment_type' => 0,
1288
			'no_message' => 0,
1289
			'current_member' => $memID,
1290
			'is_approved' => 1,
1291
			'board' => $board,
1292
			'sort' => $sort,
1293
			'offset' => $start,
1294
			'limit' => $items_per_page,
1295
		)
1296
	);
1297
	$attachments = array();
1298
	while ($row = $smcFunc['db_fetch_assoc']($request))
1299
		$attachments[] = array(
1300
			'id' => $row['id_attach'],
1301
			'filename' => $row['filename'],
1302
			'downloads' => $row['downloads'],
1303
			'subject' => censorText($row['subject']),
1304
			'posted' => $row['poster_time'],
1305
			'msg' => $row['id_msg'],
1306
			'topic' => $row['id_topic'],
1307
			'board' => $row['id_board'],
1308
			'board_name' => $row['name'],
1309
			'approved' => $row['approved'],
1310
		);
1311
1312
	$smcFunc['db_free_result']($request);
1313
1314
	return $attachments;
1315
}
1316
1317
/**
1318
 * Gets the total number of attachments for a member
1319
 *
1320
 * @param array $boardsAllowed An array of the IDs of the boards they can see
1321
 * @param int $memID The ID of the member
1322
 * @return int The number of attachments
1323
 */
1324
function list_getNumAttachments($boardsAllowed, $memID)
1325
{
1326
	global $board, $smcFunc, $modSettings, $context;
1327
1328
	// Get the total number of attachments they have posted.
1329
	$request = $smcFunc['db_query']('', '
1330
		SELECT COUNT(*)
1331
		FROM {db_prefix}attachments AS a
1332
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1333
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1334
		WHERE a.attachment_type = {int:attachment_type}
1335
			AND a.id_msg != {int:no_message}
1336
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
1337
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
1338
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1339
			AND m.approved = {int:is_approved}'),
1340
		array(
1341
			'boards_list' => $boardsAllowed,
1342
			'attachment_type' => 0,
1343
			'no_message' => 0,
1344
			'current_member' => $memID,
1345
			'is_approved' => 1,
1346
			'board' => $board,
1347
		)
1348
	);
1349
	list ($attachCount) = $smcFunc['db_fetch_row']($request);
1350
	$smcFunc['db_free_result']($request);
1351
1352
	return $attachCount;
1353
}
1354
1355
/**
1356
 * Show all the unwatched topics.
1357
 *
1358
 * @param int $memID The ID of the member
1359
 */
1360
function showUnwatched($memID)
1361
{
1362
	global $txt, $user_info, $scripturl, $modSettings, $context, $options, $sourcedir;
1363
1364
	// Only the owner can see the list (if the function is enabled of course)
1365
	if ($user_info['id'] != $memID)
1366
		return;
1367
1368
	require_once($sourcedir . '/Subs-List.php');
1369
1370
	// And here they are: the topics you don't like
1371
	$listOptions = array(
1372
		'id' => 'unwatched_topics',
1373
		'width' => '100%',
1374
		'items_per_page' => (empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page'])) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'],
1375
		'no_items_label' => $txt['unwatched_topics_none'],
1376
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=unwatchedtopics;u=' . $memID,
1377
		'default_sort_col' => 'started_on',
1378
		'get_items' => array(
1379
			'function' => 'list_getUnwatched',
1380
			'params' => array(
1381
				$memID,
1382
			),
1383
		),
1384
		'get_count' => array(
1385
			'function' => 'list_getNumUnwatched',
1386
			'params' => array(
1387
				$memID,
1388
			),
1389
		),
1390
		'columns' => array(
1391
			'subject' => array(
1392
				'header' => array(
1393
					'value' => $txt['subject'],
1394
					'class' => 'lefttext',
1395
					'style' => 'width: 30%;',
1396
				),
1397
				'data' => array(
1398
					'sprintf' => array(
1399
						'format' => '<a href="' . $scripturl . '?topic=%1$d.0">%2$s</a>',
1400
						'params' => array(
1401
							'id_topic' => false,
1402
							'subject' => false,
1403
						),
1404
					),
1405
				),
1406
				'sort' => array(
1407
					'default' => 'm.subject',
1408
					'reverse' => 'm.subject DESC',
1409
				),
1410
			),
1411
			'started_by' => array(
1412
				'header' => array(
1413
					'value' => $txt['started_by'],
1414
					'style' => 'width: 15%;',
1415
				),
1416
				'data' => array(
1417
					'db' => 'started_by',
1418
				),
1419
				'sort' => array(
1420
					'default' => 'mem.real_name',
1421
					'reverse' => 'mem.real_name DESC',
1422
				),
1423
			),
1424
			'started_on' => array(
1425
				'header' => array(
1426
					'value' => $txt['on'],
1427
					'class' => 'lefttext',
1428
					'style' => 'width: 20%;',
1429
				),
1430
				'data' => array(
1431
					'db' => 'started_on',
1432
					'timeformat' => true,
1433
				),
1434
				'sort' => array(
1435
					'default' => 'm.poster_time',
1436
					'reverse' => 'm.poster_time DESC',
1437
				),
1438
			),
1439
			'last_post_by' => array(
1440
				'header' => array(
1441
					'value' => $txt['last_post'],
1442
					'style' => 'width: 15%;',
1443
				),
1444
				'data' => array(
1445
					'db' => 'last_post_by',
1446
				),
1447
				'sort' => array(
1448
					'default' => 'mem.real_name',
1449
					'reverse' => 'mem.real_name DESC',
1450
				),
1451
			),
1452
			'last_post_on' => array(
1453
				'header' => array(
1454
					'value' => $txt['on'],
1455
					'class' => 'lefttext',
1456
					'style' => 'width: 20%;',
1457
				),
1458
				'data' => array(
1459
					'db' => 'last_post_on',
1460
					'timeformat' => true,
1461
				),
1462
				'sort' => array(
1463
					'default' => 'm.poster_time',
1464
					'reverse' => 'm.poster_time DESC',
1465
				),
1466
			),
1467
		),
1468
	);
1469
1470
	// Create the request list.
1471
	createList($listOptions);
1472
1473
	$context['sub_template'] = 'show_list';
1474
	$context['default_list'] = 'unwatched_topics';
1475
}
1476
1477
/**
1478
 * Gets information about unwatched (disregarded) topics. Callback for the list in show_unwatched
1479
 *
1480
 * @param int $start The item to start with (for pagination purposes)
1481
 * @param int $items_per_page How many items to show on each page
1482
 * @param string $sort A string indicating how to sort the results
1483
 * @param int $memID The ID of the member
1484
 * @return array An array of information about the unwatched topics
1485
 */
1486
function list_getUnwatched($start, $items_per_page, $sort, $memID)
1487
{
1488
	global $smcFunc;
1489
1490
	// Get the list of topics we can see
1491
	$request = $smcFunc['db_query']('', '
1492
		SELECT lt.id_topic
1493
		FROM {db_prefix}log_topics as lt
1494
			LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1495
			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')) ? '
1496
			LEFT JOIN {db_prefix}members as mem ON (m.id_member = mem.id_member)' : '') . '
1497
		WHERE lt.id_member = {int:current_member}
1498
			AND unwatched = 1
1499
			AND {query_see_message_board}
1500
		ORDER BY {raw:sort}
1501
		LIMIT {int:offset}, {int:limit}',
1502
		array(
1503
			'current_member' => $memID,
1504
			'sort' => $sort,
1505
			'offset' => $start,
1506
			'limit' => $items_per_page,
1507
		)
1508
	);
1509
1510
	$topics = array();
1511
	while ($row = $smcFunc['db_fetch_assoc']($request))
1512
		$topics[] = $row['id_topic'];
1513
1514
	$smcFunc['db_free_result']($request);
1515
1516
	// Any topics found?
1517
	$topicsInfo = array();
1518
	if (!empty($topics))
1519
	{
1520
		$request = $smcFunc['db_query']('', '
1521
			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
1522
			FROM {db_prefix}topics AS t
1523
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
1524
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1525
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
1526
				LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1527
			WHERE t.id_topic IN ({array_int:topics})',
1528
			array(
1529
				'topics' => $topics,
1530
			)
1531
		);
1532
		while ($row = $smcFunc['db_fetch_assoc']($request))
1533
			$topicsInfo[] = $row;
1534
		$smcFunc['db_free_result']($request);
1535
	}
1536
1537
	return $topicsInfo;
1538
}
1539
1540
/**
1541
 * Count the number of topics in the unwatched list
1542
 *
1543
 * @param int $memID The ID of the member
1544
 * @return int The number of unwatched topics
1545
 */
1546
function list_getNumUnwatched($memID)
1547
{
1548
	global $smcFunc;
1549
1550
	// Get the total number of attachments they have posted.
1551
	$request = $smcFunc['db_query']('', '
1552
		SELECT COUNT(*)
1553
		FROM {db_prefix}log_topics as lt
1554
		LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1555
		WHERE lt.id_member = {int:current_member}
1556
			AND lt.unwatched = 1
1557
			AND {query_see_topic_board}',
1558
		array(
1559
			'current_member' => $memID,
1560
		)
1561
	);
1562
	list ($unwatchedCount) = $smcFunc['db_fetch_row']($request);
1563
	$smcFunc['db_free_result']($request);
1564
1565
	return $unwatchedCount;
1566
}
1567
1568
/**
1569
 * Gets the user stats for display
1570
 *
1571
 * @param int $memID The ID of the member
1572
 */
1573
function statPanel($memID)
1574
{
1575
	global $txt, $scripturl, $context, $user_profile, $user_info, $modSettings, $smcFunc;
1576
1577
	$context['page_title'] = $txt['statPanel_showStats'] . ' ' . $user_profile[$memID]['real_name'];
1578
1579
	// Is the load average too high to allow searching just now?
1580
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_userstats']) && $context['load_average'] >= $modSettings['loadavg_userstats'])
1581
		fatal_lang_error('loadavg_userstats_disabled', false);
1582
1583
	// General user statistics.
1584
	$timeDays = floor($user_profile[$memID]['total_time_logged_in'] / 86400);
1585
	$timeHours = floor(($user_profile[$memID]['total_time_logged_in'] % 86400) / 3600);
1586
	$context['time_logged_in'] = ($timeDays > 0 ? $timeDays . $txt['total_time_logged_days'] : '') . ($timeHours > 0 ? $timeHours . $txt['total_time_logged_hours'] : '') . floor(($user_profile[$memID]['total_time_logged_in'] % 3600) / 60) . $txt['total_time_logged_minutes'];
1587
	$context['num_posts'] = comma_format($user_profile[$memID]['posts']);
1588
	// Menu tab
1589
	$context[$context['profile_menu_name']]['tab_data'] = array(
1590
		'title' => $txt['statPanel_generalStats'] . ' - ' . $context['member']['name'],
1591
		'icon' => 'stats_info.png'
1592
	);
1593
1594
	// Number of topics started and Number polls started
1595
	$result = $smcFunc['db_query']('', '
1596
		SELECT COUNT(*), COUNT( CASE WHEN id_poll != {int:no_poll} THEN 1 ELSE NULL END )
1597
		FROM {db_prefix}topics
1598
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1599
			AND id_board != {int:recycle_board}' : ''),
1600
		array(
1601
			'current_member' => $memID,
1602
			'recycle_board' => $modSettings['recycle_board'],
1603
			'no_poll' => 0,
1604
		)
1605
	);
1606
	list ($context['num_topics'], $context['num_polls']) = $smcFunc['db_fetch_row']($result);
1607
	$smcFunc['db_free_result']($result);
1608
1609
	// Number polls voted in.
1610
	$result = $smcFunc['db_query']('distinct_poll_votes', '
1611
		SELECT COUNT(DISTINCT id_poll)
1612
		FROM {db_prefix}log_polls
1613
		WHERE id_member = {int:current_member}',
1614
		array(
1615
			'current_member' => $memID,
1616
		)
1617
	);
1618
	list ($context['num_votes']) = $smcFunc['db_fetch_row']($result);
1619
	$smcFunc['db_free_result']($result);
1620
1621
	// Format the numbers...
1622
	$context['num_topics'] = comma_format($context['num_topics']);
1623
	$context['num_polls'] = comma_format($context['num_polls']);
1624
	$context['num_votes'] = comma_format($context['num_votes']);
1625
1626
	// Grab the board this member posted in most often.
1627
	$result = $smcFunc['db_query']('', '
1628
		SELECT
1629
			b.id_board, MAX(b.name) AS name, MAX(b.num_posts) AS num_posts, COUNT(*) AS message_count
1630
		FROM {db_prefix}messages AS m
1631
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1632
		WHERE m.id_member = {int:current_member}
1633
			AND b.count_posts = {int:count_enabled}
1634
			AND {query_see_board}
1635
		GROUP BY b.id_board
1636
		ORDER BY message_count DESC
1637
		LIMIT 10',
1638
		array(
1639
			'current_member' => $memID,
1640
			'count_enabled' => 0,
1641
		)
1642
	);
1643
	$context['popular_boards'] = array();
1644
	while ($row = $smcFunc['db_fetch_assoc']($result))
1645
	{
1646
		$context['popular_boards'][$row['id_board']] = array(
1647
			'id' => $row['id_board'],
1648
			'posts' => $row['message_count'],
1649
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1650
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1651
			'posts_percent' => $user_profile[$memID]['posts'] == 0 ? 0 : ($row['message_count'] * 100) / $user_profile[$memID]['posts'],
1652
			'total_posts' => $row['num_posts'],
1653
			'total_posts_member' => $user_profile[$memID]['posts'],
1654
		);
1655
	}
1656
	$smcFunc['db_free_result']($result);
1657
1658
	// Now get the 10 boards this user has most often participated in.
1659
	$result = $smcFunc['db_query']('profile_board_stats', '
1660
		SELECT
1661
			b.id_board, MAX(b.name) AS name, b.num_posts, COUNT(*) AS message_count,
1662
			CASE WHEN COUNT(*) > MAX(b.num_posts) THEN 1 ELSE COUNT(*) / MAX(b.num_posts) END * 100 AS percentage
1663
		FROM {db_prefix}messages AS m
1664
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1665
		WHERE m.id_member = {int:current_member}
1666
			AND {query_see_board}
1667
		GROUP BY b.id_board, b.num_posts
1668
		ORDER BY percentage DESC
1669
		LIMIT 10',
1670
		array(
1671
			'current_member' => $memID,
1672
		)
1673
	);
1674
	$context['board_activity'] = array();
1675
	while ($row = $smcFunc['db_fetch_assoc']($result))
1676
	{
1677
		$context['board_activity'][$row['id_board']] = array(
1678
			'id' => $row['id_board'],
1679
			'posts' => $row['message_count'],
1680
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1681
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1682
			'percent' => comma_format((float) $row['percentage'], 2),
1683
			'posts_percent' => (float) $row['percentage'],
1684
			'total_posts' => $row['num_posts'],
1685
		);
1686
	}
1687
	$smcFunc['db_free_result']($result);
1688
1689
	// Posting activity by time.
1690
	$result = $smcFunc['db_query']('user_activity_by_time', '
1691
		SELECT
1692
			HOUR(FROM_UNIXTIME(poster_time + {int:time_offset})) AS hour,
1693
			COUNT(*) AS post_count
1694
		FROM {db_prefix}messages
1695
		WHERE id_member = {int:current_member}' . ($modSettings['totalMessages'] > 100000 ? '
1696
			AND id_topic > {int:top_ten_thousand_topics}' : '') . '
1697
		GROUP BY hour',
1698
		array(
1699
			'current_member' => $memID,
1700
			'top_ten_thousand_topics' => $modSettings['totalTopics'] - 10000,
1701
			'time_offset' => (($user_info['time_offset'] + $modSettings['time_offset']) * 3600),
1702
		)
1703
	);
1704
	$maxPosts = $realPosts = 0;
1705
	$context['posts_by_time'] = array();
1706
	while ($row = $smcFunc['db_fetch_assoc']($result))
1707
	{
1708
		// Cast as an integer to remove the leading 0.
1709
		$row['hour'] = (int) $row['hour'];
1710
1711
		$maxPosts = max($row['post_count'], $maxPosts);
1712
		$realPosts += $row['post_count'];
1713
1714
		$context['posts_by_time'][$row['hour']] = array(
1715
			'hour' => $row['hour'],
1716
			'hour_format' => stripos($user_info['time_format'], '%p') === false ? $row['hour'] : date('g a', mktime($row['hour'])),
1717
			'posts' => $row['post_count'],
1718
			'posts_percent' => 0,
1719
			'is_last' => $row['hour'] == 23,
1720
		);
1721
	}
1722
	$smcFunc['db_free_result']($result);
1723
1724
	if ($maxPosts > 0)
1725
		for ($hour = 0; $hour < 24; $hour++)
1726
		{
1727
			if (!isset($context['posts_by_time'][$hour]))
1728
				$context['posts_by_time'][$hour] = array(
1729
					'hour' => $hour,
1730
					'hour_format' => stripos($user_info['time_format'], '%p') === false ? $hour : date('g a', mktime($hour)),
1731
					'posts' => 0,
1732
					'posts_percent' => 0,
1733
					'relative_percent' => 0,
1734
					'is_last' => $hour == 23,
1735
				);
1736
			else
1737
			{
1738
				$context['posts_by_time'][$hour]['posts_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $realPosts);
1739
				$context['posts_by_time'][$hour]['relative_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $maxPosts);
1740
			}
1741
		}
1742
1743
	// Put it in the right order.
1744
	ksort($context['posts_by_time']);
1745
1746
	/**
1747
	 * Adding new entries:
1748
	 * 'key' => array(
1749
	 * 		'text' => string, // The text that will be shown next to the entry.
1750
	 * 		'url' => string, // OPTIONAL: The entry will be a url
1751
	 * ),
1752
	 *
1753
	 * 'key' will be used to look up the language string as $txt['statPanel_' . $key].
1754
	 * Make sure to add a new entry when writing your mod!
1755
	 */
1756
	$context['text_stats'] = array(
1757
		'total_time_online' => array(
1758
			'text' => $context['time_logged_in'],
1759
		),
1760
		'total_posts' => array(
1761
			'text' => $context['num_posts'] . ' ' . $txt['statPanel_posts'],
1762
			'url' => $scripturl . '?action=profile;area=showposts;sa=messages;u=' . $memID
1763
		),
1764
		'total_topics' => array(
1765
			'text' => $context['num_topics'] . ' ' . $txt['statPanel_topics'],
1766
			'url' => $scripturl . '?action=profile;area=showposts;sa=topics;u=' . $memID
1767
		),
1768
		'users_polls' => array(
1769
			'text' => $context['num_polls'] . ' ' . $txt['statPanel_polls'],
1770
		),
1771
		'users_votes' => array(
1772
			'text' => $context['num_votes'] . ' ' . $txt['statPanel_votes']
1773
		)
1774
	);
1775
1776
	// Custom stats (just add a template_layer to add it to the template!)
1777
	call_integration_hook('integrate_profile_stats', array($memID, &$context['text_stats']));
1778
}
1779
1780
/**
1781
 * Loads up the information for the "track user" section of the profile
1782
 *
1783
 * @param int $memID The ID of the member
1784
 */
1785
function tracking($memID)
1786
{
1787
	global $context, $txt, $modSettings, $user_profile;
1788
1789
	$subActions = array(
1790
		'activity' => array('trackActivity', $txt['trackActivity'], 'moderate_forum'),
1791
		'ip' => array('TrackIP', $txt['trackIP'], 'moderate_forum'),
1792
		'edits' => array('trackEdits', $txt['trackEdits'], 'moderate_forum'),
1793
		'groupreq' => array('trackGroupReq', $txt['trackGroupRequests'], 'approve_group_requests'),
1794
		'logins' => array('TrackLogins', $txt['trackLogins'], 'moderate_forum'),
1795
	);
1796
1797
	foreach ($subActions as $sa => $action)
1798
	{
1799
		if (!allowedTo($action[2]))
1800
			unset($subActions[$sa]);
1801
	}
1802
1803
	// Create the tabs for the template.
1804
	$context[$context['profile_menu_name']]['tab_data'] = array(
1805
		'title' => $txt['tracking'],
1806
		'description' => $txt['tracking_description'],
1807
		'icon' => 'profile_hd.png',
1808
		'tabs' => array(
1809
			'activity' => array(),
1810
			'ip' => array(),
1811
			'edits' => array(),
1812
			'groupreq' => array(),
1813
			'logins' => array(),
1814
		),
1815
	);
1816
1817
	// Moderation must be on to track edits.
1818
	if (empty($modSettings['userlog_enabled']))
1819
		unset($context[$context['profile_menu_name']]['tab_data']['edits'], $subActions['edits']);
1820
1821
	// Group requests must be active to show it...
1822
	if (empty($modSettings['show_group_membership']))
1823
		unset($context[$context['profile_menu_name']]['tab_data']['groupreq'], $subActions['groupreq']);
1824
1825
	if (empty($subActions))
1826
		fatal_lang_error('no_access', false);
1827
1828
	$keys = array_keys($subActions);
1829
	$default = array_shift($keys);
1830
	$context['tracking_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : $default;
1831
1832
	// Set a page title.
1833
	$context['page_title'] = $txt['trackUser'] . ' - ' . $subActions[$context['tracking_area']][1] . ' - ' . $user_profile[$memID]['real_name'];
1834
1835
	// Pass on to the actual function.
1836
	$context['sub_template'] = $subActions[$context['tracking_area']][0];
1837
	$call = call_helper($subActions[$context['tracking_area']][0], true);
1838
1839
	if (!empty($call))
1840
		call_user_func($call, $memID);
0 ignored issues
show
Bug introduced by
It seems like $call can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1840
		call_user_func(/** @scrutinizer ignore-type */ $call, $memID);
Loading history...
1841
}
1842
1843
/**
1844
 * Handles tracking a user's activity
1845
 *
1846
 * @param int $memID The ID of the member
1847
 */
1848
function trackActivity($memID)
1849
{
1850
	global $scripturl, $txt, $modSettings, $sourcedir;
1851
	global $user_profile, $context, $smcFunc;
1852
1853
	// Verify if the user has sufficient permissions.
1854
	isAllowedTo('moderate_forum');
1855
1856
	$context['last_ip'] = $user_profile[$memID]['member_ip'];
1857
	if ($context['last_ip'] != $user_profile[$memID]['member_ip2'])
1858
		$context['last_ip2'] = $user_profile[$memID]['member_ip2'];
1859
	$context['member']['name'] = $user_profile[$memID]['real_name'];
1860
1861
	// Set the options for the list component.
1862
	$listOptions = array(
1863
		'id' => 'track_user_list',
1864
		'title' => $txt['errors_by'] . ' ' . $context['member']['name'],
1865
		'items_per_page' => $modSettings['defaultMaxListItems'],
1866
		'no_items_label' => $txt['no_errors_from_user'],
1867
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=user;u=' . $memID,
1868
		'default_sort_col' => 'date',
1869
		'get_items' => array(
1870
			'function' => 'list_getUserErrors',
1871
			'params' => array(
1872
				'le.id_member = {int:current_member}',
1873
				array('current_member' => $memID),
1874
			),
1875
		),
1876
		'get_count' => array(
1877
			'function' => 'list_getUserErrorCount',
1878
			'params' => array(
1879
				'id_member = {int:current_member}',
1880
				array('current_member' => $memID),
1881
			),
1882
		),
1883
		'columns' => array(
1884
			'ip_address' => array(
1885
				'header' => array(
1886
					'value' => $txt['ip_address'],
1887
				),
1888
				'data' => array(
1889
					'sprintf' => array(
1890
						'format' => '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=%1$s;u=' . $memID . '">%1$s</a>',
1891
						'params' => array(
1892
							'ip' => false,
1893
						),
1894
					),
1895
				),
1896
				'sort' => array(
1897
					'default' => 'le.ip',
1898
					'reverse' => 'le.ip DESC',
1899
				),
1900
			),
1901
			'message' => array(
1902
				'header' => array(
1903
					'value' => $txt['message'],
1904
				),
1905
				'data' => array(
1906
					'sprintf' => array(
1907
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
1908
						'params' => array(
1909
							'message' => false,
1910
							'url' => false,
1911
						),
1912
					),
1913
				),
1914
			),
1915
			'date' => array(
1916
				'header' => array(
1917
					'value' => $txt['date'],
1918
				),
1919
				'data' => array(
1920
					'db' => 'time',
1921
				),
1922
				'sort' => array(
1923
					'default' => 'le.id_error DESC',
1924
					'reverse' => 'le.id_error',
1925
				),
1926
			),
1927
		),
1928
		'additional_rows' => array(
1929
			array(
1930
				'position' => 'after_title',
1931
				'value' => $txt['errors_desc'],
1932
			),
1933
		),
1934
	);
1935
1936
	// Create the list for viewing.
1937
	require_once($sourcedir . '/Subs-List.php');
1938
	createList($listOptions);
1939
1940
	// @todo cache this
1941
	// If this is a big forum, or a large posting user, let's limit the search.
1942
	if ($modSettings['totalMessages'] > 50000 && $user_profile[$memID]['posts'] > 500)
1943
	{
1944
		$request = $smcFunc['db_query']('', '
1945
			SELECT MAX(id_msg)
1946
			FROM {db_prefix}messages AS m
1947
			WHERE m.id_member = {int:current_member}',
1948
			array(
1949
				'current_member' => $memID,
1950
			)
1951
		);
1952
		list ($max_msg_member) = $smcFunc['db_fetch_row']($request);
1953
		$smcFunc['db_free_result']($request);
1954
1955
		// There's no point worrying ourselves with messages made yonks ago, just get recent ones!
1956
		$min_msg_member = max(0, $max_msg_member - $user_profile[$memID]['posts'] * 3);
1957
	}
1958
1959
	// Default to at least the ones we know about.
1960
	$ips = array(
1961
		$user_profile[$memID]['member_ip'],
1962
		$user_profile[$memID]['member_ip2'],
1963
	);
1964
1965
	// @todo cache this
1966
	// Get all IP addresses this user has used for his messages.
1967
	$request = $smcFunc['db_query']('', '
1968
		SELECT poster_ip
1969
		FROM {db_prefix}messages
1970
		WHERE id_member = {int:current_member}
1971
		' . (isset($min_msg_member) ? '
1972
			AND id_msg >= {int:min_msg_member} AND id_msg <= {int:max_msg_member}' : '') . '
1973
		GROUP BY poster_ip',
1974
		array(
1975
			'current_member' => $memID,
1976
			'min_msg_member' => !empty($min_msg_member) ? $min_msg_member : 0,
1977
			'max_msg_member' => !empty($max_msg_member) ? $max_msg_member : 0,
1978
		)
1979
	);
1980
	$context['ips'] = array();
1981
	while ($row = $smcFunc['db_fetch_assoc']($request))
1982
	{
1983
		$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>';
1984
		$ips[] = inet_dtop($row['poster_ip']);
1985
	}
1986
	$smcFunc['db_free_result']($request);
1987
1988
	// Now also get the IP addresses from the error messages.
1989
	$request = $smcFunc['db_query']('', '
1990
		SELECT COUNT(*) AS error_count, ip
1991
		FROM {db_prefix}log_errors
1992
		WHERE id_member = {int:current_member}
1993
		GROUP BY ip',
1994
		array(
1995
			'current_member' => $memID,
1996
		)
1997
	);
1998
	$context['error_ips'] = array();
1999
	while ($row = $smcFunc['db_fetch_assoc']($request))
2000
	{
2001
		$row['ip'] = inet_dtop($row['ip']);
2002
		$context['error_ips'][] = '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=' . $row['ip'] . ';u=' . $memID . '">' . $row['ip'] . '</a>';
2003
		$ips[] = $row['ip'];
2004
	}
2005
	$smcFunc['db_free_result']($request);
2006
2007
	// Find other users that might use the same IP.
2008
	$ips = array_unique($ips);
2009
	$context['members_in_range'] = array();
2010
	if (!empty($ips))
2011
	{
2012
		// Get member ID's which are in messages...
2013
		$request = $smcFunc['db_query']('', '
2014
			SELECT DISTINCT mem.id_member
2015
			FROM {db_prefix}messages AS m
2016
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
2017
			WHERE m.poster_ip IN ({array_inet:ip_list})
2018
				AND mem.id_member != {int:current_member}',
2019
			array(
2020
				'current_member' => $memID,
2021
				'ip_list' => $ips,
2022
			)
2023
		);
2024
		$message_members = array();
2025
		while ($row = $smcFunc['db_fetch_assoc']($request))
2026
			$message_members[] = $row['id_member'];
2027
		$smcFunc['db_free_result']($request);
2028
2029
		// Fetch their names, cause of the GROUP BY doesn't like giving us that normally.
2030
		if (!empty($message_members))
2031
		{
2032
			$request = $smcFunc['db_query']('', '
2033
				SELECT id_member, real_name
2034
				FROM {db_prefix}members
2035
				WHERE id_member IN ({array_int:message_members})',
2036
				array(
2037
					'message_members' => $message_members,
2038
					'ip_list' => $ips,
2039
				)
2040
			);
2041
			while ($row = $smcFunc['db_fetch_assoc']($request))
2042
				$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
2043
			$smcFunc['db_free_result']($request);
2044
		}
2045
2046
		$request = $smcFunc['db_query']('', '
2047
			SELECT id_member, real_name
2048
			FROM {db_prefix}members
2049
			WHERE id_member != {int:current_member}
2050
				AND member_ip IN ({array_inet:ip_list})',
2051
			array(
2052
				'current_member' => $memID,
2053
				'ip_list' => $ips,
2054
			)
2055
		);
2056
		while ($row = $smcFunc['db_fetch_assoc']($request))
2057
			$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
2058
		$smcFunc['db_free_result']($request);
2059
	}
2060
}
2061
2062
/**
2063
 * Get the number of user errors
2064
 *
2065
 * @param string $where A query to limit which errors are counted
2066
 * @param array $where_vars The parameters for $where
2067
 * @return int Number of user errors
2068
 */
2069
function list_getUserErrorCount($where, $where_vars = array())
2070
{
2071
	global $smcFunc;
2072
2073
	$request = $smcFunc['db_query']('', '
2074
		SELECT COUNT(id_error)
2075
		FROM {db_prefix}log_errors
2076
		WHERE ' . $where,
2077
		$where_vars
2078
	);
2079
	list ($count) = $smcFunc['db_fetch_row']($request);
2080
	$smcFunc['db_free_result']($request);
2081
2082
	return (int) $count;
2083
}
2084
2085
/**
2086
 * Gets all of the errors generated by a user's actions. Callback for the list in track_activity
2087
 *
2088
 * @param int $start Which item to start with (for pagination purposes)
2089
 * @param int $items_per_page How many items to show on each page
2090
 * @param string $sort A string indicating how to sort the results
2091
 * @param string $where A query indicating how to filter the results (eg 'id_member={int:id_member}')
2092
 * @param array $where_vars An array of parameters for $where
2093
 * @return array An array of information about the error messages
2094
 */
2095
function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars = array())
2096
{
2097
	global $smcFunc, $txt, $scripturl;
2098
2099
	// Get a list of error messages from this ip (range).
2100
	$request = $smcFunc['db_query']('', '
2101
		SELECT
2102
			le.log_time, le.ip, le.url, le.message, COALESCE(mem.id_member, 0) AS id_member,
2103
			COALESCE(mem.real_name, {string:guest_title}) AS display_name, mem.member_name
2104
		FROM {db_prefix}log_errors AS le
2105
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = le.id_member)
2106
		WHERE ' . $where . '
2107
		ORDER BY {raw:sort}
2108
		LIMIT {int:start}, {int:max}',
2109
		array_merge($where_vars, array(
2110
			'guest_title' => $txt['guest_title'],
2111
			'sort' => $sort,
2112
			'start' => $start,
2113
			'max' => $items_per_page,
2114
		))
2115
	);
2116
	$error_messages = array();
2117
	while ($row = $smcFunc['db_fetch_assoc']($request))
2118
		$error_messages[] = array(
2119
			'ip' => inet_dtop($row['ip']),
2120
			'member_link' => $row['id_member'] > 0 ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>' : $row['display_name'],
2121
			'message' => strtr($row['message'], array('&lt;span class=&quot;remove&quot;&gt;' => '', '&lt;/span&gt;' => '')),
2122
			'url' => $row['url'],
2123
			'time' => timeformat($row['log_time']),
2124
			'timestamp' => forum_time(true, $row['log_time']),
2125
		);
2126
	$smcFunc['db_free_result']($request);
2127
2128
	return $error_messages;
2129
}
2130
2131
/**
2132
 * Gets the number of posts made from a particular IP
2133
 *
2134
 * @param string $where A query indicating which posts to count
2135
 * @param array $where_vars The parameters for $where
2136
 * @return int Count of messages matching the IP
2137
 */
2138
function list_getIPMessageCount($where, $where_vars = array())
2139
{
2140
	global $smcFunc, $user_info;
2141
2142
	$request = $smcFunc['db_query']('', '
2143
		SELECT COUNT(id_msg)
2144
		FROM {db_prefix}messages AS m
2145
		WHERE {query_see_message_board} AND ' . $where,
2146
		$where_vars
2147
	);
2148
	list ($count) = $smcFunc['db_fetch_row']($request);
2149
	$smcFunc['db_free_result']($request);
2150
2151
	return (int) $count;
2152
}
2153
2154
/**
2155
 * Gets all the posts made from a particular IP
2156
 *
2157
 * @param int $start Which item to start with (for pagination purposes)
2158
 * @param int $items_per_page How many items to show on each page
2159
 * @param string $sort A string indicating how to sort the results
2160
 * @param string $where A query to filter which posts are returned
2161
 * @param array $where_vars An array of parameters for $where
2162
 * @return array An array containing information about the posts
2163
 */
2164
function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars = array())
2165
{
2166
	global $smcFunc, $scripturl, $user_info;
2167
2168
	// Get all the messages fitting this where clause.
2169
	$request = $smcFunc['db_query']('', '
2170
		SELECT
2171
			m.id_msg, m.poster_ip, COALESCE(mem.real_name, m.poster_name) AS display_name, mem.id_member,
2172
			m.subject, m.poster_time, m.id_topic, m.id_board
2173
		FROM {db_prefix}messages AS m
2174
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
2175
		WHERE {query_see_message_board} AND ' . $where . '
2176
		ORDER BY {raw:sort}
2177
		LIMIT {int:start}, {int:max}',
2178
		array_merge($where_vars, array(
2179
			'sort' => $sort,
2180
			'start' => $start,
2181
			'max' => $items_per_page,
2182
		))
2183
	);
2184
	$messages = array();
2185
	while ($row = $smcFunc['db_fetch_assoc']($request))
2186
		$messages[] = array(
2187
			'ip' => inet_dtop($row['poster_ip']),
2188
			'member_link' => empty($row['id_member']) ? $row['display_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>',
2189
			'board' => array(
2190
				'id' => $row['id_board'],
2191
				'href' => $scripturl . '?board=' . $row['id_board']
2192
			),
2193
			'topic' => $row['id_topic'],
2194
			'id' => $row['id_msg'],
2195
			'subject' => $row['subject'],
2196
			'time' => timeformat($row['poster_time']),
2197
			'timestamp' => forum_time(true, $row['poster_time'])
2198
		);
2199
	$smcFunc['db_free_result']($request);
2200
2201
	return $messages;
2202
}
2203
2204
/**
2205
 * Handles tracking a particular IP address
2206
 *
2207
 * @param int $memID The ID of a member whose IP we want to track
2208
 */
2209
function TrackIP($memID = 0)
2210
{
2211
	global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir;
2212
	global $context, $options, $smcFunc;
2213
2214
	// Can the user do this?
2215
	isAllowedTo('moderate_forum');
2216
2217
	if ($memID == 0)
2218
	{
2219
		$context['ip'] = ip2range($user_info['ip']);
2220
		loadTemplate('Profile');
2221
		loadLanguage('Profile');
2222
		$context['sub_template'] = 'trackIP';
2223
		$context['page_title'] = $txt['profile'];
2224
		$context['base_url'] = $scripturl . '?action=trackip';
2225
	}
2226
	else
2227
	{
2228
		$context['ip'] = ip2range($user_profile[$memID]['member_ip']);
2229
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2230
	}
2231
2232
	// Searching?
2233
	if (isset($_REQUEST['searchip']))
2234
		$context['ip'] = ip2range(trim($_REQUEST['searchip']));
2235
2236
	if (count($context['ip']) !== 2)
2237
		fatal_lang_error('invalid_tracking_ip', false);
2238
2239
	$ip_string = array('{inet:ip_address_low}', '{inet:ip_address_high}');
2240
	$fields = array(
2241
		'ip_address_low' => $context['ip']['low'],
2242
		'ip_address_high' => $context['ip']['high'],
2243
	);
2244
2245
	$ip_var = $context['ip'];
2246
2247
	if ($context['ip']['low'] !== $context['ip']['high'])
2248
		$context['ip'] = $context['ip']['low'] . ' - ' . $context['ip']['high'];
2249
	else
2250
		$context['ip'] = $context['ip']['low'];
2251
2252
	if (empty($context['tracking_area']))
2253
		$context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip'];
2254
2255
	$request = $smcFunc['db_query']('', '
2256
		SELECT id_member, real_name AS display_name, member_ip
2257
		FROM {db_prefix}members
2258
		WHERE member_ip >= ' . $ip_string[0] . ' and member_ip <= ' . $ip_string[1],
2259
		$fields
2260
	);
2261
	$context['ips'] = array();
2262
	while ($row = $smcFunc['db_fetch_assoc']($request))
2263
		$context['ips'][inet_dtop($row['member_ip'])][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
2264
	$smcFunc['db_free_result']($request);
2265
2266
	ksort($context['ips']);
2267
2268
	// For messages we use the "messages per page" option
2269
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
2270
2271
	// Gonna want this for the list.
2272
	require_once($sourcedir . '/Subs-List.php');
2273
2274
	// Start with the user messages.
2275
	$listOptions = array(
2276
		'id' => 'track_message_list',
2277
		'title' => $txt['messages_from_ip'] . ' ' . $context['ip'],
2278
		'start_var_name' => 'messageStart',
2279
		'items_per_page' => $maxPerPage,
2280
		'no_items_label' => $txt['no_messages_from_ip'],
2281
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2282
		'default_sort_col' => 'date',
2283
		'get_items' => array(
2284
			'function' => 'list_getIPMessages',
2285
			'params' => array(
2286
				'm.poster_ip >= ' . $ip_string[0] . ' and m.poster_ip <= ' . $ip_string[1],
2287
				$fields,
2288
			),
2289
		),
2290
		'get_count' => array(
2291
			'function' => 'list_getIPMessageCount',
2292
			'params' => array(
2293
				'm.poster_ip >= ' . $ip_string[0] . ' and m.poster_ip <= ' . $ip_string[1],
2294
				$fields,
2295
			),
2296
		),
2297
		'columns' => array(
2298
			'ip_address' => array(
2299
				'header' => array(
2300
					'value' => $txt['ip_address'],
2301
				),
2302
				'data' => array(
2303
					'sprintf' => array(
2304
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2305
						'params' => array(
2306
							'ip' => false,
2307
						),
2308
					),
2309
				),
2310
				'sort' => array(
2311
					'default' => 'm.poster_ip',
2312
					'reverse' => 'm.poster_ip DESC',
2313
				),
2314
			),
2315
			'poster' => array(
2316
				'header' => array(
2317
					'value' => $txt['poster'],
2318
				),
2319
				'data' => array(
2320
					'db' => 'member_link',
2321
				),
2322
			),
2323
			'subject' => array(
2324
				'header' => array(
2325
					'value' => $txt['subject'],
2326
				),
2327
				'data' => array(
2328
					'sprintf' => array(
2329
						'format' => '<a href="' . $scripturl . '?topic=%1$s.msg%2$s#msg%2$s" rel="nofollow">%3$s</a>',
2330
						'params' => array(
2331
							'topic' => false,
2332
							'id' => false,
2333
							'subject' => false,
2334
						),
2335
					),
2336
				),
2337
			),
2338
			'date' => array(
2339
				'header' => array(
2340
					'value' => $txt['date'],
2341
				),
2342
				'data' => array(
2343
					'db' => 'time',
2344
				),
2345
				'sort' => array(
2346
					'default' => 'm.id_msg DESC',
2347
					'reverse' => 'm.id_msg',
2348
				),
2349
			),
2350
		),
2351
		'additional_rows' => array(
2352
			array(
2353
				'position' => 'after_title',
2354
				'value' => $txt['messages_from_ip_desc'],
2355
			),
2356
		),
2357
	);
2358
2359
	// Create the messages list.
2360
	createList($listOptions);
2361
2362
	// Set the options for the error lists.
2363
	$listOptions = array(
2364
		'id' => 'track_user_list',
2365
		'title' => $txt['errors_from_ip'] . ' ' . $context['ip'],
2366
		'start_var_name' => 'errorStart',
2367
		'items_per_page' => $modSettings['defaultMaxListItems'],
2368
		'no_items_label' => $txt['no_errors_from_ip'],
2369
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2370
		'default_sort_col' => 'date2',
2371
		'get_items' => array(
2372
			'function' => 'list_getUserErrors',
2373
			'params' => array(
2374
				'le.ip >= ' . $ip_string[0] . ' and le.ip <= ' . $ip_string[1],
2375
				$fields,
2376
			),
2377
		),
2378
		'get_count' => array(
2379
			'function' => 'list_getUserErrorCount',
2380
			'params' => array(
2381
				'ip >= ' . $ip_string[0] . ' and ip <= ' . $ip_string[1],
2382
				$fields,
2383
			),
2384
		),
2385
		'columns' => array(
2386
			'ip_address2' => array(
2387
				'header' => array(
2388
					'value' => $txt['ip_address'],
2389
				),
2390
				'data' => array(
2391
					'sprintf' => array(
2392
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2393
						'params' => array(
2394
							'ip' => false,
2395
						),
2396
					),
2397
				),
2398
				'sort' => array(
2399
					'default' => 'le.ip',
2400
					'reverse' => 'le.ip DESC',
2401
				),
2402
			),
2403
			'display_name' => array(
2404
				'header' => array(
2405
					'value' => $txt['display_name'],
2406
				),
2407
				'data' => array(
2408
					'db' => 'member_link',
2409
				),
2410
			),
2411
			'message' => array(
2412
				'header' => array(
2413
					'value' => $txt['message'],
2414
				),
2415
				'data' => array(
2416
					'sprintf' => array(
2417
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
2418
						'params' => array(
2419
							'message' => false,
2420
							'url' => false,
2421
						),
2422
					),
2423
				),
2424
			),
2425
			'date2' => array(
2426
				'header' => array(
2427
					'value' => $txt['date'],
2428
				),
2429
				'data' => array(
2430
					'db' => 'time',
2431
				),
2432
				'sort' => array(
2433
					'default' => 'le.id_error DESC',
2434
					'reverse' => 'le.id_error',
2435
				),
2436
			),
2437
		),
2438
		'additional_rows' => array(
2439
			array(
2440
				'position' => 'after_title',
2441
				'value' => $txt['errors_from_ip_desc'],
2442
			),
2443
		),
2444
	);
2445
2446
	// Create the error list.
2447
	createList($listOptions);
2448
2449
	// Allow 3rd party integrations to add in their own lists or whatever.
2450
	$context['additional_track_lists'] = array();
2451
	call_integration_hook('integrate_profile_trackip', array($ip_string, $ip_var));
2452
2453
	$context['single_ip'] = ($ip_var['low'] === $ip_var['high']);
2454
	if ($context['single_ip'])
2455
	{
2456
		$context['whois_servers'] = array(
2457
			'afrinic' => array(
2458
				'name' => $txt['whois_afrinic'],
2459
				'url' => 'https://www.afrinic.net/cgi-bin/whois?searchtext=' . $context['ip'],
2460
			),
2461
			'apnic' => array(
2462
				'name' => $txt['whois_apnic'],
2463
				'url' => 'https://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'],
2464
			),
2465
			'arin' => array(
2466
				'name' => $txt['whois_arin'],
2467
				'url' => 'https://whois.arin.net/rest/ip/' . $context['ip'],
2468
			),
2469
			'lacnic' => array(
2470
				'name' => $txt['whois_lacnic'],
2471
				'url' => 'https://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'],
2472
			),
2473
			'ripe' => array(
2474
				'name' => $txt['whois_ripe'],
2475
				'url' => 'https://apps.db.ripe.net/search/query.html?searchtext=' . $context['ip'],
2476
			),
2477
		);
2478
	}
2479
}
2480
2481
/**
2482
 * Tracks a user's logins.
2483
 *
2484
 * @param int $memID The ID of the member
2485
 */
2486
function TrackLogins($memID = 0)
2487
{
2488
	global $scripturl, $txt, $sourcedir, $context;
2489
2490
	// Gonna want this for the list.
2491
	require_once($sourcedir . '/Subs-List.php');
2492
2493
	if ($memID == 0)
2494
		$context['base_url'] = $scripturl . '?action=trackip';
2495
	else
2496
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2497
2498
	// Start with the user messages.
2499
	$listOptions = array(
2500
		'id' => 'track_logins_list',
2501
		'title' => $txt['trackLogins'],
2502
		'no_items_label' => $txt['trackLogins_none_found'],
2503
		'base_href' => $context['base_url'],
2504
		'get_items' => array(
2505
			'function' => 'list_getLogins',
2506
			'params' => array(
2507
				'id_member = {int:current_member}',
2508
				array('current_member' => $memID),
2509
			),
2510
		),
2511
		'get_count' => array(
2512
			'function' => 'list_getLoginCount',
2513
			'params' => array(
2514
				'id_member = {int:current_member}',
2515
				array('current_member' => $memID),
2516
			),
2517
		),
2518
		'columns' => array(
2519
			'time' => array(
2520
				'header' => array(
2521
					'value' => $txt['date'],
2522
				),
2523
				'data' => array(
2524
					'db' => 'time',
2525
				),
2526
			),
2527
			'ip' => array(
2528
				'header' => array(
2529
					'value' => $txt['ip_address'],
2530
				),
2531
				'data' => array(
2532
					'sprintf' => array(
2533
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a> (<a href="' . $context['base_url'] . ';searchip=%2$s">%2$s</a>) ',
2534
						'params' => array(
2535
							'ip' => false,
2536
							'ip2' => false
2537
						),
2538
					),
2539
				),
2540
			),
2541
		),
2542
		'additional_rows' => array(
2543
			array(
2544
				'position' => 'after_title',
2545
				'value' => $txt['trackLogins_desc'],
2546
			),
2547
		),
2548
	);
2549
2550
	// Create the messages list.
2551
	createList($listOptions);
2552
2553
	$context['sub_template'] = 'show_list';
2554
	$context['default_list'] = 'track_logins_list';
2555
}
2556
2557
/**
2558
 * Finds the total number of tracked logins for a particular user
2559
 *
2560
 * @param string $where A query to limit which logins are counted
2561
 * @param array $where_vars An array of parameters for $where
2562
 * @return int count of messages matching the IP
2563
 */
2564
function list_getLoginCount($where, $where_vars = array())
2565
{
2566
	global $smcFunc;
2567
2568
	$request = $smcFunc['db_query']('', '
2569
		SELECT COUNT(*) AS message_count
2570
		FROM {db_prefix}member_logins
2571
		WHERE id_member = {int:id_member}',
2572
		array(
2573
			'id_member' => $where_vars['current_member'],
2574
		)
2575
	);
2576
	list ($count) = $smcFunc['db_fetch_row']($request);
2577
	$smcFunc['db_free_result']($request);
2578
2579
	return (int) $count;
2580
}
2581
2582
/**
2583
 * Callback for the list in trackLogins.
2584
 *
2585
 * @param int $start Which item to start with (not used here)
2586
 * @param int $items_per_page How many items to show on each page (not used here)
2587
 * @param string $sort A string indicating
2588
 * @param string $where A query to filter results (not used here)
2589
 * @param array $where_vars An array of parameters for $where. Only 'current_member' (the ID of the member) is used here
2590
 * @return array An array of information about user logins
2591
 */
2592
function list_getLogins($start, $items_per_page, $sort, $where, $where_vars = array())
2593
{
2594
	global $smcFunc;
2595
2596
	$request = $smcFunc['db_query']('', '
2597
		SELECT time, ip, ip2
2598
		FROM {db_prefix}member_logins
2599
		WHERE id_member = {int:id_member}
2600
		ORDER BY time DESC',
2601
		array(
2602
			'id_member' => $where_vars['current_member'],
2603
		)
2604
	);
2605
	$logins = array();
2606
	while ($row = $smcFunc['db_fetch_assoc']($request))
2607
		$logins[] = array(
2608
			'time' => timeformat($row['time']),
2609
			'ip' => inet_dtop($row['ip']),
2610
			'ip2' => inet_dtop($row['ip2']),
2611
		);
2612
	$smcFunc['db_free_result']($request);
2613
2614
	return $logins;
2615
}
2616
2617
/**
2618
 * Tracks a user's profile edits
2619
 *
2620
 * @param int $memID The ID of the member
2621
 */
2622
function trackEdits($memID)
2623
{
2624
	global $scripturl, $txt, $modSettings, $sourcedir, $context, $smcFunc;
2625
2626
	require_once($sourcedir . '/Subs-List.php');
2627
2628
	// Get the names of any custom fields.
2629
	$request = $smcFunc['db_query']('', '
2630
		SELECT col_name, field_name, bbc
2631
		FROM {db_prefix}custom_fields',
2632
		array(
2633
		)
2634
	);
2635
	$context['custom_field_titles'] = array();
2636
	while ($row = $smcFunc['db_fetch_assoc']($request))
2637
		$context['custom_field_titles']['customfield_' . $row['col_name']] = array(
2638
			'title' => $row['field_name'],
2639
			'parse_bbc' => $row['bbc'],
2640
		);
2641
	$smcFunc['db_free_result']($request);
2642
2643
	// Set the options for the error lists.
2644
	$listOptions = array(
2645
		'id' => 'edit_list',
2646
		'title' => $txt['trackEdits'],
2647
		'items_per_page' => $modSettings['defaultMaxListItems'],
2648
		'no_items_label' => $txt['trackEdit_no_edits'],
2649
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=edits;u=' . $memID,
2650
		'default_sort_col' => 'time',
2651
		'get_items' => array(
2652
			'function' => 'list_getProfileEdits',
2653
			'params' => array(
2654
				$memID,
2655
			),
2656
		),
2657
		'get_count' => array(
2658
			'function' => 'list_getProfileEditCount',
2659
			'params' => array(
2660
				$memID,
2661
			),
2662
		),
2663
		'columns' => array(
2664
			'action' => array(
2665
				'header' => array(
2666
					'value' => $txt['trackEdit_action'],
2667
				),
2668
				'data' => array(
2669
					'db' => 'action_text',
2670
				),
2671
			),
2672
			'before' => array(
2673
				'header' => array(
2674
					'value' => $txt['trackEdit_before'],
2675
				),
2676
				'data' => array(
2677
					'db' => 'before',
2678
				),
2679
			),
2680
			'after' => array(
2681
				'header' => array(
2682
					'value' => $txt['trackEdit_after'],
2683
				),
2684
				'data' => array(
2685
					'db' => 'after',
2686
				),
2687
			),
2688
			'time' => array(
2689
				'header' => array(
2690
					'value' => $txt['date'],
2691
				),
2692
				'data' => array(
2693
					'db' => 'time',
2694
				),
2695
				'sort' => array(
2696
					'default' => 'id_action DESC',
2697
					'reverse' => 'id_action',
2698
				),
2699
			),
2700
			'applicator' => array(
2701
				'header' => array(
2702
					'value' => $txt['trackEdit_applicator'],
2703
				),
2704
				'data' => array(
2705
					'db' => 'member_link',
2706
				),
2707
			),
2708
		),
2709
	);
2710
2711
	// Create the error list.
2712
	createList($listOptions);
2713
2714
	$context['sub_template'] = 'show_list';
2715
	$context['default_list'] = 'edit_list';
2716
}
2717
2718
/**
2719
 * How many edits?
2720
 *
2721
 * @param int $memID The ID of the member
2722
 * @return int The number of profile edits
2723
 */
2724
function list_getProfileEditCount($memID)
2725
{
2726
	global $smcFunc;
2727
2728
	$request = $smcFunc['db_query']('', '
2729
		SELECT COUNT(*) AS edit_count
2730
		FROM {db_prefix}log_actions
2731
		WHERE id_log = {int:log_type}
2732
			AND id_member = {int:owner}',
2733
		array(
2734
			'log_type' => 2,
2735
			'owner' => $memID,
2736
		)
2737
	);
2738
	list ($edit_count) = $smcFunc['db_fetch_row']($request);
2739
	$smcFunc['db_free_result']($request);
2740
2741
	return (int) $edit_count;
2742
}
2743
2744
/**
2745
 * Loads up information about a user's profile edits. Callback for the list in trackEdits()
2746
 *
2747
 * @param int $start Which item to start with (for pagination purposes)
2748
 * @param int $items_per_page How many items to show on each page
2749
 * @param string $sort A string indicating how to sort the results
2750
 * @param int $memID The ID of the member
2751
 * @return array An array of information about the profile edits
2752
 */
2753
function list_getProfileEdits($start, $items_per_page, $sort, $memID)
2754
{
2755
	global $smcFunc, $txt, $scripturl, $context;
2756
2757
	// Get a list of error messages from this ip (range).
2758
	$request = $smcFunc['db_query']('', '
2759
		SELECT
2760
			id_action, id_member, ip, log_time, action, extra
2761
		FROM {db_prefix}log_actions
2762
		WHERE id_log = {int:log_type}
2763
			AND id_member = {int:owner}
2764
		ORDER BY {raw:sort}
2765
		LIMIT {int:start}, {int:max}',
2766
		array(
2767
			'log_type' => 2,
2768
			'owner' => $memID,
2769
			'sort' => $sort,
2770
			'start' => $start,
2771
			'max' => $items_per_page,
2772
		)
2773
	);
2774
	$edits = array();
2775
	$members = array();
2776
	while ($row = $smcFunc['db_fetch_assoc']($request))
2777
	{
2778
		$extra = $smcFunc['json_decode']($row['extra'], true);
2779
		if (!empty($extra['applicator']))
2780
			$members[] = $extra['applicator'];
2781
2782
		// Work out what the name of the action is.
2783
		if (isset($txt['trackEdit_action_' . $row['action']]))
2784
			$action_text = $txt['trackEdit_action_' . $row['action']];
2785
		elseif (isset($txt[$row['action']]))
2786
			$action_text = $txt[$row['action']];
2787
		// Custom field?
2788
		elseif (isset($context['custom_field_titles'][$row['action']]))
2789
			$action_text = $context['custom_field_titles'][$row['action']]['title'];
2790
		else
2791
			$action_text = $row['action'];
2792
2793
		// Parse BBC?
2794
		$parse_bbc = isset($context['custom_field_titles'][$row['action']]) && $context['custom_field_titles'][$row['action']]['parse_bbc'] ? true : false;
2795
2796
		$edits[] = array(
2797
			'id' => $row['id_action'],
2798
			'ip' => inet_dtop($row['ip']),
2799
			'id_member' => !empty($extra['applicator']) ? $extra['applicator'] : 0,
2800
			'member_link' => $txt['trackEdit_deleted_member'],
2801
			'action' => $row['action'],
2802
			'action_text' => $action_text,
2803
			'before' => !empty($extra['previous']) ? ($parse_bbc ? parse_bbc($extra['previous']) : $extra['previous']) : '',
2804
			'after' => !empty($extra['new']) ? ($parse_bbc ? parse_bbc($extra['new']) : $extra['new']) : '',
2805
			'time' => timeformat($row['log_time']),
2806
		);
2807
	}
2808
	$smcFunc['db_free_result']($request);
2809
2810
	// Get any member names.
2811
	if (!empty($members))
2812
	{
2813
		$request = $smcFunc['db_query']('', '
2814
			SELECT
2815
				id_member, real_name
2816
			FROM {db_prefix}members
2817
			WHERE id_member IN ({array_int:members})',
2818
			array(
2819
				'members' => $members,
2820
			)
2821
		);
2822
		$members = array();
2823
		while ($row = $smcFunc['db_fetch_assoc']($request))
2824
			$members[$row['id_member']] = $row['real_name'];
2825
		$smcFunc['db_free_result']($request);
2826
2827
		foreach ($edits as $key => $value)
2828
			if (isset($members[$value['id_member']]))
2829
				$edits[$key]['member_link'] = '<a href="' . $scripturl . '?action=profile;u=' . $value['id_member'] . '">' . $members[$value['id_member']] . '</a>';
2830
	}
2831
2832
	return $edits;
2833
}
2834
2835
/**
2836
 * Display the history of group requests made by the user whose profile we are viewing.
2837
 *
2838
 * @param int $memID The ID of the member
2839
 */
2840
function trackGroupReq($memID)
2841
{
2842
	global $scripturl, $txt, $modSettings, $sourcedir, $context;
2843
2844
	require_once($sourcedir . '/Subs-List.php');
2845
2846
	// Set the options for the error lists.
2847
	$listOptions = array(
2848
		'id' => 'request_list',
2849
		'title' => sprintf($txt['trackGroupRequests_title'], $context['member']['name']),
2850
		'items_per_page' => $modSettings['defaultMaxListItems'],
2851
		'no_items_label' => $txt['requested_none'],
2852
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=groupreq;u=' . $memID,
2853
		'default_sort_col' => 'time_applied',
2854
		'get_items' => array(
2855
			'function' => 'list_getGroupRequests',
2856
			'params' => array(
2857
				$memID,
2858
			),
2859
		),
2860
		'get_count' => array(
2861
			'function' => 'list_getGroupRequestsCount',
2862
			'params' => array(
2863
				$memID,
2864
			),
2865
		),
2866
		'columns' => array(
2867
			'group' => array(
2868
				'header' => array(
2869
					'value' => $txt['requested_group'],
2870
				),
2871
				'data' => array(
2872
					'db' => 'group_name',
2873
				),
2874
			),
2875
			'group_reason' => array(
2876
				'header' => array(
2877
					'value' => $txt['requested_group_reason'],
2878
				),
2879
				'data' => array(
2880
					'db' => 'group_reason',
2881
				),
2882
			),
2883
			'time_applied' => array(
2884
				'header' => array(
2885
					'value' => $txt['requested_group_time'],
2886
				),
2887
				'data' => array(
2888
					'db' => 'time_applied',
2889
					'timeformat' => true,
2890
				),
2891
				'sort' => array(
2892
					'default' => 'time_applied DESC',
2893
					'reverse' => 'time_applied',
2894
				),
2895
			),
2896
			'outcome' => array(
2897
				'header' => array(
2898
					'value' => $txt['requested_group_outcome'],
2899
				),
2900
				'data' => array(
2901
					'db' => 'outcome',
2902
				),
2903
			),
2904
		),
2905
	);
2906
2907
	// Create the error list.
2908
	createList($listOptions);
2909
2910
	$context['sub_template'] = 'show_list';
2911
	$context['default_list'] = 'request_list';
2912
}
2913
2914
/**
2915
 * How many edits?
2916
 *
2917
 * @param int $memID The ID of the member
2918
 * @return int The number of profile edits
2919
 */
2920
function list_getGroupRequestsCount($memID)
2921
{
2922
	global $smcFunc, $user_info;
2923
2924
	$request = $smcFunc['db_query']('', '
2925
		SELECT COUNT(*) AS req_count
2926
		FROM {db_prefix}log_group_requests AS lgr
2927
		WHERE id_member = {int:memID}
2928
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']),
2929
		array(
2930
			'memID' => $memID,
2931
		)
2932
	);
2933
	list ($report_count) = $smcFunc['db_fetch_row']($request);
2934
	$smcFunc['db_free_result']($request);
2935
2936
	return (int) $report_count;
2937
}
2938
2939
/**
2940
 * Loads up information about a user's group requests. Callback for the list in trackGroupReq()
2941
 *
2942
 * @param int $start Which item to start with (for pagination purposes)
2943
 * @param int $items_per_page How many items to show on each page
2944
 * @param string $sort A string indicating how to sort the results
2945
 * @param int $memID The ID of the member
2946
 * @return array An array of information about the user's group requests
2947
 */
2948
function list_getGroupRequests($start, $items_per_page, $sort, $memID)
2949
{
2950
	global $smcFunc, $txt, $scripturl, $user_info;
2951
2952
	$groupreq = array();
2953
2954
	$request = $smcFunc['db_query']('', '
2955
		SELECT
2956
			lgr.id_group, mg.group_name, mg.online_color, lgr.time_applied, lgr.reason, lgr.status,
2957
			ma.id_member AS id_member_acted, COALESCE(ma.member_name, lgr.member_name_acted) AS act_name, lgr.time_acted, lgr.act_reason
2958
		FROM {db_prefix}log_group_requests AS lgr
2959
			LEFT JOIN {db_prefix}members AS ma ON (lgr.id_member_acted = ma.id_member)
2960
			INNER JOIN {db_prefix}membergroups AS mg ON (lgr.id_group = mg.id_group)
2961
		WHERE lgr.id_member = {int:memID}
2962
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']) . '
2963
		ORDER BY {raw:sort}
2964
		LIMIT {int:start}, {int:max}',
2965
		array(
2966
			'memID' => $memID,
2967
			'sort' => $sort,
2968
			'start' => $start,
2969
			'max' => $items_per_page,
2970
		)
2971
	);
2972
	while ($row = $smcFunc['db_fetch_assoc']($request))
2973
	{
2974
		$this_req = array(
2975
			'group_name' => empty($row['online_color']) ? $row['group_name'] : '<span style="color:' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
2976
			'group_reason' => $row['reason'],
2977
			'time_applied' => $row['time_applied'],
2978
		);
2979
		switch ($row['status'])
2980
		{
2981
			case 0:
2982
				$this_req['outcome'] = $txt['outcome_pending'];
2983
				break;
2984
			case 1:
2985
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2986
				$this_req['outcome'] = sprintf($txt['outcome_approved'], $member_link, timeformat($row['time_acted']));
2987
				break;
2988
			case 2:
2989
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
2990
				$this_req['outcome'] = sprintf(!empty($row['act_reason']) ? $txt['outcome_refused_reason'] : $txt['outcome_refused'], $member_link, timeformat($row['time_acted']), $row['act_reason']);
2991
				break;
2992
		}
2993
2994
		$groupreq[] = $this_req;
2995
	}
2996
	$smcFunc['db_free_result']($request);
2997
2998
	return $groupreq;
2999
}
3000
3001
/**
3002
 * Shows which permissions a user has
3003
 *
3004
 * @param int $memID The ID of the member
3005
 */
3006
function showPermissions($memID)
3007
{
3008
	global $txt, $board;
3009
	global $user_profile, $context, $sourcedir, $smcFunc;
3010
3011
	// Verify if the user has sufficient permissions.
3012
	isAllowedTo('manage_permissions');
3013
3014
	loadLanguage('ManagePermissions');
3015
	loadLanguage('Admin');
3016
	loadTemplate('ManageMembers');
3017
3018
	// Load all the permission profiles.
3019
	require_once($sourcedir . '/ManagePermissions.php');
3020
	loadPermissionProfiles();
3021
3022
	$context['member']['id'] = $memID;
3023
	$context['member']['name'] = $user_profile[$memID]['real_name'];
3024
3025
	$context['page_title'] = $txt['showPermissions'];
3026
	$board = empty($board) ? 0 : (int) $board;
3027
	$context['board'] = $board;
3028
3029
	// Determine which groups this user is in.
3030
	if (empty($user_profile[$memID]['additional_groups']))
3031
		$curGroups = array();
3032
	else
3033
		$curGroups = explode(',', $user_profile[$memID]['additional_groups']);
3034
	$curGroups[] = $user_profile[$memID]['id_group'];
3035
	$curGroups[] = $user_profile[$memID]['id_post_group'];
3036
3037
	// Load a list of boards for the jump box - except the defaults.
3038
	$request = $smcFunc['db_query']('order_by_board_order', '
3039
		SELECT b.id_board, b.name, b.id_profile, b.member_groups, COALESCE(mods.id_member, modgs.id_group, 0) AS is_mod
3040
		FROM {db_prefix}boards AS b
3041
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
3042
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:current_groups}))
3043
		WHERE {query_see_board}',
3044
		array(
3045
			'current_member' => $memID,
3046
			'current_groups' => $curGroups,
3047
		)
3048
	);
3049
	$context['boards'] = array();
3050
	$context['no_access_boards'] = array();
3051
	while ($row = $smcFunc['db_fetch_assoc']($request))
3052
	{
3053
		if (count(array_intersect($curGroups, explode(',', $row['member_groups']))) === 0 && !$row['is_mod'])
3054
			$context['no_access_boards'][] = array(
3055
				'id' => $row['id_board'],
3056
				'name' => $row['name'],
3057
				'is_last' => false,
3058
			);
3059
		elseif ($row['id_profile'] != 1 || $row['is_mod'])
3060
			$context['boards'][$row['id_board']] = array(
3061
				'id' => $row['id_board'],
3062
				'name' => $row['name'],
3063
				'selected' => $board == $row['id_board'],
3064
				'profile' => $row['id_profile'],
3065
				'profile_name' => $context['profiles'][$row['id_profile']]['name'],
3066
			);
3067
	}
3068
	$smcFunc['db_free_result']($request);
3069
3070
	require_once($sourcedir . '/Subs-Boards.php');
3071
	sortBoards($context['boards']);
3072
3073
	if (!empty($context['no_access_boards']))
3074
		$context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true;
3075
3076
	$context['member']['permissions'] = array(
3077
		'general' => array(),
3078
		'board' => array()
3079
	);
3080
3081
	// If you're an admin we know you can do everything, we might as well leave.
3082
	$context['member']['has_all_permissions'] = in_array(1, $curGroups);
3083
	if ($context['member']['has_all_permissions'])
3084
		return;
3085
3086
	$denied = array();
3087
3088
	// Get all general permissions.
3089
	$result = $smcFunc['db_query']('', '
3090
		SELECT p.permission, p.add_deny, mg.group_name, p.id_group
3091
		FROM {db_prefix}permissions AS p
3092
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = p.id_group)
3093
		WHERE p.id_group IN ({array_int:group_list})
3094
		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',
3095
		array(
3096
			'group_list' => $curGroups,
3097
			'newbie_group' => 4,
3098
		)
3099
	);
3100
	while ($row = $smcFunc['db_fetch_assoc']($result))
3101
	{
3102
		// We don't know about this permission, it doesn't exist :P.
3103
		if (!isset($txt['permissionname_' . $row['permission']]))
3104
			continue;
3105
3106
		if (empty($row['add_deny']))
3107
			$denied[] = $row['permission'];
3108
3109
		// Permissions that end with _own or _any consist of two parts.
3110
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
3111
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
3112
		else
3113
			$name = $txt['permissionname_' . $row['permission']];
3114
3115
		// Add this permission if it doesn't exist yet.
3116
		if (!isset($context['member']['permissions']['general'][$row['permission']]))
3117
			$context['member']['permissions']['general'][$row['permission']] = array(
3118
				'id' => $row['permission'],
3119
				'groups' => array(
3120
					'allowed' => array(),
3121
					'denied' => array()
3122
				),
3123
				'name' => $name,
3124
				'is_denied' => false,
3125
				'is_global' => true,
3126
			);
3127
3128
		// Add the membergroup to either the denied or the allowed groups.
3129
		$context['member']['permissions']['general'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name'];
3130
3131
		// Once denied is always denied.
3132
		$context['member']['permissions']['general'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
3133
	}
3134
	$smcFunc['db_free_result']($result);
3135
3136
	$request = $smcFunc['db_query']('', '
3137
		SELECT
3138
			bp.add_deny, bp.permission, bp.id_group, mg.group_name' . (empty($board) ? '' : ',
3139
			b.id_profile, CASE WHEN (mods.id_member IS NULL AND modgs.id_group IS NULL) THEN 0 ELSE 1 END AS is_moderator') . '
3140
		FROM {db_prefix}board_permissions AS bp' . (empty($board) ? '' : '
3141
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = {int:current_board})
3142
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
3143
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list}))') . '
3144
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = bp.id_group)
3145
		WHERE bp.id_profile = {raw:current_profile}
3146
			AND bp.id_group IN ({array_int:group_list}' . (empty($board) ? ')' : ', {int:moderator_group})
3147
			AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group})'),
3148
		array(
3149
			'current_board' => $board,
3150
			'group_list' => $curGroups,
3151
			'current_member' => $memID,
3152
			'current_profile' => empty($board) ? '1' : 'b.id_profile',
3153
			'moderator_group' => 3,
3154
		)
3155
	);
3156
3157
	while ($row = $smcFunc['db_fetch_assoc']($request))
3158
	{
3159
		// We don't know about this permission, it doesn't exist :P.
3160
		if (!isset($txt['permissionname_' . $row['permission']]))
3161
			continue;
3162
3163
		// The name of the permission using the format 'permission name' - 'own/any topic/event/etc.'.
3164
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
3165
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
3166
		else
3167
			$name = $txt['permissionname_' . $row['permission']];
3168
3169
		// Create the structure for this permission.
3170
		if (!isset($context['member']['permissions']['board'][$row['permission']]))
3171
			$context['member']['permissions']['board'][$row['permission']] = array(
3172
				'id' => $row['permission'],
3173
				'groups' => array(
3174
					'allowed' => array(),
3175
					'denied' => array()
3176
				),
3177
				'name' => $name,
3178
				'is_denied' => false,
3179
				'is_global' => empty($board),
3180
			);
3181
3182
		$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'];
3183
3184
		$context['member']['permissions']['board'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
3185
	}
3186
	$smcFunc['db_free_result']($request);
3187
}
3188
3189
/**
3190
 * View a member's warnings
3191
 *
3192
 * @param int $memID The ID of the member
3193
 */
3194
function viewWarning($memID)
3195
{
3196
	global $modSettings, $context, $sourcedir, $txt, $scripturl;
3197
3198
	// Firstly, can we actually even be here?
3199
	if (!($context['user']['is_owner'] && allowedTo('view_warning_own')) && !allowedTo('view_warning_any') && !allowedTo('issue_warning') && !allowedTo('moderate_forum'))
3200
		fatal_lang_error('no_access', false);
3201
3202
	// Make sure things which are disabled stay disabled.
3203
	$modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110;
3204
	$modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110;
3205
	$modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110;
3206
3207
	// Let's use a generic list to get all the current warnings, and use the issue warnings grab-a-granny thing.
3208
	require_once($sourcedir . '/Subs-List.php');
3209
	require_once($sourcedir . '/Profile-Actions.php');
3210
3211
	$listOptions = array(
3212
		'id' => 'view_warnings',
3213
		'title' => $txt['profile_viewwarning_previous_warnings'],
3214
		'items_per_page' => $modSettings['defaultMaxListItems'],
3215
		'no_items_label' => $txt['profile_viewwarning_no_warnings'],
3216
		'base_href' => $scripturl . '?action=profile;area=viewwarning;sa=user;u=' . $memID,
3217
		'default_sort_col' => 'log_time',
3218
		'get_items' => array(
3219
			'function' => 'list_getUserWarnings',
3220
			'params' => array(
3221
				$memID,
3222
			),
3223
		),
3224
		'get_count' => array(
3225
			'function' => 'list_getUserWarningCount',
3226
			'params' => array(
3227
				$memID,
3228
			),
3229
		),
3230
		'columns' => array(
3231
			'log_time' => array(
3232
				'header' => array(
3233
					'value' => $txt['profile_warning_previous_time'],
3234
				),
3235
				'data' => array(
3236
					'db' => 'time',
3237
				),
3238
				'sort' => array(
3239
					'default' => 'lc.log_time DESC',
3240
					'reverse' => 'lc.log_time',
3241
				),
3242
			),
3243
			'reason' => array(
3244
				'header' => array(
3245
					'value' => $txt['profile_warning_previous_reason'],
3246
					'style' => 'width: 50%;',
3247
				),
3248
				'data' => array(
3249
					'db' => 'reason',
3250
				),
3251
			),
3252
			'level' => array(
3253
				'header' => array(
3254
					'value' => $txt['profile_warning_previous_level'],
3255
				),
3256
				'data' => array(
3257
					'db' => 'counter',
3258
				),
3259
				'sort' => array(
3260
					'default' => 'lc.counter DESC',
3261
					'reverse' => 'lc.counter',
3262
				),
3263
			),
3264
		),
3265
		'additional_rows' => array(
3266
			array(
3267
				'position' => 'after_title',
3268
				'value' => $txt['profile_viewwarning_desc'],
3269
				'class' => 'smalltext',
3270
				'style' => 'padding: 2ex;',
3271
			),
3272
		),
3273
	);
3274
3275
	// Create the list for viewing.
3276
	require_once($sourcedir . '/Subs-List.php');
3277
	createList($listOptions);
3278
3279
	// Create some common text bits for the template.
3280
	$context['level_effects'] = array(
3281
		0 => '',
3282
		$modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'],
3283
		$modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'],
3284
		$modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'],
3285
	);
3286
	$context['current_level'] = 0;
3287
	foreach ($context['level_effects'] as $limit => $dummy)
3288
		if ($context['member']['warning'] >= $limit)
3289
			$context['current_level'] = $limit;
3290
}
3291
3292
/**
3293
 * Sets the icon for a fetched alert.
3294
 *
3295
 * @param array The alert that we want to set an icon for.
0 ignored issues
show
Bug introduced by
The type The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3296
 */
3297
function set_alert_icon($alert)
3298
{
3299
	global $settings;
3300
3301
	switch ($alert['content_type'])
3302
	{
3303
		case 'topic':
3304
		case 'board':
3305
			{
3306
				switch ($alert['content_action'])
3307
				{
3308
					case 'reply':
3309
					case 'topic':
3310
						$class = 'main_icons posts';
3311
						break;
3312
3313
					case 'move':
3314
						$src = $settings['images_url'] . '/post/moved.png';
3315
						break;
3316
3317
					case 'remove':
3318
						$class = 'main_icons delete';
3319
						break;
3320
3321
					case 'lock':
3322
					case 'unlock':
3323
						$class = 'main_icons lock';
3324
						break;
3325
3326
					case 'sticky':
3327
					case 'unsticky':
3328
						$class = 'main_icons sticky';
3329
						break;
3330
3331
					case 'split':
3332
						$class = 'main_icons split_button';
3333
						break;
3334
3335
					case 'merge':
3336
						$class = 'main_icons merge';
3337
						break;
3338
3339
					case 'unapproved_topic':
3340
					case 'unapproved_post':
3341
						$class = 'main_icons post_moderation_moderate';
3342
						break;
3343
3344
					default:
3345
						$class = 'main_icons posts';
3346
						break;
3347
				}
3348
			}
3349
			break;
3350
3351
		case 'msg':
3352
			{
3353
				switch ($alert['content_action'])
3354
				{
3355
					case 'like':
3356
						$class = 'main_icons like';
3357
						break;
3358
3359
					case 'mention':
3360
						$class = 'main_icons im_on';
3361
						break;
3362
3363
					case 'quote':
3364
						$class = 'main_icons quote';
3365
						break;
3366
3367
					case 'unapproved_attachment':
3368
						$class = 'main_icons post_moderation_attach';
3369
						break;
3370
3371
					case 'report':
3372
					case 'report_reply':
3373
						$class = 'main_icons post_moderation_moderate';
3374
						break;
3375
3376
					default:
3377
						$class = 'main_icons posts';
3378
						break;
3379
				}
3380
			}
3381
			break;
3382
3383
		case 'member':
3384
			{
3385
				switch ($alert['content_action'])
3386
				{
3387
					case 'register_standard':
3388
					case 'register_approval':
3389
					case 'register_activation':
3390
						$class = 'main_icons members';
3391
						break;
3392
3393
					case 'report':
3394
					case 'report_reply':
3395
						$class = 'main_icons members_watched';
3396
						break;
3397
3398
					case 'buddy_request':
3399
						$class = 'main_icons people';
3400
						break;
3401
3402
					case 'group_request':
3403
						$class = 'main_icons members_request';
3404
						break;
3405
3406
					default:
3407
						$class = 'main_icons members';
3408
						break;
3409
				}
3410
			}
3411
			break;
3412
3413
		case 'groupr':
3414
			$class = 'main_icons members_request';
3415
			break;
3416
3417
		case 'event':
3418
			$class = 'main_icons calendar';
3419
			break;
3420
3421
		case 'paidsubs':
3422
			$class = 'main_icons paid';
3423
			break;
3424
3425
		case 'birthday':
3426
			$src = $settings['images_url'] . '/cake.png';
3427
			break;
3428
3429
		default:
3430
			$class = 'main_icons alerts';
3431
			break;
3432
	}
3433
3434
	if (isset($class))
3435
		return '<span class="alert_icon ' . $class . '"></span>';
3436
	elseif (isset($src))
3437
		return '<img class="alert_icon" src="' . $src . '">';
3438
	else
3439
		return '';
3440
}
3441
3442
?>