Issues (1014)

Sources/Who.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is mainly concerned with the Who's Online list.
5
 * Although, it also handles credits. :P
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines https://www.simplemachines.org
11
 * @copyright 2022 Simple Machines and individual contributors
12
 * @license https://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1.2
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * Who's online, and what are they doing?
22
 * This function prepares the who's online data for the Who template.
23
 * It requires the who_view permission.
24
 * It is enabled with the who_enabled setting.
25
 * It is accessed via ?action=who.
26
 *
27
 * Uses Who template, main sub-template
28
 * Uses Who language file.
29
 */
30
function Who()
31
{
32
	global $context, $scripturl, $txt, $modSettings, $memberContext, $smcFunc;
33
34
	// Permissions, permissions, permissions.
35
	isAllowedTo('who_view');
36
37
	// You can't do anything if this is off.
38
	if (empty($modSettings['who_enabled']))
39
		fatal_lang_error('who_off', false);
40
41
	// Discourage robots from indexing this page.
42
	$context['robot_no_index'] = true;
43
44
	// Load the 'Who' template.
45
	loadTemplate('Who');
46
	loadLanguage('Who');
47
48
	// Sort out... the column sorting.
49
	$sort_methods = array(
50
		'user' => 'mem.real_name',
51
		'time' => 'lo.log_time'
52
	);
53
54
	$show_methods = array(
55
		'members' => '(lo.id_member != 0)',
56
		'guests' => '(lo.id_member = 0)',
57
		'all' => '1=1',
58
	);
59
60
	// Store the sort methods and the show types for use in the template.
61
	$context['sort_methods'] = array(
62
		'user' => $txt['who_user'],
63
		'time' => $txt['who_time'],
64
	);
65
	$context['show_methods'] = array(
66
		'all' => $txt['who_show_all'],
67
		'members' => $txt['who_show_members_only'],
68
		'guests' => $txt['who_show_guests_only'],
69
	);
70
71
	// Can they see spiders too?
72
	if (!empty($modSettings['show_spider_online']) && ($modSettings['show_spider_online'] == 2 || allowedTo('admin_forum')) && !empty($modSettings['spider_name_cache']))
73
	{
74
		$show_methods['spiders'] = '(lo.id_member = 0 AND lo.id_spider > 0)';
75
		$show_methods['guests'] = '(lo.id_member = 0 AND lo.id_spider = 0)';
76
		$context['show_methods']['spiders'] = $txt['who_show_spiders_only'];
77
	}
78
	elseif (empty($modSettings['show_spider_online']) && isset($_SESSION['who_online_filter']) && $_SESSION['who_online_filter'] == 'spiders')
79
		unset($_SESSION['who_online_filter']);
80
81
	// Does the user prefer a different sort direction?
82
	if (isset($_REQUEST['sort']) && isset($sort_methods[$_REQUEST['sort']]))
83
	{
84
		$context['sort_by'] = $_SESSION['who_online_sort_by'] = $_REQUEST['sort'];
85
		$sort_method = $sort_methods[$_REQUEST['sort']];
86
	}
87
	// Did we set a preferred sort order earlier in the session?
88
	elseif (isset($_SESSION['who_online_sort_by']))
89
	{
90
		$context['sort_by'] = $_SESSION['who_online_sort_by'];
91
		$sort_method = $sort_methods[$_SESSION['who_online_sort_by']];
92
	}
93
	// Default to last time online.
94
	else
95
	{
96
		$context['sort_by'] = $_SESSION['who_online_sort_by'] = 'time';
97
		$sort_method = 'lo.log_time';
98
	}
99
100
	$context['sort_direction'] = isset($_REQUEST['asc']) || (isset($_REQUEST['sort_dir']) && $_REQUEST['sort_dir'] == 'asc') ? 'up' : 'down';
101
102
	$conditions = array();
103
	if (!allowedTo('moderate_forum'))
104
		$conditions[] = '(COALESCE(mem.show_online, 1) = 1)';
105
106
	// Fallback to top filter?
107
	if (isset($_REQUEST['submit_top']) && isset($_REQUEST['show_top']))
108
		$_REQUEST['show'] = $_REQUEST['show_top'];
109
	// Does the user wish to apply a filter?
110
	if (isset($_REQUEST['show']) && isset($show_methods[$_REQUEST['show']]))
111
		$context['show_by'] = $_SESSION['who_online_filter'] = $_REQUEST['show'];
112
	// Perhaps we saved a filter earlier in the session?
113
	elseif (isset($_SESSION['who_online_filter']))
114
		$context['show_by'] = $_SESSION['who_online_filter'];
115
	else
116
		$context['show_by'] = 'members';
117
118
	$conditions[] = $show_methods[$context['show_by']];
119
120
	// Get the total amount of members online.
121
	$request = $smcFunc['db_query']('', '
122
		SELECT COUNT(*)
123
		FROM {db_prefix}log_online AS lo
124
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)' . (!empty($conditions) ? '
125
		WHERE ' . implode(' AND ', $conditions) : ''),
126
		array(
127
		)
128
	);
129
	list ($totalMembers) = $smcFunc['db_fetch_row']($request);
130
	$smcFunc['db_free_result']($request);
