Issues (1014)

Sources/Profile-View.php (8 issues)

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

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

28
		fatal_lang_error('not_a_user', false, /** @scrutinizer ignore-type */ 404);
Loading history...
29
30
	// Set up the stuff and load the user.
31
	$context += array(
32
		'page_title' => sprintf($txt['profile_of_username'], $memberContext[$memID]['name']),
33
		'can_send_pm' => allowedTo('pm_send'),
34
		'can_have_buddy' => allowedTo('profile_extra_own') && !empty($modSettings['enable_buddylist']),
35
		'can_issue_warning' => allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1,
36
		'can_view_warning' => (allowedTo('moderate_forum') || allowedTo('issue_warning') || allowedTo('view_warning_any') || ($context['user']['is_owner'] && allowedTo('view_warning_own'))) && $modSettings['warning_settings'][0] === '1'
37
	);
38
39
	$context['member'] = &$memberContext[$memID];
40
41
	// Set a canonical URL for this page.
42
	$context['canonical_url'] = $scripturl . '?action=profile;u=' . $memID;
43
44
	// Are there things we don't show?
45
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
46
	// Menu tab
47
	$context[$context['profile_menu_name']]['tab_data'] = array(
48
		'title' => $txt['summary'],
49
		'icon_class' => 'main_icons profile_hd'
50
	);
51
52
	// See if they have broken any warning levels...
53
	list ($modSettings['warning_enable'], $modSettings['user_limit']) = explode(',', $modSettings['warning_settings']);
54
	if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $context['member']['warning'])
55
		$context['warning_status'] = $txt['profile_warning_is_muted'];
56
	elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $context['member']['warning'])
57
		$context['warning_status'] = $txt['profile_warning_is_moderation'];
58
	elseif (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $context['member']['warning'])
59
		$context['warning_status'] = $txt['profile_warning_is_watch'];
60
61
	// They haven't even been registered for a full day!?
62
	$days_registered = (int) ((time() - $user_profile[$memID]['date_registered']) / (3600 * 24));
63
	if (empty($user_profile[$memID]['date_registered']) || $days_registered < 1)
64
		$context['member']['posts_per_day'] = $txt['not_applicable'];
65
	else
66
		$context['member']['posts_per_day'] = comma_format($context['member']['real_posts'] / $days_registered, 3);
67
68
	// Set the age...
69
	if (empty($context['member']['birth_date']) || substr($context['member']['birth_date'], 0, 4) < 1002)
70
	{
71
		$context['member'] += array(
72
			'age' => $txt['not_applicable'],
73
			'today_is_birthday' => false
74
		);
75
	}
76
	else
77
	{
78
		list ($birth_year, $birth_month, $birth_day) = sscanf($context['member']['birth_date'], '%d-%d-%d');
79
		$datearray = getdate(time());
80
		$context['member'] += array(
81
			'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),
82
			'today_is_birthday' => $datearray['mon'] == $birth_month && $datearray['mday'] == $birth_day && $birth_year > 1004
83
		);
84
	}
85
86
	if (allowedTo('moderate_forum'))
87
	{
88
		// Make sure it's a valid ip address; otherwise, don't bother...
89
		if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $memberContext[$memID]['ip']) == 1 && empty($modSettings['disableHostnameLookup']))
90
			$context['member']['hostname'] = host_from_ip($memberContext[$memID]['ip']);
91
		else
92
			$context['member']['hostname'] = '';
93
94
		$context['can_see_ip'] = true;
95
	}
96
	else
97
		$context['can_see_ip'] = false;
98
99
	// Are they hidden?
100
	$context['member']['is_hidden'] = empty($user_profile[$memID]['show_online']);
101
	$context['member']['show_last_login'] = allowedTo('admin_forum') || !$context['member']['is_hidden'];
102
103
	if (!empty($modSettings['who_enabled']) && $context['member']['show_last_login'])
104
	{
105
		include_once($sourcedir . '/Who.php');
106
		$action = determineActions($user_profile[$memID]['url']);
107
108
		if ($action !== false)
0 ignored issues
show
The condition $action !== false is always true.
Loading history...
109
			$context['member']['action'] = $action;
110
	}
111
112
	// If the user is awaiting activation, and the viewer has permission - setup some activation context messages.
113
	if ($context['member']['is_activated'] % 10 != 1 && allowedTo('moderate_forum'))
114
	{
115
		$context['activate_type'] = $context['member']['is_activated'];
116
		// What should the link text be?
117
		$context['activate_link_text'] = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15)) ? $txt['account_approve'] : $txt['account_activate'];
118
119
		// Should we show a custom message?
120
		$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'];
121
122
		// If they can be approved, we need to set up a token for them.
123
		$context['token_check'] = 'profile-aa' . $memID;
124
		createToken($context['token_check'], 'get');
125
126
		// Puerile comment
127
		$type = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15)) ? 'approve' : 'activate';
