ssi_boardNews()   F
last analyzed

Complexity

Conditions 25
Paths > 20000

Size

Total Lines 167
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 25
eloc 96
nc 42039
nop 6
dl 0
loc 167
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Provides ways to add information to your website by linking to and capturing output
5
 * from ElkArte
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
use BBC\ParserWrapper;
19
use ElkArte\Cache\Cache;
20
use ElkArte\Controller\Poll;
21
use ElkArte\EventManager;
22
use ElkArte\Helper\Util;
23
use ElkArte\MembersList;
24
use ElkArte\MessageTopicIcons;
25
use ElkArte\User;
26
27
/**
28
 * Set this to one of three values depending on what you want to happen in the case of a fatal error.
29
 *  - false: Default, will just load the error sub template and die - not putting any theme layers around it.
30
 *  - true: Will load the error sub template AND put the template layers around it (Not useful if on total custom pages).
31
 *  - string: Name of a callback function to call in the event of an error to allow you to define your own methods. Will die after function returns.
32
 */
33
$ssi_on_error_method = false;
34
35
/**
36
 * Don't do john didley if the forum's been shut down completely.
37
 */
38
// $ssi_maintenance_off = false;
39
40
/**
41
 * Define a theme for SSI (integer)
42
 */
43
// $ssi_theme = 0;
44
45
/**
46
 * An array of layers to use.
47
 */
48
// $ssi_layers = array();
49
50
/**
51
 * Gzip output? (because it must be boolean and true, this can't be hacked.)
52
 */
53
// $ssi_gzip = false;
54
55
/**
56
 * Should we ban from SSI as well?
57
 */
58
// $ssi_ban = false;
59
60
/**
61
 * Do we allow guests in here?
62
 */
63
// $ssi_guest_access = false;
64
65
// We are in Elk, but from the side-entrance.
66
if (!defined('ELKBOOT'))
67
{
68
	define('ELK', 'SSI');
69
70
	require_once(__DIR__ . '/bootstrap.php');
71
	$bootstrap = new Bootstrap(true);
72
}
73
74
// The globals that were created during the bootstrap process
75
global $time_start, $maintenance, $msubject, $mmessage, $mbname, $language;
76
global $boardurl, $webmaster_email, $cookiename;
77
global $db_type, $db_server, $db_name, $db_user, $db_prefix, $db_persist, $db_error_send;
78
global $modSettings, $context, $user_info, $topic, $board, $txt;
79
global $ssi_db_user, $scripturl, $ssi_db_passwd, $db_passwd;
80
global $boarddir, $sourcedir, $db_show_debug, $ssi_error_reporting;
81
82
// Have the ability to easily add functions to SSI.
83
call_integration_hook('integrate_SSI');
84
85
// Call a function passed by GET.
86
if (isset($_GET['ssi_function']) && function_exists('ssi_' . $_GET['ssi_function']) && (!empty($modSettings['allow_guestAccess']) || User::$info->is_guest === false))
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
87
{
88
	call_user_func('ssi_' . $_GET['ssi_function']);
89
	exit;
90
}
91
92
if (isset($_GET['ssi_function']))
93
{
94
	exit;
95
}
96
97
// You shouldn't just access SSI.php directly by URL!!
98
if (basename($_SERVER['PHP_SELF']) === 'SSI.php')
99
{
100
	die(sprintf($txt['ssi_not_direct'], User::$info->is_admin ? "'" . addslashes(__FILE__) . "'" : "'SSI.php'"));
0 ignored issues
show
Bug Best Practice introduced by
The property is_admin does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
101
}
102
103
error_reporting($ssi_error_reporting);
104
105
return true;
106
107
/**
108
 * This shuts down the SSI and shows the footer.
109
 */
110
function ssi_shutdown()
111
{
112
	if (!isset($_GET['ssi_function']) || $_GET['ssi_function'] !== 'shutdown')
113
	{
114
		template_footer();
115
	}
116
}
117
118
/**
119
 * Display a welcome message, like:
120
 * "Hey, User, you have 0 messages, 0 are new."
121
 *
122
 * @param string $output_method The output method. If 'echo', will display
123
 * everything. Otherwise returns an array of user info.
124
 */
125
function ssi_welcome($output_method = 'echo')
126
{
127
	global $context, $txt, $scripturl;
128
129
	if ($output_method === 'echo')
130
	{
131
		if ($context['user']['is_guest'])
132
		{
133
			echo replaceBasicActionUrl($txt[$context['can_register'] ? 'welcome_guest_register' : 'welcome_guest']);
134
		}
135
		else
136
		{
137
			echo $txt['hello_member'], ' <strong>', $context['user']['name'], '</strong>', allowedTo('pm_read') ? ', ' . (empty($context['user']['messages']) ? $txt['msg_alert_no_messages'] : (($context['user']['messages'] == 1 ? sprintf($txt['msg_alert_one_message'], $scripturl . '?action=pm') : sprintf($txt['msg_alert_many_message'], $scripturl . '?action=pm', $context['user']['messages'])) . ', ' . ($context['user']['unread_messages'] == 1 ? $txt['msg_alert_one_new'] : sprintf($txt['msg_alert_many_new'], $context['user']['unread_messages'])))) : '';
138
		}
139
	}
140
	// Don't echo... then do what?!
141
	else
142
	{
143
		return $context['user'];
144
	}
145
}
146
147
/**
148
 * Display a menu bar, like is displayed at the top of the forum.
149
 *
150
 * @param string $output_method The output method. If 'echo', will display
151
 * everything. Otherwise returns an array of menu data.
152
 */
153
function ssi_menubar($output_method = 'echo')
154
{
155
	global $context;
156
157
	if ($output_method === 'echo')
158
	{
159
		template_menu();
160
	}
161
	// What else could this do?
162
	else
163
	{
164
		return $context['menu_buttons'];
165
	}
166
}
167
168
/**
169
 * Show a logout link.
170
 *
171
 * @param string $redirect_to A URL to redirect the user to after they log out.
172
 * @param string $output_method = 'echo; The output method. If 'echo', shows a logout link,
173
 * otherwise returns HTML.
174
 */
175
function ssi_logout($redirect_to = '', $output_method = 'echo')
176
{
177
	global $context, $txt, $scripturl;
178
179
	if ($redirect_to !== '')
180
	{
181
		$_SESSION['logout_url'] = $redirect_to;
182
	}
183
184
	// Guests can't log out.
185
	if ($context['user']['is_guest'])
186
	{
187
		return false;
188
	}
189
190
	$link = '<a class="linkbutton" href="' . $scripturl . '?action=logout;' . $context['session_var'] . '=' . $context['session_id'] . '">' . $txt['logout'] . '</a>';
191
192
	if ($output_method === 'echo')
193
	{
194
		echo $link;
195
	}
196
	else
197
	{
198
		return $link;
199
	}
200
}
201
202
/**
203
 * Recent post list:
204
 *  [board] Subject by Poster Date
205
 *
206
 * @param int $num_recent How many recent posts to display
207
 * @param int[]|null $exclude_boards If set, doesn't show posts from the specified boards
208
 * @param int[]|null $include_boards If set, only includes posts from the specified boards
209
 * @param string $output_method The output method. If 'echo', displays the posts, otherwise
210
 * returns an array of information about them.
211
 * @param bool $limit_body Whether or not to only show the first 384 characters of each post
212
 * @todo this may use getLastPosts with some modification
213
 *
214
 */
