Passed
Pull Request — release-2.1 (#6452)
by John
04:04
created

showPosts()   F

Complexity

Conditions 84

Size

Total Lines 375
Code Lines 214

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 84
eloc 214
c 3
b 0
f 0
nop 1
dl 0
loc 375
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2021 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC3
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);
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 && $birth_year > 1004
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)
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, false, 'minimal');
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(
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
	$formats = array_map(function ($format) use ($scripturl) {
363
		$format['link'] = str_replace('{scripturl}', $scripturl, $format['link']);
364
		$format['text'] = str_replace('{scripturl}', $scripturl, $format['text']);
365
366
		return $format;
367
	}, $formats);
368
369
	// If we need to check board access, use the correct board access filter for the member in question.
370
	if ((!isset($user_info['query_see_board']) || $user_info['id'] != $memID) && (!empty($possible_msgs) || !empty($possible_topics)))
371
		$qb = build_query_board($memID);
372
	else
373
		$qb['query_see_board'] = '{query_see_board}';
374
375
	// For anything that needs more info and/or wants us to check board or topic access, let's do that.
376
	if (!empty($possible_msgs))
377
	{
378
		$flipped_msgs = array();
379
		foreach ($possible_msgs as $id_alert => $id_msg)
380
		{
381
			if (!isset($flipped_msgs[$id_msg]))
382
				$flipped_msgs[$id_msg] = array();
383
384
			$flipped_msgs[$id_msg][] = $id_alert;
385
		}
386
387
		$request = $smcFunc['db_query']('', '
388
			SELECT m.id_msg, m.id_topic, m.subject, b.id_board, b.name AS board_name
389
			FROM {db_prefix}messages AS m
390
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
391
			WHERE ' . $qb['query_see_board'] . '
392
				AND m.id_msg IN ({array_int:msgs})
393
			ORDER BY m.id_msg',
394
			array(
395
				'msgs' => $possible_msgs,
396
			)
397
		);
398
		while ($row = $smcFunc['db_fetch_assoc']($request))
399
		{
400
			foreach ($flipped_msgs[$row['id_msg']] as $id_alert)
401
			{
402
				$alerts[$id_alert]['content_data'] = $row;
403
				$alerts[$id_alert]['visible'] = true;
404
			}
405
		}
406
		$smcFunc['db_free_result']($request);
407
	}
408
	if (!empty($possible_topics))
409
	{
410
		$flipped_topics = array();
411
		foreach ($possible_topics as $id_alert => $id_topic)
412
		{
413
			if (!isset($flipped_topics[$id_topic]))
414
				$flipped_topics[$id_topic] = array();
415
416
			$flipped_topics[$id_topic][] = $id_alert;
417
		}
418
419
		$request = $smcFunc['db_query']('', '
420
			SELECT m.id_msg, t.id_topic, m.subject, b.id_board, b.name AS board_name
421
			FROM {db_prefix}topics AS t
422
				INNER JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
423
				INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
424
			WHERE ' . $qb['query_see_board'] . '
425
				AND t.id_topic IN ({array_int:topics})',
426
			array(
427
				'topics' => $possible_topics,
428
			)
429
		);
430
		while ($row = $smcFunc['db_fetch_assoc']($request))
431
		{
432
			foreach ($flipped_topics[$row['id_topic']] as $id_alert)
433
			{
434
				$alerts[$id_alert]['content_data'] = $row;
435
				$alerts[$id_alert]['visible'] = true;
436
			}
437
		}
438
		$smcFunc['db_free_result']($request);
439
	}
440
441
	// 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)
442
	foreach ($alerts as $id_alert => $dummy)
443
	{
444
		// There's no point showing alerts for inaccessible content.
445
		if (!$alerts[$id_alert]['visible'])
446
		{
447
			unset($alerts[$id_alert]);
448
			continue;
449
		}
450
		else
451
			unset($alerts[$id_alert]['visible']);
452
453
		// Did a mod already take care of this one?
454
		if (!empty($alerts[$id_alert]['text']))
455
			continue;
456
457
		// For developer convenience.
458
		$alert = &$alerts[$id_alert];
459
460
		// The info in extra might outdated if the topic was moved, the message's subject was changed, etc.
461
		if (!empty($alert['content_data']))
462
		{
463
			$data = $alert['content_data'];
464
465
			// Make sure msg, topic, and board info are correct.
466
			$patterns = array();
467
			$replacements = array();
468
			foreach (array('msg', 'topic', 'board') as $item)
469
			{
470
				if (isset($data['id_' . $item]))
471
				{
472
					$separator = $item == 'msg' ? '=?' : '=';
473
474
					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)
475
					{
476
						$patterns[] = '/\b' . $item . $separator . '\d+/';
477
						$replacements[] = $item . $separator . $data['id_' . $item];
478
					}
479
480
					$alert['extra'][$item] = $data['id_' . $item];
481
				}
482
			}
483
			if (!empty($patterns))
484
				$alert['extra']['content_link'] = preg_replace($patterns, $replacements, $alert['extra']['content_link']);
485
486
			// Make sure the subject is correct.
487
			if (isset($data['subject']))
488
				$alert['extra']['content_subject'] = $data['subject'];
489
490
			// Keep track of this so we can use it below.
491
			if (isset($data['board_name']))
492
				$alert['extra']['board_name'] = $data['board_name'];
493
494
			unset($alert['content_data']);
495
		}
496
497
		// Do we want to link to the topic in general or the new messages specifically?
498
		if (isset($possible_topics[$id_alert]) && in_array($alert['content_action'], array('reply', 'topic', 'unapproved_reply')))
499
				$alert['extra']['topic_suffix'] = 'new;topicseen#new';
500
		elseif (isset($alert['extra']['topic']))
501
			$alert['extra']['topic_suffix'] = '0';
502
503
		// Make sure profile alerts have what they need.
504
		if (in_array($id_alert, $profile_alerts))
505
		{
506
			if (empty($alert['extra']['user_id']))
507
				$alert['extra']['user_id'] = $alert['content_id'];
508
509
			if (isset($user_profile[$alert['extra']['user_id']]))
510
				$alert['extra']['user_name'] = $user_profile[$alert['extra']['user_id']]['real_name'];
511
		}
512
513
		// If we loaded the sender's profile, we may as well use it.
514
		$sender_id = !empty($alert['sender_id']) ? $alert['sender_id'] : 0;
515
		if (isset($user_profile[$sender_id]))
516
			$alert['sender_name'] = $user_profile[$sender_id]['real_name'];
517
518
		// If requested, include the sender's avatar data.
519
		if ($with_avatar && !empty($senders[$sender_id]))
520
			$alert['sender'] = $senders[$sender_id];
521
522
		// Next, build the message strings.
523
		foreach ($formats as $msg_type => $format_info)
524
		{
525
			// Get the values to use in the formatted string, in the right order.
526
			$msg_values = array_replace(
527
				array_fill_keys($format_info['required'], ''),
528
				array_intersect_key($alert['extra'], array_flip($format_info['required']))
529
			);
530
531
			// Assuming all required values are present, build the message.
532
			if (!in_array('', $msg_values))
533
				$alert['extra'][$msg_type] = vsprintf($formats[$msg_type][$alert['show_links'] ? 'link' : 'text'], $msg_values);
534
535
			elseif (in_array($msg_type, array('msg_msg', 'topic_msg', 'board_msg')))
536
				$alert['extra'][$msg_type] = $txt[$msg_type == 'board_msg' ? 'board_na' : 'topic_na'];
537
			else
538
				$alert['extra'][$msg_type] = '(' . $txt['not_applicable'] . ')';
539
		}
540
541
		// Show the formatted time in alerts about subscriptions.
542
		if ($alert['content_type'] == 'paidsubs' && isset($alert['extra']['end_time']))
543
		{
544
			// If the subscription already expired, say so.
545
			if ($alert['extra']['end_time'] < time())
546
				$alert['content_action'] = 'expired';
547
548
			// Present a nicely formatted date.
549
			$alert['extra']['end_time'] = timeformat($alert['extra']['end_time']);
550
		}
551
552
		// Now set the main URL that this alert should take the user to.
553
		$alert['target_href'] = '';
554
555
		// Priority goes to explicitly specified links.
556
		if (isset($alert['extra']['content_link']))
557
			$alert['target_href'] = $alert['extra']['content_link'];
558
559
		elseif (isset($alert['extra']['report_link']))
560
			$alert['target_href'] = $scripturl . $alert['extra']['report_link'];
561
562
		// Next, try determining the link based on the content action.
563
		if (empty($alert['target_href']) && in_array($alert['content_action'], array('register_approval', 'group_request', 'buddy_request')))
564
		{
565
			switch ($alert['content_action'])
566
			{
567
				case 'register_approval':
568
					$alert['target_href'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve';
569
					break;
570
571
				case 'group_request':
572
					$alert['target_href'] = $scripturl . '?action=moderate;area=groups;sa=requests';
573
					break;
574
575
				case 'buddy_request':
576
					if (!empty($alert['id_member_started']))
577
						$alert['target_href'] = $scripturl . '?action=profile;u=' . $alert['id_member_started'];
578
					break;
579
580
				default:
581
					break;
582
			}
583
		}
584
585
		// Or maybe we can determine the link based on the content type.
586
		if (empty($alert['target_href']) && in_array($alert['content_type'], array('msg', 'member', 'event')))
587
		{
588
			switch ($alert['content_type'])
589
			{
590
				case 'msg':
591
					if (!empty($alert['content_id']))
592
						$alert['target_href'] = $scripturl . '?msg=' . $alert['content_id'];
593
					break;
594
595
				case 'member':
596
					if (!empty($alert['id_member_started']))
597
						$alert['target_href'] = $scripturl . '?action=profile;u=' . $alert['id_member_started'];
598
					break;
599
600
				case 'event':
601
					if (!empty($alert['extra']['event_id']))
602
						$alert['target_href'] = $scripturl . '?action=calendar;event=' . $alert['extra']['event_id'];
603
					break;
604
605
				default:
606
					break;
607
			}
608
609
		}
610
611
		// Finally, set this alert's text string.
612
		$string = 'alert_' . $alert['content_type'] . '_' . $alert['content_action'];
613
614
		// This kludge exists because the alert content_types prior to 2.1 RC3 were a bit haphazard.
615
		// This can be removed once all the translated language files have been updated.
616
		if (!isset($txt[$string]))
617
		{
618
			if (strpos($alert['content_action'], 'unapproved_') === 0)
619
				$string = 'alert_' . $alert['content_action'];
620
621
			if ($alert['content_type'] === 'member' && in_array($alert['content_action'], array('report', 'report_reply')))
622
				$string = 'alert_profile_' . $alert['content_action'];
623
624
			if ($alert['content_type'] === 'member' && $alert['content_action'] === 'buddy_request')
625
				$string = 'alert_buddy_' . $alert['content_action'];
626
		}
627
628
		if (isset($txt[$string]))
629
		{
630
			$substitutions = array(
631
				'{scripturl}' => $scripturl,
632
				'{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>',
633
			);
634
635
			if (is_array($alert['extra']))
636
			{
637
				foreach ($alert['extra'] as $k => $v)
638
					$substitutions['{' . $k . '}'] = $v;
639
			}
640
641
			$alert['text'] = strtr($txt[$string], $substitutions);
642
		}
643
644
		// Unset the reference variable to avoid any surprises in subsequent loops.
645
		unset($alert);
646
	}
647
648
	return $alerts;
649
}
650
651
/**
652
 * Shows all alerts for a member
653
 *
654
 * @param int $memID The ID of the member
655
 */
656
function showAlerts($memID)
657
{
658
	global $context, $smcFunc, $txt, $sourcedir, $scripturl, $options;
659
660
	require_once($sourcedir . '/Profile-Modify.php');
661
662
	// Are we opening a specific alert? (i.e.: ?action=profile;area=showalerts;alert=12345)
663
	if (!empty($_REQUEST['alert']))
664
	{
665
		$alert_id = (int) $_REQUEST['alert'];
666
		$alerts = fetch_alerts($memID, $alert_id);
667
		$alert = array_pop($alerts);
668
669
		/*
670
		 * MOD AUTHORS:
671
		 * To control this redirect, use the 'integrate_fetch_alerts' hook to
672
		 * set the value of $alert['extra']['content_link'], which will become
673
		 * the value for $alert['target_href'].
674
		 */
675
676
		// In case it failed to determine this alert's link
677
		if (empty($alert['target_href']))
678
			redirectexit('action=profile;area=showalerts');
679
680
		// Mark the alert as read while we're at it.
681
		alert_mark($memID, $alert_id, 1);
682
683
		// Take the user to the content
684
		redirectexit($alert['target_href']);
685
	}
686
687
	// Prepare the pagination vars.
688
	$maxIndex = 10;
689
	$context['start'] = (int) isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
690
	$count = alert_count($memID);
691
692
	// Fix invalid 'start' offsets.
693
	if ($context['start'] > $count)
694
		$context['start'] = $count - ($count % $maxIndex);
695
	else
696
		$context['start'] = $context['start'] - ($context['start'] % $maxIndex);
697
698
	// Get the alerts.
699
	$context['alerts'] = fetch_alerts($memID, true, $maxIndex, $context['start'], true, true);
700
	$toMark = false;
701
	$action = '';
702
703
	//  Are we using checkboxes?
704
	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1;
705
706
	// Create the pagination.
707
	$context['pagination'] = constructPageIndex($scripturl . '?action=profile;area=showalerts;u=' . $memID, $context['start'], $count, $maxIndex, false);
708
709
	// Set some JavaScript for checking all alerts at once.
710
	if ($context['showCheckboxes'])
711
		addInlineJavaScript('
712
		$(function(){
713
			$(\'#select_all\').on(\'change\', function() {
714
				var checkboxes = $(\'ul.quickbuttons\').find(\':checkbox\');
715
				if($(this).prop(\'checked\')) {
716
					checkboxes.prop(\'checked\', true);
717
				}
718
				else {
719
					checkboxes.prop(\'checked\', false);
720
				}
721
			});
722
		});', true);
723
724
	// The quickbuttons
725
	foreach ($context['alerts'] as $id => $alert)
726
	{
727
		$context['alerts'][$id]['quickbuttons'] = array(
728
			'delete' => array(
729
				'label' => $txt['delete'],
730
				'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'] : ''),
731
				'class' => 'you_sure',
732
				'icon' => 'remove_button'
733
			),
734
			'mark' => array(
735
				'label' => $alert['is_read'] != 0 ? $txt['mark_unread'] : $txt['mark_read_short'],
736
				'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'] : ''),
737
				'icon' => $alert['is_read'] != 0 ? 'unread_button' : 'read_button',
738
			),
739
			'view' => array(
740
				'label' => $txt['view'],
741
				'href' => $alert['target_href'],
742
				'icon' => 'move',
743
			),
744
			'quickmod' => array(
745
    			'class' => 'inline_mod_check',
746
				'content' => '<input type="checkbox" name="mark[' . $id . ']" value="' . $id . '">',
747
				'show' => $context['showCheckboxes']
748
			)
749
		);
750
	}
751
752
	// The Delete all unread link.
753
	$context['alert_purge_link'] = $scripturl . '?action=profile;u=' . $context['id_member'] . ';area=showalerts;do=purge;' . $context['session_var'] . '=' . $context['session_id'] . (!empty($context['start']) ? ';start=' . $context['start'] : '');
754
755
	// Set a nice message.
756
	if (!empty($_SESSION['update_message']))
757
	{
758
		$context['update_message'] = $txt['profile_updated_own'];
759
		unset($_SESSION['update_message']);
760
	}
761
762
	// Saving multiple changes?
763
	if (isset($_GET['save']) && !empty($_POST['mark']))
764
	{
765
		// Get the values.
766
		$toMark = array_map('intval', (array) $_POST['mark']);
767
768
		// Which action?
769
		$action = !empty($_POST['mark_as']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_POST['mark_as'])) : '';
770
	}