131
132
	// Prepare some page index variables.
133
	$context['page_index'] = constructPageIndex($scripturl . '?action=who;sort=' . $context['sort_by'] . ($context['sort_direction'] == 'up' ? ';asc' : '') . ';show=' . $context['show_by'], $_REQUEST['start'], $totalMembers, $modSettings['defaultMaxMembers']);
134
	$context['start'] = $_REQUEST['start'];
135
136
	// Look for people online, provided they don't mind if you see they are.
137
	$request = $smcFunc['db_query']('', '
138
		SELECT
139
			lo.log_time, lo.id_member, lo.url, lo.ip AS ip, mem.real_name,
140
			lo.session, mg.online_color, COALESCE(mem.show_online, 1) AS show_online,
141
			lo.id_spider
142
		FROM {db_prefix}log_online AS lo
143
			LEFT JOIN {db_prefix}members AS mem ON (lo.id_member = mem.id_member)
144
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_member} THEN mem.id_post_group ELSE mem.id_group END)' . (!empty($conditions) ? '
145
		WHERE ' . implode(' AND ', $conditions) : '') . '
146
		ORDER BY {raw:sort_method} {raw:sort_direction}
147
		LIMIT {int:offset}, {int:limit}',
148
		array(
149
			'regular_member' => 0,
150
			'sort_method' => $sort_method,
151
			'sort_direction' => $context['sort_direction'] == 'up' ? 'ASC' : 'DESC',
152
			'offset' => $context['start'],
153
			'limit' => $modSettings['defaultMaxMembers'],
154
		)
155
	);
156
	$context['members'] = array();
157
	$member_ids = array();
158
	$url_data = array();
159
	while ($row = $smcFunc['db_fetch_assoc']($request))
160
	{
161
		$actions = $smcFunc['json_decode']($row['url'], true);
162
		if ($actions === array())
163
			continue;
164
165
		// Send the information to the template.
166
		$context['members'][$row['session']] = array(
167
			'id' => $row['id_member'],
168
			'ip' => allowedTo('moderate_forum') ? inet_dtop($row['ip']) : '',
169
			// It is *going* to be today or yesterday, so why keep that information in there?
170
			'time' => strtr(timeformat($row['log_time']), array($txt['today'] => '', $txt['yesterday'] => '')),
171
			'timestamp' => $row['log_time'],
172
			'query' => $actions,
173
			'is_hidden' => $row['show_online'] == 0,
174
			'id_spider' => $row['id_spider'],
175
			'color' => empty($row['online_color']) ? '' : $row['online_color']
176
		);
177
178
		$url_data[$row['session']] = array($row['url'], $row['id_member']);
179
		$member_ids[] = $row['id_member'];
180
	}
181
	$smcFunc['db_free_result']($request);
182
183
	// Load the user data for these members.
184
	loadMemberData($member_ids);
185
186
	// Load up the guest user.
187
	$memberContext[0] = array(
188
		'id' => 0,
189
		'name' => $txt['guest_title'],
190
		'group' => $txt['guest_title'],
191
		'href' => '',
192
		'link' => $txt['guest_title'],
193
		'email' => $txt['guest_title'],
194
		'is_guest' => true
195
	);
196
197
	// Are we showing spiders?
198
	$spiderContext = array();
199
	if (!empty($modSettings['show_spider_online']) && ($modSettings['show_spider_online'] == 2 || allowedTo('admin_forum')) && !empty($modSettings['spider_name_cache']))
200
	{
201
		foreach ($smcFunc['json_decode']($modSettings['spider_name_cache'], true) as $id => $name)
202
			$spiderContext[$id] = array(
203
				'id' => 0,
204
				'name' => $name,
205
				'group' => $txt['spiders'],
206
				'href' => '',
207
				'link' => $name,
208
				'email' => $name,
209
				'is_guest' => true
210
			);
211
	}
212
213
	$url_data = determineActions($url_data);
214
215
	// Setup the linktree and page title (do it down here because the language files are now loaded..)
216
	$context['page_title'] = $txt['who_title'];
217
	$context['linktree'][] = array(
218
		'url' => $scripturl . '?action=who',
219
		'name' => $txt['who_title']
220
	);
221
222
	// Put it in the context variables.
223
	foreach ($context['members'] as $i => $member)
224
	{
225
		if ($member['id'] != 0)
226
			$member['id'] = loadMemberContext($member['id']) ? $member['id'] : 0;
227
228
		// Keep the IP that came from the database.
229
		$memberContext[$member['id']]['ip'] = $member['ip'];
230
		$context['members'][$i]['action'] = isset($url_data[$i]) ? $url_data[$i] : array('label' => 'who_hidden', 'class' => 'em');
231
		if ($member['id'] == 0 && isset($spiderContext[$member['id_spider']]))
232
			$context['members'][$i] += $spiderContext[$member['id_spider']];
233
		else
234
			$context['members'][$i] += $memberContext[$member['id']];
235
	}
236
237
	// Some people can't send personal messages...
238
	$context['can_send_pm'] = allowedTo('pm_send');
239
	$context['can_send_email'] = allowedTo('send_email_to_members');
240
241
	// any profile fields disabled?
