recentlyLogged()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 2
dl 0
loc 19
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Moderation log helper functions.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
use ElkArte\Helper\Util;
18
use ElkArte\User;
19
20
/**
21
 * Get the number of mod log entries.
22
 * Callback for createList() in Modlog::action_log().
23
 *
24
 * @param string|null $query_string
25
 * @param array $query_params
26
 * @param int $log_type
27
 *
28
 * @return int
29
 */
30
function list_getModLogEntryCount($query_string = '', $query_params = [], $log_type = 1)
31
{
32 2
	$db = database();
33
34 2
	$modlog_query = allowedTo('admin_forum') || User::$info->mod_cache['bq'] == '1=1' ? '1=1' : (User::$info->mod_cache['bq'] == '0=1' ? 'lm.id_board = 0 AND lm.id_topic = 0' : (strtr(User::$info->mod_cache['bq'], ['id_board' => 'b.id_board']) . ' AND ' . strtr(User::$info->mod_cache['bq'], ['id_board' => 't.id_board'])));
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
35
36 2
	$request = $db->query('', '
37
		SELECT 
38
			COUNT(*)
39
		FROM {db_prefix}log_actions AS lm
40
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lm.id_member)
41
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END)
42
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lm.id_board)
43
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lm.id_topic)
44
		WHERE id_log = {int:log_type}
45 2
			AND {raw:modlog_query}'	. (!empty($query_string) ? '
46 2
			AND ' . $query_string : ''),
47 2
		array_merge($query_params, [
48 2
			'reg_group_id' => 0,
49 2
			'log_type' => $log_type,
50 2
			'modlog_query' => $modlog_query,
51
		])
52
	);
53 2
	list ($entry_count) = $request->fetch_row();
54 2
	$request->free_result();
55
56 2
	return $entry_count;
57
}
58
59
/**
60
 * Gets the moderation log entries that match the specified parameters.
61
 * Callback for createList() in Modlog::action_log().
62
 *
63
 * @param int $start The item to start with (for pagination purposes)
64
 * @param int $items_per_page The number of items to show per page
65
 * @param string $sort A string indicating how to sort the results
66
 * @param string|null $query_string
67
 * @param array $query_params
68
 * @param int $log_type
69
 *
70
 * @return array
71
 */
72
function list_getModLogEntries($start, $items_per_page, $sort, $query_string = '', $query_params = [], $log_type = 1)
73
{
74
	global $context, $scripturl, $txt;
75 2
76
	$db = database();
77 2
78
	$modlog_query = allowedTo('admin_forum') || User::$info->mod_cache['bq'] == '1=1' ? '1=1' : (User::$info->mod_cache['bq'] == '0=1' ? 'lm.id_board = 0 AND lm.id_topic = 0' : (strtr(User::$info->mod_cache['bq'], ['id_board' => 'b.id_board']) . ' AND ' . strtr(User::$info->mod_cache['bq'], ['id_board' => 't.id_board'])));
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
79 2
80
	// Do a little bit of self protection.
81
	if (!isset($context['hoursdisable']))
82 2
	{
83
		$context['hoursdisable'] = 24;
84 2
	}
85
86
	// Can they see the IP address?
87
	$seeIP = allowedTo('moderate_forum');
88 2
89
	// Here we have the query getting the log details.
90
	$request = $db->fetchQuery('
91 2
		SELECT
92
			lm.id_action, lm.id_member, lm.ip, lm.log_time, lm.action, lm.id_board, lm.id_topic, lm.id_msg, lm.extra,
93
			mem.real_name, mg.group_name
94
		FROM {db_prefix}log_actions AS lm
95
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lm.id_member)
96
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:reg_group_id} THEN mem.id_post_group ELSE mem.id_group END)
97
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lm.id_board)
98
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lm.id_topic)
99
			WHERE id_log = {int:log_type}
100
				AND {raw:modlog_query}'
101
		. (!empty($query_string) ? '
102 2
				AND ' . $query_string : '') . '
103 2
		ORDER BY ' . $sort . '
104 2
		LIMIT ' . $items_per_page . '  OFFSET ' . $start,
105 2
		array_merge($query_params, [
106 2
			'reg_group_id' => 0,
107 2
			'log_type' => $log_type,
108 2
			'modlog_query' => $modlog_query,
109 2
		])
110
	);
111
112
	// Arrays for decoding objects into.
113
	$topics = [];
114 2
	$boards = [];
115 2
	$members = [];
116 2
	$messages = [];
117 2
	$entries = [];