771
772
	// A single change.
773
	if (!empty($_GET['do']) && !empty($_GET['aid']))
774
	{
775
		$toMark = (int) $_GET['aid'];
776
		$action = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_GET['do']));
777
	}
778
	// Delete all read alerts.
779
	elseif (!empty($_GET['do']) && $_GET['do'] === 'purge')
780
		$action = 'purge';
781
782
	// Save the changes.
783
	if (!empty($action) && (!empty($toMark) || $action === 'purge'))
784
	{
785
		checkSession('request');
786
787
		// Call it!
788
		if ($action == 'remove')
789
			alert_delete($toMark, $memID);
790
791
		elseif ($action == 'purge')
792
			alert_purge($memID);
793
794
		else
795
			alert_mark($memID, $toMark, $action == 'read' ? 1 : 0);
796
797
		// Set a nice update message.
798
		$_SESSION['update_message'] = true;
799
800
		// Redirect.
801
		redirectexit('action=profile;area=showalerts;u=' . $memID . (!empty($context['start']) ? ';start=' . $context['start'] : ''));
802
	}
803
}
804
805
/**
806
 * Show all posts by a member
807
 *
808
 * @todo This function needs to be split up properly.
809
 *
810
 * @param int $memID The ID of the member
811
 */
812
function showPosts($memID)
813
{
814
	global $txt, $user_info, $scripturl, $modSettings, $options;
815
	global $context, $user_profile, $sourcedir, $smcFunc, $board;
816
817
	// Some initial context.
818
	$context['start'] = (int) $_REQUEST['start'];
819
	$context['current_member'] = $memID;
820
821
	// Create the tabs for the template.
822
	$context[$context['profile_menu_name']]['tab_data'] = array(
823
		'title' => $txt['showPosts'],
824
		'description' => $txt['showPosts_help'],
825
		'icon' => 'profile_hd.png',
826
		'tabs' => array(
827
			'messages' => array(
828
			),
829
			'topics' => array(
830
			),
831
			'unwatchedtopics' => array(
832
			),
833
			'attach' => array(
834
			),
835
		),
836
	);
837
838
	// Shortcut used to determine which $txt['show*'] string to use for the title, based on the SA
839
	$title = array(
840
		'attach' => 'Attachments',
841
		'topics' => 'Topics'
842
	);
843
844
	if ($context['user']['is_owner'])
845
		$title['unwatchedtopics'] = 'Unwatched';
846
847
	// Set the page title
848
	if (isset($_GET['sa']) && array_key_exists($_GET['sa'], $title))
849
		$context['page_title'] = $txt['show' . $title[$_GET['sa']]];
850
	else
851
		$context['page_title'] = $txt['showPosts'];
852
853
	$context['page_title'] .= ' - ' . $user_profile[$memID]['real_name'];
854
855
	// Is the load average too high to allow searching just now?
856
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_show_posts']) && $context['load_average'] >= $modSettings['loadavg_show_posts'])
857
		fatal_lang_error('loadavg_show_posts_disabled', false);
858
859
	// If we're specifically dealing with attachments use that function!
860
	if (isset($_GET['sa']) && $_GET['sa'] == 'attach')
861
		return showAttachments($memID);
862
	// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
863
	elseif (isset($_GET['sa']) && $_GET['sa'] == 'unwatchedtopics' && $context['user']['is_owner'])
864
		return showUnwatched($memID);
865
866
	// Are we just viewing topics?
867
	$context['is_topics'] = isset($_GET['sa']) && $_GET['sa'] == 'topics' ? true : false;
868
869
	// If just deleting a message, do it and then redirect back.
870
	if (isset($_GET['delete']) && !$context['is_topics'])
871
	{
872
		checkSession('get');
873
874
		// We need msg info for logging.
875
		$request = $smcFunc['db_query']('', '
876
			SELECT subject, id_member, id_topic, id_board
877
			FROM {db_prefix}messages
878
			WHERE id_msg = {int:id_msg}',
879
			array(
880
				'id_msg' => (int) $_GET['delete'],
881
			)
882
		);
883
		$info = $smcFunc['db_fetch_row']($request);
884
		$smcFunc['db_free_result']($request);
885
886
		// Trying to remove a message that doesn't exist.
887
		if (empty($info))
888
			redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
889
890
		// We can be lazy, since removeMessage() will check the permissions for us.
891
		require_once($sourcedir . '/RemoveTopic.php');
892
		removeMessage((int) $_GET['delete']);
893
894
		// Add it to the mod log.
895
		if (allowedTo('delete_any') && (!allowedTo('delete_own') || $info[1] != $user_info['id']))
896
			logAction('delete', array('topic' => $info[2], 'subject' => $info[0], 'member' => $info[1], 'board' => $info[3]));
897
898
		// Back to... where we are now ;).
899
		redirectexit('action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start']);
900
	}
901
902
	// Default to 10.
903
	if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
904
		$_REQUEST['viewscount'] = '10';
905
906
	if ($context['is_topics'])
907
		$request = $smcFunc['db_query']('', '
908
			SELECT COUNT(*)
909
			FROM {db_prefix}topics AS t' . '
910
			WHERE {query_see_topic_board}
911
				AND t.id_member_started = {int:current_member}' . (!empty($board) ? '
912
				AND t.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
913
				AND t.approved = {int:is_approved}'),
914
			array(
915
				'current_member' => $memID,
916
				'is_approved' => 1,
917
				'board' => $board,
918
			)
919
		);
920
	else
921
		$request = $smcFunc['db_query']('', '
922
			SELECT COUNT(*)
923
			FROM {db_prefix}messages AS m
924
			WHERE {query_see_message_board} AND m.id_member = {int:current_member}' . (!empty($board) ? '
925
				AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
926
				AND m.approved = {int:is_approved}'),
927
			array(
928
				'current_member' => $memID,
929
				'is_approved' => 1,
930
				'board' => $board,
931
			)
932
		);
933
	list ($msgCount) = $smcFunc['db_fetch_row']($request);
934
	$smcFunc['db_free_result']($request);
935
936
	$request = $smcFunc['db_query']('', '
937
		SELECT MIN(id_msg), MAX(id_msg)
938
		FROM {db_prefix}messages AS m
939
		WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
940
			AND m.id_board = {int:board}' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
941
			AND m.approved = {int:is_approved}'),
942
		array(
943
			'current_member' => $memID,
944
			'is_approved' => 1,
945
			'board' => $board,
946
		)
947
	);
948
	list ($min_msg_member, $max_msg_member) = $smcFunc['db_fetch_row']($request);
949
	$smcFunc['db_free_result']($request);
950
951
	$range_limit = '';
952
953
	if ($context['is_topics'])
954
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page']) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'];
955
	else
956
		$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
957
958
	$maxIndex = $maxPerPage;
959
960
	// Make sure the starting place makes sense and construct our friend the page index.
961
	$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : '') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex);
962
	$context['current_page'] = $context['start'] / $maxIndex;
963
964
	// Reverse the query if we're past 50% of the pages for better performance.
965
	$start = $context['start'];
966
	$reverse = $_REQUEST['start'] > $msgCount / 2;
967
	if ($reverse)
968
	{
969
		$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
970
		$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
971
	}
972
973
	// Guess the range of messages to be shown.
974
	if ($msgCount > 1000)
975
	{
976
		$margin = floor(($max_msg_member - $min_msg_member) * (($start + $maxPerPage) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
977
		// Make a bigger margin for topics only.
978
		if ($context['is_topics'])
979
		{
980
			$margin *= 5;
981
			$range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin);
982
		}
983
		else
984
			$range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin);
985
	}
986
987
	// Find this user's posts.  The left join on categories somehow makes this faster, weird as it looks.
988
	$looped = false;
989
	while (true)
990
	{
991
		if ($context['is_topics'])
992
		{
993
			$request = $smcFunc['db_query']('', '
994
				SELECT
995
					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,
996
					t.approved, m.body, m.smileys_enabled, m.subject, m.poster_time, m.id_topic, m.id_msg
997
				FROM {db_prefix}topics AS t
998
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
999
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1000
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1001
				WHERE t.id_member_started = {int:current_member}' . (!empty($board) ? '
1002
					AND t.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
1003
					AND ' . $range_limit) . '
1004
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1005
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
1006
				ORDER BY t.id_first_msg ' . ($reverse ? 'ASC' : 'DESC') . '
1007
				LIMIT {int:start}, {int:max}',
1008
				array(
1009
					'current_member' => $memID,
1010
					'is_approved' => 1,
1011
					'board' => $board,
1012
					'start' => $start,
1013
					'max' => $maxIndex,
1014
				)
1015
			);
1016
		}
1017
		else
1018
		{
1019
			$request = $smcFunc['db_query']('', '
1020
				SELECT
1021
					b.id_board, b.name AS bname, c.id_cat, c.name AS cname, m.id_topic, m.id_msg,
1022
					t.id_member_started, t.id_first_msg, t.id_last_msg, m.body, m.smileys_enabled,
1023
					m.subject, m.poster_time, m.approved
1024
				FROM {db_prefix}messages AS m
1025
					INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1026
					INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1027
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1028
				WHERE m.id_member = {int:current_member}' . (!empty($board) ? '
1029
					AND b.id_board = {int:board}' : '') . (empty($range_limit) ? '' : '
1030
					AND ' . $range_limit) . '
1031
					AND {query_see_board}' . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1032
					AND t.approved = {int:is_approved} AND m.approved = {int:is_approved}') . '
1033
				ORDER BY m.id_msg ' . ($reverse ? 'ASC' : 'DESC') . '
1034
				LIMIT {int:start}, {int:max}',
1035
				array(
1036
					'current_member' => $memID,
1037
					'is_approved' => 1,
1038
					'board' => $board,
1039
					'start' => $start,
1040
					'max' => $maxIndex,
1041
				)
1042
			);
1043
		}
1044
1045
		// Make sure we quit this loop.
1046
		if ($smcFunc['db_num_rows']($request) === $maxIndex || $looped || $range_limit == '')
1047
			break;
1048
		$looped = true;
1049
		$range_limit = '';
1050
	}
1051
1052
	// Start counting at the number of the first message displayed.
1053
	$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
1054
	$context['posts'] = array();
1055
	$board_ids = array('own' => array(), 'any' => array());
1056
	while ($row = $smcFunc['db_fetch_assoc']($request))
1057
	{
1058
		// Censor....
1059
		censorText($row['body']);
1060
		censorText($row['subject']);
1061
1062
		// Do the code.
1063
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
1064
1065
		// And the array...
1066
		$context['posts'][$counter += $reverse ? -1 : 1] = array(
1067
			'body' => $row['body'],
1068
			'counter' => $counter,
1069
			'category' => array(
1070
				'name' => $row['cname'],
1071
				'id' => $row['id_cat']
1072
			),
1073
			'board' => array(
1074
				'name' => $row['bname'],
1075
				'id' => $row['id_board']
1076
			),
1077
			'topic' => $row['id_topic'],
1078
			'subject' => $row['subject'],
1079
			'start' => 'msg' . $row['id_msg'],
1080
			'time' => timeformat($row['poster_time']),
1081
			'timestamp' => forum_time(true, $row['poster_time']),
1082
			'id' => $row['id_msg'],
1083
			'can_reply' => false,
1084
			'can_mark_notify' => !$context['user']['is_guest'],
1085
			'can_delete' => false,
1086
			'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()),
1087
			'approved' => $row['approved'],
1088
			'css_class' => $row['approved'] ? 'windowbg' : 'approvebg',
1089
		);
1090
1091
		if ($user_info['id'] == $row['id_member_started'])
1092
			$board_ids['own'][$row['id_board']][] = $counter;
1093
		$board_ids['any'][$row['id_board']][] = $counter;
1094
	}
1095
	$smcFunc['db_free_result']($request);
1096
1097
	// All posts were retrieved in reverse order, get them right again.
1098
	if ($reverse)
1099
		$context['posts'] = array_reverse($context['posts'], true);
1100
1101
	// These are all the permissions that are different from board to board..
1102
	if ($context['is_topics'])
1103
		$permissions = array(
1104
			'own' => array(
1105
				'post_reply_own' => 'can_reply',
1106
			),
1107
			'any' => array(
1108
				'post_reply_any' => 'can_reply',
1109
			)
1110
		);
1111
	else
1112
		$permissions = array(
1113
			'own' => array(
1114
				'post_reply_own' => 'can_reply',
1115
				'delete_own' => 'can_delete',
1116
			),
1117
			'any' => array(
1118
				'post_reply_any' => 'can_reply',
1119
				'delete_any' => 'can_delete',
1120
			)
1121
		);
1122
1123
	// Create an array for the permissions.
1124
	$boards_can = boardsAllowedTo(array_keys(iterator_to_array(
1125
		new RecursiveIteratorIterator(new RecursiveArrayIterator($permissions)))
1126
	), true, false);
1127
1128
	// For every permission in the own/any lists...
1129
	foreach ($permissions as $type => $list)