242
	$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
243
244
}
245
246
/**
247
 * This function determines the actions of the members passed in urls.
248
 *
249
 * Adding actions to the Who's Online list:
250
 * Adding actions to this list is actually relatively easy...
251
 *  - for actions anyone should be able to see, just add a string named whoall_ACTION.
252
 *    (where ACTION is the action used in index.php.)
253
 *  - for actions that have a subaction which should be represented differently, use whoall_ACTION_SUBACTION.
254
 *  - for actions that include a topic, and should be restricted, use whotopic_ACTION.
255
 *  - for actions that use a message, by msg or quote, use whopost_ACTION.
256
 *  - for administrator-only actions, use whoadmin_ACTION.
257
 *  - for actions that should be viewable only with certain permissions,
258
 *    use whoallow_ACTION and add a list of possible permissions to the
259
 *    $allowedActions array, using ACTION as the key.
260
 *
261
 * @param mixed $urls a single url (string) or an array of arrays, each inner array being (JSON-encoded request data, id_member)
262
 * @param string|bool $preferred_prefix = false
263
 * @return array an array of descriptions if you passed an array, otherwise the string describing their current location.
264
 */
265
function determineActions($urls, $preferred_prefix = false)
266
{
267
	global $txt, $user_info, $modSettings, $smcFunc, $scripturl, $context;
268
269
	if (!allowedTo('who_view'))
270
		return array();
271
	loadLanguage('Who');
272
273
	// Actions that require a specific permission level.
274
	$allowedActions = array(
275
		'admin' => array('moderate_forum', 'manage_membergroups', 'manage_bans', 'admin_forum', 'manage_permissions', 'send_mail', 'manage_attachments', 'manage_smileys', 'manage_boards', 'edit_news'),
276
		'ban' => array('manage_bans'),
277
		'boardrecount' => array('admin_forum'),
278
		'calendar' => array('calendar_view'),
279
		'corefeatures' => array('admin_forum'),
280
		'editnews' => array('edit_news'),
281
		'featuresettings' => array('admin_forum'),
282
		'languages' => array('admin_forum'),
283
		'logs' => array('admin_forum'),
284
		'mailing' => array('send_mail'),
285
		'mailqueue' => array('admin_forum'),
286
		'maintain' => array('admin_forum'),
287
		'manageattachments' => array('manage_attachments'),
288
		'manageboards' => array('manage_boards'),
289
		'managecalendar' => array('admin_forum'),
290
		'managesearch' => array('admin_forum'),
291
		'managesmileys' => array('manage_smileys'),
292
		'membergroups' => array('manage_membergroups'),
293
		'mlist' => array('view_mlist'),
294
		'moderate' => array('access_mod_center', 'moderate_forum', 'manage_membergroups'),
295
		'modsettings' => array('admin_forum'),
296
		'news' => array('edit_news', 'send_mail', 'admin_forum'),
297
		'optimizetables' => array('admin_forum'),
298
		'packages' => array('admin_forum'),
299
		'paidsubscribe' => array('admin_forum'),
300
		'permissions' => array('manage_permissions'),
301
		'postsettings' => array('admin_forum'),
302
		'regcenter' => array('admin_forum', 'moderate_forum'),
303
		'repairboards' => array('admin_forum'),
304
		'reports' => array('admin_forum'),
305
		'scheduledtasks' => array('admin_forum'),
306
		'search' => array('search_posts'),
307
		'search2' => array('search_posts'),
308
		'securitysettings' => array('admin_forum'),
309
		'sengines' => array('admin_forum'),
310
		'serversettings' => array('admin_forum'),
311
		'setcensor' => array('moderate_forum'),
312
		'setreserve' => array('moderate_forum'),
313
		'stats' => array('view_stats'),
314
		'theme' => array('admin_forum'),
315
		'viewerrorlog' => array('admin_forum'),
316
		'viewmembers' => array('moderate_forum'),
317
	);
318
	call_integration_hook('who_allowed', array(&$allowedActions));
319
320
	if (!is_array($urls))
321
		$url_list = array(array($urls, $user_info['id']));
322
	else
323
		$url_list = $urls;
324
325
	// These are done to later query these in large chunks. (instead of one by one.)
326
	$topic_ids = array();
327
	$profile_ids = array();
328
	$board_ids = array();
329
330
	$data = array();
331
	foreach ($url_list as $k => $url)
332
	{
333
		// Get the request parameters..
334
		$actions = $smcFunc['json_decode']($url[0], true);
335
		if ($actions === array())
336
			continue;
337
338
		// If it's the admin or moderation center, and there is an area set, use that instead.
339
		if (isset($actions['action']) && ($actions['action'] == 'admin' || $actions['action'] == 'moderate') && isset($actions['area']))
340
			$actions['action'] = $actions['area'];
341
342
		// Check if there was no action or the action is display.
343
		if (!isset($actions['action']) || $actions['action'] == 'display')
344
		{
345
			// It's a topic!  Must be!
346
			if (isset($actions['topic']))
347
			{
348
				// Assume they can't view it, and queue it up for later.
349
				$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
350
				$topic_ids[(int) $actions['topic']][$k] = $txt['who_topic'];
351
			}
352
			// It's a board!
353
			elseif (isset($actions['board']))
354
			{
355
				// Hide first, show later.
356
				$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
357
				$board_ids[$actions['board']][$k] = $txt['who_board'];
358
			}
359
			// It's the board index!!  It must be!
360
			else
361
				$data[$k] = sprintf($txt['who_index'], $scripturl, $context['forum_name_html_safe']);
362
		}
363
		// Probably an error or some goon?
364
		elseif ($actions['action'] == '')
365
			$data[$k] = sprintf($txt['who_index'], $scripturl, $context['forum_name_html_safe']);
366
		// Some other normal action...?
367
		else
368
		{
369
			// Viewing/editing a profile.
370
			if ($actions['action'] == 'profile')
371
			{
372
				// Whose?  Their own?
373
				if (empty($actions['u']))
374
					$actions['u'] = $url[1];
375
376
				$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
377
				$profile_ids[(int) $actions['u']][$k] = $actions['u'] == $url[1] ? $txt['who_viewownprofile'] : $txt['who_viewprofile'];
378
			}
379
			elseif (($actions['action'] == 'post' || $actions['action'] == 'post2') && empty($actions['topic']) && isset($actions['board']))
380
			{
381
				$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
382
				$board_ids[(int) $actions['board']][$k] = isset($actions['poll']) ? $txt['who_poll'] : $txt['who_post'];
383
			}
384
			// A subaction anyone can view... if the language string is there, show it.
385
			elseif (isset($actions['sa']) && isset($txt['whoall_' . $actions['action'] . '_' . $actions['sa']]))
386
				$data[$k] = $preferred_prefix && isset($txt[$preferred_prefix . $actions['action'] . '_' . $actions['sa']]) ? $txt[$preferred_prefix . $actions['action'] . '_' . $actions['sa']] : sprintf($txt['whoall_' . $actions['action'] . '_' . $actions['sa']], $scripturl);
0 ignored issues
show
Are you sure $preferred_prefix of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

386
				$data[$k] = $preferred_prefix && isset($txt[/** @scrutinizer ignore-type */ $preferred_prefix . $actions['action'] . '_' . $actions['sa']]) ? $txt[$preferred_prefix . $actions['action'] . '_' . $actions['sa']] : sprintf($txt['whoall_' . $actions['action'] . '_' . $actions['sa']], $scripturl);
Loading history...
387
			// An action any old fellow can look at. (if ['whoall_' . $action] exists, we know everyone can see it.)
388
			elseif (isset($txt['whoall_' . $actions['action']]))
389
				$data[$k] = $preferred_prefix && isset($txt[$preferred_prefix . $actions['action']]) ? $txt[$preferred_prefix . $actions['action']] : sprintf($txt['whoall_' . $actions['action']], $scripturl);
390
			// Viewable if and only if they can see the board...
391
			elseif (isset($txt['whotopic_' . $actions['action']]))
392
			{
393
				// Find out what topic they are accessing.
394
				$topic = (int) (isset($actions['topic']) ? $actions['topic'] : (isset($actions['from']) ? $actions['from'] : 0));
395
396
				$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
397
				$topic_ids[$topic][$k] = $txt['whotopic_' . $actions['action']];
398
			}
399
			elseif (isset($txt['whopost_' . $actions['action']]))
400
			{
401
				// Find out what message they are accessing.
402
				$msgid = (int) (isset($actions['msg']) ? $actions['msg'] : (isset($actions['quote']) ? $actions['quote'] : 0));
403
404
				$result = $smcFunc['db_query']('', '
405
					SELECT m.id_topic, m.subject
406
					FROM {db_prefix}messages AS m
407
						' . ($modSettings['postmod_active'] ? 'INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.approved = {int:is_approved})' : '') . '
408
					WHERE m.id_msg = {int:id_msg}
409
						AND {query_see_message_board}' . ($modSettings['postmod_active'] ? '
410
						AND m.approved = {int:is_approved}' : '') . '
411
					LIMIT 1',
412
					array(
413
						'is_approved' => 1,
414
						'id_msg' => $msgid,
415
					)
416
				);
417
				list ($id_topic, $subject) = $smcFunc['db_fetch_row']($result);
418
				$data[$k] = sprintf($txt['whopost_' . $actions['action']], $id_topic, $subject, $scripturl);
419
				$smcFunc['db_free_result']($result);
420
421
				if (empty($id_topic))
422
					$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
423
			}
424
			// Viewable only by administrators.. (if it starts with whoadmin, it's admin only!)
425
			elseif (allowedTo('moderate_forum') && isset($txt['whoadmin_' . $actions['action']]))
426
				$data[$k] = sprintf($txt['whoadmin_' . $actions['action']], $scripturl);
427
			// Viewable by permission level.
428
			elseif (isset($allowedActions[$actions['action']]))
429
			{
430
				if (allowedTo($allowedActions[$actions['action']]) && !empty($txt['whoallow_' . $actions['action']]))
431
					$data[$k] = sprintf($txt['whoallow_' . $actions['action']], $scripturl);
432
				elseif (in_array('moderate_forum', $allowedActions[$actions['action']]))
433
					$data[$k] = $txt['who_moderate'];
434
				elseif (in_array('admin_forum', $allowedActions[$actions['action']]))
435
					$data[$k] = $txt['who_admin'];
436
				else
437
					$data[$k] = array('label' => 'who_hidden', 'class' => 'em');
438
			}
439
			elseif (!empty($actions['action']))
440
				$data[$k] = $txt['who_generic'] . ' ' . $actions['action'];
441
			else
442
				$data[$k] = array('label' => 'who_unknown', 'class' => 'em');
443
		}
444
445
		if (isset($actions['error']))
446
		{
447
			loadLanguage('Errors');
448
449
			if (isset($txt[$actions['error']]))
450
				$error_message = str_replace('"', '&quot;', empty($actions['error_params']) ? $txt[$actions['error']] : vsprintf($txt[$actions['error']], (array) $actions['error_params']));
451
			elseif ($actions['error'] == 'guest_login')
452
				$error_message = str_replace('"', '&quot;', $txt['who_guest_login']);
453
			else
454
				$error_message = str_replace('"', '&quot;', $actions['error']);
455
456
			if (!empty($error_message))
457
			{
458
				$error_message = ' <span class="main_icons error" title="' . $error_message . '"></span>';
459
460
				if (is_array($data[$k]))
461
					$data[$k]['error_message'] = $error_message;
462
				else
463
					$data[$k] .= $error_message;
464
			}
465
		}
466
467
		// Maybe the action is integrated into another system?
468
		if (count($integrate_actions = call_integration_hook('integrate_whos_online', array($actions))) > 0)
469
		{
470
			foreach ($integrate_actions as $integrate_action)
471
			{
472
				if (!empty($integrate_action))
473
				{
474
					$data[$k] = $integrate_action;
475
					if (isset($actions['topic']) && isset($topic_ids[(int) $actions['topic']][$k]))
476
						$topic_ids[(int) $actions['topic']][$k] = $integrate_action;
477
					if (isset($actions['board']) && isset($board_ids[(int) $actions['board']][$k]))
478
						$board_ids[(int) $actions['board']][$k] = $integrate_action;
479
					if (isset($actions['u']) && isset($profile_ids[(int) $actions['u']][$k]))
480
						$profile_ids[(int) $actions['u']][$k] = $integrate_action;
481
					break;
482
				}
483
			}
484
		}
485
	}