118 2
	while (($row = $request->fetch_assoc()))
119 2
	{
120
		$row['extra'] = Util::unserialize($row['extra']);
121
122
		// Corrupt?
123
		$row['extra'] = is_array($row['extra']) ? $row['extra'] : [];
124
125
		// Add on some of the column stuff info
126
		if (!empty($row['id_board']))
127
		{
128
			if ($row['action'] == 'move')
129
			{
130
				$row['extra']['board_to'] = $row['id_board'];
131
			}
132
			else
133
			{
134
				$row['extra']['board'] = $row['id_board'];
135
			}
136
		}
137
138
		if (!empty($row['id_topic']))
139
		{
140
			$row['extra']['topic'] = $row['id_topic'];
141
		}
142
143
		if (!empty($row['id_msg']))
144
		{
145
			$row['extra']['message'] = $row['id_msg'];
146
		}
147
148
		// Is this associated with a topic?
149
		if (isset($row['extra']['topic']))
150
		{
151
			$topics[(int) $row['extra']['topic']][] = $row['id_action'];
152
		}
153
154
		if (isset($row['extra']['new_topic']))
155
		{
156
			$topics[(int) $row['extra']['new_topic']][] = $row['id_action'];
157
		}
158
159
		// How about a member?
160
		if (isset($row['extra']['member']))
161
		{
162
			// Guests don't have names!
163
			if (empty($row['extra']['member']))
164
			{
165
				$row['extra']['member'] = $txt['modlog_parameter_guest'];
166
			}
167
			else
168
			{
169
				// Try to find it...
170
				$members[(int) $row['extra']['member']][] = $row['id_action'];
171
			}
172
		}
173
174
		// Associated with a board?
175
		if (isset($row['extra']['board_to']))
176
		{
177
			$boards[(int) $row['extra']['board_to']][] = $row['id_action'];
178
		}
179
180
		if (isset($row['extra']['board_from']))
181
		{
182
			$boards[(int) $row['extra']['board_from']][] = $row['id_action'];
183
		}
184
185
		if (isset($row['extra']['board']))
186
		{
187
			$boards[(int) $row['extra']['board']][] = $row['id_action'];
188
		}
189
190
		// A message?
191
		if (isset($row['extra']['message']))
192
		{
193
			$messages[(int) $row['extra']['message']][] = $row['id_action'];
194
		}
195
196
		// IP Info?
197
		if (isset($row['extra']['ip_range']))
198
		{
199
			if ($seeIP)
200
			{
201
				$row['extra']['ip_range'] = '<a href="' . $scripturl . '?action=trackip;searchip=' . $row['extra']['ip_range'] . '">' . $row['extra']['ip_range'] . '</a>';
202
			}
203
			else
204
			{
205
				$row['extra']['ip_range'] = $txt['logged'];
206
			}
207
		}
208
209
		// Email?
210
		if (isset($row['extra']['email']))
211
		{
212
			$row['extra']['email'] = '<a href="mailto:' . $row['extra']['email'] . '">' . $row['extra']['email'] . '</a>';
213
		}
214
215
		// Bans are complex.
216
		if ($row['action'] == 'ban')
217
		{
218
			if (!isset($row['extra']['new']) || $row['extra']['new'] == 1)
219
			{
220
				$row['action_text'] = $txt['modlog_ac_ban'];
221
			}
222
			elseif ($row['extra']['new'] == 0)
223
			{
224
				$row['action_text'] = $txt['modlog_ac_ban_update'];
225
			}
226
			else
227
			{
228
				$row['action_text'] = $txt['modlog_ac_ban_remove'];
229
			}
230
231
			foreach (['member', 'email', 'ip_range', 'hostname'] as $type)
232
			{
233
				if (isset($row['extra'][$type]))
234
				{
235
					$row['action_text'] .= $txt['modlog_ac_ban_trigger_' . $type];
236
				}
237
			}
238
		}
239
240
		// The array to go to the template. Note here that action is set to a "default" value of the action doesn't match anything in the descriptions. Allows easy adding of logging events with basic details.
241
		$entries[$row['id_action']] = [
242
			'id' => $row['id_action'],
243
			'ip' => $seeIP ? $row['ip'] : $txt['logged'],
244
			'position' => empty($row['real_name']) && empty($row['group_name']) ? $txt['guest'] : $row['group_name'],
245
			'moderator_link' => $row['id_member'] && !empty($row['real_name']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' : (empty($row['real_name']) ? ($txt['guest'] . (!empty($row['extra']['member_acted']) ? ' (' . $row['extra']['member_acted'] . ')' : '')) : $row['real_name']),
246
			'time' => standardTime($row['log_time']),
247
			'html_time' => htmlTime($row['log_time']),
248
			'timestamp' => forum_time(true, $row['log_time']),
249
			'editable' => time() > $row['log_time'] + $context['hoursdisable'] * 3600,
250
			'extra' => $row['extra'],
251
			'action' => $row['action'],
252
			'action_text' => $row['action_text'] ?? '',
253
		];
254
	}
255
	$request->free_result();
256 2
257
	if (!empty($boards))
258 2
	{
259
		require_once(SUBSDIR . '/Boards.subs.php');
260
		$boards_info = fetchBoardsInfo(['boards' => array_keys($boards)]);
261
262
		foreach ($boards_info as $row)
263
		{
264
			foreach ($boards[$row['id_board']] as $action)
265
			{
266
				// Make the board number into a link - dealing with moving too.
267
				if (isset($entries[$action]['extra']['board_to']) && $entries[$action]['extra']['board_to'] == $row['id_board'])
268
				{
269
					$entries[$action]['extra']['board_to'] = '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>';
270
				}
271
				elseif (isset($entries[$action]['extra']['board_from']) && $entries[$action]['extra']['board_from'] == $row['id_board'])
272
				{
273
					$entries[$action]['extra']['board_from'] = '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>';
274
				}
275
				elseif (isset($entries[$action]['extra']['board']) && $entries[$action]['extra']['board'] == $row['id_board'])
276
				{
277
					$entries[$action]['extra']['board'] = '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>';
278
				}
279
			}
280
		}
281
	}
282
283
	if (!empty($topics))
284 2
	{
285
		$request = $db->fetchQuery('
286
			SELECT 
287
				ms.subject, t.id_topic
288
			FROM {db_prefix}topics AS t
289
				INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
290
			WHERE t.id_topic IN ({array_int:topic_list})
291
			LIMIT ' . count(array_keys($topics)),
292
			[
293
				'topic_list' => array_keys($topics),
294
			]
295
		);
296
		while (($row = $request->fetch_assoc()))
297
		{
298
			foreach ($topics[$row['id_topic']] as $action)
299
			{
300
				$this_action = &$entries[$action];
301
302
				// This isn't used in the current theme.
303
				$this_action['topic'] = [
304
					'id' => $row['id_topic'],
305
					'subject' => $row['subject'],
306
					'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
307
					'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>'
308
				];
309
310
				// Make the topic number into a link - dealing with splitting too.
311
				if (isset($this_action['extra']['topic']) && $this_action['extra']['topic'] == $row['id_topic'])
312
				{
313
					$this_action['extra']['topic'] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.' . (isset($this_action['extra']['message']) ? 'msg' . $this_action['extra']['message'] . '#msg' . $this_action['extra']['message'] : '0') . '">' . $row['subject'] . '</a>';
314
				}
315
				elseif (isset($this_action['extra']['new_topic']) && $this_action['extra']['new_topic'] == $row['id_topic'])
316
				{
317
					$this_action['extra']['new_topic'] = '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.' . (isset($this_action['extra']['message']) ? 'msg' . $this_action['extra']['message'] . '#msg' . $this_action['extra']['message'] : '0') . '">' . $row['subject'] . '</a>';
318
				}
319
			}
320
		}
321
		$request->free_result();
322
	}
323
324
	if (!empty($messages))
325 2
	{
326
		$request = $db->query('', '
327
			SELECT 
328
				id_msg, subject
329
			FROM {db_prefix}messages
330
			WHERE id_msg IN ({array_int:message_list})
331
			LIMIT ' . count(array_keys($messages)),
332
			[
333
				'message_list' => array_keys($messages),
334
			]
335
		);
336
		while (($row = $request->fetch_assoc()))
337
		{
338
			foreach ($messages[$row['id_msg']] as $action)
339
			{
340
				$this_action = &$entries[$action];
341
342
				// This isn't used in the current theme.
343
				$this_action['message'] = [
344
					'id' => $row['id_msg'],
345
					'subject' => $row['subject'],
346
					'href' => $scripturl . '?msg=' . $row['id_msg'],
347
					'link' => '<a href="' . $scripturl . '?msg=' . $row['id_msg'] . '">' . $row['subject'] . '</a>',
348
				];
349
350
				// Make the message number into a link.
351
				if (isset($this_action['extra']['message']) && $this_action['extra']['message'] == $row['id_msg'])
352
				{
353
					$this_action['extra']['message'] = '<a href="' . $scripturl . '?msg=' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
354
				}
355
			}
356
		}
357
		$request->free_result();
358
	}
359
360
	if (!empty($members))
361 2
	{
362
		require_once(SUBSDIR . '/Members.subs.php');
363
364
		// Get the latest activated member's display name.
365
		$result = getBasicMemberData(array_keys($members));
366
		foreach ($result as $row)
367
		{
368
			foreach ($members[$row['id_member']] as $action)
369
			{
370
				// Not used currently.
371
				$entries[$action]['member'] = [
372
					'id' => $row['id_member'],
373
					'name' => $row['real_name'],
374
					'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
375
					'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>'
376
				];
377
378
				// Make the member number into a name.
379
				$entries[$action]['extra']['member'] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>';
380
			}
381
		}
382
	}
383
384
	// Do some formatting of the action string.
385
	$callback = new ModLogEntriesReplacement();
386 2
	$callback->entries = $entries;
387 2
	foreach ($entries as $k => $entry)
388 2
	{
389
		// Make any message info links so its easier to go find that message.
390
		if (isset($entry['extra']['message']) && (empty($entry['message']) || empty($entry['message']['id'])))
391
		{
392
			$entries[$k]['extra']['message'] = '<a href="' . $scripturl . '?msg=' . $entry['extra']['message'] . '">' . $entry['extra']['message'] . '</a>';
393
		}
394
395
		// Mark up any deleted members, topics and boards.
396
		foreach (['board', 'board_from', 'board_to', 'member', 'topic', 'new_topic'] as $type)
397
		{
398
			if (!empty($entry['extra'][$type]) && is_numeric($entry['extra'][$type]))
399
			{
400
				$entries[$k]['extra'][$type] = sprintf($txt['modlog_id'], $entry['extra'][$type]);
401
			}
402
		}
403
404
		if (empty($entries[$k]['action_text']))
405
		{
406
			$entries[$k]['action_text'] = $txt['modlog_ac_' . $entry['action']] ?? $entry['action'];
407
		}
408
409
		$callback->key = $k;
410
		$entries[$k]['action_text'] = preg_replace_callback('~\{([A-Za-z\d_]+)\}~i', [$callback, 'callback'], $entries[$k]['action_text']);
411
	}
412
413
	// Back we go!
414
	return $entries;
415 2
}
416
417
/**
418
 * Mod Log Replacement Callback.
419
 *
420
 * Our callback that does the actual replacement.
421
 *
422
 */