1130
	{
1131
		foreach ($list as $permission => $allowed)
1132
		{
1133
			// Get the boards they can do this on...
1134
			$boards = $boards_can[$permission];
1135
1136
			// Hmm, they can do it on all boards, can they?
1137
			if (!empty($boards) && $boards[0] == 0)
1138
				$boards = array_keys($board_ids[$type]);
1139
1140
			// Now go through each board they can do the permission on.
1141
			foreach ($boards as $board_id)
1142
			{
1143
				// There aren't any posts displayed from this board.
1144
				if (!isset($board_ids[$type][$board_id]))
1145
					continue;
1146
1147
				// Set the permission to true ;).
1148
				foreach ($board_ids[$type][$board_id] as $counter)
1149
					$context['posts'][$counter][$allowed] = true;
1150
			}
1151
		}
1152
	}
1153
1154
	// Clean up after posts that cannot be deleted and quoted.
1155
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
1156
	foreach ($context['posts'] as $counter => $dummy)
1157
	{
1158
		$context['posts'][$counter]['can_delete'] &= $context['posts'][$counter]['delete_possible'];
1159
		$context['posts'][$counter]['can_quote'] = $context['posts'][$counter]['can_reply'] && $quote_enabled;
1160
	}
1161
1162
	// Allow last minute changes.
1163
	call_integration_hook('integrate_profile_showPosts');
1164
1165
	foreach ($context['posts'] as $key => $post)
1166
	{
1167
		$context['posts'][$key]['quickbuttons'] = array(
1168
			'reply' => array(
1169
				'label' => $txt['reply'],
1170
				'href' => $scripturl.'?action=post;topic='.$post['topic'].'.'.$post['start'],
1171
				'icon' => 'reply_button',
1172
				'show' => $post['can_reply']
1173
			),
1174
			'quote' => array(
1175
				'label' => $txt['quote_action'],
1176
				'href' => $scripturl.'?action=post;topic='.$post['topic'].'.'.$post['start'].';quote='.$post['id'],
1177
				'icon' => 'quote',
1178
				'show' => $post['can_quote']
1179
			),
1180
			'remove' => array(
1181
				'label' => $txt['remove'],
1182
				'href' => $scripturl.'?action=deletemsg;msg='.$post['id'].';topic='.$post['topic'].';profile;u='.$context['member']['id'].';start='.$context['start'].';'.$context['session_var'].'='.$context['session_id'],
1183
				'javascript' => 'data-confirm="'.$txt['remove_message'].'"',
1184
				'class' => 'you_sure',
1185
				'icon' => 'remove_button',
1186
				'show' => $post['can_delete']
1187
			)
1188
		);
1189
	}
1190
}
1191
1192
/**
1193
 * Show all the attachments belonging to a member.
1194
 *
1195
 * @param int $memID The ID of the member
1196
 */
1197
function showAttachments($memID)
1198
{
1199
	global $txt, $scripturl, $modSettings;
1200
	global $sourcedir;
1201
1202
	// OBEY permissions!
1203
	$boardsAllowed = boardsAllowedTo('view_attachments');
1204
1205
	// Make sure we can't actually see anything...
1206
	if (empty($boardsAllowed))
1207
		$boardsAllowed = array(-1);
1208
1209
	require_once($sourcedir . '/Subs-List.php');
1210
1211
	// This is all the information required to list attachments.
1212
	$listOptions = array(
1213
		'id' => 'attachments',
1214
		'width' => '100%',
1215
		'items_per_page' => $modSettings['defaultMaxListItems'],
1216
		'no_items_label' => $txt['show_attachments_none'],
1217
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=attach;u=' . $memID,
1218
		'default_sort_col' => 'filename',
1219
		'get_items' => array(
1220
			'function' => 'list_getAttachments',
1221
			'params' => array(
1222
				$boardsAllowed,
1223
				$memID,
1224
			),
1225
		),
1226
		'get_count' => array(
1227
			'function' => 'list_getNumAttachments',
1228
			'params' => array(
1229
				$boardsAllowed,
1230
				$memID,
1231
			),
1232
		),
1233
		'data_check' => array(
1234
			'class' => function($data)
1235
			{
1236
				return $data['approved'] ? '' : 'approvebg';
1237
			}
1238
		),
1239
		'columns' => array(
1240
			'filename' => array(
1241
				'header' => array(
1242
					'value' => $txt['show_attach_filename'],
1243
					'class' => 'lefttext',
1244
					'style' => 'width: 25%;',
1245
				),
1246
				'data' => array(
1247
					'sprintf' => array(
1248
						'format' => '<a href="' . $scripturl . '?action=dlattach;topic=%1$d.0;attach=%2$d">%3$s</a>%4$s',
1249
						'params' => array(
1250
							'topic' => true,
1251
							'id' => true,
1252
							'filename' => false,
1253
							'awaiting_approval' => false,
1254
						),
1255
					),
1256
				),
1257
				'sort' => array(
1258
					'default' => 'a.filename',
1259
					'reverse' => 'a.filename DESC',
1260
				),
1261
			),
1262
			'downloads' => array(
1263
				'header' => array(
1264
					'value' => $txt['show_attach_downloads'],
1265
					'style' => 'width: 12%;',
1266
				),
1267
				'data' => array(
1268
					'db' => 'downloads',
1269
					'comma_format' => true,
1270
				),
1271
				'sort' => array(
1272
					'default' => 'a.downloads',
1273
					'reverse' => 'a.downloads DESC',
1274
				),
1275
			),
1276
			'subject' => array(
1277
				'header' => array(
1278
					'value' => $txt['message'],
1279
					'class' => 'lefttext',
1280
					'style' => 'width: 30%;',
1281
				),
1282
				'data' => array(
1283
					'sprintf' => array(
1284
						'format' => '<a href="' . $scripturl . '?msg=%1$d">%2$s</a>',
1285
						'params' => array(
1286
							'msg' => true,
1287
							'subject' => false,
1288
						),
1289
					),
1290
				),
1291
				'sort' => array(
1292
					'default' => 'm.subject',
1293
					'reverse' => 'm.subject DESC',
1294
				),
1295
			),
1296
			'posted' => array(
1297
				'header' => array(
1298
					'value' => $txt['show_attach_posted'],
1299
					'class' => 'lefttext',
1300
				),
1301
				'data' => array(
1302
					'db' => 'posted',
1303
					'timeformat' => true,
1304
				),
1305
				'sort' => array(
1306
					'default' => 'm.poster_time',
1307
					'reverse' => 'm.poster_time DESC',
1308
				),
1309
			),
1310
		),
1311
	);
1312
1313
	// Create the request list.
1314
	createList($listOptions);
1315
}
1316
1317
/**
1318
 * Get a list of attachments for a member. Callback for the list in showAttachments()
1319
 *
1320
 * @param int $start Which item to start with (for pagination purposes)
1321
 * @param int $items_per_page How many items to show on each page
1322
 * @param string $sort A string indicating how to sort the results
1323
 * @param array $boardsAllowed An array containing the IDs of the boards they can see
1324
 * @param int $memID The ID of the member
1325
 * @return array An array of information about the attachments
1326
 */
1327
function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed, $memID)
1328
{
1329
	global $smcFunc, $board, $modSettings, $context, $txt;
1330
1331
	// Retrieve some attachments.
1332
	$request = $smcFunc['db_query']('', '
1333
		SELECT a.id_attach, a.id_msg, a.filename, a.downloads, a.approved, m.id_msg, m.id_topic,
1334
			m.id_board, m.poster_time, m.subject, b.name
1335
		FROM {db_prefix}attachments AS a
1336
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1337
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1338
		WHERE a.attachment_type = {int:attachment_type}
1339
			AND a.id_msg != {int:no_message}
1340
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
1341
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
1342
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || allowedTo('approve_posts') || $context['user']['is_owner'] ? '' : '
1343
			AND a.approved = {int:is_approved}') . '
1344
		ORDER BY {raw:sort}
1345
		LIMIT {int:offset}, {int:limit}',
1346
		array(
1347
			'boards_list' => $boardsAllowed,
1348
			'attachment_type' => 0,
1349
			'no_message' => 0,
1350
			'current_member' => $memID,
1351
			'is_approved' => 1,
1352
			'board' => $board,
1353
			'sort' => $sort,
1354
			'offset' => $start,
1355
			'limit' => $items_per_page,
1356
		)
1357
	);
1358
	$attachments = array();
1359
	while ($row = $smcFunc['db_fetch_assoc']($request))
1360
		$attachments[] = array(
1361
			'id' => $row['id_attach'],
1362
			'filename' => $row['filename'],
1363
			'downloads' => $row['downloads'],
1364
			'subject' => censorText($row['subject']),
1365
			'posted' => $row['poster_time'],
1366
			'msg' => $row['id_msg'],
1367
			'topic' => $row['id_topic'],
1368
			'board' => $row['id_board'],
1369
			'board_name' => $row['name'],
1370
			'approved' => $row['approved'],
1371
			'awaiting_approval' => (empty($row['approved']) ? ' <em>(' . $txt['awaiting_approval'] . ')</em>' : ''),
1372
		);
1373
1374
	$smcFunc['db_free_result']($request);
1375
1376
	return $attachments;
1377
}
1378
1379
/**
1380
 * Gets the total number of attachments for a member
1381
 *
1382
 * @param array $boardsAllowed An array of the IDs of the boards they can see
1383
 * @param int $memID The ID of the member
1384
 * @return int The number of attachments
1385
 */
1386
function list_getNumAttachments($boardsAllowed, $memID)
1387
{
1388
	global $board, $smcFunc, $modSettings, $context;
1389
1390
	// Get the total number of attachments they have posted.
1391
	$request = $smcFunc['db_query']('', '
1392
		SELECT COUNT(*)
1393
		FROM {db_prefix}attachments AS a
1394
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)
1395
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
1396
		WHERE a.attachment_type = {int:attachment_type}
1397
			AND a.id_msg != {int:no_message}
1398
			AND m.id_member = {int:current_member}' . (!empty($board) ? '
1399
			AND b.id_board = {int:board}' : '') . (!in_array(0, $boardsAllowed) ? '
1400
			AND b.id_board IN ({array_int:boards_list})' : '') . (!$modSettings['postmod_active'] || $context['user']['is_owner'] ? '' : '
1401
			AND m.approved = {int:is_approved}'),
1402
		array(
1403
			'boards_list' => $boardsAllowed,
1404
			'attachment_type' => 0,
1405
			'no_message' => 0,
1406
			'current_member' => $memID,
1407
			'is_approved' => 1,
1408
			'board' => $board,
1409
		)
1410
	);
1411
	list ($attachCount) = $smcFunc['db_fetch_row']($request);
1412
	$smcFunc['db_free_result']($request);
1413
1414
	return $attachCount;
1415
}
1416
1417
/**
1418
 * Show all the unwatched topics.
1419
 *
1420
 * @param int $memID The ID of the member
1421
 */
1422
function showUnwatched($memID)
1423
{
1424
	global $txt, $user_info, $scripturl, $modSettings, $context, $options, $sourcedir;
1425
1426
	// Only the owner can see the list (if the function is enabled of course)
1427
	if ($user_info['id'] != $memID)
1428
		return;
1429
1430
	require_once($sourcedir . '/Subs-List.php');
1431
1432
	// And here they are: the topics you don't like
1433
	$listOptions = array(
1434
		'id' => 'unwatched_topics',
1435
		'width' => '100%',
1436
		'items_per_page' => (empty($modSettings['disableCustomPerPage']) && !empty($options['topics_per_page'])) ? $options['topics_per_page'] : $modSettings['defaultMaxTopics'],
1437
		'no_items_label' => $txt['unwatched_topics_none'],
1438
		'base_href' => $scripturl . '?action=profile;area=showposts;sa=unwatchedtopics;u=' . $memID,
1439
		'default_sort_col' => 'started_on',
1440
		'get_items' => array(
1441
			'function' => 'list_getUnwatched',
1442
			'params' => array(
1443
				$memID,
1444
			),
1445
		),
1446
		'get_count' => array(
1447
			'function' => 'list_getNumUnwatched',
1448
			'params' => array(
1449
				$memID,
1450
			),
1451
		),
1452
		'columns' => array(
1453
			'subject' => array(
1454
				'header' => array(
1455
					'value' => $txt['subject'],
1456
					'class' => 'lefttext',
1457
					'style' => 'width: 30%;',
1458
				),
1459
				'data' => array(
1460
					'sprintf' => array(
1461
						'format' => '<a href="' . $scripturl . '?topic=%1$d.0">%2$s</a>',
1462
						'params' => array(
1463
							'id_topic' => false,
1464
							'subject' => false,
1465
						),
1466
					),
1467
				),
1468
				'sort' => array(
1469
					'default' => 'm.subject',
1470
					'reverse' => 'm.subject DESC',
1471
				),
1472
			),
1473
			'started_by' => array(
1474
				'header' => array(
1475
					'value' => $txt['started_by'],
1476
					'style' => 'width: 15%;',
1477
				),
1478
				'data' => array(
1479
					'db' => 'started_by',
1480
				),
1481
				'sort' => array(
1482
					'default' => 'mem.real_name',
1483
					'reverse' => 'mem.real_name DESC',
1484
				),
1485
			),
1486
			'started_on' => array(
1487
				'header' => array(
1488
					'value' => $txt['on'],
1489
					'class' => 'lefttext',
1490
					'style' => 'width: 20%;',
1491
				),
1492
				'data' => array(
1493
					'db' => 'started_on',
1494
					'timeformat' => true,
1495
				),
1496
				'sort' => array(
1497
					'default' => 'm.poster_time',
1498
					'reverse' => 'm.poster_time DESC',
1499
				),
1500
			),
1501
			'last_post_by' => array(
1502
				'header' => array(
1503
					'value' => $txt['last_post'],
1504
					'style' => 'width: 15%;',
1505
				),
1506
				'data' => array(
1507
					'db' => 'last_post_by',
1508
				),
1509
				'sort' => array(
1510
					'default' => 'mem.real_name',
1511
					'reverse' => 'mem.real_name DESC',
1512
				),
1513
			),
1514
			'last_post_on' => array(
1515
				'header' => array(
1516
					'value' => $txt['on'],
1517
					'class' => 'lefttext',
1518
					'style' => 'width: 20%;',
1519
				),
1520
				'data' => array(
1521
					'db' => 'last_post_on',
1522
					'timeformat' => true,
1523
				),
1524
				'sort' => array(
1525
					'default' => 'm.poster_time',
1526
					'reverse' => 'm.poster_time DESC',
1527
				),
1528
			),
1529
		),
1530
	);
1531
1532
	// Create the request list.
1533
	createList($listOptions);
1534
1535
	$context['sub_template'] = 'show_list';
1536
	$context['default_list'] = 'unwatched_topics';
1537
}
1538
1539
/**
1540
 * Gets information about unwatched (disregarded) topics. Callback for the list in show_unwatched
1541
 *
1542
 * @param int $start The item to start with (for pagination purposes)
1543
 * @param int $items_per_page How many items to show on each page
1544
 * @param string $sort A string indicating how to sort the results
1545
 * @param int $memID The ID of the member
1546
 * @return array An array of information about the unwatched topics
1547
 */