128
		$context['activate_link'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=' . $type . ';' . $context['session_var'] . '=' . $context['session_id'] . ';' . $context[$context['token_check'] . '_token_var'] . '=' . $context[$context['token_check'] . '_token'];
129
	}
130
131
	// Is the signature even enabled on this forum?
132
	$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
133
134
	// Prevent signature images from going outside the box.
135
	if ($context['signature_enabled'])
136
	{
137
		list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
138
		$sig_limits = explode(',', $sig_limits);
139
140
		if (!empty($sig_limits[5]) || !empty($sig_limits[6]))
141
			addInlineCss('
142
	.signature img { ' . (!empty($sig_limits[5]) ? 'max-width: ' . (int) $sig_limits[5] . 'px; ' : '') . (!empty($sig_limits[6]) ? 'max-height: ' . (int) $sig_limits[6] . 'px; ' : '') . '}');
143
	}
144
145
	// How about, are they banned?
146
	$context['member']['bans'] = array();
147
	if (allowedTo('moderate_forum'))
148
	{
149
		// Can they edit the ban?
150
		$context['can_edit_ban'] = allowedTo('manage_bans');
151
152
		$ban_query = array();
153
		$ban_query_vars = array(
154
			'time' => time(),
155
		);
156
		$ban_query[] = 'id_member = ' . $context['member']['id'];
157
		$ban_query[] = ' {inet:ip} BETWEEN bi.ip_low and bi.ip_high';
158
		$ban_query_vars['ip'] = $memberContext[$memID]['ip'];
159
		// Do we have a hostname already?
160
		if (!empty($context['member']['hostname']))
161
		{
162
			$ban_query[] = '({string:hostname} LIKE hostname)';
163
			$ban_query_vars['hostname'] = $context['member']['hostname'];
164
		}
165
		// Check their email as well...
166
		if (strlen($context['member']['email']) != 0)
167
		{
168
			$ban_query[] = '({string:email} LIKE bi.email_address)';
169
			$ban_query_vars['email'] = $context['member']['email'];
170
		}
171
172
		// So... are they banned?  Dying to know!
173
		$request = $smcFunc['db_query']('', '
174
			SELECT bg.id_ban_group, bg.name, bg.cannot_access, bg.cannot_post,
175
				bg.cannot_login, bg.reason
176
			FROM {db_prefix}ban_items AS bi
177
				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}))
178
			WHERE (' . implode(' OR ', $ban_query) . ')',
179
			$ban_query_vars
180
		);
181
		while ($row = $smcFunc['db_fetch_assoc']($request))
182
		{
183
			// Work out what restrictions we actually have.
184
			$ban_restrictions = array();
185
			foreach (array('access', 'login', 'post') as $type)
186
				if ($row['cannot_' . $type])
187
					$ban_restrictions[] = $txt['ban_type_' . $type];
188
189
			// No actual ban in place?
190
			if (empty($ban_restrictions))
191
				continue;
192
193
			// Prepare the link for context.
194
			$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>');
195
196
			$context['member']['bans'][$row['id_ban_group']] = array(
197
				'reason' => empty($row['reason']) ? '' : '<br><br><strong>' . $txt['ban_reason'] . ':</strong> ' . $row['reason'],
198
				'cannot' => array(
199
					'access' => !empty($row['cannot_access']),
200
					'post' => !empty($row['cannot_post']),
201
					'login' => !empty($row['cannot_login']),
202
				),
203
				'explanation' => $ban_explanation,
204
			);
205
		}
206
		$smcFunc['db_free_result']($request);
207
	}
208
	loadCustomFields($memID);
209
210
	$context['print_custom_fields'] = array();
211
212
	// Any custom profile fields?
213
	if (!empty($context['custom_fields']))
214
		foreach ($context['custom_fields'] as $custom)
215
			$context['print_custom_fields'][$context['cust_profile_fields_placement'][$custom['placement']]][] = $custom;
216
217
}
218
219
/**
220
 * Fetch the alerts a member currently has.
221
 *
222
 * @param int $memID The ID of the member.
223
 * @param mixed $to_fetch Alerts to fetch: true/false for all/unread, or a list of one or more IDs.
224
 * @param array $limit Maximum number of alerts to fetch (0 for no limit).
225
 * @param array $offset Number of alerts to skip for pagination. Ignored if $to_fetch is a list of IDs.
226
 * @param bool $with_avatar Whether to load the avatar of the alert sender.
227
 * @param bool $show_links Whether to show links in the constituent parts of the alert meessage.
228
 * @return array An array of information about the fetched alerts.
229
 */