215
function ssi_recentPosts($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo', $limit_body = true)
216
{
217
	global $modSettings;
218
219
	// Excluding certain boards...
220
	if ($exclude_boards === null && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0)
221
	{
222
		$exclude_boards = array($modSettings['recycle_board']);
223
	}
224
	elseif (empty($exclude_boards))
225
	{
226
		$exclude_boards = array();
227
	}
228
	elseif (is_array($exclude_boards))
0 ignored issues
show
introduced by
The condition is_array($exclude_boards) is always true.
Loading history...
229
	{
230
		$exclude_boards = $exclude_boards;
231
	}
232
	else
233
	{
234
		$exclude_boards = array($exclude_boards);
235
	}
236
237
	// What about including certain boards - note we do some protection here as pre-2.0 didn't have this parameter.
238
	if (is_array($include_boards) || (int) $include_boards === $include_boards)
239
	{
240
		$include_boards = is_array($include_boards) ? $include_boards : array($include_boards);
0 ignored issues
show
introduced by
The condition is_array($include_boards) is always true.
Loading history...
241
	}
242
	elseif ($include_boards !== null)
0 ignored issues
show
introduced by
The condition $include_boards !== null is always false.
Loading history...
243
	{
244
		$include_boards = array();
245
	}
246
247
	// Let's restrict the query boys (and girls)
248
	$query_where = '
249
		m.id_msg >= {int:min_message_id}
250
		' . (empty($exclude_boards) ? '' : '
251
		AND b.id_board NOT IN ({array_int:exclude_boards})') . '
252
		' . ($include_boards === null ? '' : '
253
		AND b.id_board IN ({array_int:include_boards})') . '
254
		AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? '
255
		AND m.approved = {int:is_approved}' : '');
256
257
	$query_where_params = array(
258
		'is_approved' => 1,
259
		'include_boards' => $include_boards ?? '',
260
		'exclude_boards' => empty($exclude_boards) ? '' : $exclude_boards,
261
		'min_message_id' => $modSettings['maxMsgID'] - 25 * min($num_recent, 5),
262
	);
263
264
	// Past to this simpleton of a function...
265
	return ssi_queryPosts($query_where, $query_where_params, $num_recent, 'm.id_msg DESC', $output_method, $limit_body);
266
}
267
268
/**
269
 * Fetch a post with a particular ID.
270
 *
271
 * - By default will only show if you have permission to the see the board
272
 * in question - this can be overridden.
273
 *
274
 * @param int[] $post_ids An array containing the IDs of the posts to show
275
 * @param bool $override_permissions Whether to ignore permissions. If true, will show posts even
276
 * if the user doesn't have permission to see them.
277
 * @param string $output_method = 'echo; The output method. If 'echo', displays the posts,
278
 * otherwise returns an array of info about them
279
 * @todo this may use getRecentPosts with some modification
280
 *
281
 */
282
function ssi_fetchPosts($post_ids = array(), $override_permissions = false, $output_method = 'echo')
283
{
284
	global $modSettings;
285
286
	if (empty($post_ids))
287
	{
288
		return '';
289
	}
290
291
	// Allow the user to request more than one - why not?
292
	$post_ids = is_array($post_ids) ? $post_ids : array($post_ids);
0 ignored issues
show
introduced by
The condition is_array($post_ids) is always true.
Loading history...
293
294
	// Restrict the posts required...
295
	$query_where = '
296
		m.id_msg IN ({array_int:message_list})' . ($override_permissions ? '' : '
297
			AND {query_wanna_see_board}') . ($modSettings['postmod_active'] ? '
298
			AND m.approved = {int:is_approved}' : '');
299
	$query_where_params = array(
300
		'message_list' => $post_ids,
301
		'is_approved' => 1,
302
	);
303
304
	// Then make the query and dump the data.
305
	return ssi_queryPosts($query_where, $query_where_params, '', 'm.id_msg DESC', $output_method, false, $override_permissions);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $query_limit of ssi_queryPosts(). ( Ignorable by Annotation )

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

305
	return ssi_queryPosts($query_where, $query_where_params, /** @scrutinizer ignore-type */ '', 'm.id_msg DESC', $output_method, false, $override_permissions);
Loading history...
306
}
307
308
/**
309
 * This handles actually pulling post info
310
 *
311
 * - removes code duplication in other queries
312
 * - don't call it direct unless you really know what you're up to.
313
 *
314
 * @param string $query_where The WHERE clause for the query
315
 * @param mixed[] $query_where_params An array of parameters for the WHERE clause
316
 * @param int $query_limit The maximum number of rows to return
317
 * @param string $query_order The ORDER BY clause for the query
318
 * @param string $output_method = 'echo; The output method. If 'echo', displays the posts,
319
 * otherwise returns an array of info about them.
320
 * @param bool $limit_body If true, will only show the first 384 characters of the post
321
 * rather than all of it
322
 * @param bool $override_permissions Whether or not to ignore permissions. If true, will
323
 * show all posts regardless of whether the user can actually see them
324
 * @todo if ssi_recentPosts and ssi_fetchPosts will use Recent.subs.php this can be removed
325
 *
326
 */
327
function ssi_queryPosts($query_where = '', $query_where_params = array(), $query_limit = 10, $query_order = 'm.id_msg DESC', $output_method = 'echo', $limit_body = false, $override_permissions = false)
328
{
329
	global $scripturl, $txt, $modSettings;
330
331
	$db = database();
332
333
	// Find all the posts. Newer ones will have higher IDs.
334
	$request = $db->fetchQuery('
335
		SELECT
336
			m.poster_time, m.subject, m.id_topic, m.id_member, m.id_msg, m.id_board, b.name AS board_name,
337
			COALESCE(mem.real_name, m.poster_name) AS poster_name, ' . (User::$info->is_guest ? '1 AS is_read, 0 AS new_from' : '
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
338
			COALESCE(lt.id_msg, lmr.id_msg, 0) >= m.id_msg_modified AS is_read,
339
			COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from') . ', ' . ($limit_body ? 'SUBSTRING(m.body, 1, 384) AS body' : 'm.body') . ', m.smileys_enabled
340
		FROM {db_prefix}messages AS m
341
			JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
342
			JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
343
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (User::$info->is_guest ? '' : '
344
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = m.id_topic AND lt.id_member = {int:current_member})
345
			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = m.id_board AND lmr.id_member = {int:current_member})') . '
346
		WHERE 1=1 ' . ($override_permissions ? '' : '
347
			AND {query_wanna_see_board}') . ($modSettings['postmod_active'] ? '
348
			AND m.approved = {int:is_approved}
349
			AND t.approved = {int:is_approved}' : '') . '
350
		' . (empty($query_where) ? '' : 'AND ' . $query_where) . '
351
		ORDER BY ' . $query_order . '
352
		' . (empty($query_limit) ? '' : 'LIMIT {int:query_limit}'),
353
		array_merge($query_where_params, array(
354
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
355
			'is_approved' => 1,
356
			'query_limit' => $query_limit,
357
		))
358
	);
359
360
	$bbc_parser = ParserWrapper::instance();
361
362
	$posts = array();
363
	while ($row = $request->fetch_assoc())
364
	{
365
		$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
366
367
		// Censor it!
368
		$row['subject'] = censor($row['subject']);
369
		$row['body'] = censor($row['body']);
370
371
		$preview = strip_tags(strtr($row['body'], array('<br />' => '&#10;')));
372
373
		// Build the array.
374
		$posts[] = array(
375
			'id' => $row['id_msg'],
376
			'board' => array(
377
				'id' => $row['id_board'],
378
				'name' => $row['board_name'],
379
				'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
380
				'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['board_name'] . '</a>'
381
			),
382
			'topic' => $row['id_topic'],
383
			'poster' => array(
384
				'id' => $row['id_member'],
385
				'name' => $row['poster_name'],
386
				'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
387
				'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['poster_name'] . '</a>'
388
			),
389
			'subject' => $row['subject'],
390
			'short_subject' => Util::shorten_text($row['subject'], empty($modSettings['ssi_subject_length']) ? 24 : $modSettings['ssi_subject_length']),
391
			'preview' => Util::shorten_text($preview, empty($modSettings['ssi_preview_length']) ? 128 : $modSettings['ssi_preview_length']),
392
			'body' => $row['body'],
393
			'time' => standardTime($row['poster_time']),
394
			'html_time' => htmlTime($row['poster_time']),
395
			'timestamp' => forum_time(true, $row['poster_time']),
396
			'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#new',
397
			'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '" rel="nofollow">' . $row['subject'] . '</a>',
398
			'new' => !empty($row['is_read']),
399
			'is_new' => empty($row['is_read']),
400
			'new_from' => $row['new_from'],
401
		);
402
	}
403
404
	$request->free_result();
405
406
	// Just return it.
407
	if ($output_method !== 'echo' || empty($posts))
408
	{
409
		return $posts;
410
	}
411
412
	echo '
413
		<table class="ssi_table">';
414
415
	foreach ($posts as $post)
416
	{
417
		echo '
418
			<tr>
419
				<td class="righttext">
420
					[', $post['board']['link'], ']
421
				</td>
422
				<td class="top">
423
					<a href="', $post['href'], '">', $post['subject'], '</a>
424
					', $txt['by'], ' ', $post['poster']['link'], '
425
					', $post['is_new'] ? '<a href="' . $scripturl . '?topic=' . $post['topic'] . '.msg' . $post['new_from'] . ';topicseen#new" rel="nofollow" class="new_posts">' . $txt['new'] . '</a>' : '', '
426
				</td>
427
				<td class="righttext">
428
					', $post['time'], '
429
				</td>
430
			</tr>';
431
	}
432
433
	echo '
434
		</table>';
435
}
436
437
/**
438
 * Generates a recent topic list
439
 *
440
 * - [board] Subject by Poster Date
441
 *
442
 * @param int $num_recent How many recent topics to show
443
 * @param int[]|null $exclude_boards If set, exclude topics from the specified board(s)
444
 * @param bool|null $include_boards If set, only include topics from the specified board(s)
445
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of topics,
446
 * otherwise returns an array of info about them
447
 */
448
function ssi_recentTopics($num_recent = 8, $exclude_boards = null, $include_boards = null, $output_method = 'echo')
449
{
450
	global $settings, $scripturl, $txt, $modSettings;
451
452
	$db = database();
453
454
	if ($exclude_boards === null && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0)
455
	{
456
		$exclude_boards = array($modSettings['recycle_board']);
457
	}
458
	else
459
	{
460
		$exclude_boards = empty($exclude_boards) ? array() : (is_array($exclude_boards) ? $exclude_boards : array($exclude_boards));
0 ignored issues
show
introduced by
The condition is_array($exclude_boards) is always true.
Loading history...
461
	}
462
463
	// Only some boards?.
464
	if (is_array($include_boards) || (int) $include_boards === $include_boards)
0 ignored issues
show
introduced by
The condition (int)$include_boards === $include_boards is always false.
Loading history...
465
	{
466
		$include_boards = is_array($include_boards) ? $include_boards : array($include_boards);
467
	}
468
	elseif ($include_boards !== null)
469
	{
470
		$output_method = $include_boards;
471
		$include_boards = array();
472
	}
473
474
	$icon_sources = new MessageTopicIcons(!empty($modSettings['messageIconChecks_enable']), $settings['theme_dir']);
475
476
	// Find all the posts in distinct topics. Newer ones will have higher IDs.
477
	$topics = array();
478
	$db->fetchQuery('
479
		SELECT
480
			t.id_topic, b.id_board, b.name AS board_name
481
		FROM {db_prefix}topics AS t
482
			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
483
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
484
		WHERE t.id_last_msg >= {int:min_message_id}' . (empty($exclude_boards) ? '' : '
485
			AND b.id_board NOT IN ({array_int:exclude_boards})') . (empty($include_boards) ? '' : '
486
			AND b.id_board IN ({array_int:include_boards})') . '
487
			AND {query_wanna_see_board}' . ($modSettings['postmod_active'] ? '
488
			AND t.approved = {int:is_approved}
489
			AND ml.approved = {int:is_approved}' : '') . '
490
		ORDER BY t.id_last_msg DESC
491
		LIMIT {int:num_recent}',
492
		array(
493
			'include_boards' => empty($include_boards) ? '' : $include_boards,
494
			'exclude_boards' => empty($exclude_boards) ? '' : $exclude_boards,
495
			'min_message_id' => $modSettings['maxMsgID'] - 35 * min($num_recent, 5),
496
			'is_approved' => 1,
497
			'num_recent' => $num_recent,
498
		)
499
	)->fetch_callback(
500
		static function ($row) use (&$topics) {
501
			$topics[$row['id_topic']] = $row;
502
		}
503
	);
504
505
	// Did we find anything? If not, bail.
506
	if (empty($topics))
507
	{
508
		return array();
509
	}
510
511
	$topic_list = array_keys($topics);
512
513
	// Count number of new posts per topic.
514
	if (User::$info->is_guest === false)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
515
	{
516
		$db->fetchQuery('
517
			SELECT
518
				m.id_topic, COALESCE(lt.id_msg, lmr.id_msg, -2) + 1 AS new_from
519
			FROM {db_prefix}messages AS m
520
				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = m.id_topic AND lt.id_member = {int:current_member})
521
				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = m.id_board AND lmr.id_member = {int:current_member})
522
			WHERE
523
				m.id_topic IN ({array_int:topic_list})
524
				AND (m.id_msg > COALESCE(lt.id_msg, lmr.id_msg, 0))
525
			GROUP BY m.id_topic, lt.id_msg, lmr.id_msg',
526
			array(
527
				'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
528
				'topic_list' => $topic_list
529
			)
530
		)->fetch_callback(
531
			static function ($row) use (&$topics) {
532
				$topics[$row['id_topic']] += $row;
533
			}
534
		);