1548
function list_getUnwatched($start, $items_per_page, $sort, $memID)
1549
{
1550
	global $smcFunc;
1551
1552
	// Get the list of topics we can see
1553
	$request = $smcFunc['db_query']('', '
1554
		SELECT lt.id_topic
1555
		FROM {db_prefix}log_topics as lt
1556
			LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1557
			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')) ? '
1558
			LEFT JOIN {db_prefix}members as mem ON (m.id_member = mem.id_member)' : '') . '
1559
		WHERE lt.id_member = {int:current_member}
1560
			AND unwatched = 1
1561
			AND {query_see_message_board}
1562
		ORDER BY {raw:sort}
1563
		LIMIT {int:offset}, {int:limit}',
1564
		array(
1565
			'current_member' => $memID,
1566
			'sort' => $sort,
1567
			'offset' => $start,
1568
			'limit' => $items_per_page,
1569
		)
1570
	);
1571
1572
	$topics = array();
1573
	while ($row = $smcFunc['db_fetch_assoc']($request))
1574
		$topics[] = $row['id_topic'];
1575
1576
	$smcFunc['db_free_result']($request);
1577
1578
	// Any topics found?
1579
	$topicsInfo = array();
1580
	if (!empty($topics))
1581
	{
1582
		$request = $smcFunc['db_query']('', '
1583
			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
1584
			FROM {db_prefix}topics AS t
1585
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
1586
				INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1587
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
1588
				LEFT JOIN {db_prefix}members AS memf ON (memf.id_member = mf.id_member)
1589
			WHERE t.id_topic IN ({array_int:topics})',
1590
			array(
1591
				'topics' => $topics,
1592
			)
1593
		);
1594
		while ($row = $smcFunc['db_fetch_assoc']($request))
1595
			$topicsInfo[] = $row;
1596
		$smcFunc['db_free_result']($request);
1597
	}
1598
1599
	return $topicsInfo;
1600
}
1601
1602
/**
1603
 * Count the number of topics in the unwatched list
1604
 *
1605
 * @param int $memID The ID of the member
1606
 * @return int The number of unwatched topics
1607
 */
1608
function list_getNumUnwatched($memID)
1609
{
1610
	global $smcFunc;
1611
1612
	// Get the total number of attachments they have posted.
1613
	$request = $smcFunc['db_query']('', '
1614
		SELECT COUNT(*)
1615
		FROM {db_prefix}log_topics as lt
1616
		LEFT JOIN {db_prefix}topics as t ON (lt.id_topic = t.id_topic)
1617
		WHERE lt.id_member = {int:current_member}
1618
			AND lt.unwatched = 1
1619
			AND {query_see_topic_board}',
1620
		array(
1621
			'current_member' => $memID,
1622
		)
1623
	);
1624
	list ($unwatchedCount) = $smcFunc['db_fetch_row']($request);
1625
	$smcFunc['db_free_result']($request);
1626
1627
	return $unwatchedCount;
1628
}
1629
1630
/**
1631
 * Gets the user stats for display
1632
 *
1633
 * @param int $memID The ID of the member
1634
 */