230
function fetch_alerts($memID, $to_fetch = false, $limit = 0, $offset = 0, $with_avatar = false, $show_links = false)
231
{
232
	global $smcFunc, $txt, $scripturl, $user_info, $user_profile, $modSettings, $context;
233
234
	// Are we being asked for some specific alerts?
235
	$alertIDs = is_bool($to_fetch) ? array() : array_filter(array_map('intval', (array) $to_fetch));
236
237
	// Basic sanitation.
238
	$memID = (int) $memID;
239
	$unread = $to_fetch === false;
240
	
241
	if (empty($limit) || $limit > 1000)
242
		$limit = min(!empty($modSettings['alerts_per_page']) && (int) $modSettings['alerts_per_page'] < 1000 ? (int) $modSettings['alerts_per_page'] : 1000, 1000);
243
244
	$offset = !empty($alertIDs) ? 0 : max(0, (int) $offset);
245
	$with_avatar = !empty($with_avatar);
246
	$show_links = !empty($show_links);
247
248
	// Arrays we'll need.
249
	$alerts = array();
250
	$senders = array();
251
	$profiles = array();
252
	$profile_alerts = array();
253
	$possible_msgs = array();
254
	$possible_topics = array();
255
256
	// Get the basic alert info.
257
	$request = $smcFunc['db_query']('', '
258
		SELECT a.id_alert, a.alert_time, a.is_read, a.extra,
259
			a.content_type, a.content_id, a.content_action,
260
			mem.id_member AS sender_id, COALESCE(mem.real_name, a.member_name) AS sender_name' . ($with_avatar ? ',
261
			mem.email_address AS sender_email, mem.avatar AS sender_avatar, f.filename AS sender_filename' : '') . '
262
		FROM {db_prefix}user_alerts AS a
263
			LEFT JOIN {db_prefix}members AS mem ON (a.id_member_started = mem.id_member)' . ($with_avatar ? '
264
			LEFT JOIN {db_prefix}attachments AS f ON (mem.id_member = f.id_member)' : '') . '
265
		WHERE a.id_member = {int:id_member}' . ($unread ? '
266
			AND a.is_read = 0' : '') . (!empty($alertIDs) ? '
267
			AND a.id_alert IN ({array_int:alertIDs})' : '') . '
268
		ORDER BY id_alert DESC' . (!empty($limit) ? '
269
		LIMIT {int:limit}' : '') . (!empty($offset) ?'
270
		OFFSET {int:offset}' : ''),
271
		array(
272
			'id_member' => $memID,
273
			'alertIDs' => $alertIDs,
274
			'limit' => $limit,
275
			'offset' => $offset,
276
		)
277
	);
278
	while ($row = $smcFunc['db_fetch_assoc']($request))
279
	{
280
		$id_alert = array_shift($row);
281
		$row['time'] = timeformat($row['alert_time']);
282
		$row['extra'] = !empty($row['extra']) ? $smcFunc['json_decode']($row['extra'], true) : array();
283
		$alerts[$id_alert] = $row;
284
285
		if (!empty($row['sender_email']))
286
		{
287
			$senders[$row['sender_id']] = array(
288
				'email' => $row['sender_email'],
289
				'avatar' => $row['sender_avatar'],
290
				'filename' => $row['sender_filename'],
291
			);
292
		}
293
294
		if ($row['content_type'] == 'profile')
295
		{
296
			$profiles[] = $row['content_id'];
297
			$profile_alerts[] = $id_alert;
298
		}
299
300
		// For these types, we need to check whether they can actually see the content.
301
		if ($row['content_type'] == 'msg')
302
		{
303
			$alerts[$id_alert]['visible'] = false;
304
			$possible_msgs[$id_alert] = $row['content_id'];
305
		}
306
		elseif (in_array($row['content_type'], array('topic', 'board')))
307
		{
308
			$alerts[$id_alert]['visible'] = false;
309
			$possible_topics[$id_alert] = $row['content_id'];
310
		}
311
		// For the rest, they can always see it.
312
		else
313
			$alerts[$id_alert]['visible'] = true;
314
315
		// Are we showing multiple links or one big main link ?
316
		$alerts[$id_alert]['show_links'] = $show_links || (isset($row['extra']['show_links']) && $row['extra']['show_links']);
317
318
		// Set an appropriate icon.
319
		$alerts[$id_alert]['icon'] = set_alert_icon($alerts[$id_alert]);
320
	}
321
	$smcFunc['db_free_result']($request);
322
323
	// Look up member info of anyone we need it for.
324
	if (!empty($profiles))
325
		loadMemberData($profiles, false, 'minimal');
326
327
	// Get the senders' avatars.
328
	if ($with_avatar)
329
	{
330
		foreach ($senders as $sender_id => $sender)
331
			$senders[$sender_id]['avatar'] = set_avatar_data($sender);
332
333
		$context['avatar_url'] = $modSettings['avatar_url'];
334
	}
335
336
	// Now go through and actually make with the text.
337
	loadLanguage('Alerts');
338
339
	// Some sprintf formats for generating links/strings.
340
	// 'required' is an array of keys in $alert['extra'] that should be used to generate the message, ordered to match the sprintf formats.
341
	// 'link' and 'text' are the sprintf formats that will be used when $alert['show_links'] is true or false, respectively.
342
	$formats['msg_msg'] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$formats was never initialized. Although not strictly required by PHP, it is generally a good practice to add $formats = array(); before regardless.
Loading history...
343
		'required' => array('content_subject', 'topic', 'msg'),
344
		'link' => '<a href="{scripturl}?topic=%2$d.msg%3$d#msg%3$d">%1$s</a>',
345
		'text' => '<strong>%1$s</strong>',
346
	);