535
	}
536
537
	// Find all the posts in distinct topics. Newer ones will have higher IDs.
538
	$request = $db->fetchQuery('
539
		SELECT
540
			ml.poster_time, ml.id_member, ml.id_msg, ml.smileys_enabled, ml.icon,
541
			mf.subject, mf.id_member AS id_op_member, 
542
			t.id_topic, t.num_replies, t.num_views, t.id_last_msg, t.id_first_msg,
543
			mg.online_color,
544
			COALESCE(mem.real_name, ml.poster_name) AS poster_name,
545
			COALESCE(memop.real_name, mf.poster_name) AS op_name,
546
			SUBSTRING(ml.body, 1, 384) AS body
547
		FROM {db_prefix}topics AS t
548
			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
549
			INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
550
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ml.id_member)
551
			LEFT JOIN {db_prefix}members AS memop ON (memop.id_member = mf.id_member)
552
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)
553
		WHERE t.id_topic IN ({array_int:topic_list})',
554
		array(
555
			'topic_list' => $topic_list
556
		)
557
	);
558
	$bbc_parser = ParserWrapper::instance();
559
	$posts = array();
560
	while (($row = $request->fetch_assoc()))
561
	{
562
		$row['body'] = strip_tags(strtr($bbc_parser->parseMessage($row['body'], $row['smileys_enabled']), array('<br />' => '&#10;')));
563
564
		// Censor the subject and body.
565
		$row['subject'] = censor($row['subject']);
566
		$row['body'] = censor($row['body']);
567
568
		$row['body'] = Util::shorten_text($row['body'], 128);
569
570
		// Build the array.
571
		$posts[$row['id_last_msg']] = array(
572
			'board' => array(
573
				'id' => $topics[$row['id_topic']]['id_board'],
574
				'name' => $topics[$row['id_topic']]['board_name'],
575
				'href' => $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0',
576
				'link' => '<a href="' . $scripturl . '?board=' . $topics[$row['id_topic']]['id_board'] . '.0">' . $topics[$row['id_topic']]['board_name'] . '</a>',
577
			),
578
			'topic' => $row['id_topic'],
579
			'poster' => array(
580
				'id' => $row['id_member'],
581
				'name' => $row['poster_name'],
582
				'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
583
				'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['poster_name'] . '</a>'
584
			),
585
			'original_poster' => array(
586
				'id' => $row['id_op_member'],
587
				'name' => $row['op_name'],
588
				'href' => empty($row['id_op_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_op_member'],
589
				'link' => empty($row['id_op_member']) ? $row['op_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_op_member'] . '">' . $row['op_name'] . '</a>'
590
			),
591
			'subject' => $row['subject'],
592
			'replies' => $row['num_replies'],
593
			'views' => $row['num_views'],
594
			'short_subject' => Util::shorten_text($row['subject'], 25),
595
			'preview' => $row['body'],
596
			'time' => standardTime($row['poster_time']),
597
			'html_time' => htmlTime($row['poster_time']),
598
			'timestamp' => forum_time(true, $row['poster_time']),
599
			'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . ';topicseen#new',
600
			'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#new" rel="nofollow">' . $row['subject'] . '</a>',
601
			'is_new' => !empty($topics[$row['id_topic']]['new_from']),
602
			'new_from' => empty($topics[$row['id_topic']]['new_from']) ? 0 : $topics[$row['id_topic']]['new_from'],
603
			'icon' => '<img src="' . $icon_sources->getIconURL($row['icon']) . '" class="icon-middle" alt="' . $row['icon'] . '" />',
604
		);
605
	}
606
607
	$request->free_result();
608
	krsort($posts);
609
610
	// Just return it.
611
	if ($output_method !== 'echo' || empty($posts))
612
	{
613
		return $posts;
614
	}
615
616
	echo '
617
		<table class="ssi_table">';
618
619
	foreach ($posts as $post)
620
	{
621
		echo '
622
			<tr>
623
				<td class="righttext top">
624
					[', $post['board']['link'], ']
625
				</td>
626
				<td class="top">
627
					<a href="', $post['href'], '">', $post['subject'], '</a>
628
					', $txt['by'], ' ', $post['poster']['link'], '
629
					', $post['is_new'] ? '<a href="' . $scripturl . '?topic=' . $post['topic'] . '.msg' . $post['new_from'] . ';topicseen#new" rel="nofollow" class="new_posts">' . $txt['new'] . '</a>' : '', '
630
				</td>
631
				<td class="righttext">
632
					', $post['time'], '
633
				</td>
634
			</tr>';
635
	}
636
637
	echo '
638
		</table>';
639
}
640
641
/**
642
 * Show the top poster's name and profile link.
643
 *
644
 * @param int $topNumber How many top posters to list
645
 * @param string $output_method = 'echo' The output method. If 'echo', will display a list of
646
 * users, otherwise returns an array of info about them.
647
 */
648
function ssi_topPoster($topNumber = 1, $output_method = 'echo')
649
{
650
	require_once(SUBSDIR . '/Stats.subs.php');
651
	$top_posters = topPosters($topNumber);
652
653
	// Just return all the top posters.
654
	if ($output_method !== 'echo')
655
	{
656
		return $top_posters;
657
	}
658
659
	// Make a quick array to list the links in.
660
	$temp_array = array();
661
	foreach ($top_posters as $member)
662
	{
663
		$temp_array[] = $member['link'];
664
	}
665
666
	echo implode(', ', $temp_array);
667
}
668
669
/**
670
 * Show boards by activity.
671
 *
672
 * @param int $num_top How many boards to display
673
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of
674
 * boards, otherwise returns an array of info about them.
675
 */
676
function ssi_topBoards($num_top = 10, $output_method = 'echo')
677
{
678
	global $txt;
679
680
	require_once(SUBSDIR . '/Stats.subs.php');
681
682
	// Find boards with lots of posts.
683
	$boards = topBoards($num_top, true);
684
685
	foreach ($boards as $id => $board)
686
	{
687
		$boards[$id]['new'] = empty($board['is_read']);
688
	}
689
690
	// If we shouldn't output or have nothing to output, just jump out.
691
	if ($output_method !== 'echo' || empty($boards))
692
	{
693
		return $boards;
694
	}
695
696
	echo '
697
		<table class="ssi_table">
698
			<tr>
699
				<th>', $txt['board'], '</th>
700
				<th class="centertext">', $txt['board_topics'], '</th>
701
				<th class="centertext">', $txt['posts'], '</th>
702
			</tr>';
703
704
	foreach ($boards as $board)
705
	{
706
		echo '
707
			<tr>
708
				<td>', $board['new'] ? ' <a href="' . $board['href'] . '" class="new_posts">' . $txt['new'] . '</a> ' : '', $board['link'], '</td>
709
				<td class="centertext">', $board['num_topics'], '</td>
710
				<td class="centertext">', $board['num_posts'], '</td>
711
			</tr>';
712
	}
713
714
	echo '
715
		</table>';
716
}
717
718
/**
719
 * Shows the top topics.
720
 *
721
 * @param string $type Can be one of type or replies
722
 * @param int $num_topics = 10, How many topics to display
723
 * @param string $output_method = 'echo' The output method. If 'echo', displays a
724
 * list of topics, otherwise returns an array of info about them.
725
 */
726
function ssi_topTopics($type = 'replies', $num_topics = 10, $output_method = 'echo')
727
{
728
	global $txt, $scripturl;
729
730
	require_once(SUBSDIR . '/Stats.subs.php');
731
732
	$function = function_exists('topTopic' . ucfirst($type)) ? 'topTopic' . ucfirst($type) : 'topTopicReplies';
733
734
	$topics = $function($num_topics);
735
736
	foreach ($topics as $topic_id => $row)
737
	{
738
		$row['subject'] = censor($row['subject']);
739
740
		$topics[$topic_id]['href'] = $scripturl . '?topic=' . $row['id'] . '.0';
741
		$topics[$topic_id]['link'] = '<a href="' . $scripturl . '?topic=' . $row['id'] . '.0">' . $row['subject'] . '</a>';
742
	}
743
744
	if ($output_method !== 'echo' || empty($topics))
745
	{
746
		return $topics;
747
	}
748
749
	echo '
750
		<table class="top_topic ssi_table">
751
			<tr>
752
				<th class="link"></th>
753
				<th class="centertext views">', $txt['views'], '</th>
754
				<th class="centertext num_replies">', $txt['replies'], '</th>
755
			</tr>';
756
757
	foreach ($topics as $topic)
758
	{
759
		echo '
760
			<tr>
761
				<td class="link">
762
					', $topic['link'], '
763
				</td>
764
				<td class="centertext views">', $topic['num_views'], '</td>
765
				<td class="centertext num_replies">', $topic['num_replies'], '</td>
766
			</tr>';
767
	}
768
769
	echo '
770
		</table>';
771
}
772
773
/**
774
 * Shows the top topics, by replies.
775
 *
776
 * @param int $num_topics = 10, How many topics to show
777
 * @param string $output_method = 'echo'  The output method. If 'echo', displays a list of topics,
778
 * otherwise returns an array of info about them
779
 */
780
function ssi_topTopicsReplies($num_topics = 10, $output_method = 'echo')
781
{
782
	return ssi_topTopics('replies', $num_topics, $output_method);
783
}
784
785
/**
786
 * Shows the top topics, by views.
787
 *
788
 * @param int $num_topics = 10, How many topics to show
789
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of topics,
790
 * otherwise returns an array of info about them
791
 */
792
function ssi_topTopicsViews($num_topics = 10, $output_method = 'echo')
793
{
794
	return ssi_topTopics('views', $num_topics, $output_method);
795
}
796
797
/**
798
 * Show a link to the latest member:
799
 *
800
 * - Please welcome, Someone, our latest member.
801
 *
802
 * @param string $output_method = 'echo' The output method. If 'echo', returns a string
803
 * with a link to the latest member's profile, otherwise returns an array of info about them.
804
 */