1635
function statPanel($memID)
1636
{
1637
	global $txt, $scripturl, $context, $user_profile, $user_info, $modSettings, $smcFunc;
1638
1639
	$context['page_title'] = $txt['statPanel_showStats'] . ' ' . $user_profile[$memID]['real_name'];
1640
1641
	// Is the load average too high to allow searching just now?
1642
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_userstats']) && $context['load_average'] >= $modSettings['loadavg_userstats'])
1643
		fatal_lang_error('loadavg_userstats_disabled', false);
1644
1645
	// General user statistics.
1646
	$timeDays = floor($user_profile[$memID]['total_time_logged_in'] / 86400);
1647
	$timeHours = floor(($user_profile[$memID]['total_time_logged_in'] % 86400) / 3600);
1648
	$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'];
1649
	$context['num_posts'] = comma_format($user_profile[$memID]['posts']);
1650
	// Menu tab
1651
	$context[$context['profile_menu_name']]['tab_data'] = array(
1652
		'title' => $txt['statPanel_generalStats'] . ' - ' . $context['member']['name'],
1653
		'icon' => 'stats_info.png'
1654
	);
1655
1656
	// Number of topics started and Number polls started
1657
	$result = $smcFunc['db_query']('', '
1658
		SELECT COUNT(*), COUNT( CASE WHEN id_poll != {int:no_poll} THEN 1 ELSE NULL END )
1659
		FROM {db_prefix}topics
1660
		WHERE id_member_started = {int:current_member}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1661
			AND id_board != {int:recycle_board}' : ''),
1662
		array(
1663
			'current_member' => $memID,
1664
			'recycle_board' => $modSettings['recycle_board'],
1665
			'no_poll' => 0,
1666
		)
1667
	);
1668
	list ($context['num_topics'], $context['num_polls']) = $smcFunc['db_fetch_row']($result);
1669
	$smcFunc['db_free_result']($result);
1670
1671
	// Number polls voted in.
1672
	$result = $smcFunc['db_query']('distinct_poll_votes', '
1673
		SELECT COUNT(DISTINCT id_poll)
1674
		FROM {db_prefix}log_polls
1675
		WHERE id_member = {int:current_member}',
1676
		array(
1677
			'current_member' => $memID,
1678
		)
1679
	);
1680
	list ($context['num_votes']) = $smcFunc['db_fetch_row']($result);
1681
	$smcFunc['db_free_result']($result);
1682
1683
	// Format the numbers...
1684
	$context['num_topics'] = comma_format($context['num_topics']);
1685
	$context['num_polls'] = comma_format($context['num_polls']);
1686
	$context['num_votes'] = comma_format($context['num_votes']);
1687
1688
	// Grab the board this member posted in most often.
1689
	$result = $smcFunc['db_query']('', '
1690
		SELECT
1691
			b.id_board, MAX(b.name) AS name, MAX(b.num_posts) AS num_posts, COUNT(*) AS message_count
1692
		FROM {db_prefix}messages AS m
1693
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1694
		WHERE m.id_member = {int:current_member}
1695
			AND b.count_posts = {int:count_enabled}
1696
			AND {query_see_board}
1697
		GROUP BY b.id_board
1698
		ORDER BY message_count DESC
1699
		LIMIT 10',
1700
		array(
1701
			'current_member' => $memID,
1702
			'count_enabled' => 0,
1703
		)
1704
	);
1705
	$context['popular_boards'] = array();
1706
	while ($row = $smcFunc['db_fetch_assoc']($result))
1707
	{
1708
		$context['popular_boards'][$row['id_board']] = array(
1709
			'id' => $row['id_board'],
1710
			'posts' => $row['message_count'],
1711
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1712
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1713
			'posts_percent' => $user_profile[$memID]['posts'] == 0 ? 0 : ($row['message_count'] * 100) / $user_profile[$memID]['posts'],
1714
			'total_posts' => $row['num_posts'],
1715
			'total_posts_member' => $user_profile[$memID]['posts'],
1716
		);
1717
	}
1718
	$smcFunc['db_free_result']($result);
1719
1720
	// Now get the 10 boards this user has most often participated in.
1721
	$result = $smcFunc['db_query']('profile_board_stats', '
1722
		SELECT
1723
			b.id_board, MAX(b.name) AS name, b.num_posts, COUNT(*) AS message_count,
1724
			CASE WHEN COUNT(*) > MAX(b.num_posts) THEN 1 ELSE COUNT(*) / MAX(b.num_posts) END * 100 AS percentage
1725
		FROM {db_prefix}messages AS m
1726
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1727
		WHERE m.id_member = {int:current_member}
1728
			AND {query_see_board}
1729
		GROUP BY b.id_board, b.num_posts
1730
		ORDER BY percentage DESC
1731
		LIMIT 10',
1732
		array(
1733
			'current_member' => $memID,
1734
		)
1735
	);
1736
	$context['board_activity'] = array();
1737
	while ($row = $smcFunc['db_fetch_assoc']($result))
1738
	{
1739
		$context['board_activity'][$row['id_board']] = array(
1740
			'id' => $row['id_board'],
1741
			'posts' => $row['message_count'],
1742
			'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
1743
			'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
1744
			'percent' => comma_format((float) $row['percentage'], 2),
1745
			'posts_percent' => (float) $row['percentage'],
1746
			'total_posts' => $row['num_posts'],
1747
		);
1748
	}
1749
	$smcFunc['db_free_result']($result);
1750
1751
	// Posting activity by time.
1752
	$result = $smcFunc['db_query']('user_activity_by_time', '
1753
		SELECT
1754
			HOUR(FROM_UNIXTIME(poster_time + {int:time_offset})) AS hour,
1755
			COUNT(*) AS post_count
1756
		FROM (
1757
			SELECT poster_time, id_msg
1758
			FROM {db_prefix}messages WHERE id_member = {int:current_member}
1759
			ORDER BY id_msg DESC
1760
			LIMIT {int:max_messages}
1761
		) a
1762
		GROUP BY hour',
1763
		array(
1764
			'current_member' => $memID,
1765
			'time_offset' => (($user_info['time_offset'] + $modSettings['time_offset']) * 3600),
1766
			'max_messages' => 1001,
1767
		)
1768
	);
1769
	$maxPosts = $realPosts = 0;
1770
	$context['posts_by_time'] = array();
1771
	while ($row = $smcFunc['db_fetch_assoc']($result))
1772
	{
1773
		// Cast as an integer to remove the leading 0.
1774
		$row['hour'] = (int) $row['hour'];
1775
1776
		$maxPosts = max($row['post_count'], $maxPosts);
1777
		$realPosts += $row['post_count'];
1778
1779
		$context['posts_by_time'][$row['hour']] = array(
1780
			'hour' => $row['hour'],
1781
			'hour_format' => stripos($user_info['time_format'], '%p') === false ? $row['hour'] : date('g a', mktime($row['hour'])),
1782
			'posts' => $row['post_count'],
1783
			'posts_percent' => 0,
1784
			'is_last' => $row['hour'] == 23,
1785
		);
1786
	}
1787
	$smcFunc['db_free_result']($result);
1788
1789
	if ($maxPosts > 0)
1790
		for ($hour = 0; $hour < 24; $hour++)
1791
		{
1792
			if (!isset($context['posts_by_time'][$hour]))
1793
				$context['posts_by_time'][$hour] = array(
1794
					'hour' => $hour,
1795
					'hour_format' => stripos($user_info['time_format'], '%p') === false ? $hour : date('g a', mktime($hour)),
1796
					'posts' => 0,
1797
					'posts_percent' => 0,
1798
					'relative_percent' => 0,
1799
					'is_last' => $hour == 23,
1800
				);
1801
			else
1802
			{
1803
				$context['posts_by_time'][$hour]['posts_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $realPosts);
1804
				$context['posts_by_time'][$hour]['relative_percent'] = round(($context['posts_by_time'][$hour]['posts'] * 100) / $maxPosts);
1805
			}
1806
		}
1807
1808
	// Put it in the right order.
1809
	ksort($context['posts_by_time']);
1810
1811
	/**
1812
	 * Adding new entries:
1813
	 * 'key' => array(
1814
	 * 		'text' => string, // The text that will be shown next to the entry.
1815
	 * 		'url' => string, // OPTIONAL: The entry will be a url
1816
	 * ),
1817
	 *
1818
	 * 'key' will be used to look up the language string as $txt['statPanel_' . $key].
1819
	 * Make sure to add a new entry when writing your mod!
1820
	 */
1821
	$context['text_stats'] = array(
1822
		'total_time_online' => array(
1823
			'text' => $context['time_logged_in'],
1824
		),
1825
		'total_posts' => array(
1826
			'text' => $context['num_posts'] . ' ' . $txt['statPanel_posts'],
1827
			'url' => $scripturl . '?action=profile;area=showposts;sa=messages;u=' . $memID
1828
		),
1829
		'total_topics' => array(
1830
			'text' => $context['num_topics'] . ' ' . $txt['statPanel_topics'],
1831
			'url' => $scripturl . '?action=profile;area=showposts;sa=topics;u=' . $memID
1832
		),
1833
		'users_polls' => array(
1834
			'text' => $context['num_polls'] . ' ' . $txt['statPanel_polls'],
1835
		),
1836
		'users_votes' => array(
1837
			'text' => $context['num_votes'] . ' ' . $txt['statPanel_votes']
1838
		)
1839
	);
1840
1841
	// Custom stats (just add a template_layer to add it to the template!)
1842
	call_integration_hook('integrate_profile_stats', array($memID, &$context['text_stats']));
1843
}
1844
1845
/**
1846
 * Loads up the information for the "track user" section of the profile
1847
 *
1848
 * @param int $memID The ID of the member
1849
 */
1850
function tracking($memID)
1851
{
1852
	global $context, $txt, $modSettings, $user_profile;
1853
1854
	$subActions = array(
1855
		'activity' => array('trackActivity', $txt['trackActivity'], 'moderate_forum'),
1856
		'ip' => array('TrackIP', $txt['trackIP'], 'moderate_forum'),
1857
		'edits' => array('trackEdits', $txt['trackEdits'], 'moderate_forum'),
1858
		'groupreq' => array('trackGroupReq', $txt['trackGroupRequests'], 'approve_group_requests'),
1859
		'logins' => array('TrackLogins', $txt['trackLogins'], 'moderate_forum'),
1860
	);
1861
1862
	foreach ($subActions as $sa => $action)
1863
	{
1864
		if (!allowedTo($action[2]))
1865
			unset($subActions[$sa]);
1866
	}
1867
1868
	// Create the tabs for the template.
1869
	$context[$context['profile_menu_name']]['tab_data'] = array(
1870
		'title' => $txt['tracking'],
1871
		'description' => $txt['tracking_description'],
1872
		'icon' => 'profile_hd.png',
1873
		'tabs' => array(
1874
			'activity' => array(),
1875
			'ip' => array(),
1876
			'edits' => array(),
1877
			'groupreq' => array(),
1878
			'logins' => array(),
1879
		),
1880
	);
1881
1882
	// Moderation must be on to track edits.
1883
	if (empty($modSettings['userlog_enabled']))
1884
		unset($context[$context['profile_menu_name']]['tab_data']['edits'], $subActions['edits']);
1885
1886
	// Group requests must be active to show it...
1887
	if (empty($modSettings['show_group_membership']))
1888
		unset($context[$context['profile_menu_name']]['tab_data']['groupreq'], $subActions['groupreq']);
1889
1890
	if (empty($subActions))
1891
		fatal_lang_error('no_access', false);
1892
1893
	$keys = array_keys($subActions);
1894
	$default = array_shift($keys);
1895
	$context['tracking_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : $default;
1896
1897
	// Set a page title.
1898
	$context['page_title'] = $txt['trackUser'] . ' - ' . $subActions[$context['tracking_area']][1] . ' - ' . $user_profile[$memID]['real_name'];
1899
1900
	// Pass on to the actual function.
1901
	$context['sub_template'] = $subActions[$context['tracking_area']][0];
1902
	$call = call_helper($subActions[$context['tracking_area']][0], true);
1903
1904
	if (!empty($call))
1905
		call_user_func($call, $memID);
1906
}
1907
1908
/**
1909
 * Handles tracking a user's activity
1910
 *
1911
 * @param int $memID The ID of the member
1912
 */
1913
function trackActivity($memID)
1914
{
1915
	global $scripturl, $txt, $modSettings, $sourcedir;
1916
	global $user_profile, $context, $smcFunc;
1917
1918
	// Verify if the user has sufficient permissions.
1919
	isAllowedTo('moderate_forum');
1920
1921
	$context['last_ip'] = $user_profile[$memID]['member_ip'];
1922
	if ($context['last_ip'] != $user_profile[$memID]['member_ip2'])
1923
		$context['last_ip2'] = $user_profile[$memID]['member_ip2'];
1924
	$context['member']['name'] = $user_profile[$memID]['real_name'];
1925
1926
	// Set the options for the list component.
1927
	$listOptions = array(
1928
		'id' => 'track_user_list',
1929
		'title' => $txt['errors_by'] . ' ' . $context['member']['name'],
1930
		'items_per_page' => $modSettings['defaultMaxListItems'],
1931
		'no_items_label' => $txt['no_errors_from_user'],
1932
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=user;u=' . $memID,
1933
		'default_sort_col' => 'date',
1934
		'get_items' => array(
1935
			'function' => 'list_getUserErrors',
1936
			'params' => array(
1937
				'le.id_member = {int:current_member}',
1938
				array('current_member' => $memID),
1939
			),
1940
		),
1941
		'get_count' => array(
1942
			'function' => 'list_getUserErrorCount',
1943
			'params' => array(
1944
				'id_member = {int:current_member}',
1945
				array('current_member' => $memID),
1946
			),
1947
		),
1948
		'columns' => array(
1949
			'ip_address' => array(
1950
				'header' => array(
1951
					'value' => $txt['ip_address'],
1952
				),
1953
				'data' => array(
1954
					'sprintf' => array(
1955
						'format' => '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=%1$s;u=' . $memID . '">%1$s</a>',
1956
						'params' => array(
1957
							'ip' => false,
1958
						),
1959
					),
1960
				),
1961
				'sort' => array(
1962
					'default' => 'le.ip',
1963
					'reverse' => 'le.ip DESC',
1964
				),
1965
			),
1966
			'message' => array(
1967
				'header' => array(
1968
					'value' => $txt['message'],
1969
				),
1970
				'data' => array(
1971
					'sprintf' => array(
1972
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
1973
						'params' => array(
1974
							'message' => false,
1975
							'url' => false,
1976
						),
1977
					),
1978
				),
1979
			),
1980
			'date' => array(
1981
				'header' => array(
1982
					'value' => $txt['date'],
1983
				),
1984
				'data' => array(
1985
					'db' => 'time',
1986
				),
1987
				'sort' => array(
1988
					'default' => 'le.id_error DESC',
1989
					'reverse' => 'le.id_error',
1990
				),
1991
			),
1992
		),
1993
		'additional_rows' => array(
1994
			array(
1995
				'position' => 'after_title',
1996
				'value' => $txt['errors_desc'],
1997
			),
1998
		),
1999
	);
2000
2001
	// Create the list for viewing.
2002
	require_once($sourcedir . '/Subs-List.php');
2003
	createList($listOptions);
2004
2005
	// @todo cache this
2006
	// If this is a big forum, or a large posting user, let's limit the search.
2007
	if ($modSettings['totalMessages'] > 50000 && $user_profile[$memID]['posts'] > 500)
2008
	{
2009
		$request = $smcFunc['db_query']('', '
2010
			SELECT MAX(id_msg)
2011
			FROM {db_prefix}messages AS m
2012
			WHERE m.id_member = {int:current_member}',
2013
			array(
2014
				'current_member' => $memID,
2015
			)
2016
		);
2017
		list ($max_msg_member) = $smcFunc['db_fetch_row']($request);
2018
		$smcFunc['db_free_result']($request);
2019
2020
		// There's no point worrying ourselves with messages made yonks ago, just get recent ones!
2021
		$min_msg_member = max(0, $max_msg_member - $user_profile[$memID]['posts'] * 3);
2022
	}
2023
2024
	// Default to at least the ones we know about.
2025
	$ips = array(
2026
		$user_profile[$memID]['member_ip'],
2027
		$user_profile[$memID]['member_ip2'],
2028
	);
2029
2030
	// @todo cache this
2031
	// Get all IP addresses this user has used for his messages.
2032
	$request = $smcFunc['db_query']('', '
2033
		SELECT poster_ip
2034
		FROM {db_prefix}messages
2035
		WHERE id_member = {int:current_member}
2036
		' . (isset($min_msg_member) ? '
2037
			AND id_msg >= {int:min_msg_member} AND id_msg <= {int:max_msg_member}' : '') . '
2038
		GROUP BY poster_ip',
2039
		array(
2040
			'current_member' => $memID,
2041
			'min_msg_member' => !empty($min_msg_member) ? $min_msg_member : 0,
2042
			'max_msg_member' => !empty($max_msg_member) ? $max_msg_member : 0,
2043
		)
2044
	);
2045
	$context['ips'] = array();
2046
	while ($row = $smcFunc['db_fetch_assoc']($request))
2047
	{
2048
		$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>';
2049
		$ips[] = inet_dtop($row['poster_ip']);
2050
	}
2051
	$smcFunc['db_free_result']($request);
2052
2053
	// Now also get the IP addresses from the error messages.
2054
	$request = $smcFunc['db_query']('', '
2055
		SELECT COUNT(*) AS error_count, ip
2056
		FROM {db_prefix}log_errors
2057
		WHERE id_member = {int:current_member}
2058
		GROUP BY ip',
2059
		array(
2060
			'current_member' => $memID,
2061
		)
2062
	);
2063
	$context['error_ips'] = array();
2064
	while ($row = $smcFunc['db_fetch_assoc']($request))
2065
	{
2066
		$row['ip'] = inet_dtop($row['ip']);
2067
		$context['error_ips'][] = '<a href="' . $scripturl . '?action=profile;area=tracking;sa=ip;searchip=' . $row['ip'] . ';u=' . $memID . '">' . $row['ip'] . '</a>';
2068
		$ips[] = $row['ip'];
2069
	}
2070
	$smcFunc['db_free_result']($request);
2071
2072
	// Find other users that might use the same IP.
2073
	$ips = array_unique($ips);
2074
	$context['members_in_range'] = array();
2075
	if (!empty($ips))
2076
	{
2077
		// Get member ID's which are in messages...
2078
		$request = $smcFunc['db_query']('', '
2079
			SELECT DISTINCT mem.id_member
2080
			FROM {db_prefix}messages AS m
2081
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
2082
			WHERE m.poster_ip IN ({array_inet:ip_list})
2083
				AND mem.id_member != {int:current_member}',
2084
			array(
2085
				'current_member' => $memID,
2086
				'ip_list' => $ips,
2087
			)
2088
		);
2089
		$message_members = array();
2090
		while ($row = $smcFunc['db_fetch_assoc']($request))
2091
			$message_members[] = $row['id_member'];
2092
		$smcFunc['db_free_result']($request);
2093
2094
		// Fetch their names, cause of the GROUP BY doesn't like giving us that normally.
2095
		if (!empty($message_members))
2096
		{
2097
			$request = $smcFunc['db_query']('', '
2098
				SELECT id_member, real_name
2099
				FROM {db_prefix}members
2100
				WHERE id_member IN ({array_int:message_members})',
2101
				array(
2102
					'message_members' => $message_members,
2103
					'ip_list' => $ips,
2104
				)
2105
			);
2106
			while ($row = $smcFunc['db_fetch_assoc']($request))
2107
				$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
2108
			$smcFunc['db_free_result']($request);
2109
		}
2110
2111
		$request = $smcFunc['db_query']('', '
2112
			SELECT id_member, real_name
2113
			FROM {db_prefix}members
2114
			WHERE id_member != {int:current_member}
2115
				AND member_ip IN ({array_inet:ip_list})',
2116
			array(
2117
				'current_member' => $memID,
2118
				'ip_list' => $ips,
2119
			)
2120
		);
2121
		while ($row = $smcFunc['db_fetch_assoc']($request))
2122
			$context['members_in_range'][$row['id_member']] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
2123
		$smcFunc['db_free_result']($request);
2124
	}
2125
}
2126
2127
/**
2128
 * Get the number of user errors
2129
 *
2130
 * @param string $where A query to limit which errors are counted
2131
 * @param array $where_vars The parameters for $where
2132
 * @return int Number of user errors
2133
 */
2134
function list_getUserErrorCount($where, $where_vars = array())
2135
{
2136
	global $smcFunc;
2137
2138
	$request = $smcFunc['db_query']('', '
2139
		SELECT COUNT(*)
2140
		FROM {db_prefix}log_errors
2141
		WHERE ' . $where,
2142
		$where_vars
2143
	);
2144
	list ($count) = $smcFunc['db_fetch_row']($request);
2145
	$smcFunc['db_free_result']($request);
2146
2147
	return (int) $count;
2148
}
2149
2150
/**
2151
 * Gets all of the errors generated by a user's actions. Callback for the list in track_activity
2152
 *
2153
 * @param int $start Which item to start with (for pagination purposes)
2154
 * @param int $items_per_page How many items to show on each page
2155
 * @param string $sort A string indicating how to sort the results
2156
 * @param string $where A query indicating how to filter the results (eg 'id_member={int:id_member}')
2157
 * @param array $where_vars An array of parameters for $where
2158
 * @return array An array of information about the error messages
2159
 */
2160
function list_getUserErrors($start, $items_per_page, $sort, $where, $where_vars = array())
2161
{
2162
	global $smcFunc, $txt, $scripturl;
2163
2164
	// Get a list of error messages from this ip (range).
2165
	$request = $smcFunc['db_query']('', '
2166
		SELECT
2167
			le.log_time, le.ip, le.url, le.message, COALESCE(mem.id_member, 0) AS id_member,
2168
			COALESCE(mem.real_name, {string:guest_title}) AS display_name, mem.member_name
2169
		FROM {db_prefix}log_errors AS le
2170
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = le.id_member)
2171
		WHERE ' . $where . '
2172
		ORDER BY {raw:sort}
2173
		LIMIT {int:start}, {int:max}',
2174
		array_merge($where_vars, array(
2175
			'guest_title' => $txt['guest_title'],
2176
			'sort' => $sort,
2177
			'start' => $start,
2178
			'max' => $items_per_page,
2179
		))
2180
	);
2181
	$error_messages = array();
2182
	while ($row = $smcFunc['db_fetch_assoc']($request))
2183
		$error_messages[] = array(
2184
			'ip' => inet_dtop($row['ip']),
2185
			'member_link' => $row['id_member'] > 0 ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>' : $row['display_name'],
2186
			'message' => strtr($row['message'], array('&lt;span class=&quot;remove&quot;&gt;' => '', '&lt;/span&gt;' => '')),
2187
			'url' => $row['url'],
2188
			'time' => timeformat($row['log_time']),
2189
			'timestamp' => forum_time(true, $row['log_time']),
2190
		);
2191
	$smcFunc['db_free_result']($request);
2192
2193
	return $error_messages;
2194
}
2195
2196
/**
2197
 * Gets the number of posts made from a particular IP
2198
 *
2199
 * @param string $where A query indicating which posts to count
2200
 * @param array $where_vars The parameters for $where
2201
 * @return int Count of messages matching the IP
2202
 */
2203
function list_getIPMessageCount($where, $where_vars = array())
2204
{
2205
	global $smcFunc, $user_info;
2206
2207
	$request = $smcFunc['db_query']('', '
2208
		SELECT COUNT(*)
2209
		FROM {db_prefix}messages AS m
2210
		WHERE {query_see_message_board} AND ' . $where,
2211
		$where_vars
2212
	);
2213
	list ($count) = $smcFunc['db_fetch_row']($request);
2214
	$smcFunc['db_free_result']($request);
2215
2216
	return (int) $count;
2217
}
2218
2219
/**
2220
 * Gets all the posts made from a particular IP
2221
 *
2222
 * @param int $start Which item to start with (for pagination purposes)
2223
 * @param int $items_per_page How many items to show on each page
2224
 * @param string $sort A string indicating how to sort the results
2225
 * @param string $where A query to filter which posts are returned
2226
 * @param array $where_vars An array of parameters for $where
2227
 * @return array An array containing information about the posts
2228
 */
2229
function list_getIPMessages($start, $items_per_page, $sort, $where, $where_vars = array())
2230
{
2231
	global $smcFunc, $scripturl, $user_info;
2232
2233
	// Get all the messages fitting this where clause.
2234
	$request = $smcFunc['db_query']('', '
2235
		SELECT
2236
			m.id_msg, m.poster_ip, COALESCE(mem.real_name, m.poster_name) AS display_name, mem.id_member,
2237
			m.subject, m.poster_time, m.id_topic, m.id_board
2238
		FROM {db_prefix}messages AS m
2239
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
2240
		WHERE {query_see_message_board} AND ' . $where . '
2241
		ORDER BY {raw:sort}
2242
		LIMIT {int:start}, {int:max}',
2243
		array_merge($where_vars, array(
2244
			'sort' => $sort,
2245
			'start' => $start,
2246
			'max' => $items_per_page,
2247
		))
2248
	);
2249
	$messages = array();
2250
	while ($row = $smcFunc['db_fetch_assoc']($request))
2251
		$messages[] = array(
2252
			'ip' => inet_dtop($row['poster_ip']),
2253
			'member_link' => empty($row['id_member']) ? $row['display_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>',
2254
			'board' => array(
2255
				'id' => $row['id_board'],
2256
				'href' => $scripturl . '?board=' . $row['id_board']
2257
			),
2258
			'topic' => $row['id_topic'],
2259
			'id' => $row['id_msg'],
2260
			'subject' => $row['subject'],
2261
			'time' => timeformat($row['poster_time']),
2262
			'timestamp' => forum_time(true, $row['poster_time'])
2263
		);
2264
	$smcFunc['db_free_result']($request);
2265
2266
	return $messages;
2267
}
2268
2269
/**
2270
 * Handles tracking a particular IP address
2271
 *
2272
 * @param int $memID The ID of a member whose IP we want to track
2273
 */
2274
function TrackIP($memID = 0)
2275
{
2276
	global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir;
2277
	global $context, $options, $smcFunc;
2278
2279
	// Can the user do this?
2280
	isAllowedTo('moderate_forum');
2281
2282
	if ($memID == 0)
2283
	{
2284
		$context['ip'] = ip2range($user_info['ip']);
2285
		loadTemplate('Profile');
2286
		loadLanguage('Profile');
2287
		$context['sub_template'] = 'trackIP';
2288
		$context['page_title'] = $txt['profile'];
2289
		$context['base_url'] = $scripturl . '?action=trackip';
2290
	}
2291
	else
2292
	{
2293
		$context['ip'] = ip2range($user_profile[$memID]['member_ip']);
2294
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2295
	}
2296
2297
	// Searching?
2298
	if (isset($_REQUEST['searchip']))
2299
		$context['ip'] = ip2range(trim($_REQUEST['searchip']));
2300
2301
	if (count($context['ip']) !== 2)
2302
		fatal_lang_error('invalid_tracking_ip', false);
2303
2304
	$ip_string = array('{inet:ip_address_low}', '{inet:ip_address_high}');
2305
	$fields = array(
2306
		'ip_address_low' => $context['ip']['low'],
2307
		'ip_address_high' => $context['ip']['high'],
2308
	);
2309
2310
	$ip_var = $context['ip'];
2311
2312
	if ($context['ip']['low'] !== $context['ip']['high'])
2313
		$context['ip'] = $context['ip']['low'] . ' - ' . $context['ip']['high'];
2314
	else
2315
		$context['ip'] = $context['ip']['low'];
2316
2317
	if (empty($context['tracking_area']))
2318
		$context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip'];
2319
2320
	$request = $smcFunc['db_query']('', '
2321
		SELECT id_member, real_name AS display_name, member_ip
2322
		FROM {db_prefix}members
2323
		WHERE member_ip >= ' . $ip_string[0] . ' and member_ip <= ' . $ip_string[1],
2324
		$fields
2325
	);
2326
	$context['ips'] = array();
2327
	while ($row = $smcFunc['db_fetch_assoc']($request))
2328
		$context['ips'][inet_dtop($row['member_ip'])][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>';
2329
	$smcFunc['db_free_result']($request);
2330
2331
	ksort($context['ips']);
2332
2333
	// For messages we use the "messages per page" option
2334
	$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
2335
2336
	// Gonna want this for the list.
2337
	require_once($sourcedir . '/Subs-List.php');
2338
2339
	// Start with the user messages.
2340
	$listOptions = array(
2341
		'id' => 'track_message_list',
2342
		'title' => $txt['messages_from_ip'] . ' ' . $context['ip'],
2343
		'start_var_name' => 'messageStart',
2344
		'items_per_page' => $maxPerPage,
2345
		'no_items_label' => $txt['no_messages_from_ip'],
2346
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2347
		'default_sort_col' => 'date',
2348
		'get_items' => array(
2349
			'function' => 'list_getIPMessages',
2350
			'params' => array(
2351
				'm.poster_ip >= ' . $ip_string[0] . ' and m.poster_ip <= ' . $ip_string[1],
2352
				$fields,
2353
			),
2354
		),
2355
		'get_count' => array(
2356
			'function' => 'list_getIPMessageCount',
2357
			'params' => array(
2358
				'm.poster_ip >= ' . $ip_string[0] . ' and m.poster_ip <= ' . $ip_string[1],
2359
				$fields,
2360
			),
2361
		),
2362
		'columns' => array(
2363
			'ip_address' => array(
2364
				'header' => array(
2365
					'value' => $txt['ip_address'],
2366
				),
2367
				'data' => array(
2368
					'sprintf' => array(
2369
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2370
						'params' => array(
2371
							'ip' => false,
2372
						),
2373
					),
2374
				),
2375
				'sort' => array(
2376
					'default' => 'm.poster_ip',
2377
					'reverse' => 'm.poster_ip DESC',
2378
				),
2379
			),
2380
			'poster' => array(
2381
				'header' => array(
2382
					'value' => $txt['poster'],
2383
				),
2384
				'data' => array(
2385
					'db' => 'member_link',
2386
				),
2387
			),
2388
			'subject' => array(
2389
				'header' => array(
2390
					'value' => $txt['subject'],
2391
				),
2392
				'data' => array(
2393
					'sprintf' => array(
2394
						'format' => '<a href="' . $scripturl . '?topic=%1$s.msg%2$s#msg%2$s" rel="nofollow">%3$s</a>',
2395
						'params' => array(
2396
							'topic' => false,
2397
							'id' => false,
2398
							'subject' => false,
2399
						),
2400
					),
2401
				),
2402
			),
2403
			'date' => array(
2404
				'header' => array(
2405
					'value' => $txt['date'],
2406
				),
2407
				'data' => array(
2408
					'db' => 'time',
2409
				),
2410
				'sort' => array(
2411
					'default' => 'm.id_msg DESC',
2412
					'reverse' => 'm.id_msg',
2413
				),
2414
			),
2415
		),
2416
		'additional_rows' => array(
2417
			array(
2418
				'position' => 'after_title',
2419
				'value' => $txt['messages_from_ip_desc'],
2420
			),
2421
		),
2422
	);
2423
2424
	// Create the messages list.
2425
	createList($listOptions);
2426
2427
	// Set the options for the error lists.
2428
	$listOptions = array(
2429
		'id' => 'track_user_list',
2430
		'title' => $txt['errors_from_ip'] . ' ' . $context['ip'],
2431
		'start_var_name' => 'errorStart',
2432
		'items_per_page' => $modSettings['defaultMaxListItems'],
2433
		'no_items_label' => $txt['no_errors_from_ip'],
2434
		'base_href' => $context['base_url'] . ';searchip=' . $context['ip'],
2435
		'default_sort_col' => 'date2',
2436
		'get_items' => array(
2437
			'function' => 'list_getUserErrors',
2438
			'params' => array(
2439
				'le.ip >= ' . $ip_string[0] . ' and le.ip <= ' . $ip_string[1],
2440
				$fields,
2441
			),
2442
		),
2443
		'get_count' => array(
2444
			'function' => 'list_getUserErrorCount',
2445
			'params' => array(
2446
				'ip >= ' . $ip_string[0] . ' and ip <= ' . $ip_string[1],
2447
				$fields,
2448
			),
2449
		),
2450
		'columns' => array(
2451
			'ip_address2' => array(
2452
				'header' => array(
2453
					'value' => $txt['ip_address'],
2454
				),
2455
				'data' => array(
2456
					'sprintf' => array(
2457
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>',
2458
						'params' => array(
2459
							'ip' => false,
2460
						),
2461
					),
2462
				),
2463
				'sort' => array(
2464
					'default' => 'le.ip',
2465
					'reverse' => 'le.ip DESC',
2466
				),
2467
			),
2468
			'display_name' => array(
2469
				'header' => array(
2470
					'value' => $txt['display_name'],
2471
				),
2472
				'data' => array(
2473
					'db' => 'member_link',
2474
				),
2475
			),
2476
			'message' => array(
2477
				'header' => array(
2478
					'value' => $txt['message'],
2479
				),
2480
				'data' => array(
2481
					'sprintf' => array(
2482
						'format' => '%1$s<br><a href="%2$s">%2$s</a>',
2483
						'params' => array(
2484
							'message' => false,
2485
							'url' => false,
2486
						),
2487
					),
2488
				),
2489
			),
2490
			'date2' => array(
2491
				'header' => array(
2492
					'value' => $txt['date'],
2493
				),
2494
				'data' => array(
2495
					'db' => 'time',
2496
				),
2497
				'sort' => array(
2498
					'default' => 'le.id_error DESC',
2499
					'reverse' => 'le.id_error',
2500
				),
2501
			),
2502
		),
2503
		'additional_rows' => array(
2504
			array(
2505
				'position' => 'after_title',
2506
				'value' => $txt['errors_from_ip_desc'],
2507
			),
2508
		),
2509
	);
2510
2511
	// Create the error list.
2512
	createList($listOptions);
2513
2514
	// Allow 3rd party integrations to add in their own lists or whatever.
2515
	$context['additional_track_lists'] = array();
2516
	call_integration_hook('integrate_profile_trackip', array($ip_string, $ip_var));
2517
2518
	$context['single_ip'] = ($ip_var['low'] === $ip_var['high']);
2519
	if ($context['single_ip'])
2520
	{
2521
		$context['whois_servers'] = array(
2522
			'apnic' => array(
2523
				'name' => $txt['whois_apnic'],
2524
				'url' => 'https://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'],
2525
			),
2526
			'arin' => array(
2527
				'name' => $txt['whois_arin'],
2528
				'url' => 'https://whois.arin.net/rest/ip/' . $context['ip'],
2529
			),
2530
			'lacnic' => array(
2531
				'name' => $txt['whois_lacnic'],
2532
				'url' => 'https://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'],
2533
			),
2534
			'ripe' => array(
2535
				'name' => $txt['whois_ripe'],
2536
				'url' => 'https://apps.db.ripe.net/search/query.html?searchtext=' . $context['ip'],
2537
			),
2538
		);
2539
	}
2540
}
2541
2542
/**
2543
 * Tracks a user's logins.
2544
 *
2545
 * @param int $memID The ID of the member
2546
 */
2547
function TrackLogins($memID = 0)
2548
{
2549
	global $scripturl, $txt, $sourcedir, $context;
2550
2551
	// Gonna want this for the list.
2552
	require_once($sourcedir . '/Subs-List.php');
2553
2554
	if ($memID == 0)
2555
		$context['base_url'] = $scripturl . '?action=trackip';
2556
	else
2557
		$context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID;
2558
2559
	// Start with the user messages.
2560
	$listOptions = array(
2561
		'id' => 'track_logins_list',
2562
		'title' => $txt['trackLogins'],
2563
		'no_items_label' => $txt['trackLogins_none_found'],
2564
		'base_href' => $context['base_url'],
2565
		'get_items' => array(
2566
			'function' => 'list_getLogins',
2567
			'params' => array(
2568
				'id_member = {int:current_member}',
2569
				array('current_member' => $memID),
2570
			),
2571
		),
2572
		'get_count' => array(
2573
			'function' => 'list_getLoginCount',
2574
			'params' => array(
2575
				'id_member = {int:current_member}',
2576
				array('current_member' => $memID),
2577
			),
2578
		),
2579
		'columns' => array(
2580
			'time' => array(
2581
				'header' => array(
2582
					'value' => $txt['date'],
2583
				),
2584
				'data' => array(
2585
					'db' => 'time',
2586
				),
2587
			),
2588
			'ip' => array(
2589
				'header' => array(
2590
					'value' => $txt['ip_address'],
2591
				),
2592
				'data' => array(
2593
					'sprintf' => array(
2594
						'format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a> (<a href="' . $context['base_url'] . ';searchip=%2$s">%2$s</a>) ',
2595
						'params' => array(
2596
							'ip' => false,
2597
							'ip2' => false
2598
						),
2599
					),
2600
				),
2601
			),
2602
		),
2603
		'additional_rows' => array(
2604
			array(
2605
				'position' => 'after_title',
2606
				'value' => $txt['trackLogins_desc'],
2607
			),
2608
		),
2609
	);
2610
2611
	// Create the messages list.
2612
	createList($listOptions);
2613
2614
	$context['sub_template'] = 'show_list';
2615
	$context['default_list'] = 'track_logins_list';
2616
}
2617
2618
/**
2619
 * Finds the total number of tracked logins for a particular user
2620
 *
2621
 * @param string $where A query to limit which logins are counted
2622
 * @param array $where_vars An array of parameters for $where
2623
 * @return int count of messages matching the IP
2624
 */
2625
function list_getLoginCount($where, $where_vars = array())
2626
{
2627
	global $smcFunc;
2628
2629
	$request = $smcFunc['db_query']('', '
2630
		SELECT COUNT(*) AS message_count
2631
		FROM {db_prefix}member_logins
2632
		WHERE id_member = {int:id_member}',
2633
		array(
2634
			'id_member' => $where_vars['current_member'],
2635
		)
2636
	);
2637
	list ($count) = $smcFunc['db_fetch_row']($request);
2638
	$smcFunc['db_free_result']($request);
2639
2640
	return (int) $count;
2641
}
2642
2643
/**
2644
 * Callback for the list in trackLogins.
2645
 *
2646
 * @param int $start Which item to start with (not used here)
2647
 * @param int $items_per_page How many items to show on each page (not used here)
2648
 * @param string $sort A string indicating
2649
 * @param string $where A query to filter results (not used here)
2650
 * @param array $where_vars An array of parameters for $where. Only 'current_member' (the ID of the member) is used here
2651
 * @return array An array of information about user logins
2652
 */
2653
function list_getLogins($start, $items_per_page, $sort, $where, $where_vars = array())
2654
{
2655
	global $smcFunc;
2656
2657
	$request = $smcFunc['db_query']('', '
2658
		SELECT time, ip, ip2
2659
		FROM {db_prefix}member_logins
2660
		WHERE id_member = {int:id_member}
2661
		ORDER BY time DESC',
2662
		array(
2663
			'id_member' => $where_vars['current_member'],
2664
		)
2665
	);
2666
	$logins = array();
2667
	while ($row = $smcFunc['db_fetch_assoc']($request))
2668
		$logins[] = array(
2669
			'time' => timeformat($row['time']),
2670
			'ip' => inet_dtop($row['ip']),
2671
			'ip2' => inet_dtop($row['ip2']),
2672
		);
2673
	$smcFunc['db_free_result']($request);
2674
2675
	return $logins;
2676
}
2677
2678
/**
2679
 * Tracks a user's profile edits
2680
 *
2681
 * @param int $memID The ID of the member
2682
 */
2683
function trackEdits($memID)
2684
{
2685
	global $scripturl, $txt, $modSettings, $sourcedir, $context, $smcFunc;
2686
2687
	require_once($sourcedir . '/Subs-List.php');
2688
2689
	// Get the names of any custom fields.
2690
	$request = $smcFunc['db_query']('', '
2691
		SELECT col_name, field_name, bbc
2692
		FROM {db_prefix}custom_fields',
2693
		array(
2694
		)
2695
	);
2696
	$context['custom_field_titles'] = array();
2697
	while ($row = $smcFunc['db_fetch_assoc']($request))
2698
		$context['custom_field_titles']['customfield_' . $row['col_name']] = array(
2699
			'title' => $row['field_name'],
2700
			'parse_bbc' => $row['bbc'],
2701
		);
2702
	$smcFunc['db_free_result']($request);
2703
2704
	// Set the options for the error lists.
2705
	$listOptions = array(
2706
		'id' => 'edit_list',
2707
		'title' => $txt['trackEdits'],
2708
		'items_per_page' => $modSettings['defaultMaxListItems'],
2709
		'no_items_label' => $txt['trackEdit_no_edits'],
2710
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=edits;u=' . $memID,
2711
		'default_sort_col' => 'time',
2712
		'get_items' => array(
2713
			'function' => 'list_getProfileEdits',
2714
			'params' => array(
2715
				$memID,
2716
			),
2717
		),
2718
		'get_count' => array(
2719
			'function' => 'list_getProfileEditCount',
2720
			'params' => array(
2721
				$memID,
2722
			),
2723
		),
2724
		'columns' => array(
2725
			'action' => array(
2726
				'header' => array(
2727
					'value' => $txt['trackEdit_action'],
2728
				),
2729
				'data' => array(
2730
					'db' => 'action_text',
2731
				),
2732
			),
2733
			'before' => array(
2734
				'header' => array(
2735
					'value' => $txt['trackEdit_before'],
2736
				),
2737
				'data' => array(
2738
					'db' => 'before',
2739
				),
2740
			),
2741
			'after' => array(
2742
				'header' => array(
2743
					'value' => $txt['trackEdit_after'],
2744
				),
2745
				'data' => array(
2746
					'db' => 'after',
2747
				),
2748
			),
2749
			'time' => array(
2750
				'header' => array(
2751
					'value' => $txt['date'],
2752
				),
2753
				'data' => array(
2754
					'db' => 'time',
2755
				),
2756
				'sort' => array(
2757
					'default' => 'id_action DESC',
2758
					'reverse' => 'id_action',
2759
				),
2760
			),
2761
			'applicator' => array(
2762
				'header' => array(
2763
					'value' => $txt['trackEdit_applicator'],
2764
				),
2765
				'data' => array(
2766
					'db' => 'member_link',
2767
				),
2768
			),
2769
		),
2770
	);
2771
2772
	// Create the error list.
2773
	createList($listOptions);
2774
2775
	$context['sub_template'] = 'show_list';
2776
	$context['default_list'] = 'edit_list';
2777
}
2778
2779
/**
2780
 * How many edits?
2781
 *
2782
 * @param int $memID The ID of the member
2783
 * @return int The number of profile edits
2784
 */
2785
function list_getProfileEditCount($memID)
2786
{
2787
	global $smcFunc;
2788
2789
	$request = $smcFunc['db_query']('', '
2790
		SELECT COUNT(*) AS edit_count
2791
		FROM {db_prefix}log_actions
2792
		WHERE id_log = {int:log_type}
2793
			AND id_member = {int:owner}',
2794
		array(
2795
			'log_type' => 2,
2796
			'owner' => $memID,
2797
		)
2798
	);
2799
	list ($edit_count) = $smcFunc['db_fetch_row']($request);
2800
	$smcFunc['db_free_result']($request);
2801
2802
	return (int) $edit_count;
2803
}
2804
2805
/**
2806
 * Loads up information about a user's profile edits. Callback for the list in trackEdits()
2807
 *
2808
 * @param int $start Which item to start with (for pagination purposes)
2809
 * @param int $items_per_page How many items to show on each page
2810
 * @param string $sort A string indicating how to sort the results
2811
 * @param int $memID The ID of the member
2812
 * @return array An array of information about the profile edits
2813
 */
2814
function list_getProfileEdits($start, $items_per_page, $sort, $memID)
2815
{
2816
	global $smcFunc, $txt, $scripturl, $context;
2817
2818
	// Get a list of error messages from this ip (range).
2819
	$request = $smcFunc['db_query']('', '
2820
		SELECT
2821
			id_action, id_member, ip, log_time, action, extra
2822
		FROM {db_prefix}log_actions
2823
		WHERE id_log = {int:log_type}
2824
			AND id_member = {int:owner}
2825
		ORDER BY {raw:sort}
2826
		LIMIT {int:start}, {int:max}',
2827
		array(
2828
			'log_type' => 2,
2829
			'owner' => $memID,
2830
			'sort' => $sort,
2831
			'start' => $start,
2832
			'max' => $items_per_page,
2833
		)
2834
	);
2835
	$edits = array();
2836
	$members = array();
2837
	while ($row = $smcFunc['db_fetch_assoc']($request))
2838
	{
2839
		$extra = $smcFunc['json_decode']($row['extra'], true);
2840
		if (!empty($extra['applicator']))
2841
			$members[] = $extra['applicator'];
2842
2843
		// Work out what the name of the action is.
2844
		if (isset($txt['trackEdit_action_' . $row['action']]))
2845
			$action_text = $txt['trackEdit_action_' . $row['action']];
2846
		elseif (isset($txt[$row['action']]))
2847
			$action_text = $txt[$row['action']];
2848
		// Custom field?
2849
		elseif (isset($context['custom_field_titles'][$row['action']]))
2850
			$action_text = $context['custom_field_titles'][$row['action']]['title'];
2851
		else
2852
			$action_text = $row['action'];
2853
2854
		// Parse BBC?
2855
		$parse_bbc = isset($context['custom_field_titles'][$row['action']]) && $context['custom_field_titles'][$row['action']]['parse_bbc'] ? true : false;
2856
2857
		$edits[] = array(
2858
			'id' => $row['id_action'],
2859
			'ip' => inet_dtop($row['ip']),
2860
			'id_member' => !empty($extra['applicator']) ? $extra['applicator'] : 0,
2861
			'member_link' => $txt['trackEdit_deleted_member'],
2862
			'action' => $row['action'],
2863
			'action_text' => $action_text,
2864
			'before' => !empty($extra['previous']) ? ($parse_bbc ? parse_bbc($extra['previous']) : $extra['previous']) : '',
2865
			'after' => !empty($extra['new']) ? ($parse_bbc ? parse_bbc($extra['new']) : $extra['new']) : '',
2866
			'time' => timeformat($row['log_time']),
2867
		);
2868
	}
2869
	$smcFunc['db_free_result']($request);
2870
2871
	// Get any member names.
2872
	if (!empty($members))
2873
	{
2874
		$request = $smcFunc['db_query']('', '
2875
			SELECT
2876
				id_member, real_name
2877
			FROM {db_prefix}members
2878
			WHERE id_member IN ({array_int:members})',
2879
			array(
2880
				'members' => $members,
2881
			)
2882
		);
2883
		$members = array();
2884
		while ($row = $smcFunc['db_fetch_assoc']($request))
2885
			$members[$row['id_member']] = $row['real_name'];
2886
		$smcFunc['db_free_result']($request);
2887
2888
		foreach ($edits as $key => $value)
2889
			if (isset($members[$value['id_member']]))
2890
				$edits[$key]['member_link'] = '<a href="' . $scripturl . '?action=profile;u=' . $value['id_member'] . '">' . $members[$value['id_member']] . '</a>';
2891
	}
2892
2893
	return $edits;
2894
}
2895
2896
/**
2897
 * Display the history of group requests made by the user whose profile we are viewing.
2898
 *
2899
 * @param int $memID The ID of the member
2900
 */
2901
function trackGroupReq($memID)
2902
{
2903
	global $scripturl, $txt, $modSettings, $sourcedir, $context;
2904
2905
	require_once($sourcedir . '/Subs-List.php');
2906
2907
	// Set the options for the error lists.
2908
	$listOptions = array(
2909
		'id' => 'request_list',
2910
		'title' => sprintf($txt['trackGroupRequests_title'], $context['member']['name']),
2911
		'items_per_page' => $modSettings['defaultMaxListItems'],
2912
		'no_items_label' => $txt['requested_none'],
2913
		'base_href' => $scripturl . '?action=profile;area=tracking;sa=groupreq;u=' . $memID,
2914
		'default_sort_col' => 'time_applied',
2915
		'get_items' => array(
2916
			'function' => 'list_getGroupRequests',
2917
			'params' => array(
2918
				$memID,
2919
			),
2920
		),
2921
		'get_count' => array(
2922
			'function' => 'list_getGroupRequestsCount',
2923
			'params' => array(
2924
				$memID,
2925
			),
2926
		),
2927
		'columns' => array(
2928
			'group' => array(
2929
				'header' => array(
2930
					'value' => $txt['requested_group'],
2931
				),
2932
				'data' => array(
2933
					'db' => 'group_name',
2934
				),
2935
			),
2936
			'group_reason' => array(
2937
				'header' => array(
2938
					'value' => $txt['requested_group_reason'],
2939
				),
2940
				'data' => array(
2941
					'db' => 'group_reason',
2942
				),
2943
			),
2944
			'time_applied' => array(
2945
				'header' => array(
2946
					'value' => $txt['requested_group_time'],
2947
				),
2948
				'data' => array(
2949
					'db' => 'time_applied',
2950
					'timeformat' => true,
2951
				),
2952
				'sort' => array(
2953
					'default' => 'time_applied DESC',
2954
					'reverse' => 'time_applied',
2955
				),
2956
			),
2957
			'outcome' => array(
2958
				'header' => array(
2959
					'value' => $txt['requested_group_outcome'],
2960
				),
2961
				'data' => array(
2962
					'db' => 'outcome',
2963
				),
2964
			),
2965
		),
2966
	);
2967
2968
	// Create the error list.
2969
	createList($listOptions);
2970
2971
	$context['sub_template'] = 'show_list';
2972
	$context['default_list'] = 'request_list';
2973
}
2974
2975
/**
2976
 * How many edits?
2977
 *
2978
 * @param int $memID The ID of the member
2979
 * @return int The number of profile edits
2980
 */
2981
function list_getGroupRequestsCount($memID)
2982
{
2983
	global $smcFunc, $user_info;
2984
2985
	$request = $smcFunc['db_query']('', '
2986
		SELECT COUNT(*) AS req_count
2987
		FROM {db_prefix}log_group_requests AS lgr
2988
		WHERE id_member = {int:memID}
2989
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']),
2990
		array(
2991
			'memID' => $memID,
2992
		)
2993
	);
2994
	list ($report_count) = $smcFunc['db_fetch_row']($request);
2995
	$smcFunc['db_free_result']($request);
2996
2997
	return (int) $report_count;
2998
}
2999
3000
/**
3001
 * Loads up information about a user's group requests. Callback for the list in trackGroupReq()
3002
 *
3003
 * @param int $start Which item to start with (for pagination purposes)
3004
 * @param int $items_per_page How many items to show on each page
3005
 * @param string $sort A string indicating how to sort the results
3006
 * @param int $memID The ID of the member
3007
 * @return array An array of information about the user's group requests
3008
 */
3009
function list_getGroupRequests($start, $items_per_page, $sort, $memID)
3010
{
3011
	global $smcFunc, $txt, $scripturl, $user_info;
3012
3013
	$groupreq = array();
3014
3015
	$request = $smcFunc['db_query']('', '
3016
		SELECT
3017
			lgr.id_group, mg.group_name, mg.online_color, lgr.time_applied, lgr.reason, lgr.status,
3018
			ma.id_member AS id_member_acted, COALESCE(ma.member_name, lgr.member_name_acted) AS act_name, lgr.time_acted, lgr.act_reason
3019
		FROM {db_prefix}log_group_requests AS lgr
3020
			LEFT JOIN {db_prefix}members AS ma ON (lgr.id_member_acted = ma.id_member)
3021
			INNER JOIN {db_prefix}membergroups AS mg ON (lgr.id_group = mg.id_group)
3022
		WHERE lgr.id_member = {int:memID}
3023
			AND ' . ($user_info['mod_cache']['gq'] == '1=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']) . '
3024
		ORDER BY {raw:sort}
3025
		LIMIT {int:start}, {int:max}',
3026
		array(
3027
			'memID' => $memID,
3028
			'sort' => $sort,
3029
			'start' => $start,
3030
			'max' => $items_per_page,
3031
		)
3032
	);
3033
	while ($row = $smcFunc['db_fetch_assoc']($request))
3034
	{
3035
		$this_req = array(
3036
			'group_name' => empty($row['online_color']) ? $row['group_name'] : '<span style="color:' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
3037
			'group_reason' => $row['reason'],
3038
			'time_applied' => $row['time_applied'],
3039
		);
3040
		switch ($row['status'])
3041
		{
3042
			case 0:
3043
				$this_req['outcome'] = $txt['outcome_pending'];
3044
				break;
3045
			case 1:
3046
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
3047
				$this_req['outcome'] = sprintf($txt['outcome_approved'], $member_link, timeformat($row['time_acted']));
3048
				break;
3049
			case 2:
3050
				$member_link = empty($row['id_member_acted']) ? $row['act_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_acted'] . '">' . $row['act_name'] . '</a>';
3051
				$this_req['outcome'] = sprintf(!empty($row['act_reason']) ? $txt['outcome_refused_reason'] : $txt['outcome_refused'], $member_link, timeformat($row['time_acted']), $row['act_reason']);
3052
				break;
3053
		}
3054
3055
		$groupreq[] = $this_req;
3056
	}
3057
	$smcFunc['db_free_result']($request);
3058
3059
	return $groupreq;
3060
}
3061
3062
/**
3063
 * Shows which permissions a user has
3064
 *
3065
 * @param int $memID The ID of the member
3066
 */
3067
function showPermissions($memID)
3068
{
3069
	global $txt, $board, $modSettings;
3070
	global $user_profile, $context, $sourcedir, $smcFunc;
3071
3072
	// Verify if the user has sufficient permissions.
3073
	isAllowedTo('manage_permissions');
3074
3075
	loadLanguage('ManagePermissions');
3076
	loadLanguage('Admin');
3077
	loadTemplate('ManageMembers');
3078
3079
	// Load all the permission profiles.
3080
	require_once($sourcedir . '/ManagePermissions.php');
3081
	loadPermissionProfiles();
3082
3083
	$context['member']['id'] = $memID;
3084
	$context['member']['name'] = $user_profile[$memID]['real_name'];
3085
3086
	$context['page_title'] = $txt['showPermissions'];
3087
	$board = empty($board) ? 0 : (int) $board;
3088
	$context['board'] = $board;
3089
3090
	// Determine which groups this user is in.
3091
	$curGroups = explode(',', $user_profile[$memID]['additional_groups']);
3092
	$curGroups[] = $user_profile[$memID]['id_group'];
3093
	$curGroups[] = $user_profile[$memID]['id_post_group'];
3094
3095
	// Load a list of boards for the jump box - except the defaults.
3096
	$request = $smcFunc['db_query']('order_by_board_order', '
3097
		SELECT b.id_board, b.name, b.id_profile, b.member_groups, COALESCE(mods.id_member, modgs.id_group, 0) AS is_mod
3098
		FROM {db_prefix}boards AS b
3099
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
3100
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:current_groups}))
3101
		WHERE {query_see_board}',