347
	$formats['topic_msg'] = array(
348
		'required' => array('content_subject', 'topic', 'topic_suffix'),
349
		'link' => '<a href="{scripturl}?topic=%2$d.%3$s">%1$s</a>',
350
		'text' => '<strong>%1$s</strong>',
351
	);
352
	$formats['board_msg'] = array(
353
		'required' => array('board_name', 'board'),
354
		'link' => '<a href="{scripturl}?board=%2$d.0">%1$s</a>',
355
		'text' => '<strong>%1$s</strong>',
356
	);
357
	$formats['profile_msg'] = array(
358
		'required' => array('user_name', 'user_id'),
359
		'link' => '<a href="{scripturl}?action=profile;u=%2$d">%1$s</a>',
360
		'text' => '<strong>%1$s</strong>',
361
	);
362
363
	// Hooks might want to do something snazzy around their own content types - including enforcing permissions if appropriate.
364
	call_integration_hook('integrate_fetch_alerts', array(&$alerts, &$formats));
365
366
	// Substitute $scripturl into the link formats. (Done here to make life easier for hooked mods.)
367
	$formats = array_map(
368
		function ($format) use ($scripturl)
369
		{
370
			$format['link'] = str_replace('{scripturl}', $scripturl, $format['link']);
371
			$format['text'] = str_replace('{scripturl}', $scripturl, $format['text']);
372
373
			return $format;
374
		},
375
		$formats
376
	);
377
378
	// If we need to check board access, use the correct board access filter for the member in question.
379
	if ((!isset($user_info['query_see_board']) || $user_info['id'] != $memID) && (!empty($possible_msgs) || !empty($possible_topics)))
380
		$qb = build_query_board($memID);
381
	else
382
		$qb['query_see_board'] = '{query_see_board}';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$qb was never initialized. Although not strictly required by PHP, it is generally a good practice to add $qb = array(); before regardless.
Loading history...
383
384
	// For anything that needs more info and/or wants us to check board or topic access, let's do that.
385
	if (!empty($possible_msgs))
386
	{
387
		$flipped_msgs = array();
388
		foreach ($possible_msgs as $id_alert => $id_msg)
389
		{
390
			if (!isset($flipped_msgs[$id_msg]))
391
				$flipped_msgs[$id_msg] = array();
392
393
			$flipped_msgs[$id_msg][] = $id_alert;
394
		}
395
396
		$request = $smcFunc['db_query']('', '
397
			SELECT m.id_msg, m.id_topic, m.subject, b.id_board, b.name AS board_name
398
			FROM {db_prefix}messages AS m
399
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
400
			WHERE ' . $qb['query_see_board'] . '
401
				AND m.id_msg IN ({array_int:msgs})
402
			ORDER BY m.id_msg',
403
			array(
404
				'msgs' => $possible_msgs,
405
			)
406
		);
407
		while ($row = $smcFunc['db_fetch_assoc']($request))
408
		{
409
			foreach ($flipped_msgs[$row['id_msg']] as $id_alert)
410
			{
411
				$alerts[$id_alert]['content_data'] = $row;
412
				$alerts[$id_alert]['visible'] = true;
413
			}
414
		}
415
		$smcFunc['db_free_result']($request);
416
	}
417
	if (!empty($possible_topics))
418
	{
419
		$flipped_topics = array();
420
		foreach ($possible_topics as $id_alert => $id_topic)
421
		{
422
			if (!isset($flipped_topics[$id_topic]))
423
				$flipped_topics[$id_topic] = array();
424
425
			$flipped_topics[$id_topic][] = $id_alert;
426
		}
427
428
		$request = $smcFunc['db_query']('', '
429
			SELECT m.id_msg, t.id_topic, m.subject, b.id_board, b.name AS board_name
430
			FROM {db_prefix}topics AS t
431
				INNER JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
432
				INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
433
			WHERE ' . $qb['query_see_board'] . '
434
				AND t.id_topic IN ({array_int:topics})',
435
			array(
436
				'topics' => $possible_topics,
437
			)
438
		);
439
		while ($row = $smcFunc['db_fetch_assoc']($request))
440
		{
441
			foreach ($flipped_topics[$row['id_topic']] as $id_alert)
442
			{
443
				$alerts[$id_alert]['content_data'] = $row;
444
				$alerts[$id_alert]['visible'] = true;
445
			}
446
		}
447
		$smcFunc['db_free_result']($request);
448
	}
449
450
	// 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)
451
	foreach ($alerts as $id_alert => $dummy)