805
function ssi_latestMember($output_method = 'echo')
806
{
807
	global $txt, $context;
808
809
	if ($output_method === 'echo')
810
	{
811
		echo '
812
		', sprintf($txt['welcome_newest_member'], $context['common_stats']['latest_member']['link']), '<br />';
813
	}
814
	else
815
	{
816
		return $context['common_stats']['latest_member'];
817
	}
818
}
819
820
/**
821
 * Fetch a random member
822
 *
823
 * @param string $random_type = '', if type set to 'day' will only change once a day!
824
 * @param string $output_method = 'echo' The output method. If 'echo', displays a link to the member's
825
 * profile, otherwise returns an array of info about them.
826
 */
827
function ssi_randomMember($random_type = '', $output_method = 'echo')
828
{
829
	global $modSettings;
830
831
	// If we're looking for something to stay the same each day then seed the generator.
832
	if ($random_type === 'day')
833
	{
834
		// Set the seed to change only once per day.
835
		mt_srand(floor(time() / 86400));
0 ignored issues
show
Bug introduced by
floor(time() / 86400) of type double is incompatible with the type integer expected by parameter $seed of mt_srand(). ( Ignorable by Annotation )

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

835
		mt_srand(/** @scrutinizer ignore-type */ floor(time() / 86400));
Loading history...
836
	}
837
838
	// Get the lowest ID we're interested in.
839
	$member_id = mt_rand(1, $modSettings['latestMember']);
840
841
	$result = ssi_queryMembers('member_greater_equal', $member_id, 1, 'id_member ASC', $output_method);
842
843
	// If we got nothing do the reverse - in case of unactivated members.
844
	if (empty($result))
845
	{
846
		$result = ssi_queryMembers('member_lesser_equal', $member_id, 1, 'id_member DESC', $output_method);
847
	}
848
849
	// Just to be sure put the random generator back to something... random.
850
	if ($random_type !== '')
851
	{
852
		mt_srand(time());
853
	}
854
855
	return $result;
856
}
857
858
/**
859
 * Fetch a specific member.
860
 *
861
 * @param int[] $member_ids = array() The IDs of the members to fetch
862
 * @param string $output_method = 'echo' The output method. If 'echo', displays a
863
 * list of links to the members' profiles, otherwise returns an array of info about them.
864
 */
865
function ssi_fetchMember($member_ids = array(), $output_method = 'echo')
866
{
867
	if (empty($member_ids))
868
	{
869
		return '';
870
	}
871
872
	// Can have more than one member if you really want...
873
	$member_ids = is_array($member_ids) ? $member_ids : array($member_ids);
0 ignored issues
show
introduced by
The condition is_array($member_ids) is always true.
Loading history...
874
875
	// Then make the query and dump the data.
876
	return ssi_queryMembers('members', $member_ids, '', 'id_member', $output_method);
877
}
878
879
/**
880
 * Fetch all members in the specified group
881
 *
882
 * @param int|null $group_id The ID of the group to get members from
883
 * @param string $output_method = 'echo' The output method. If 'echo', returns a list of
884
 * group members, otherwise returns an array of info about them.
885
 */
886
function ssi_fetchGroupMembers($group_id = null, $output_method = 'echo')
887
{
888
	if ($group_id === null)
889
	{
890
		return false;
891
	}
892
893
	return ssi_queryMembers('group_list', is_array($group_id) ? $group_id : array($group_id), '', 'real_name', $output_method);
0 ignored issues
show
introduced by
The condition is_array($group_id) is always false.
Loading history...
894
}
895
896
/**
897
 * Fetch some member data!
898
 *
899
 * - Gathers info about members based on the specified parameters.
900
 * - Used by other functions to eliminate duplication.
901
 *
902
 * @param string|null $query_where The info for the WHERE clause of the query
903
 * @param string|string[] $query_where_params The parameters for the WHERE clause
904
 * @param string|int $query_limit The number of rows to return or an empty string to return all
905
 * @param string $query_order The info for the ORDER BY clause of the query
906
 * @param string $output_method The output method. If 'echo', displays a list of members,
907
 * otherwise returns an array of info about them
908
 */
909
function ssi_queryMembers($query_where = null, $query_where_params = array(), $query_limit = '', $query_order = 'id_member DESC', $output_method = 'echo')
910
{
911
	if ($query_where === null)
912
	{
913
		return false;
914
	}
915
916
	require_once(SUBSDIR . '/Members.subs.php');
917
	$members_data = retrieveMemberData(array(
918
		$query_where => $query_where_params,
919
		'limit' => empty($query_limit) ? 10 : (int) $query_limit,
920
		'order_by' => $query_order,
921
		'activated_status' => 1,
922
	));
923
924
	$members = array();
925
	foreach ($members_data['member_info'] as $row)
926
	{
927
		$members[] = $row['id'];
928
	}
929
930
	if (empty($members))
931
	{
932
		return array();
933
	}
934
935
	// Load the members.
936
	MembersList::load($members);
937
938
	// Draw the table!
939
	if ($output_method === 'echo')
940
	{
941
		echo '
942
		<table class="ssi_table">';
943
	}
944
945
	$query_members = array();
946
	foreach ($members as $id)
947
	{
948
		$member = MembersList::get($id);
949
		// Load their context data.
950
		if ($member->isEmpty())
951
		{
952
			continue;
953
		}
954
955
		$member->loadContext();
956
957
		// Store this member's information.
958
		$query_members[$id] = $member;
959
960
		// Only do something if we're echo'ing.
961
		if ($output_method === 'echo')
962
		{
963
			echo '
964
			<tr>
965
				<td class="centertext">
966
					', $query_members[$id]['link'], '
967
					<br />', $query_members[$id]['avatar']['image'], '
968
				</td>
969
			</tr>';
970
		}
971
	}
972
973
	// End the table if appropriate.
974
	if ($output_method === 'echo')
975
	{
976
		echo '
977
		</table>';
978
	}
979
980
	// Send back the data.
981
	return $query_members;
982
}
983
984
/**
985
 * Show some basic stats:
986
 *
987
 * - Total This: XXXX, etc.
988
 *
989
 * @param string $output_method The output method. If 'echo', displays the stats,
990
 * otherwise returns an array of info about them
991
 */
992
function ssi_boardStats($output_method = 'echo')
993
{
994
	global $txt, $scripturl, $modSettings;
995
996
	if (!allowedTo('view_stats'))
997
	{
998
		return false;
999
	}
1000
1001
	require_once(SUBSDIR . '/Boards.subs.php');
1002
	require_once(SUBSDIR . '/Stats.subs.php');
1003
1004
	$totals = array(
1005
		'members' => $modSettings['totalMembers'],
1006
		'posts' => $modSettings['totalMessages'],
1007
		'topics' => $modSettings['totalTopics'],
1008
		'boards' => countBoards(),
1009
		'categories' => numCategories(),
1010
	);
1011
1012
	if ($output_method !== 'echo')
1013
	{
1014
		return $totals;
1015
	}
1016
1017
	echo '
1018
		', $txt['total_members'], ': <a href="', $scripturl . '?action=memberlist">', comma_format($totals['members']), '</a><br />
1019
		', $txt['total_posts'], ': ', comma_format($totals['posts']), '<br />
1020
		', $txt['total_topics'], ': ', comma_format($totals['topics']), ' <br />
1021
		', $txt['total_cats'], ': ', comma_format($totals['categories']), '<br />
1022
		', $txt['total_boards'], ': ', comma_format($totals['boards']);
1023
}
1024
1025
/**
1026
 * Shows a list of online users:
1027
 *
1028
 * - YY Guests, ZZ Users and then a list...
1029
 *
1030
 * @param string $output_method The output method. If 'echo', displays the stats,
1031
 * otherwise returns an array of info about them
1032
 */
1033
function ssi_whosOnline($output_method = 'echo')
1034
{
1035
	global $txt, $settings;
1036
1037
	require_once(SUBSDIR . '/MembersOnline.subs.php');
1038
	$membersOnlineOptions = array(
1039
		'show_hidden' => allowedTo('moderate_forum'),
1040
	);
1041
	$return = getMembersOnlineStats($membersOnlineOptions);
1042
1043
	// Add some redundancy for backwards compatibility reasons.
1044
	if ($output_method !== 'echo')
1045
	{
1046
		return $return + array(
1047
			'users' => $return['users_online'],
1048
			'guests' => $return['num_guests'],
1049
			'hidden' => $return['num_users_hidden'],
1050
			'buddies' => $return['num_buddies'],
1051
			'num_users' => $return['num_users_online'],
1052
			'total_users' => $return['num_users_online'] + $return['num_guests'],
1053
		);
1054
	}
1055
1056
	echo '
1057
		', comma_format($return['num_guests']), ' ', $return['num_guests'] == 1 ? $txt['guest'] : $txt['guests'], ', ', comma_format($return['num_users_online']), ' ', $return['num_users_online'] == 1 ? $txt['user'] : $txt['users'];
1058
1059
	$bracketList = array();
1060
	if (!empty(User::$info->buddies))
0 ignored issues
show
Bug Best Practice introduced by
The property buddies does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1061
	{
1062
		$bracketList[] = comma_format($return['num_buddies']) . ' ' . ($return['num_buddies'] == 1 ? $txt['buddy'] : $txt['buddies']);
1063
	}
1064
1065
	if (!empty($return['num_spiders']))
1066
	{
1067
		$bracketList[] = comma_format($return['num_spiders']) . ' ' . ($return['num_spiders'] == 1 ? $txt['spider'] : $txt['spiders']);
1068
	}
1069
1070
	if (!empty($return['num_users_hidden']))
1071
	{
1072
		$bracketList[] = comma_format($return['num_users_hidden']) . ' ' . $txt['hidden'];
1073
	}
1074
1075
	if (!empty($bracketList))
1076
	{
1077
		echo ' (' . implode(', ', $bracketList) . ')';
1078
	}
1079
1080
	echo '<br />
1081
			', implode(', ', $return['list_users_online']);
1082
1083
	// Showing membergroups?
1084
	if (empty($settings['show_group_key']))
1085
	{
1086
		return;
1087
	}
1088
1089
	if (empty($return['membergroups']))
1090
	{
1091
		return;
1092
	}
1093
1094
	echo '<br />
1095
			[' . implode(']&nbsp;&nbsp;[', $return['membergroups']) . ']';
1096
}
1097
1098
/**
1099
 * Just like whosOnline except it also logs the online presence.
1100
 *
1101
 * @param string $output_method The output method. If 'echo', displays the stats,
1102
 * otherwise returns an array of info about them
1103
 */