3102
		array(
3103
			'current_member' => $memID,
3104
			'current_groups' => $curGroups,
3105
		)
3106
	);
3107
	$context['boards'] = array();
3108
	$context['no_access_boards'] = array();
3109
	while ($row = $smcFunc['db_fetch_assoc']($request))
3110
	{
3111
		$mergeGroups = array_merge(
3112
			explode(',', $row['member_groups']),
3113
			explode(',' $modSettings['board_manager_groups'])
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_VARIABLE, expecting ',' or ')' on line 3113 at column 15
Loading history...
3114
		);
3115
		if (empty(array_intersect($curGroups, $mergeGroups)) && !$row['is_mod'])
3116
			$context['no_access_boards'][] = array(
3117
				'id' => $row['id_board'],
3118
				'name' => $row['name'],
3119
				'is_last' => false,
3120
			);
3121
		elseif ($row['id_profile'] != 1 || $row['is_mod'])
3122
			$context['boards'][$row['id_board']] = array(
3123
				'id' => $row['id_board'],
3124
				'name' => $row['name'],
3125
				'selected' => $board == $row['id_board'],
3126
				'profile' => $row['id_profile'],
3127
				'profile_name' => $context['profiles'][$row['id_profile']]['name'],
3128
			);
3129
	}
3130
	$smcFunc['db_free_result']($request);