452
	{
453
		// There's no point showing alerts for inaccessible content.
454
		if (!$alerts[$id_alert]['visible'])
455
		{
456
			unset($alerts[$id_alert]);
457
			continue;
458
		}
459
		else
460
			unset($alerts[$id_alert]['visible']);
461
462
		// Did a mod already take care of this one?
463
		if (!empty($alerts[$id_alert]['text']))
464
			continue;
465
466
		// For developer convenience.
467
		$alert = &$alerts[$id_alert];
468
469
		// The info in extra might outdated if the topic was moved, the message's subject was changed, etc.
470
		if (!empty($alert['content_data']))
471
		{
472
			$data = $alert['content_data'];
473
474
			// Make sure msg, topic, and board info are correct.
475
			$patterns = array();
476
			$replacements = array();
477
			foreach (array('msg', 'topic', 'board') as $item)
478
			{
479
				if (isset($data['id_' . $item]))
480
				{
481
					$separator = $item == 'msg' ? '=?' : '=';
482
483
					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)
484
					{
485
						$patterns[] = '/\b' . $item . $separator . '\d+/';
486
						$replacements[] = $item . $separator . $data['id_' . $item];
487
					}
488
489
					$alert['extra'][$item] = $data['id_' . $item];
490
				}
491
			}
492
			if (!empty($patterns))
493
				$alert['extra']['content_link'] = preg_replace($patterns, $replacements, $alert['extra']['content_link']);
494
495
			// Make sure the subject is correct.
496
			if (isset($data['subject']))
497
				$alert['extra']['content_subject'] = $data['subject'];
498
499
			// Keep track of this so we can use it below.
500
			if (isset($data['board_name']))
501
				$alert['extra']['board_name'] = $data['board_name'];
502
503
			unset($alert['content_data']);
504
		}
505
506
		// Do we want to link to the topic in general or the new messages specifically?
507
		if (isset($possible_topics[$id_alert]) && in_array($alert['content_action'], array('reply', 'topic', 'unapproved_reply')))
508
				$alert['extra']['topic_suffix'] = 'new;topicseen#new';
509
		elseif (isset($alert['extra']['topic']))
510
			$alert['extra']['topic_suffix'] = '0';
511
512
		// Make sure profile alerts have what they need.
513
		if (in_array($id_alert, $profile_alerts))
514
		{
515
			if (empty($alert['extra']['user_id']))
516
				$alert['extra']['user_id'] = $alert['content_id'];
517
518
			if (isset($user_profile[$alert['extra']['user_id']]))
519
				$alert['extra']['user_name'] = $user_profile[$alert['extra']['user_id']]['real_name'];
520
		}
521
522
		// If we loaded the sender's profile, we may as well use it.
523
		$sender_id = !empty($alert['sender_id']) ? $alert['sender_id'] : 0;
524
		if (isset($user_profile[$sender_id]))
525
			$alert['sender_name'] = $user_profile[$sender_id]['real_name'];
526
527
		// If requested, include the sender's avatar data.
528
		if ($with_avatar && !empty($senders[$sender_id]))
529
			$alert['sender'] = $senders[$sender_id];
530
531
		// Next, build the message strings.
532
		foreach ($formats as $msg_type => $format_info)
533
		{
534
			// Get the values to use in the formatted string, in the right order.
535
			$msg_values = array_replace(
536
				array_fill_keys($format_info['required'], ''),
537
				array_intersect_key($alert['extra'], array_flip($format_info['required']))
538
			);
539
540
			// Assuming all required values are present, build the message.
541
			if (!in_array('', $msg_values))
542
				$alert['extra'][$msg_type] = vsprintf($formats[$msg_type][$alert['show_links'] ? 'link' : 'text'], $msg_values);
543
544
			elseif (in_array($msg_type, array('msg_msg', 'topic_msg', 'board_msg')))
545
				$alert['extra'][$msg_type] = $txt[$msg_type == 'board_msg' ? 'board_na' : 'topic_na'];
546
			else
547
				$alert['extra'][$msg_type] = '(' . $txt['not_applicable'] . ')';
548
		}
549
550
		// Show the formatted time in alerts about subscriptions.
551
		if ($alert['content_type'] == 'paidsubs' && isset($alert['extra']['end_time']))
552
		{
553
			// If the subscription already expired, say so.
554
			if ($alert['extra']['end_time'] < time())
555
				$alert['content_action'] = 'expired';
556
557
			// Present a nicely formatted date.
558
			$alert['extra']['end_time'] = timeformat($alert['extra']['end_time']);
559
		}
560
561
		// Now set the main URL that this alert should take the user to.
562
		$alert['target_href'] = '';
563
564
		// Priority goes to explicitly specified links.
565
		if (isset($alert['extra']['content_link']))
566
			$alert['target_href'] = $alert['extra']['content_link'];
567
568
		elseif (isset($alert['extra']['report_link']))
569
			$alert['target_href'] = $scripturl . $alert['extra']['report_link'];
570
571
		// Next, try determining the link based on the content action.
572
		if (empty($alert['target_href']) && in_array($alert['content_action'], array('register_approval', 'group_request', 'buddy_request')))