1104
function ssi_logOnline($output_method = 'echo')
1105
{
1106
	writeLog();
1107
1108
	if ($output_method !== 'echo')
1109
	{
1110
		return ssi_whosOnline($output_method);
1111
	}
1112
1113
	ssi_whosOnline($output_method);
1114
}
1115
1116
/**
1117
 * Shows a login box.
1118
 *
1119
 * @param string $redirect_to = '' The URL to redirect the user to after they login
1120
 * @param string $output_method = 'echo' The output method. If 'echo' and the user is a guest, displays a
1121
 * login box, otherwise returns whether the user is a guest
1122
 */
1123
function ssi_login($redirect_to = '', $output_method = 'echo')
1124
{
1125
	global $scripturl, $txt, $modSettings, $context, $settings;
1126
1127
	if ($redirect_to !== '')
1128
	{
1129
		$_SESSION['login_url'] = $redirect_to;
1130
	}
1131
1132
	if ($output_method !== 'echo' || User::$info->is_guest === false)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1133
	{
1134
		return User::$info->is_guest;
1135
	}
1136
1137
	$context['default_username'] = isset($_POST['user']) ? preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($_POST['user'], ENT_COMPAT, 'UTF-8')) : '';
1138
1139
	echo '
1140
		<form action="', $scripturl, '?action=login2" name="frmLogin" id="frmLogin" method="post" accept-charset="UTF-8">
1141
		<div class="login centertext">
1142
			<div class="well">';
1143
1144
	// Did they make a mistake last time?
1145
	if (!empty($context['login_errors']))
1146
	{
1147
		echo '
1148
			<p class="errorbox">', implode('<br />', $context['login_errors']), '</p><br />';
1149
	}
1150
1151
	// Or perhaps there's some special description for this time?
1152
	if (isset($context['description']))
1153
	{
1154
		echo '
1155
				<p class="description">', $context['description'], '</p>';
1156
	}
1157
1158
	// Now just get the basic information - username, password, etc.
1159
	echo '
1160
				<dl>
1161
					<dt>', $txt['username'], ':</dt>
1162
					<dd>
1163
						<input type="text" name="user" size="20" value="', $context['default_username'], '" class="input_text" autofocus="autofocus" autocomplete="username" placeholder="', $txt['username'], '" />
1164
					</dd>
1165
					<dt>', $txt['password'], ':</dt>
1166
					<dd>
1167
						<input type="password" name="passwrd" value="" size="20" class="input_password" autocomplete="current-password" placeholder="', $txt['password'], '" />
1168
					</dd>
1169
				</dl>
1170
				<input type="submit" value="', $txt['login'], '" />
1171
				<p class="smalltext">
1172
					<a href="', $scripturl, '?action=reminder">', $txt['forgot_your_password'], '</a>
1173
				</p>
1174
				<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
1175
				<input type="hidden" name="', $context['login_token_var'], '" value="', $context['login_token'], '" />
1176
			</div>
1177
		</div>
1178
		</form>';
1179
1180
	// Focus on the correct input - username or password.
1181
	echo '
1182
		<script>
1183
			document.forms.frmLogin.', isset($context['default_username']) && $context['default_username'] !== '' ? 'passwrd' : 'user', '.focus();
1184
		</script>';
1185
}
1186
1187
/**
1188
 * Show the most-voted-in poll.
1189
 *
1190
 * @param string $output_method = 'echo; The output method. If 'echo', displays the poll,
1191
 * otherwise returns an array of info about it
1192
 */
1193
function ssi_topPoll($output_method = 'echo')
1194
{
1195
	// Just use recentPoll, no need to duplicate code...
1196
	return ssi_recentPoll(true, $output_method);
1197
}
1198
1199
/**
1200
 * Show the most recently posted poll.
1201
 *
1202
 * @param bool $topPollInstead = false Show the top poll (based on votes) instead of the most recent one
1203
 * @param string $output_method = string The output method. If 'echo', displays the poll,
1204
 * otherwise returns an array of info about it.
1205
 */
1206
function ssi_recentPoll($topPollInstead = false, $output_method = 'echo')
1207
{
1208
	global $txt, $boardurl, $context, $modSettings;
1209
1210
	$boardsAllowed = array_intersect(boardsAllowedTo('poll_view'), boardsAllowedTo('poll_vote'));
1211
1212
	if (empty($boardsAllowed))
1213
	{
1214
		return array();
1215
	}
1216
1217
	$db = database();
1218
1219
	$row = $db->fetchQuery('
1220
		SELECT 
1221
			p.id_poll, p.question, t.id_topic, p.max_votes, p.guest_vote, p.hide_results, p.expire_time
1222
		FROM {db_prefix}polls AS p
1223
			INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ')
1224
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)' . ($topPollInstead ? '
1225
			INNER JOIN {db_prefix}poll_choices AS pc ON (pc.id_poll = p.id_poll)' : '') . '
1226
			LEFT JOIN {db_prefix}log_polls AS lp ON (lp.id_poll = p.id_poll 
1227
				AND lp.id_member > {int:no_member} 
1228
				AND lp.id_member = {int:current_member})
1229
		WHERE p.voting_locked = {int:voting_opened}
1230
			AND (p.expire_time = {int:no_expiration} OR {int:current_time} < p.expire_time)
1231
			AND ' . (User::$info->is_guest ? 'p.guest_vote = {int:guest_vote_allowed}' : 'lp.id_choice IS NULL') . '
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1232
			AND {query_wanna_see_board}' . (in_array(0, $boardsAllowed) ? '' : '
1233
			AND b.id_board IN ({array_int:boards_allowed_list})') . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1234
			AND b.id_board != {int:recycle_enable}' : '') . '
1235
		ORDER BY ' . ($topPollInstead ? 'pc.votes' : 'p.id_poll') . ' DESC
1236
		LIMIT 1',
1237
		array(
1238
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1239
			'boards_allowed_list' => $boardsAllowed,
1240
			'is_approved' => 1,
1241
			'guest_vote_allowed' => 1,
1242
			'no_member' => 0,
1243
			'voting_opened' => 0,
1244
			'no_expiration' => 0,
1245
			'current_time' => time(),
1246
			'recycle_enable' => $modSettings['recycle_board'],
1247
		)
1248
	)->fetch_assoc();
1249
1250
	// This user has voted on all the polls.
1251
	if (empty($row))
1252
	{
1253
		return array();
1254
	}
1255
1256
	// If this is a guest who's voted we'll through ourselves to show poll to show the results.
1257
	if (User::$info->is_guest && (!$row['guest_vote'] || (isset($_COOKIE['guest_poll_vote']) && in_array($row['id_poll'], explode(',', $_COOKIE['guest_poll_vote'])))))
1258
	{
1259
		return ssi_showPoll($row['id_topic'], $output_method);
1260
	}
1261
1262
	$request = $db->fetchQuery('
1263
		SELECT 
1264
			COUNT(DISTINCT id_member)
1265
		FROM {db_prefix}log_polls
1266
		WHERE id_poll = {int:current_poll}',
1267
		array(
1268
			'current_poll' => $row['id_poll'],
1269
		)
1270
	);
1271
	[$total] = $request->fetch_row();
1272
	$request->free_result();
1273
1274
	$options = array();
1275
	$db->fetchQuery('
1276
		SELECT 
1277
			id_choice, label, votes
1278
		FROM {db_prefix}poll_choices
1279
		WHERE id_poll = {int:current_poll}',
1280
		array(
1281
			'current_poll' => $row['id_poll'],
1282
		)
1283
	)->fetch_callback(
1284
		static function ($rowChoice) use (&$options) {
1285
			$rowChoice['label'] = censor($rowChoice['label']);
1286
			$options[$rowChoice['id_choice']] = array($rowChoice['label'], $rowChoice['votes']);
1287
		}
1288
	);
1289
1290
	// Can they view it?
1291
	$is_expired = !empty($row['expire_time']) && $row['expire_time'] < time();
1292
	$allow_view_results = allowedTo('moderate_board') || $row['hide_results'] == 0 || $is_expired;
1293
1294
	$return = array(
1295
		'id' => $row['id_poll'],
1296
		'image' => 'poll',
1297
		'question' => $row['question'],
1298
		'total_votes' => $total,
1299
		'is_locked' => false,
1300
		'topic' => $row['id_topic'],
1301
		'allow_view_results' => $allow_view_results,
1302
		'options' => array()
1303
	);
1304
1305
	$bbc_parser = ParserWrapper::instance();
1306
1307
	// Calculate the percentages and bar lengths...
1308
	$divisor = $return['total_votes'] == 0 ? 1 : $return['total_votes'];
1309
	foreach ($options as $i => $option)
1310
	{
1311
		$bar = floor(($option[1] * 100) / $divisor);
1312
		$barWide = $bar == 0 ? 1 : floor(($bar * 5) / 3);
1313
		$return['options'][$i] = array(
1314
			'id' => 'options-' . ($topPollInstead ? 'top-' : 'recent-') . $i,
1315
			'percent' => $bar,
1316
			'votes' => $option[1], /* Todo: gradient div will need some visual tweaking. Or replace with the current bar_ndt code from Poll.subs */
1317
			'bar' => '<div class="poll_gradient" style="width: ' . $barWide . 'px;"></div>',
1318
			'option' => $bbc_parser->parsePoll($option[0]),
1319
			'vote_button' => '<input type="' . ($row['max_votes'] > 1 ? 'checkbox' : 'radio') . '" name="options[]" id="options-' . ($topPollInstead ? 'top-' : 'recent-') . $i . '" value="' . $i . '" class="input_' . ($row['max_votes'] > 1 ? 'check' : 'radio') . '" />'
1320
		);
1321
	}
1322
1323
	$return['allowed_warning'] = $row['max_votes'] > 1 ? sprintf($txt['poll_options6'], min(count($options), $row['max_votes'])) : '';
1324
1325
	if ($output_method !== 'echo')
1326
	{
1327
		return $return;
1328
	}
1329
1330
	if ($allow_view_results)
1331
	{
1332
		echo '
1333
		<form class="ssi_poll" action="', $boardurl, '/SSI.php?ssi_function=pollVote" method="post" accept-charset="UTF-8">
1334
			<strong>', $return['question'], '</strong><br />
1335
			', empty($return['allowed_warning']) ? '' : $return['allowed_warning'] . '<br />';
1336
1337
		foreach ($return['options'] as $option)
1338
		{
1339
			echo '
1340
			<label for="', $option['id'], '">', $option['vote_button'], ' ', $option['option'], '</label><br />';
1341
		}
1342
1343
		echo '
1344
			<input type="submit" value="', $txt['poll_vote'], '" />
1345
			<input type="hidden" name="poll" value="', $return['id'], '" />
1346
			<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
1347
		</form>';
1348
	}