3131
3132
	require_once($sourcedir . '/Subs-Boards.php');
3133
	sortBoards($context['boards']);
3134
3135
	if (!empty($context['no_access_boards']))
3136
		$context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true;
3137
3138
	$context['member']['permissions'] = array(
3139
		'general' => array(),
3140
		'board' => array()
3141
	);
3142
3143
	// If you're an admin we know you can do everything, we might as well leave.
3144
	$context['member']['has_all_permissions'] = in_array(1, $curGroups);
3145
	if ($context['member']['has_all_permissions'])
3146
		return;
3147
3148
	$denied = array();
3149
3150
	// Get all general permissions.
3151
	$result = $smcFunc['db_query']('', '
3152
		SELECT p.permission, p.add_deny, mg.group_name, p.id_group
3153
		FROM {db_prefix}permissions AS p
3154
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = p.id_group)
3155
		WHERE p.id_group IN ({array_int:group_list})
3156
		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',
3157
		array(
3158
			'group_list' => $curGroups,
3159
			'newbie_group' => 4,
3160
		)
3161
	);
3162
	while ($row = $smcFunc['db_fetch_assoc']($result))
3163
	{
3164
		// We don't know about this permission, it doesn't exist :P.
3165
		if (!isset($txt['permissionname_' . $row['permission']]))
3166
			continue;
3167
3168
		if (empty($row['add_deny']))
3169
			$denied[] = $row['permission'];
3170
3171
		// Permissions that end with _own or _any consist of two parts.
3172
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
3173
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
3174
		else
3175
			$name = $txt['permissionname_' . $row['permission']];
3176
3177
		// Add this permission if it doesn't exist yet.
3178
		if (!isset($context['member']['permissions']['general'][$row['permission']]))
3179
			$context['member']['permissions']['general'][$row['permission']] = array(
3180
				'id' => $row['permission'],
3181
				'groups' => array(
3182
					'allowed' => array(),
3183
					'denied' => array()
3184
				),
3185
				'name' => $name,
3186
				'is_denied' => false,
3187
				'is_global' => true,
3188
			);
3189
3190
		// Add the membergroup to either the denied or the allowed groups.
3191
		$context['member']['permissions']['general'][$row['permission']]['groups'][empty($row['add_deny']) ? 'denied' : 'allowed'][] = $row['id_group'] == 0 ? $txt['membergroups_members'] : $row['group_name'];
3192
3193
		// Once denied is always denied.
3194
		$context['member']['permissions']['general'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
3195
	}
3196
	$smcFunc['db_free_result']($result);
3197
3198
	$request = $smcFunc['db_query']('', '
3199
		SELECT
3200
			bp.add_deny, bp.permission, bp.id_group, mg.group_name' . (empty($board) ? '' : ',
3201
			b.id_profile, CASE WHEN (mods.id_member IS NULL AND modgs.id_group IS NULL) THEN 0 ELSE 1 END AS is_moderator') . '
3202
		FROM {db_prefix}board_permissions AS bp' . (empty($board) ? '' : '
3203
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = {int:current_board})
3204
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
3205
			LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board AND modgs.id_group IN ({array_int:group_list}))') . '