423
class ModLogEntriesReplacement
424
{
425
	public $entries;
426
	public $key;
427
428
	/**
429
	 * Matching function to return the value in the callback
430
	 *
431
	 * @param string[] $matches
432
	 *
433
	 * @return string
434
	 */
435
	public function callback($matches): string
436
	{
437
		return $this->entries[$this->key]['extra'][$matches[1]] ?? '';
438
	}
439
}
440
441
/**
442
 * Delete logged actions.
443
 *
444
 * @param int $id_log
445
 * @param int $time
446
 * @param string[]|null $delete
447
 */
448
function deleteLogAction($id_log, $time, $delete = null)
449
{
450
	$db = database();
451
452
	$db->query('', '
453
		DELETE FROM {db_prefix}log_actions
454
		WHERE id_log = {int:moderate_log}
455
			' . (isset($delete) ? 'AND id_action IN ({array_string:delete_actions})' : '') . '
456
			AND log_time < {int:twenty_four_hours_wait}',
457
		[
458
			'twenty_four_hours_wait' => time() - $time * 3600,
459
			'delete_actions' => !is_null($delete) ? array_unique($delete) : null,
460
			'moderate_log' => $id_log,
461
		]
462
	);
463
}
464
465
/**
466
 * Checks if an action has been performed "recently"
467
 *
468
 * @param string $action Name of the action
469
 * @param int $time Timeframe since the last time the action has been performed
470
 *
471
 * @return bool
472
 */
473
function recentlyLogged($action, $time = 60)
474
{
475
	$db = database();
476
477
	$request = $db->query('', '
478
		SELECT 
479
			COUNT(*)
480
		FROM {db_prefix}log_actions
481
		WHERE action = {string:action}
482
			AND log_time >= {int:last_logged}',
483
		[
484
			'action' => $action,
485
			'last_logged' => time() - $time,
486
		]
487
	);
488
	list ($present) = $request->fetch_row();
489
	$request->free_result();
490
491
	return !empty($present);
492
}
493