1349
	else
1350
	{
1351
		echo $txt['poll_cannot_see'];
1352
	}
1353
}
1354
1355
/**
1356
 * Show a poll.
1357
 *
1358
 * - It is possible to use this function in combination with the template
1359
 * template_display_poll_above from Display.template.php, the only part missing
1360
 * is the definition of the poll moderation button array (see Display.controller.php
1361
 * for details).
1362
 *
1363
 * @param int|null $topicID = null The topic to show the poll from. If null, $_REQUEST['ssi_topic'] will
1364
 * be used instead.
1365
 * @param string $output_method = 'echo' The output method. If 'echo', displays the poll, otherwise
1366
 * returns an array of info about it.
1367
 */
1368
function ssi_showPoll($topicID = null, $output_method = 'echo')
1369
{
1370
	global $txt, $context, $scripturl;
1371
	static $last_board = null;
1372
1373
	require_once(SUBSDIR . '/Poll.subs.php');
1374
	require_once(SUBSDIR . '/Topic.subs.php');
1375
1376
	$topicID = $topicID === null && isset($_REQUEST['ssi_topic']) ? (int) $_REQUEST['ssi_topic'] : (int) $topicID;
1377
1378
	if (empty($topicID))
1379
	{
1380
		return array();
1381
	}
1382
1383
	// Get the topic starter information.
1384
	$topicinfo = getTopicInfo($topicID, 'starter');
1385
1386
	$boards_can_poll = boardsAllowedTo('poll_view');
1387
1388
	// If:
1389
	//  - is not allowed to see poll in any board,
1390
	//  - or:
1391
	//     - is not allowed in the specific board, and
1392
	//     - is not an admin
1393
	// fail
1394
	if (empty($boards_can_poll) || (!in_array($topicinfo['id_board'], $boards_can_poll) && !in_array(0, $boards_can_poll)))
1395
	{
1396
		return array();
1397
	}
1398
1399
	$context['user']['started'] = User::$info->id == $topicinfo['id_member'] && User::$info->is_guest === false;
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1400
1401
	$poll_id = associatedPoll($topicID);
1402
	loadPollContext($poll_id);
1403
1404
	if (empty($context['poll']))
1405
	{
1406
		return array();
1407
	}
1408
1409
	if ($output_method !== 'echo')
1410
	{
1411
		return $context['poll'];
1412
	}
1413
1414
	echo '
1415
		<div class="content" id="poll_options">
1416
			<h4 id="pollquestion">
1417
				', $context['poll']['question'], '
1418
			</h4>';
1419
1420
	if ($context['poll']['allow_vote'])
1421
	{
1422
		echo '
1423
			<form action="', $scripturl, '?action=poll;sa=vote;topic=', $context['current_topic'], '.', $context['start'], ';poll=', $context['poll']['id'], '" method="post" accept-charset="UTF-8">';
1424
1425
		// Show a warning if they are allowed more than one option.
1426
		if ($context['poll']['allowed_warning'])
1427
		{
1428
			echo '
1429
				<p>', $context['poll']['allowed_warning'], '</p>';
1430
		}
1431
1432
		echo '
1433
				<ul class="options">';
1434
1435
		// Show each option with its button - a radio likely.
1436
		foreach ($context['poll']['options'] as $option)
1437
		{
1438
			echo '
1439
					<li>', $option['vote_button'], ' <label for="', $option['id'], '">', $option['option'], '</label></li>';
1440
		}
1441
1442
		echo '
1443
				</ul>
1444
				<div class="submitbutton">
1445
					<input type="submit" value="', $txt['poll_vote'], '" />
1446
					<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '" />
1447
				</div>
1448
			</form>';
1449
1450
		// Is the clock ticking?
1451
		if (!empty($context['poll']['expire_time']))
1452
		{
1453
			echo '
1454
			<p><strong>', ($context['poll']['is_expired'] ? $txt['poll_expired_on'] : $txt['poll_expires_on']), ':</strong> ', $context['poll']['expire_time'], '</p>';
1455
		}
1456
1457
	}
1458
	elseif ($context['poll']['allow_view_results'])
1459
	{
1460
		echo '
1461
			<ul class="options">';
1462
1463
		// Show each option with its corresponding percentage bar.
1464
		foreach ($context['poll']['options'] as $option)
1465
		{
1466
			echo '
1467
				<li', $option['voted_this'] ? ' class="voted"' : '', '>', $option['option'], '
1468
					<div class="results">';
1469
1470
			if ($context['allow_poll_view'])
1471
			{
1472
				echo '
1473
						<div class="statsbar"> ', $option['bar_ndt'], '</div>
1474
						<span class="percentage">', $option['votes'], ' (', $option['percent'], '%)</span>';
1475
			}
1476
1477
			echo '
1478
					</div>
1479
				</li>';
1480
		}
1481
1482
		echo '
1483
			</ul>';
1484
1485
		if ($context['allow_poll_view'])
1486
		{
1487
			echo '
1488
			<p><strong>', $txt['poll_total_voters'], ':</strong> ', $context['poll']['total_votes'], '</p>';
1489
		}
1490
1491
		// Is the clock ticking?
1492
		if (!empty($context['poll']['expire_time']))
1493
		{
1494
			echo '
1495
			<p><strong>', ($context['poll']['is_expired'] ? $txt['poll_expired_on'] : $txt['poll_expires_on']), ':</strong> ', $context['poll']['expire_time'], '</p>';
1496
		}
1497
	}
1498
	// Cannot see it I'm afraid!
1499
	else
1500
	{
1501
		echo $txt['poll_cannot_see'];
1502
	}
1503
1504
	echo '
1505
			</div>';
1506
}
1507
1508
/**
1509
 * Takes care of voting - don't worry, this is done automatically.
1510
 */
1511
function ssi_pollVote()
1512
{
1513
	global $context, $topic, $board;
1514
1515
	$pollID = isset($_POST['poll']) ? (int) $_POST['poll'] : 0;
1516
1517
	if (empty($pollID) || !isset($_POST[$context['session_var']]) || $_POST[$context['session_var']] != $_SESSION['session_value'] || empty($_POST['options']))
1518
	{
1519
		echo '<!DOCTYPE html>
1520
<html>
1521
<head>
1522
	<script>
1523
		history.go(-1);
1524
	</script>
1525
</head>
1526
<body>&laquo;</body>
1527
</html>';
1528
1529
		return;
1530
	}
1531
1532
	require_once(SUBSDIR . '/Poll.subs.php');
1533
1534
	// We have to fake we are in a topic so that we can use the proper controller
1535
	[$topic, $board] = topicFromPoll($pollID);
1536
	loadBoard();
1537
1538
	$poll_action = new Poll(new EventManager());
1539
	$poll_action->setUser(User::$info);
1540
	$poll_action->pre_dispatch();
1541
1542
	// The controller takes already care of redirecting properly or fail
1543
	$poll_action->action_vote();
1544
}
1545
1546
/**
1547
 * Show a search box.
1548
 *
1549
 * @param string $output_method = 'echo'  The output method. If 'echo', displays a search box,
1550
 * otherwise returns the URL of the search page.
1551
 */
1552
function ssi_quickSearch($output_method = 'echo')
1553
{
1554
	global $scripturl, $txt;
1555
1556
	if (!allowedTo('search_posts'))
1557
	{
1558
		return '';
1559
	}
1560
1561
	if ($output_method !== 'echo')
1562
	{
1563
		return $scripturl . '?action=search';
1564
	}
1565
1566
	echo '
1567
		<form action="', $scripturl, '?action=search;sa=results" method="post" accept-charset="UTF-8">
1568
			<input type="hidden" name="advanced" value="0" />
1569
			<input type="text" name="search" size="30" class="input_text" />
1570
			<input type="submit" value="', $txt['search'], '" />
1571
		</form>';
1572
}
1573
1574
/**
1575
 * Show what would be the forum news.
1576
 *
1577
 * @param string $output_method = 'echo' The output method. If 'echo', shows the news item, otherwise returns it.
1578
 */
1579
function ssi_news($output_method = 'echo')
1580
{
1581
	global $context;
1582
1583
	if ($output_method !== 'echo')
1584
	{
1585
		return $context['random_news_line'];
1586
	}
1587
1588
	echo $context['random_news_line'];
1589
}
1590
1591
/**
1592
 * Show today's birthdays.
1593
 *
1594
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of users,
1595
 * otherwise returns an array of info about them.
1596
 */
1597
function ssi_todaysBirthdays($output_method = 'echo')
1598
{
1599
	global $scripturl, $modSettings;
1600
1601
	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view') || !allowedTo('profile_view_any'))
1602
	{
1603
		return '';
1604
	}
1605
1606
	$eventOptions = array(
1607
		'include_birthdays' => true,
1608
		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
1609
	);
1610
	$return = Cache::instance()->quick_get('calendar_index_offset_' . (User::$info->time_offset + $modSettings['time_offset']), 'subs/Calendar.subs.php', 'cache_getRecentEvents', array($eventOptions));
0 ignored issues
show
Bug Best Practice introduced by
The property time_offset does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1611
1612
	if ($output_method !== 'echo')
1613
	{
1614
		return $return['calendar_birthdays'];
1615
	}
1616
1617
	foreach ($return['calendar_birthdays'] as $member)
1618
	{
1619
		echo '
1620
			<a href="', $scripturl, '?action=profile;u=', $member['id'], '">' . $member['name'] . (isset($member['age']) ? ' (' . $member['age'] . ')' : '') . '</a>' . ($member['is_last'] ? '' : ', ');
1621
	}
1622
}
1623
1624
/**
1625
 * Show today's holidays.
1626
 *
1627
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of holidays,
1628
 * otherwise returns an array of info about them.
1629
 */