3206
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = bp.id_group)
3207
		WHERE bp.id_profile = {raw:current_profile}
3208
			AND bp.id_group IN ({array_int:group_list}' . (empty($board) ? ')' : ', {int:moderator_group})
3209
			AND (mods.id_member IS NOT NULL OR modgs.id_group IS NOT NULL OR bp.id_group != {int:moderator_group})'),
3210
		array(
3211
			'current_board' => $board,
3212
			'group_list' => $curGroups,
3213
			'current_member' => $memID,
3214
			'current_profile' => empty($board) ? '1' : 'b.id_profile',
3215
			'moderator_group' => 3,
3216
		)
3217
	);
3218
3219
	while ($row = $smcFunc['db_fetch_assoc']($request))
3220
	{
3221
		// We don't know about this permission, it doesn't exist :P.
3222
		if (!isset($txt['permissionname_' . $row['permission']]))
3223
			continue;
3224
3225
		// The name of the permission using the format 'permission name' - 'own/any topic/event/etc.'.
3226
		if (in_array(substr($row['permission'], -4), array('_own', '_any')) && isset($txt['permissionname_' . substr($row['permission'], 0, -4)]))
3227
			$name = $txt['permissionname_' . substr($row['permission'], 0, -4)] . ' - ' . $txt['permissionname_' . $row['permission']];
3228
		else
3229
			$name = $txt['permissionname_' . $row['permission']];
3230
3231
		// Create the structure for this permission.
3232
		if (!isset($context['member']['permissions']['board'][$row['permission']]))
3233
			$context['member']['permissions']['board'][$row['permission']] = array(
3234
				'id' => $row['permission'],
3235
				'groups' => array(
3236
					'allowed' => array(),
3237
					'denied' => array()
3238
				),
3239
				'name' => $name,
3240
				'is_denied' => false,
3241
				'is_global' => empty($board),
3242
			);
3243
3244
		$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'];
3245
3246
		$context['member']['permissions']['board'][$row['permission']]['is_denied'] |= empty($row['add_deny']);
3247
	}
3248
	$smcFunc['db_free_result']($request);
3249
}
3250
3251
/**
3252
 * View a member's warnings
3253
 *
3254
 * @param int $memID The ID of the member
3255
 */
3256
function viewWarning($memID)
3257
{
3258
	global $modSettings, $context, $sourcedir, $txt, $scripturl;
3259
3260
	// Firstly, can we actually even be here?
3261
	if (!($context['user']['is_owner'] && allowedTo('view_warning_own')) && !allowedTo('view_warning_any') && !allowedTo('issue_warning') && !allowedTo('moderate_forum'))
3262
		fatal_lang_error('no_access', false);
3263
3264
	// Make sure things which are disabled stay disabled.
3265
	$modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110;
3266
	$modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110;
3267
	$modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110;
3268
3269
	// Let's use a generic list to get all the current warnings, and use the issue warnings grab-a-granny thing.
3270
	require_once($sourcedir . '/Subs-List.php');
3271
	require_once($sourcedir . '/Profile-Actions.php');
3272
3273
	$listOptions = array(
3274
		'id' => 'view_warnings',
3275
		'title' => $txt['profile_viewwarning_previous_warnings'],
3276
		'items_per_page' => $modSettings['defaultMaxListItems'],
3277
		'no_items_label' => $txt['profile_viewwarning_no_warnings'],
3278
		'base_href' => $scripturl . '?action=profile;area=viewwarning;sa=user;u=' . $memID,
3279
		'default_sort_col' => 'log_time',
3280
		'get_items' => array(
3281
			'function' => 'list_getUserWarnings',
3282
			'params' => array(
3283
				$memID,
3284
			),
3285
		),
3286
		'get_count' => array(
3287
			'function' => 'list_getUserWarningCount',
3288
			'params' => array(
3289
				$memID,
3290
			),
3291
		),
3292
		'columns' => array(
3293
			'log_time' => array(
3294
				'header' => array(
3295
					'value' => $txt['profile_warning_previous_time'],
3296
				),
3297
				'data' => array(
3298
					'db' => 'time',
3299
				),
3300
				'sort' => array(
3301
					'default' => 'lc.log_time DESC',
3302
					'reverse' => 'lc.log_time',
3303
				),
3304
			),
3305
			'reason' => array(
3306
				'header' => array(
3307
					'value' => $txt['profile_warning_previous_reason'],
3308
					'style' => 'width: 50%;',
3309
				),
3310
				'data' => array(
3311
					'db' => 'reason',
3312
				),
3313
			),
3314
			'level' => array(
3315
				'header' => array(
3316
					'value' => $txt['profile_warning_previous_level'],
3317
				),
3318
				'data' => array(
3319
					'db' => 'counter',
3320
				),
3321
				'sort' => array(
3322
					'default' => 'lc.counter DESC',
3323
					'reverse' => 'lc.counter',
3324
				),
3325
			),
3326
		),
3327
		'additional_rows' => array(
3328
			array(
3329
				'position' => 'after_title',
3330
				'value' => $txt['profile_viewwarning_desc'],
3331
				'class' => 'smalltext',
3332
				'style' => 'padding: 2ex;',
3333
			),
3334
		),
3335
	);
3336
3337
	// Create the list for viewing.
3338
	require_once($sourcedir . '/Subs-List.php');
3339
	createList($listOptions);
3340
3341
	// Create some common text bits for the template.
3342
	$context['level_effects'] = array(
3343
		0 => '',
3344
		$modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'],
3345
		$modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'],
3346
		$modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'],
3347
	);
3348
	$context['current_level'] = 0;
3349
	foreach ($context['level_effects'] as $limit => $dummy)
3350
		if ($context['member']['warning'] >= $limit)
3351
			$context['current_level'] = $limit;
3352
}
3353
3354
/**
3355
 * Sets the icon for a fetched alert.
3356
 *
3357
 * @param array The alert that we want to set an icon for.
3358
 */
3359
function set_alert_icon($alert)
3360
{
3361
	global $settings;
3362
3363
	switch ($alert['content_type'])
3364
	{
3365
		case 'topic':
3366
		case 'board':
3367
			{
3368
				switch ($alert['content_action'])
3369
				{
3370
					case 'reply':
3371
					case 'topic':
3372
						$class = 'main_icons posts';
3373
						break;
3374
3375
					case 'move':
3376
						$src = $settings['images_url'] . '/post/moved.png';
3377
						break;
3378
3379
					case 'remove':
3380
						$class = 'main_icons delete';
3381
						break;
3382
3383
					case 'lock':
3384
					case 'unlock':
3385
						$class = 'main_icons lock';
3386
						break;
3387
3388
					case 'sticky':
3389
					case 'unsticky':
3390
						$class = 'main_icons sticky';
3391
						break;
3392
3393
					case 'split':
3394
						$class = 'main_icons split_button';
3395
						break;
3396
3397
					case 'merge':
3398
						$class = 'main_icons merge';
3399
						break;
3400
3401
					case 'unapproved_topic':
3402
					case 'unapproved_post':
3403
						$class = 'main_icons post_moderation_moderate';
3404
						break;
3405
3406
					default:
3407
						$class = 'main_icons posts';
3408
						break;
3409
				}
3410
			}
3411
			break;
3412
3413
		case 'msg':
3414
			{
3415
				switch ($alert['content_action'])
3416
				{
3417
					case 'like':
3418
						$class = 'main_icons like';
3419
						break;
3420
3421
					case 'mention':
3422
						$class = 'main_icons im_on';
3423
						break;
3424
3425
					case 'quote':
3426
						$class = 'main_icons quote';
3427
						break;
3428
3429
					case 'unapproved_attachment':
3430
						$class = 'main_icons post_moderation_attach';
3431
						break;
3432
3433
					case 'report':
3434
					case 'report_reply':
3435
						$class = 'main_icons post_moderation_moderate';
3436
						break;
3437
3438
					default:
3439
						$class = 'main_icons posts';
3440
						break;
3441
				}
3442
			}
3443
			break;
3444
3445
		case 'member':
3446
			{
3447
				switch ($alert['content_action'])
3448
				{
3449
					case 'register_standard':
3450
					case 'register_approval':
3451
					case 'register_activation':
3452
						$class = 'main_icons members';
3453
						break;
3454
3455
					case 'report':
3456
					case 'report_reply':
3457
						$class = 'main_icons members_watched';
3458
						break;
3459
3460
					case 'buddy_request':
3461
						$class = 'main_icons people';
3462
						break;
3463
3464
					case 'group_request':
3465
						$class = 'main_icons members_request';
3466
						break;
3467
3468
					default:
3469
						$class = 'main_icons members';
3470
						break;
3471
				}
3472
			}
3473
			break;
3474
3475
		case 'groupr':
3476
			$class = 'main_icons members_request';
3477
			break;
3478
3479
		case 'event':
3480
			$class = 'main_icons calendar';
3481
			break;
3482
3483
		case 'paidsubs':
3484
			$class = 'main_icons paid';
3485
			break;
3486
3487
		case 'birthday':
3488
			$src = $settings['images_url'] . '/cake.png';
3489
			break;
3490
3491
		default:
3492
			$class = 'main_icons alerts';
3493
			break;
3494
	}
3495
3496
	if (isset($class))
3497
		return '<span class="alert_icon ' . $class . '"></span>';
3498
	elseif (isset($src))
3499
		return '<img class="alert_icon" src="' . $src . '">';
3500
	else
3501
		return '';
3502
}
3503
3504
?>