486
487
	// Load topic names.
488
	if (!empty($topic_ids))
489
	{
490
		$result = $smcFunc['db_query']('', '
491
			SELECT t.id_topic, m.subject
492
			FROM {db_prefix}topics AS t
493
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
494
			WHERE {query_see_topic_board}
495
				AND t.id_topic IN ({array_int:topic_list})' . ($modSettings['postmod_active'] ? '
496
				AND t.approved = {int:is_approved}' : '') . '
497
			LIMIT {int:limit}',
498
			array(
499
				'topic_list' => array_keys($topic_ids),
500
				'is_approved' => 1,
501
				'limit' => count($topic_ids),
502
			)
503
		);
504
		while ($row = $smcFunc['db_fetch_assoc']($result))
505
		{
506
			// Show the topic's subject for each of the actions.
507
			foreach ($topic_ids[$row['id_topic']] as $k => $session_text)
508
				$data[$k] = sprintf($session_text, $row['id_topic'], censorText($row['subject']), $scripturl);
509
		}
510
		$smcFunc['db_free_result']($result);
511
	}
512
513
	// Load board names.
514
	if (!empty($board_ids))
515
	{
516
		$result = $smcFunc['db_query']('', '
517
			SELECT b.id_board, b.name
518
			FROM {db_prefix}boards AS b
519
			WHERE {query_see_board}
520
				AND b.id_board IN ({array_int:board_list})
521
			LIMIT {int:limit}',
522
			array(
523
				'board_list' => array_keys($board_ids),
524
				'limit' => count($board_ids),
525
			)
526
		);
527
		while ($row = $smcFunc['db_fetch_assoc']($result))
528
		{
529
			// Put the board name into the string for each member...
530
			foreach ($board_ids[$row['id_board']] as $k => $session_text)
531
				$data[$k] = sprintf($session_text, $row['id_board'], $row['name'], $scripturl);
532
		}
533
		$smcFunc['db_free_result']($result);
534
	}