573
		{
574
			switch ($alert['content_action'])
575
			{
576
				case 'register_approval':
577
					$alert['target_href'] = $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve';
578
					break;
579
580
				case 'group_request':
581
					$alert['target_href'] = $scripturl . '?action=moderate;area=groups;sa=requests';
582
					break;
583
584
				case 'buddy_request':
585
					if (!empty($alert['id_member_started']))
586
						$alert['target_href'] = $scripturl . '?action=profile;u=' . $alert['id_member_started'];
587
					break;
588
589
				default:
590
					break;
591
			}
592
		}
593
594
		// Or maybe we can determine the link based on the content type.
595
		if (empty($alert['target_href']) && in_array($alert['content_type'], array('msg', 'member', 'event')))
596
		{
597
			switch ($alert['content_type'])
598
			{
599
				case 'msg':
600
					if (!empty($alert['content_id']))
601
						$alert['target_href'] = $scripturl . '?msg=' . $alert['content_id'];
602
					break;
603
604
				case 'member':
605
					if (!empty($alert['id_member_started']))
606
						$alert['target_href'] = $scripturl . '?action=profile;u=' . $alert['id_member_started'];
607
					break;
608
609
				case 'event':
610
					if (!empty($alert['extra']['event_id']))
611
						$alert['target_href'] = $scripturl . '?action=calendar;event=' . $alert['extra']['event_id'];
612
					break;
613
614
				default:
615
					break;
616
			}
617
618
		}
619
620
		// Finally, set this alert's text string.
621
		$string = 'alert_' . $alert['content_type'] . '_' . $alert['content_action'];
622
623
		// This kludge exists because the alert content_types prior to 2.1 RC3 were a bit haphazard.
624
		// This can be removed once all the translated language files have been updated.
625
		if (!isset($txt[$string]))
626
		{
627
			if (strpos($alert['content_action'], 'unapproved_') === 0)
628
				$string = 'alert_' . $alert['content_action'];
629
630
			if ($alert['content_type'] === 'member' && in_array($alert['content_action'], array('report', 'report_reply')))
631
				$string = 'alert_profile_' . $alert['content_action'];
632
633
			if ($alert['content_type'] === 'member' && $alert['content_action'] === 'buddy_request')
634
				$string = 'alert_buddy_' . $alert['content_action'];
635
		}
636
637
		if (isset($txt[$string]))
638
		{
639
			$substitutions = array(
640
				'{scripturl}' => $scripturl,
641
				'{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>',
642
			);
643
644
			if (is_array($alert['extra']))
645
			{
646
				foreach ($alert['extra'] as $k => $v)
647
					$substitutions['{' . $k . '}'] = $v;
648
			}
649
650
			$alert['text'] = strtr($txt[$string], $substitutions);
651
		}
652
653
		// Unset the reference variable to avoid any surprises in subsequent loops.
654
		unset($alert);
655
	}
656
657
	return $alerts;
658
}
659
660
/**
661
 * Shows all alerts for a member
662
 *
663
 * @param int $memID The ID of the member
664
 */