1630
function ssi_todaysHolidays($output_method = 'echo')
1631
{
1632
	global $modSettings;
1633
1634
	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
1635
	{
1636
		return false;
1637
	}
1638
1639
	$eventOptions = array(
1640
		'include_holidays' => true,
1641
		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
1642
	);
1643
	$return = Cache::instance()->quick_get('calendar_index_offset_' . (User::$info->time_offset + $modSettings['time_offset']), 'subs/Calendar.subs.php', 'cache_getRecentEvents', array($eventOptions));
0 ignored issues
show
Bug Best Practice introduced by
The property time_offset does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1644
1645
	if ($output_method !== 'echo')
1646
	{
1647
		return $return['calendar_holidays'];
1648
	}
1649
1650
	echo '
1651
		', implode(', ', $return['calendar_holidays']);
1652
}
1653
1654
/**
1655
 * Show today's events.
1656
 *
1657
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of events,
1658
 * otherwise returns an array of info about them.
1659
 */
1660
function ssi_todaysEvents($output_method = 'echo')
1661
{
1662
	global $modSettings;
1663
1664
	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
1665
	{
1666
		return false;
1667
	}
1668
1669
	$eventOptions = array(
1670
		'include_events' => true,
1671
		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
1672
	);
1673
	$return = Cache::instance()->quick_get('calendar_index_offset_' . (User::$info->time_offset + $modSettings['time_offset']), 'subs/Calendar.subs.php', 'cache_getRecentEvents', array($eventOptions));
0 ignored issues
show
Bug Best Practice introduced by
The property time_offset does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1674
1675
	if ($output_method !== 'echo')
1676
	{
1677
		return $return['calendar_events'];
1678
	}
1679
1680
	foreach ($return['calendar_events'] as $event)
1681
	{
1682
		if ($event['can_edit'])
1683
		{
1684
			echo '
1685
	<a href="' . $event['modify_href'] . '" class="moderation_link">*</a> ';
1686
		}
1687
1688
		echo '
1689
	' . $event['link'] . ($event['is_last'] ? '' : ', ');
1690
	}
1691
}
1692
1693
/**
1694
 * Show all calendar entries for today. (birthdays, holidays, and events.)
1695
 *
1696
 * @param string $output_method = 'echo' The output method. If 'echo', displays a list of calendar
1697
 * items, otherwise returns an array of info about them.
1698
 */
1699
function ssi_todaysCalendar($output_method = 'echo')
1700
{
1701
	global $modSettings, $txt, $scripturl;
1702
1703
	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
1704
	{
1705
		return '';
1706
	}
1707
1708
	$eventOptions = array(
1709
		'include_birthdays' => allowedTo('profile_view_any'),
1710
		'include_holidays' => true,
1711
		'include_events' => true,
1712
		'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
1713
	);
1714
1715
	$return = Cache::instance()->quick_get('calendar_index_offset_' . (User::$info->time_offset + $modSettings['time_offset']), 'subs/Calendar.subs.php', 'cache_getRecentEvents', array($eventOptions));
0 ignored issues
show
Bug Best Practice introduced by
The property time_offset does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1716
1717
	if ($output_method !== 'echo')
1718
	{
1719
		return $return;
1720
	}
1721
1722
	if (!empty($return['calendar_holidays']))
1723
	{
1724
		echo '
1725
			<span class="holiday">' . $txt['calendar_prompt'] . ' ' . implode(', ', $return['calendar_holidays']) . '<br /></span>';
1726
	}
1727
1728
	if (!empty($return['calendar_birthdays']))
1729
	{
1730
		echo '
1731
			<span class="birthday">' . $txt['birthdays_upcoming'] . '</span> ';
1732
1733
		foreach ($return['calendar_birthdays'] as $member)
1734
		{
1735
			echo '
1736
			<a href="', $scripturl, '?action=profile;u=', $member['id'], '">', $member['name'], isset($member['age']) ? ' (' . $member['age'] . ')' : '', '</a>', $member['is_last'] ? '' : ', ';
1737
		}
1738
1739
		echo '
1740
			<br />';
1741
	}
1742
1743
	if (!empty($return['calendar_events']))
1744
	{
1745
		echo '
1746
			<span class="event">' . $txt['events_upcoming'] . '</span> ';
1747
1748
		foreach ($return['calendar_events'] as $event)
1749
		{
1750
			if ($event['can_edit'])
1751
			{
1752
				echo '
1753
			<a href="' . $event['modify_href'] . '" class="moderation_link">*</a> ';
1754
			}
1755
1756
			echo '
1757
			' . $event['link'] . ($event['is_last'] ? '' : ', ');
1758
		}
1759
	}
1760
}
1761
1762
/**
1763
 * Show the latest news, with a template... by board.
1764
 *
1765
 * @param int|null $board The ID of the board to get the info from. Defaults to $board or
1766
 * $_GET['board'] if not set.
1767
 * @param int|null $limit How many items to show. Defaults to $_GET['limit'] or 5 if not set.
1768
 * @param int|null $start Start with the specified item. Defaults to $_GET['start'] or 0 if not set.
1769
 * @param int|null $length How many characters to show from each post. Defaults to $_GET['length']
1770
 * or 0 (no limit) if not set.
1771
 * @param string $preview = 'first' item to fetch, first or last
1772
 * @param string $output_method = 'echo' The output method. If 'echo', displays the news items,
1773
 * otherwise returns an array of info about them.
1774
 */
1775
function ssi_boardNews($board = null, $limit = null, $start = null, $length = null, $preview = 'first', $output_method = 'echo')
1776
{
1777
	global $scripturl, $txt, $settings, $modSettings;
1778
1779
	\ElkArte\Languages\Txt::load('Stats');
1780
1781
	$db = database();
1782
1783
	// Must be integers....
1784
	if ($limit === null)
1785
	{
1786
		$limit = isset($_GET['limit']) ? (int) $_GET['limit'] : 5;
1787
	}
1788
	else
1789
	{
1790
		$limit = (int) $limit;
1791
	}
1792
1793
	if ($start === null)
1794
	{
1795
		$start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
1796
	}
1797
	else
1798
	{
1799
		$start = (int) $start;
1800
	}
1801
1802
	if ($board !== null)
1803
	{
1804
		$board = (int) $board;
1805
	}
1806
	elseif (isset($_GET['board']))
1807
	{
1808
		$board = (int) $_GET['board'];
1809
	}
1810
1811
	if ($length === null)
1812
	{
1813
		$length = isset($_GET['length']) ? (int) $_GET['length'] : 500;
1814
	}
1815
	else
1816
	{
1817
		$length = (int) $length;
1818
	}
1819
1820
	$limit = max(0, $limit);
1821
	$start = max(0, $start);
1822
1823
	// Make sure guests can see this board.
1824
	$request = $db->query('', '
1825
		SELECT 
1826
			id_board
1827
		FROM {db_prefix}boards
1828
		WHERE ' . ($board === null ? '' : 'id_board = {int:current_board}
1829
			AND ') . 'FIND_IN_SET(-1, member_groups) != 0
1830
			AND redirect = {string:blank_redirect}
1831
		LIMIT 1',
1832
		array(
1833
			'current_board' => $board,
1834
			'blank_redirect' => '',
1835
		)
1836
	);
1837
	if ($request->num_rows() === 0)
1838
	{
1839
		if ($output_method === 'echo')
1840
		{
1841
			die($txt['ssi_no_guests']);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1842
		}
1843
1844
		return array();
1845
	}
1846
1847
	[$board] = $request->fetch_row();
1848
	$request->free_result();
1849
1850
	// Load the message icons - the usual suspects.
1851
	$icon_sources = new MessageTopicIcons(!empty($modSettings['messageIconChecks_enable']), $settings['theme_dir']);
1852
1853
	// Find the posts.
1854
	$indexOptions = array(
1855
		'only_approved' => true,
1856
		'include_sticky' => false,
1857
		'ascending' => false,
1858
		'include_avatars' => false,
1859
		'previews' => $length
1860
	);
1861
1862
	require_once(SUBSDIR . '/MessageIndex.subs.php');
1863
	$topics_info = messageIndexTopics($board, 0, $start, $limit, 'first_post', 't.id_topic', $indexOptions);
1864
1865
	if (empty($topics_info))
1866
	{
1867
		return false;
1868
	}
1869
1870
	$bbc_parser = ParserWrapper::instance();
1871
1872
	$return = array();
1873
	foreach ($topics_info as $row)
1874
	{
1875
		if (!isset($row[$preview . '_body']))
1876
		{
1877
			$preview = 'first';
1878
		}
1879
1880
		$row['body'] = $row[$preview . '_body'];
1881
		$row['subject'] = $row[$preview . '_subject'];
1882
		$row['id_msg'] = $row['id_' . $preview . '_msg'];
1883
		$row['icon'] = $row[$preview . '_icon'];
1884
		$row['id_member'] = $row[$preview . '_id_member'];
1885
		$row['smileys_enabled'] = $row[$preview . '_smileys'];
1886
		$row['poster_time'] = $row[$preview . '_poster_time'];
1887
		$row['poster_name'] = $row[$preview . '_display_name'];
1888
		$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
1889
1890
		$row['subject'] = censor($row['subject']);
1891
		$row['body'] = censor($row['body']);
1892
1893
		$return[] = array(
1894
			'id' => $row['id_topic'],
1895
			'message_id' => $row['id_msg'],
1896
			'icon' => '<img src="' . $icon_sources->getIconURL($row['icon']) . '" alt="' . $row['icon'] . '" />',
1897
			'subject' => $row['subject'],
1898
			'time' => standardTime($row['poster_time']),
1899
			'html_time' => htmlTime($row['poster_time']),
1900
			'timestamp' => forum_time(true, $row['poster_time']),
1901
			'body' => $row['body'],
1902
			'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
1903
			'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['num_replies'] . ' ' . ($row['num_replies'] == 1 ? $txt['ssi_comment'] : $txt['ssi_comments']) . '</a>',
1904
			'replies' => comma_format($row['num_replies']),
1905
			'comment_href' => empty($row['locked']) ? $scripturl . '?action=post;topic=' . $row['id_topic'] . '.' . $row['num_replies'] . ';last_msg=' . $row['id_last_msg'] : '',
1906
			'comment_link' => empty($row['locked']) ? '<a href="' . $scripturl . '?action=post;topic=' . $row['id_topic'] . '.' . $row['num_replies'] . ';last_msg=' . $row['id_last_msg'] . '">' . $txt['ssi_write_comment'] . '</a>' : '',
1907
			'new_comment' => empty($row['locked']) ? '<a href="' . $scripturl . '?action=post;topic=' . $row['id_topic'] . '.' . $row['num_replies'] . '">' . $txt['ssi_write_comment'] . '</a>' : '',
1908
			'poster' => array(
1909
				'id' => $row['id_member'],
1910
				'name' => $row['poster_name'],
1911
				'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
1912
				'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['poster_name'] . '</a>'
1913
			),
1914
			'locked' => !empty($row['locked']),
1915
			'is_last' => false
1916
		);
1917
	}
1918
1919
	$return[count($return) - 1]['is_last'] = true;
1920
1921
	if ($output_method !== 'echo')
1922
	{
1923
		return $return;
1924
	}
1925
1926
	foreach ($return as $news)
1927
	{
1928
		echo '
1929
			<div class="news_item">
1930
				<h3 class="news_header">
1931
					', $news['icon'], '
1932
					<a href="', $news['href'], '">', $news['subject'], '</a>
1933
				</h3>
1934
				<div class="news_timestamp">', $news['time'], ' ', $txt['by'], ' ', $news['poster']['link'], '</div>
1935
				<div class="news_body">', $news['body'], '</div>
1936
				', $news['link'], $news['locked'] ? '' : ' | ' . $news['comment_link'], '
1937
			</div>';
1938
1939
		if (!$news['is_last'])
1940
		{
1941
			echo '
1942
			<hr />';
1943
		}
1944
	}
1945
}
1946
1947
/**
1948
 * Show the most recent events.
1949
 *
1950
 * @param int $max_events The maximum number of events to return
1951
 * @param string $output_method = 'echo' The output method. If 'echo', displays the events,
1952
 * otherwise returns an array of info about them.
1953
 */