535
536
	// Load member names for the profile. (is_not_guest permission for viewing their own profile)
537
	$allow_view_own = allowedTo('is_not_guest');
538
	$allow_view_any = allowedTo('profile_view');
539
	if (!empty($profile_ids) && ($allow_view_any || $allow_view_own))
540
	{
541
		$result = $smcFunc['db_query']('', '
542
			SELECT id_member, real_name
543
			FROM {db_prefix}members
544
			WHERE id_member IN ({array_int:member_list})
545
			LIMIT ' . count($profile_ids),
546
			array(
547
				'member_list' => array_keys($profile_ids),
548
			)
549
		);
550
		while ($row = $smcFunc['db_fetch_assoc']($result))
551
		{
552
			// If they aren't allowed to view this person's profile, skip it.
553
			if (!$allow_view_any && ($user_info['id'] != $row['id_member']))
554
				continue;
555
556
			// Set their action on each - session/text to sprintf.
557
			foreach ($profile_ids[$row['id_member']] as $k => $session_text)
558
				$data[$k] = sprintf($session_text, $row['id_member'], $row['real_name'], $scripturl);
559
		}
560
		$smcFunc['db_free_result']($result);
561
	}
562
563
	call_integration_hook('whos_online_after', array(&$urls, &$data));
564
565
	if (!is_array($urls))