665
function showAlerts($memID)
666
{
667
	global $context, $smcFunc, $txt, $sourcedir, $scripturl, $options, $modSettings;
668
669
	require_once($sourcedir . '/Profile-Modify.php');
670
671
	// Are we opening a specific alert? (i.e.: ?action=profile;area=showalerts;alert=12345)
672
	if (!empty($_REQUEST['alert']))
673
	{
674
		$alert_id = (int) $_REQUEST['alert'];
675
		$alerts = fetch_alerts($memID, $alert_id);
676
		$alert = array_pop($alerts);
677
678
		/*
679
		 * MOD AUTHORS:
680
		 * To control this redirect, use the 'integrate_fetch_alerts' hook to
681
		 * set the value of $alert['extra']['content_link'], which will become
682
		 * the value for $alert['target_href'].
683
		 */
684
685
		// In case it failed to determine this alert's link
686
		if (empty($alert['target_href']))
687
			redirectexit('action=profile;area=showalerts');
688
689
		// Mark the alert as read while we're at it.
690
		alert_mark($memID, $alert_id, 1);
691
692
		// Take the user to the content
693
		redirectexit($alert['target_href']);
694
	}
695
696
	// Prepare the pagination vars.
697
	$maxIndex = !empty($modSettings['alerts_per_page']) && (int) $modSettings['alerts_per_page'] < 1000 ? min((int) $modSettings['alerts_per_page'], 1000) : 25;
698
	$context['start'] = (int) isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
699
	$count = alert_count($memID);
700
701
	// Fix invalid 'start' offsets.
702
	if ($context['start'] > $count)
703
		$context['start'] = $count - ($count % $maxIndex);
704
	else
705
		$context['start'] = $context['start'] - ($context['start'] % $maxIndex);
706
707
	// Get the alerts.
708
	$context['alerts'] = fetch_alerts($memID, true, $maxIndex, $context['start'], true, true);
0 ignored issues
show
It seems like $maxIndex can also be of type integer; however, parameter $limit of fetch_alerts() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

708
	$context['alerts'] = fetch_alerts($memID, true, /** @scrutinizer ignore-type */ $maxIndex, $context['start'], true, true);
Loading history...
709
	$toMark = false;
710
	$action = '';
711
712
	//  Are we using checkboxes?
713
	$context['showCheckboxes'] = !empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1;
714
715
	// Create the pagination.
716
	$context['pagination'] = constructPageIndex($scripturl . '?action=profile;area=showalerts;u=' . $memID, $context['start'], $count, $maxIndex, false);
717
718
	// Set some JavaScript for checking all alerts at once.
719
	if ($context['showCheckboxes'])
720
		addInlineJavaScript('
721
		$(function(){
722
			$(\'#select_all\').on(\'change\', function() {
723
				var checkboxes = $(\'ul.quickbuttons\').find(\':checkbox\');
724
				if($(this).prop(\'checked\')) {
725
					checkboxes.prop(\'checked\', true);
726
				}
727
				else {
728
					checkboxes.prop(\'checked\', false);
729
				}
730
			});
731
		});', true);
732
733
	// The quickbuttons
734
	foreach ($context['alerts'] as $id => $alert)
735
	{
736
		$context['alerts'][$id]['quickbuttons'] = array(
737
			'delete' => array(
738
				'label' => $txt['delete'],
739
				'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'] : ''),
740
				'class' => 'you_sure',
741
				'icon' => 'remove_button'
742
			),
743
			'mark' => array(
744
				'label' => $alert['is_read'] != 0 ? $txt['mark_unread'] : $txt['mark_read_short'],
745
				'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'] : ''),
746
				'icon' => $alert['is_read'] != 0 ? 'unread_button' : 'read_button',
747
			),
748
			'view' => array(
749
				'label' => $txt['view'],
750
				'href' => $scripturl . '?action=profile;area=showalerts;alert=' . $id . ';',
751
				'icon' => 'move',
752
			),
753
			'quickmod' => array(
754
    			'class' => 'inline_mod_check',
755
				'content' => '<input type="checkbox" name="mark[' . $id . ']" value="' . $id . '">',
756
				'show' => $context['showCheckboxes']
757
			)
758
		);
759
	}
760
761
	// The Delete all unread link.
762
	$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'] : '');
763
764
	// Set a nice message.
765
	if (!empty($_SESSION['update_message']))
766
	{
767
		$context['update_message'] = $txt['profile_updated_own'];
768
		unset($_SESSION['update_message']);
769
	}
770
771
	// Saving multiple changes?
772
	if (isset($_GET['save']) && !empty($_POST['mark']))
773
	{
774
		// Get the values.
775
		$toMark = array_map('intval', (array) $_POST['mark']);
776
777
		// Which action?
778
		$action = !empty($_POST['mark_as']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_POST['mark_as'])) : '';
779
	}
780
781
	// A single change.
782
	if (!empty($_GET['do']) && !empty($_GET['aid']))
783
	{
784
		$toMark = (int) $_GET['aid'];
785
		$action = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_GET['do']));
786
	}
787
	// Delete all read alerts.
788
	elseif (!empty($_GET['do']) && $_GET['do'] === 'purge')
789
		$action = 'purge';
790
791
	// Save the changes.
792
	if (!empty($action) && (!empty($toMark) || $action === 'purge'))
793
	{
794
		checkSession('request');
795
796
		// Call it!
797
		if ($action == 'remove')
798
			alert_delete($toMark, $memID);
799
800
		elseif ($action == 'purge')
801
			alert_purge($memID);
802
803
		else
804
			alert_mark($memID, $toMark, $action == 'read' ? 1 : 0);
805
806
		// Set a nice update message.
807
		$_SESSION['update_message'] = true;
808
809
		// Redirect.
810
		redirectexit('action=profile;area=showalerts;u=' . $memID . (!empty($context['start']) ? ';start=' . $context['start'] : ''));
811
	}
812
}
813
814
/**
815
 * Show all posts by a member
816
 *
817
 * @todo This function needs to be split up properly.
818
 *
819
 * @param int $memID The ID of the member
820
 */