1954
function ssi_recentEvents($max_events = 7, $output_method = 'echo')
1955
{
1956
	global $modSettings, $txt;
1957
1958
	if (empty($modSettings['cal_enabled']) || !allowedTo('calendar_view'))
1959
	{
1960
		return false;
1961
	}
1962
1963
	require_once(SUBSDIR . '/Calendar.subs.php');
1964
1965
	// Find all events which are happening in the near future that the member can see.
1966
	$date = Util::strftime('%Y-%m-%d', forum_time(false));
1967
	$events = getEventRange($date, $date, true, $max_events);
1968
1969
	$return = array();
1970
	$duplicates = array();
1971
	foreach ($events as $date => $day_events)
1972
	{
1973
		foreach ($day_events as $row)
1974
		{
1975
			// Check if we've already come by an event linked to this same topic with the same title... and don't display it if we have.
1976
			if (!empty($duplicates[$row['title'] . $row['id_topic']]))
1977
			{
1978
				continue;
1979
			}
1980
1981
			$return[$date][] = $row;
1982
1983
			// Let's not show this one again, huh?
1984
			$duplicates[$row['title'] . $row['id_topic']] = true;
1985
		}
1986
	}
1987
1988
	foreach ($return as $mday => $array)
1989
	{
1990
		$return[$mday][count($array) - 1]['is_last'] = true;
1991
	}
1992
1993
	if ($output_method !== 'echo' || empty($return))
1994
	{
1995
		return $return;
1996
	}
1997
1998
	// Well the output method is echo.
1999
	echo '
2000
			<span class="event">' . $txt['events'] . '</span> ';
2001
2002
	foreach ($return as $array)
2003
	{
2004
		foreach ($array as $event)
2005
		{
2006
			if ($event['can_edit'])
2007
			{
2008
				echo '
2009
				<a href="' . $event['modify_href'] . '" class="moderation_link">*</a> ';
2010
			}
2011
2012
			echo '
2013
				' . $event['link'] . ($event['is_last'] ? '' : ', ');
2014
		}
2015
	}
2016
}
2017
2018
/**
2019
 * Check the passed id_member/password.
2020
 *
2021
 *  If $is_username is true, treats $id as a username.
2022
 *
2023
 * @param int|string|null $id The ID or username of a user
2024
 * @param string|null $password The password to check
2025
 * @param bool $is_username If true, treats $id as a username rather than a user ID
2026
 */
2027
function ssi_checkPassword($id = null, $password = null, $is_username = false)
2028
{
2029
	// If $id is null, this was most likely called from a query string and should do nothing.
2030
	if ($id === null)
2031
	{
2032
		return false;
2033
	}
2034
2035
	require_once(SUBSDIR . '/Auth.subs.php');
2036
2037
	$member = loadExistingMember($id, !$is_username);
2038
2039
	return validateLoginPassword($password, $member['passwd'], $member['member_name']) && $member['is_activated'] == 1;
2040
}
2041
2042
/**
2043
 * We want to show the recent attachments outside of the forum.
2044
 *
2045
 * @param int $num_attachments = 10 How many to return
2046
 * @param string[] $attachment_ext = array() Only show attachments with the specified extensions
2047
 * @param string $output_method = 'echo'  The output method. If 'echo', displays a table with links/info,
2048
 * otherwise returns an array with information about the attachments
2049
 */
2050
function ssi_recentAttachments($num_attachments = 10, $attachment_ext = array(), $output_method = 'echo')
2051
{
2052
	global $modSettings, $scripturl, $txt;
2053
2054
	// We want to make sure that we only get attachments for boards that we can see *if* any.
2055
	$attachments_boards = boardsAllowedTo('view_attachments');
2056
2057
	// No boards?  Adios amigo.
2058
	if (empty($attachments_boards))
2059
	{
2060
		return array();
2061
	}
2062
2063
	$db = database();
2064
2065
	// Is it an array?
2066
	if (!is_array($attachment_ext))
0 ignored issues
show
introduced by
The condition is_array($attachment_ext) is always true.
Loading history...
2067
	{
2068
		$attachment_ext = array($attachment_ext);
2069
	}
2070
2071
	// Lets build the query.
2072
	$attachments = array();
2073
	$db->fetchQuery('
2074
		SELECT
2075
			att.id_attach, att.id_msg, att.filename, COALESCE(att.size, 0) AS filesize, att.downloads, mem.id_member,
2076
			COALESCE(mem.real_name, m.poster_name) AS poster_name, m.id_topic, m.subject, t.id_board, m.poster_time,
2077
			att.width, att.height' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : ', COALESCE(thumb.id_attach, 0) AS id_thumb, thumb.width AS thumb_width, thumb.height AS thumb_height') . '
2078
		FROM {db_prefix}attachments AS att
2079
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = att.id_msg)
2080
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
2081
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)' . (empty($modSettings['attachmentShowImages']) || empty($modSettings['attachmentThumbnails']) ? '' : '
2082
			LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = att.id_thumb)') . '
2083
		WHERE att.attachment_type = 0' . ($attachments_boards === array(0) ? '' : '
2084
			AND m.id_board IN ({array_int:boards_can_see})') . (empty($attachment_ext) ? '' : '
2085
			AND att.fileext IN ({array_string:attachment_ext})') .
2086
			(!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
2087
			AND t.approved = {int:is_approved}
2088
			AND m.approved = {int:is_approved}
2089
			AND att.approved = {int:is_approved}') . '
2090
		ORDER BY att.id_attach DESC
2091
		LIMIT {int:num_attachments}',
2092
		array(
2093
			'boards_can_see' => $attachments_boards,
2094
			'attachment_ext' => $attachment_ext,
2095
			'num_attachments' => $num_attachments,
2096
			'is_approved' => 1,
2097
		)
2098
	)->fetch_callback(
2099
		static function ($row) use (&$attachments, $scripturl, $modSettings) {
2100
			// We have something.
2101
			$filename = preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($row['filename'], ENT_COMPAT, 'UTF-8'));
2102
2103
			// Is it an image?
2104
			$attachments[$row['id_attach']] = array(
2105
				'member' => array(
2106
					'id' => $row['id_member'],
2107
					'name' => $row['poster_name'],
2108
					'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['poster_name'] . '</a>',
2109
				),
2110
				'file' => array(
2111
					'filename' => $filename,
2112
					'filesize' => byte_format($row['filesize']),
2113
					'downloads' => $row['downloads'],
2114
					'href' => $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $row['id_attach'],
2115
					'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $row['id_attach'] . '"><i class="icon i-paperclip"><s>Attachement:</s></i> ' . $filename . '</a>',
2116
					'is_image' => !empty($row['width']) && !empty($row['height']) && !empty($modSettings['attachmentShowImages']),
2117
				),
2118
				'topic' => array(
2119
					'id' => $row['id_topic'],
2120
					'subject' => $row['subject'],
2121
					'href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'],
2122
					'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>',
2123
					'time' => standardTime($row['poster_time']),
2124
					'html_time' => htmlTime($row['poster_time']),
2125
					'timestamp' => forum_time(true, $row['poster_time']),
2126
				),
2127
			);
2128
2129
			// Images.
2130
			if ($attachments[$row['id_attach']]['file']['is_image'])
2131
			{
2132
				$id_thumb = empty($row['id_thumb']) ? $row['id_attach'] : $row['id_thumb'];
2133
				$attachments[$row['id_attach']]['file']['image'] = array(
2134
					'id' => $id_thumb,
2135
					'width' => $row['width'],
2136
					'height' => $row['height'],
2137
					'img' => '<img src="' . $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $row['id_attach'] . ';image" alt="' . $filename . '" />',
2138
					'thumb' => '<img src="' . $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $id_thumb . ';image" alt="' . $filename . '" />',
2139
					'href' => $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $id_thumb . ';image',
2140
					'link' => '<a href="' . $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $row['id_attach'] . ';image"><img src="' . $scripturl . '?action=dlattach;topic=' . $row['id_topic'] . '.0;attach=' . $id_thumb . ';image" alt="' . $filename . '" /></a>',
2141
				);
2142
			}
2143
		}
2144
	);
2145
2146
	// So you just want an array?  Here you can have it.
2147
	if ($output_method === 'array' || empty($attachments))
2148
	{
2149
		return $attachments;
2150
	}
2151
2152
	// Give them the default.
2153
	echo '
2154
		<table class="ssi_table">
2155
			<tr>
2156
				<th>', $txt['file'], '</th>
2157
				<th>', $txt['posted_by'], '</th>
2158
				<th class="centertext">', $txt['downloads'], '</th>
2159
				<th>', $txt['filesize'], '</th>
2160
			</tr>';
2161
2162
	foreach ($attachments as $attach)
2163
	{
2164
		echo '
2165
			<tr>
2166
				<td>', $attach['file']['link'], '</td>
2167
				<td>', $attach['member']['link'], '</td>
2168
				<td class="centertext">', $attach['file']['downloads'], '</td>
2169
				<td>', $attach['file']['filesize'], '</td>
2170
			</tr>';
2171
	}
2172
2173
	echo '
2174
		</table>';
2175
}
2176