566
		return isset($data[0]) ? $data[0] : false;
567
	else
568
		return $data;
569
}
570
571
/**
572
 * It prepares credit and copyright information for the credits page or the admin page
573
 *
574
 * @param bool $in_admin = false, if parameter is true the it will not load the sub-template nor the template file
575
 */
576
function Credits($in_admin = false)
577
{
578
	global $context, $smcFunc, $forum_copyright, $txt, $user_info, $scripturl;
579
580
	// Don't blink. Don't even blink. Blink and you're dead.
581
	loadLanguage('Who');
582
583
	// Discourage robots from indexing this page.
584
	$context['robot_no_index'] = true;
585
586
	if ($in_admin)
587
	{
588
		$context[$context['admin_menu_name']]['tab_data'] = array(
589
			'title' => $txt['support_credits_title'],
590
			'help' => '',
591
			'description' => '',
592
		);
593
	}
594
595
	$context['credits'] = array(
596
		array(
597
			'pretext' => $txt['credits_intro'],
598
			'title' => $txt['credits_team'],
599
			'groups' => array(
600
				array(
601
					'title' => $txt['credits_groups_pm'],
602
					'members' => array(
603
						'Aleksi "Lex" Kilpinen',
604
						// Former Project Managers
605
						'Michele "Illori" Davis',
606
						'Jessica "Suki" González',
607
						'Will "Kindred" Wagner',
608
					),
609
				),
610
				array(
611
					'title' => $txt['credits_groups_dev'],
612
					'members' => array(
613
						// Lead Developer
614
						'Jon "Sesquipedalian" Stovell',
615
						// Developers
616
						'Jessica "Suki" González',
617
						'John "live627" Rayes',
618
						'Oscar "Ozp" Rydhé',
619
						'Shawn Bulen',
620
621
						// Former Developers
622
						'Aaron van Geffen',
623
						'Antechinus',
624
						'Bjoern "Bloc" Kristiansen',
625
						'Brad "IchBin™" Grow',
626
						'Colin Schoen',
627
						'emanuele',
628
						'Hendrik Jan "Compuart" Visser',
629
						'Juan "JayBachatero" Hernandez',
630
						'Karl "RegularExpression" Benson',
631
						'Matthew "Labradoodle-360" Kerle',
632
						$user_info['is_admin'] ? 'Matt "Grudge" Wolf' : 'Grudge',
633
						'Michael "Oldiesmann" Eshom',
634
						'Michael "Thantos" Miller',
635
						'Norv',
636
						'Peter "Arantor" Spicer',
637
						'Selman "[SiNaN]" Eser',
638
						'Shitiz "Dragooon" Garg',
639
						// 'Spuds', // Doesn't want to be listed here
640
						// 'Steven "Fustrate" Hoffman',
641
						'Theodore "Orstio" Hildebrandt',
642
						'Thorsten "TE" Eurich',
643
						'winrules',
644
					),
645
				),
646
				array(
647
					'title' => $txt['credits_groups_support'],
648
					'members' => array(
649
						// Lead Support Specialist
650
						'Will "Kindred" Wagner',
651
						// Support Specialists
652
						'Doug Heffernan',
653
						'lurkalot',
654
						'shadav',
655
						'Steve',
656
657
						// Former Support Specialists
658
						'Aleksi "Lex" Kilpinen',
659
						'br360',
660
						'GigaWatt',
661
						'ziycon',
662
						'Adam Tallon',
663
						'Bigguy',
664
						'Bruno "margarett" Alves',
665
						'CapadY',
666
						'ChalkCat',
667
						'Chas Large',
668
						'Duncan85',
669
						'gbsothere',
670
						'JimM',
671
						'Justyne',
672
						'Kat',
673
						'Kevin "greyknight17" Hou',
674
						'Krash',
675
						'Mashby',
676
						'Michael Colin Blaber',
677
						'Old Fossil',
678
						'S-Ace',
679
						'Storman™',
680
						'Wade "sησω" Poulsen',
681
						'xenovanis',
682
					),
683
				),
684
				array(
685
					'title' => $txt['credits_groups_customize'],
686
					'members' => array(
687
						// Lead Customizer
688
						'Gary M. Gadsdon',
689
						// Customizers
690
						'Diego Andrés',
691
						'Jonathan "vbgamer45" Valentin',
692
						'Michael "Mick." Gomez',
693
694
						// Former Customizers
695
						'Sami "SychO" Mazouz',
696
						'Brannon "B" Hall',
697
						'Jack "akabugeyes" Thorsen',
698
						'Jason "JBlaze" Clemons',
699
						'Joey "Tyrsson" Smith',
700
						'Kays',
701
						'NanoSector',
702
						'Ricky.',
703
						'Russell "NEND" Najar',
704
						'SA™',
705
					),
706
				),
707
				array(
708
					'title' => $txt['credits_groups_docs'],
709
					'members' => array(
710
						// Doc Coordinator
711
						'Michele "Illori" Davis',
712
						// Doc Writers
713
						'Irisado',
714
715
						// Former Doc Writers
716
						'AngelinaBelle',
717
						'Chainy',
718
						'Graeme Spence',
719
						'Joshua "groundup" Dickerson',
720
					),
721
				),
722
				array(
723
					'title' => $txt['credits_groups_internationalizers'],
724
					'members' => array(
725
						// Lead Localizer
726
						'Nikola "Dzonny" Novaković',
727
						// Localizers
728
						'm4z',
729
						// Former Localizers
730
						'Francisco "d3vcho" Domínguez',
731
						'Robert Monden',
732
						'Relyana',
733
					),
734
				),
735
				array(
736
					'title' => $txt['credits_groups_marketing'],
737
					'members' => array(
738
						// Marketing Coordinator
739
740
						// Marketing
741
742
						// Former Marketing
743
						'Adish "(F.L.A.M.E.R)" Patel',
744
						'Bryan "Runic" Deakin',
745
						'Marcus "cσσкιє мσηѕтєя" Forsberg',
746
						'Ralph "[n3rve]" Otowo',
747
					),
748
				),
749
				array(
750
					'title' => $txt['credits_groups_site'],
751
					'members' => array(
752
						'Jeremy "SleePy" Darwood',
753
					),
754
				),
755
				array(
756
					'title' => $txt['credits_groups_servers'],
757
					'members' => array(
758
						'Derek Schwab',
759
						'Michael Johnson',
760
						'Liroy van Hoewijk',
761
					),
762
				),
763
			),
764
		),
765
	);
766
767
	// Give the translators some credit for their hard work.
768
	if (!is_array($txt['translation_credits']))
769
		$txt['translation_credits'] = array_filter(array_map('trim', explode(',', $txt['translation_credits'])));
770
771
	if (!empty($txt['translation_credits']))
772
		$context['credits'][] = array(
773
			'title' => $txt['credits_groups_translation'],
774
			'groups' => array(
775
				array(
776
					'title' => $txt['credits_groups_translation'],
777
					'members' => $txt['translation_credits'],
778
				),
779
			),
780
		);
781
782
	$context['credits'][] = array(
783
		'title' => $txt['credits_special'],
784
		'posttext' => $txt['credits_anyone'],
785
		'groups' => array(
786
			array(
787
				'title' => $txt['credits_groups_consultants'],
788
				'members' => array(
789
					'albertlast',
790
					'Brett Flannigan',
791
					'Mark Rose',
792
					'René-Gilles "Nao 尚" Deberdt',
793
					'tinoest',
794
					$txt['credits_code_contributors'],
795
				),
796
			),
797
			array(
798
				'title' => $txt['credits_groups_beta'],
799
				'members' => array(
800
					$txt['credits_beta_message'],
801
				),
802
			),
803
			array(
804
				'title' => $txt['credits_groups_translators'],
805
				'members' => array(
806
					$txt['credits_translators_message'],
807
				),
808
			),
809
			array(
810
				'title' => $txt['credits_groups_founder'],
811
				'members' => array(
812
					'Unknown W. "[Unknown]" Brackets',
813
				),
814
			),
815
			array(
816
				'title' => $txt['credits_groups_orignal_pm'],
817
				'members' => array(
818
					'Jeff Lewis',
819
					'Joseph Fung',
820
					'David Recordon',
821
				),
822
			),
823
			array(
824
				'title' => $txt['credits_in_memoriam'],
825
				'members' => array(
826
					'Crip',
827
					'K@',
828
					'metallica48423',
829
					'Paul_Pauline',
830
				),
831
			),
832
		),
833
	);
834
835
	// Give credit to any graphic library's, software library's, plugins etc
836
	$context['credits_software_graphics'] = array(
837
		'graphics' => array(
838
			'<a href="http://p.yusukekamiyamane.com/">Fugue Icons</a> | © 2012 Yusuke Kamiyamane | These icons are licensed under a Creative Commons Attribution 3.0 License',
839
			'<a href="https://techbase.kde.org/Projects/Oxygen/Licensing#Use_on_Websites">Oxygen Icons</a> | These icons are licensed under <a href="http://www.gnu.org/copyleft/lesser.html">GNU LGPLv3</a>',
840
		),
841
		'software' => array(
842
			'<a href="https://jquery.org/">JQuery</a> | © John Resig | Licensed under <a href="https://github.com/jquery/jquery/blob/master/LICENSE.txt">The MIT License (MIT)</a>',
843
			'<a href="https://briancherne.github.io/jquery-hoverIntent/">hoverIntent</a> | © Brian Cherne | Licensed under <a href="https://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
844
			'<a href="https://www.sceditor.com/">SCEditor</a> | © Sam Clarke | Licensed under <a href="https://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
845
			'<a href="http://wayfarerweb.com/jquery/plugins/animadrag/">animaDrag</a> | © Abel Mohler | Licensed under <a href="https://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
846
			'<a href="https://github.com/mzubala/jquery-custom-scrollbar">jQuery Custom Scrollbar</a> | © Maciej Zubala | Licensed under <a href="http://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
847
			'<a href="http://slippry.com/">jQuery Responsive Slider</a> | © booncon ROCKETS | Licensed under <a href="http://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
848
			'<a href="https://github.com/ichord/At.js">At.js</a> | © [email protected] | Licensed under <a href="https://github.com/ichord/At.js/blob/master/LICENSE-MIT">The MIT License (MIT)</a>',
849
			'<a href="https://github.com/ttsvetko/HTML5-Desktop-Notifications">HTML5 Desktop Notifications</a> | © Tsvetan Tsvetkov | Licensed under <a href="https://github.com/ttsvetko/HTML5-Desktop-Notifications/blob/master/License.txt">The Apache License Version 2.0</a>',
850
			'<a href="https://github.com/enygma/gauth">GAuth Code Generator/Validator</a> | © Chris Cornutt | Licensed under <a href="https://github.com/enygma/gauth/blob/master/LICENSE">The MIT License (MIT)</a>',
851
			'<a href="https://github.com/enyo/dropzone">Dropzone.js</a> | © Matias Meno | Licensed under <a href="http://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
852
			'<a href="https://github.com/matthiasmullie/minify">Minify</a> | © Matthias Mullie | Licensed under <a href="http://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
853
			'<a href="https://github.com/true/php-punycode">PHP-Punycode</a> | © True B.V. | Licensed under <a href="http://en.wikipedia.org/wiki/MIT_License">The MIT License (MIT)</a>',
854
		),
855
		'fonts' => array(
856
			'<a href="https://fontlibrary.org/en/font/anonymous-pro"> Anonymous Pro</a> | © 2009 | This font is licensed under the SIL Open Font License, Version 1.1',
857
			'<a href="https://fontlibrary.org/en/font/consolamono"> ConsolaMono</a> | © 2012 | This font is licensed under the SIL Open Font License, Version 1.1',
858
			'<a href="https://fontlibrary.org/en/font/phennig"> Phennig</a> | © 2009-2012 | This font is licensed under the SIL Open Font License, Version 1.1',
859
		),
860
	);
861
862
	// Support for mods that use the <credits> tag via the package manager
863
	$context['credits_modifications'] = array();
864
	if (($mods = cache_get_data('mods_credits', 86400)) === null)
865
	{
866
		$mods = array();
867
		$request = $smcFunc['db_query']('substring', '
868
			SELECT version, name, credits
869
			FROM {db_prefix}log_packages
870
			WHERE install_state = {int:installed_mods}
871
				AND credits != {string:empty}
872
				AND SUBSTRING(filename, 1, 9) != {string:patch_name}',
873
			array(
874
				'installed_mods' => 1,
875
				'patch_name' => 'smf_patch',
876
				'empty' => '',
877
			)
878
		);
879
880
		while ($row = $smcFunc['db_fetch_assoc']($request))
881
		{
882
			$credit_info = $smcFunc['json_decode']($row['credits'], true);
883
884
			$copyright = empty($credit_info['copyright']) ? '' : $txt['credits_copyright'] . ' © ' . $smcFunc['htmlspecialchars']($credit_info['copyright']);
885
			$license = empty($credit_info['license']) ? '' : $txt['credits_license'] . ': ' . (!empty($credit_info['licenseurl']) ? '<a href="' . $smcFunc['htmlspecialchars']($credit_info['licenseurl']) . '">' . $smcFunc['htmlspecialchars']($credit_info['license']) . '</a>' : $smcFunc['htmlspecialchars']($credit_info['license']));
886
			$version = $txt['credits_version'] . ' ' . $row['version'];
887
			$title = (empty($credit_info['title']) ? $row['name'] : $smcFunc['htmlspecialchars']($credit_info['title'])) . ': ' . $version;
888
889
			// build this one out and stash it away
890
			$mod_name = empty($credit_info['url']) ? $title : '<a href="' . $credit_info['url'] . '">' . $title . '</a>';
891
			$mods[] = $mod_name . (!empty($license) ? ' | ' . $license : '') . (!empty($copyright) ? ' | ' . $copyright : '');
892
		}
893
		cache_put_data('mods_credits', $mods, 86400);
894
	}
895
	$context['credits_modifications'] = $mods;
896
897
	$context['copyrights'] = array(
898
		'smf' => sprintf($forum_copyright, SMF_FULL_VERSION, SMF_SOFTWARE_YEAR, $scripturl),
899
		/* Modification Authors:  You may add a copyright statement to this array for your mods.
900
			Copyright statements should be in the form of a value only without a array key.  I.E.:
901
				'Some Mod by Thantos © 2010',
902
				$txt['some_mod_copyright'],
903
		*/
904
		'mods' => array(
905
		),
906
	);
907
908
	// Support for those that want to use a hook as well
909
	call_integration_hook('integrate_credits');
910
911
	if (!$in_admin)
912
	{
913
		loadTemplate('Who');
914
		$context['sub_template'] = 'credits';
915
		$context['robot_no_index'] = true;
916
		$context['page_title'] = $txt['credits'];
917
	}
918
}
919
920
?>