821
function showPosts($memID)
822
{
823
	global $txt, $user_info, $scripturl, $modSettings, $options;
824
	global $context, $user_profile, $sourcedir, $smcFunc, $board;
825
826
	// Some initial context.
827
	$context['start'] = (int) $_REQUEST['start'];
828
	$context['current_member'] = $memID;
829
830
	// Create the tabs for the template.
831
	$context[$context['profile_menu_name']]['tab_data'] = array(
832
		'title' => $txt['showPosts'],
833
		'description' => $txt['showPosts_help'],
834
		'icon_class' => 'main_icons profile_hd',
835
		'tabs' => array(
836
			'messages' => array(
837
			),
838
			'topics' => array(
839
			),
840
			'unwatchedtopics' => array(
841
			),
842
			'attach' => array(
843
			),
844
		),
845
	);
846
847
	// Shortcut used to determine which $txt['show*'] string to use for the title, based on the SA
848
	$title = array(
849
		'attach' => 'Attachments',
850
		'topics' => 'Topics'
851
	);
852
853
	if ($context['user']['is_owner'])
854
		$title['unwatchedtopics'] = 'Unwatched';
855
856
	// Set the page title
857
	if (isset($_GET['sa']) && array_key_exists($_GET['sa'], $title))
858
		$context['page_title'] = $txt['show' . $title[$_GET['sa']]];
859
	else
860
		$context['page_title'] = $txt['showPosts'];
861
862
	$context['page_title'] .= ' - ' . $user_profile[$memID]['real_name'];
863
864
	// Is the load average too high to allow searching just now?
865
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_show_posts']) && $context['load_average'] >= $modSettings['loadavg_show_posts'])
866
		fatal_lang_error('loadavg_show_posts_disabled', false);
867
868
	// If we're specifically dealing with attachments use that function!
869
	if (isset($_GET['sa']) && $_GET['sa'] == 'attach')
870
		return showAttachments($memID);
871
	// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
872
	elseif (isset($_GET['sa']) && $_GET['sa'] == 'unwatchedtopics' && $context['user']['is_owner'])
873
		return showUnwatched($memID);
0 ignored issues
show
Are you sure the usage of showUnwatched($memID) is correct as it seems to always return null.

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

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

}

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

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

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

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

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
3374
 */
3375
function set_alert_icon($alert)
3376
{
3377
	global $settings;
3378
3379
	switch ($alert['content_type'])
3380
	{
3381
		case 'topic':
3382
		case 'board':
3383
			{
3384
				switch ($alert['content_action'])
3385
				{
3386
					case 'reply':
3387
					case 'topic':
3388
						$class = 'main_icons posts';
3389
						break;
3390
3391
					case 'move':
3392
						$src = $settings['images_url'] . '/post/moved.png';
3393
						break;
3394
3395
					case 'remove':
3396
						$class = 'main_icons delete';
3397
						break;
3398
3399
					case 'lock':
3400
					case 'unlock':
3401
						$class = 'main_icons lock';
3402
						break;
3403
3404
					case 'sticky':
3405
					case 'unsticky':
3406
						$class = 'main_icons sticky';
3407
						break;
3408
3409
					case 'split':
3410
						$class = 'main_icons split_button';
3411
						break;
3412
3413
					case 'merge':
3414
						$class = 'main_icons merge';
3415
						break;
3416
3417
					case 'unapproved_topic':
3418
					case 'unapproved_post':
3419
						$class = 'main_icons post_moderation_moderate';
3420
						break;
3421
3422
					default:
3423
						$class = 'main_icons posts';
3424
						break;
3425
				}
3426
			}
3427
			break;
3428
3429
		case 'msg':
3430
			{
3431
				switch ($alert['content_action'])
3432
				{
3433
					case 'like':
3434
						$class = 'main_icons like';
3435
						break;
3436
3437
					case 'mention':
3438
						$class = 'main_icons im_on';
3439
						break;
3440
3441
					case 'quote':
3442
						$class = 'main_icons quote';
3443
						break;
3444
3445
					case 'unapproved_attachment':
3446
						$class = 'main_icons post_moderation_attach';
3447
						break;
3448
3449
					case 'report':
3450
					case 'report_reply':
3451
						$class = 'main_icons post_moderation_moderate';
3452
						break;
3453
3454
					default:
3455
						$class = 'main_icons posts';
3456
						break;
3457
				}
3458
			}
3459
			break;
3460
3461
		case 'member':
3462
			{
3463
				switch ($alert['content_action'])
3464
				{
3465
					case 'register_standard':
3466
					case 'register_approval':
3467
					case 'register_activation':
3468
						$class = 'main_icons members';
3469
						break;
3470
3471
					case 'report':
3472
					case 'report_reply':
3473
						$class = 'main_icons members_watched';
3474
						break;
3475
3476
					case 'buddy_request':
3477
						$class = 'main_icons people';
3478
						break;
3479
3480
					case 'group_request':
3481
						$class = 'main_icons members_request';
3482
						break;
3483
3484
					default:
3485
						$class = 'main_icons members';
3486
						break;
3487
				}
3488
			}
3489
			break;
3490
3491
		case 'groupr':
3492
			$class = 'main_icons members_request';
3493
			break;
3494
3495
		case 'event':
3496
			$class = 'main_icons calendar';
3497
			break;
3498
3499
		case 'paidsubs':
3500
			$class = 'main_icons paid';
3501
			break;
3502
3503
		case 'birthday':
3504
			$src = $settings['images_url'] . '/cake.png';
3505
			break;
3506
3507
		default:
3508
			$class = 'main_icons alerts';
3509
			break;
3510
	}
3511
3512
	if (isset($class))
3513
		return '<span class="alert_icon ' . $class . '"></span>';
3514
	elseif (isset($src))
3515
		return '<img class="alert_icon" src="' . $src . '">';
3516
	else
3517
		return '';
3518
}
3519
3520
?>