Issues (1686)

sources/subs/Messages.subs.php (12 issues)

1
<?php
2
3
/**
4
 * This file contains functions for dealing with messages.
5
 * Low-level functions, i.e. database operations needed to perform.
6
 * These functions (probably) do NOT make permissions checks. (they assume
7
 * those were already made).
8
 *
9
 * @package   ElkArte Forum
10
 * @copyright ElkArte Forum contributors
11
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
12
 *
13
 * This file contains code covered by:
14
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
15
 *
16
 * @version 2.0 dev
17
 *
18
 */
19
20
use ElkArte\MessagesDelete;
21
use ElkArte\User;
22
23
/**
24
 * Get message and attachments data, for a message ID. The function returns the data in an array.
25
 *
26
 * What it does:
27
 * - 'message' => array with all message data, body, subject, etc
28
 * - 'attachment_stuff' => array with attachments ordered by attachment id as keys
29
 *
30
 * @param int $id_msg
31
 * @param int $id_topic = 0
32
 * @param int $attachment_type = 0
33
 *
34
 * @return array|bool
35
 */
36
function messageDetails($id_msg, $id_topic = 0, $attachment_type = 0)
37
{
38
	global $modSettings;
39
40
	$db = database();
41
42
	if (empty($id_msg))
43
	{
44
		return false;
45
	}
46
47
	$message_data = $db->fetchQuery('
48
		SELECT
49
			m.id_member, m.modified_time, m.modified_name, m.smileys_enabled, m.body,
50
			m.poster_name, m.poster_email, m.subject, m.icon, m.approved,
51
			COALESCE(a.size, -1) AS filesize, a.filename, a.id_attach,
52
			a.approved AS attachment_approved, t.id_member_started AS id_member_poster,
53
			m.poster_time, log.id_action
54
		FROM {db_prefix}messages AS m
55
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
56
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_msg = m.id_msg AND a.attachment_type = {int:attachment_type})
57
			LEFT JOIN {db_prefix}log_actions AS log ON (m.id_topic = log.id_topic AND log.action = {string:announce_action})
58
		WHERE m.id_msg = {int:id_msg}
59
			AND m.id_topic = {int:current_topic}',
60
		array(
61
			'current_topic' => $id_topic,
62
			'attachment_type' => $attachment_type,
63
			'id_msg' => $id_msg,
64
			'announce_action' => 'announce_topic',
65
		)
66
	)->fetch_all();
67
68
	// The message they were trying to edit was most likely deleted.
69
	if (empty($message_data))
70
	{
71
		return false;
72
	}
73
74
	$temp = [];
75
76
	// Attachments enabled, then return the details for each
77
	if (!empty($modSettings['attachmentEnable']))
78
	{
79
		foreach ($message_data as $attachment)
80
		{
81
			if ($attachment['filesize'] >= 0)
82
			{
83
				$temp[$attachment['id_attach']] = $attachment;
84
			}
85
		}
86
87
		ksort($temp);
88
	}
89
90
	return array('message' => $message_data[0], 'attachment_stuff' => $temp);
91
}
92
93
/**
94
 * Get some basic info of a certain message
95
 * Will use query_see_board unless $override_permissions is set to true
96
 * Will return additional topic information if $detailed is set to true
97
 * Returns an associative array of the results or false on error
98
 *
99
 * @param int $id_msg
100
 * @param bool $override_permissions
101
 * @param bool $detailed
102
 * @param bool $approved only return approved messages
103
 *
104 14
 * @return mixed[]|false array of message details or false if no message found.
105
 */
106 14
function basicMessageInfo($id_msg, $override_permissions = false, $detailed = false, $approved = true)
107
{
108 14
	global $modSettings;
109
110 2
	$db = database();
111
112
	if (empty($id_msg))
113 12
	{
114
		return false;
115
	}
116 12
117 12
	$request = $db->fetchQuery('
118 12
		SELECT
119 12
			m.id_member, m.id_topic, m.id_board, m.id_msg, m.body, m.subject,
120 12
			m.poster_name, m.poster_email, m.poster_time, m.approved' . ($detailed === false ? '' : ',
121 12
			t.id_first_msg, t.num_replies, t.unapproved_posts, t.id_last_msg, t.id_member_started, t.locked, t.approved AS topic_approved') . '
122 12
		FROM {db_prefix}messages AS m' . ($override_permissions === true ? '' : '
123
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})') . ($detailed === false ? '' : '
124
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)') . '
125 12
		WHERE id_msg = {int:message}' . (empty($modSettings['postmod_active']) || allowedTo('approve_posts') || $approved === false ? '' : '
126
			AND m.approved = 1') . '
127
		LIMIT 1',
128 12
		array(
129
			'message' => $id_msg,
130 12
		)
131
	);
132
	$messageInfo = $request->fetch_assoc();
133
134
	return empty($messageInfo) ? false : $messageInfo;
135
}
136
137
/**
138
 * Get some basic info of a certain message, good to create a quote.
139
 * Very similar to many other queries, though slightly different.
140
 * Uses {query_see_board} and the 'moderate_board' permission
141
 *
142
 * @param int $id_msg
143
 * @param bool $modify
144
 *
145
 * @return array
146
 * @todo why it doesn't take into account post moderation?
147
 */
148
function quoteMessageInfo($id_msg, $modify)
149
{
150
	if (empty($id_msg))
151
	{
152
		return [];
153
	}
154
155
	$db = database();
156
157
	require_once(SUBSDIR . '/Post.subs.php');
158
159
	$moderate_boards = boardsAllowedTo('moderate_board');
160
161
	$request = $db->fetchQuery('
162
		SELECT 
163
			COALESCE(mem.real_name, m.poster_name) AS poster_name, m.poster_time, m.body, m.id_topic, m.subject,
164
			m.id_board, m.id_member, m.approved
165
		FROM {db_prefix}messages AS m
166
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
167
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
168
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
169
		WHERE m.id_msg = {int:id_msg}' . ($modify || (!empty($moderate_boards) && $moderate_boards[0] == 0) ? '' : '
170
			AND (t.locked = {int:not_locked}' . (empty($moderate_boards) ? '' : ' OR b.id_board IN ({array_int:moderation_board_list})') . ')') . '
171
		LIMIT 1',
172
		array(
173
			'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...
174
			'moderation_board_list' => $moderate_boards,
175
			'id_msg' => $id_msg,
176
			'not_locked' => 0,
177
		)
178
	);
179
180
	return $request->fetch_assoc();
181
}
182
183
/**
184
 * Checks permissions to modify a message.
185
 * This function will give a fatal error if the current user
186
 * doesn't have permissions to modify the message.
187
 *
188
 * @param int $message
189
 *
190
 * @return array|bool
191
 * @throws \ElkArte\Exceptions\Exception modify_post_time_passed
192
 */
193
function checkMessagePermissions($message)
194
{
195
	global $modSettings, $context;
196
197
	if ($message['id_member'] == User::$info->id && !allowedTo('modify_any'))
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...
198
	{
199
		// Give an extra five minutes over the disable time threshold, so they can type - assuming the post is public.
200
		if ($message['approved'] && !empty($modSettings['edit_disable_time']) && $message['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time())
201
		{
202
			throw new \ElkArte\Exceptions\Exception('modify_post_time_passed', false);
203
		}
204
		elseif ($message['id_member_poster'] == User::$info->id && !allowedTo('modify_own'))
205
		{
206
			isAllowedTo('modify_replies');
207
		}
208
		else
209
		{
210
			isAllowedTo('modify_own');
211
		}
212
	}
213
	elseif ($message['id_member_poster'] == User::$info->id && !allowedTo('modify_any'))
214
	{
215
		isAllowedTo('modify_replies');
216
	}
217
	else
218
	{
219
		isAllowedTo('modify_any');
220
	}
221
222
	if ($context['can_announce'] && !empty($message['id_action']))
223
	{
224
		return array('topic_already_announced');
225
	}
226
227
	return false;
228
}
229
230
/**
231
 * Prepare context for a message.
232
 *
233
 * What it does:
234
 * - Loads data from messageDetails into $context array
235
 *
236
 * @param mixed[] $message the message array
237
 */
238
function prepareMessageContext($message)
239
{
240
	global $context, $txt;
241
242
	// Load up 'em attachments!
243
	foreach ($message['attachment_stuff'] as $attachment)
244
	{
245
		$context['attachments']['current'][] = array(
246
			'name' => htmlspecialchars($attachment['filename'], ENT_COMPAT, 'UTF-8'),
247
			'size' => $attachment['filesize'],
248
			'id' => $attachment['id_attach'],
249
			'approved' => $attachment['attachment_approved'],
250
		);
251
	}
252
253
	// Allow moderators to change names....
254
	if (allowedTo('moderate_forum') && empty($message['message']['id_member']))
255
	{
256
		$context['name'] = htmlspecialchars($message['message']['poster_name'], ENT_COMPAT, 'UTF-8');
257
		$context['email'] = htmlspecialchars($message['message']['poster_email'], ENT_COMPAT, 'UTF-8');
258
	}
259
260
	// When was it last modified?
261
	if (!empty($message['message']['modified_time']))
262
	{
263
		$context['last_modified'] = standardTime($message['message']['modified_time']);
264
		$context['last_modified_text'] = sprintf($txt['last_edit_by'], $context['last_modified'], $message['message']['modified_name']);
265
	}
266
267
	// Show an "approve" box if the user can approve it, and the message isn't approved.
268
	if (!$message['message']['approved'] && !$context['show_approval'])
269
	{
270
		$context['show_approval'] = allowedTo('approve_posts');
271
	}
272
}
273
274
/**
275
 * This function removes all the messages of a certain user that are *not*
276
 * first messages of a topic
277
 *
278
 * @param int $memID The member id
279
 */
280
function removeNonTopicMessages($memID)
281
{
282
	$db = database();
283
284
	$db->fetchQuery('
285
		SELECT 
286
			m.id_msg
287
		FROM {db_prefix}messages AS m
288
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic
289
				AND t.id_first_msg != m.id_msg)
290
		WHERE m.id_member = {int:selected_member}',
291
		array(
292
			'selected_member' => $memID,
293
		)
294
	)->fetch_callback(
295
		function ($row) {
296
			// This could take a while... but ya know it's gonna be worth it in the end.
297
			detectServer()->setTimeLimit(300);
298
			removeMessage($row['id_msg']);
299
		}
300
	);
301
}
302
303
/**
304
 * Remove a specific message.
305
 * !! This includes permission checks.
306
 *
307
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
308
 * - uses boardurl to determine these two things.
309
 *
310
 * @param int $message The message id
311
 * @param bool $decreasePostCount if true users' post count will be reduced
312
 */
313
function removeMessage($message, $decreasePostCount = true)
314
{
315
	global $modSettings;
316
317
	$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
318
	$remover->removeMessage($message, $decreasePostCount, true);
319
}
320
321
/**
322
 * This function deals with the topic associated to a message.
323
 * It allows retrieving or updating the topic to which the message belongs.
324
 *
325
 * If $topicID is not passed, the current topic ID of the message is returned.
326
 * If $topicID is passed, the message is updated to point to the new topic.
327
 *
328
 * @param int $msg_id message ID
329
 * @param int|null $topicID = null topic ID, if null is passed the ID of the topic is retrieved and returned
330
 * @return int|false int topic ID if any, or false
331
 */
332
function associatedTopic($msg_id, $topicID = null)
333
{
334
	$db = database();
335
336
	if ($topicID === null)
337
	{
338
		$request = $db->query('', '
339
			SELECT 
340
				id_topic
341
			FROM {db_prefix}messages
342
			WHERE id_msg = {int:msg}',
343
			array(
344
				'msg' => $msg_id,
345
			)
346
		);
347
		if ($request->num_rows() !== 1)
348
		{
349
			$topic = false;
350
		}
351
		else
352
		{
353
			list ($topic) = $request->fetch_row();
354
		}
355
		$request->free_result();
356
357
		return $topic;
358
	}
359
	else
360
	{
361
		$db->query('', '
362
			UPDATE {db_prefix}messages
363
			SET 
364
				id_topic = {int:topic}
365
			WHERE id_msg = {int:msg}',
366
			array(
367
				'msg' => $msg_id,
368
				'topic' => $topicID,
369
			)
370
		);
371
	}
372
}
373
374
/**
375
 * Small function that simply verifies if the current
376
 * user can access a specific message
377
 *
378
 * @param int $id_msg a message id
379
 * @param bool $check_approval if true messages are checked for approval (default true)
380
 * @return bool
381
 */
382
function canAccessMessage($id_msg, $check_approval = true)
383 2
{
384
	$message_info = basicMessageInfo($id_msg);
385
386 2
	// Do we even have a message to speak of?
387
	if (empty($message_info))
388
	{
389
		return false;
390
	}
391
392 2
	// Check for approval status?
393
	if ($check_approval)
394
	{
395 2
		// The user can access this message if it's approved or they're owner
396
		return (!empty($message_info['approved']) || $message_info['id_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...
397
	}
398
399
	// Otherwise, nope.
400
	return false;
401
}
402
403
/**
404
 * Advance message pointer in a topic.
405
 * (in either direction)
406
 * This function is used by previousMessage() and nextMessage().
407
 * The boolean parameter $next determines the direction.
408
 *
409
 * @param int $id_msg origin message id
410
 * @param int $id_topic topic
411
 * @param bool $next = true if true, it increases the pointer, otherwise it decreases it
412
 *
413
 * @return int
414
 */
415
function messagePointer($id_msg, $id_topic, $next = true)
416
{
417
	$db = database();
418
419
	$result = $db->query('', '
420
		SELECT ' . ($next ? 'MIN(id_msg)' : 'MAX(id_msg)') . '
421
		FROM {db_prefix}messages
422
		WHERE id_topic = {int:current_topic}
423
			AND id_msg {raw:strictly} {int:topic_msg_id}',
424
		array(
425
			'current_topic' => $id_topic,
426
			'topic_msg_id' => $id_msg,
427
			'strictly' => $next ? '>' : '<'
428
		)
429
	);
430
	list ($msg) = $result->fetch_row();
431
	$result->free_result();
432
433
	return $msg;
434
}
435
436
/**
437
 * Get previous message from where we were in the topic.
438
 *
439
 * @param int $id_msg
440
 * @param int $id_topic
441
 *
442
 * @return int
443
 */
444
function previousMessage($id_msg, $id_topic)
445
{
446
	return messagePointer($id_msg, $id_topic, false);
447
}
448
449
/**
450
 * Get next message from where we were in the topic.
451
 *
452
 * @param int $id_msg
453
 * @param int $id_topic
454
 *
455
 * @return int
456
 */
457
function nextMessage($id_msg, $id_topic)
458
{
459
	return messagePointer($id_msg, $id_topic);
460
}
461
462
/**
463
 * Retrieve the message id/s at a certain position in a topic
464
 *
465
 * @param int $start the offset of the message/s
466
 * @param int $id_topic the id of the topic
467
 * @param mixed[] $params an (optional) array of params, includes:
468
 *      - 'not_in' => array - of messages to exclude
469
 *      - 'include' => array - of messages to explicitly include
470
 *      - 'only_approved' => true/false - include or exclude the unapproved messages
471
 *      - 'limit' => mixed - the number of values to return (if false, no limits applied)
472
 *
473
 * @return array
474
 * @todo very similar to selectMessages in Topics.subs.php
475
 */
476
function messageAt($start, $id_topic, $params = array())
477
{
478
	$db = database();
479
480
	$params = array_merge(
481
	// Defaults
482
		array(
483
			'not_in' => false,
484
			'include' => false,
485
			'only_approved' => false,
486
			'limit' => 1,
487
		),
488
		// Passed arguments
489
		$params,
490
		// Others
491
		array(
492
			'current_topic' => $id_topic,
493
			'start' => $start,
494
			'is_approved' => 1,
495
		)
496
	);
497
498
	$msg = array();
499
	$db->fetchQuery('
500
		SELECT 
501
			id_msg
502
		FROM {db_prefix}messages
503
		WHERE id_topic = {int:current_topic}' . (!$params['include'] ? '' : '
504
			AND id_msg IN ({array_int:include})') . (!$params['not_in'] ? '' : '
505
			AND id_msg NOT IN ({array_int:not_in})') . (!$params['only_approved'] ? '' : '
506
			AND approved = {int:is_approved}') . '
507
		ORDER BY id_msg DESC' . ($params['limit'] === false ? '' : '
508
		LIMIT {int:limit} OFFSET {int:start} '),
509
		$params
510
	)->fetch_callback(
511
		function ($row) use (&$msg) {
512
			$msg[] = $row['id_msg'];
513
		}
514
	);
515
516
	return $msg;
517
}
518
519
/**
520
 * Finds an open report for a certain message if it exists and increase the
521
 * number of reports for that message, otherwise it creates one
522
 *
523
 * @param mixed[] $message array of several message details (id_msg, id_topic, etc.)
524
 * @param string $poster_comment the comment made by the reporter
525
 *
526
 * @return bool
527
 */
528
function recordReport($message, $poster_comment)
529
{
530
	$db = database();
531
532
	$request = $db->query('', '
533
		SELECT 
534 2
			id_report, ignore_all
535
		FROM {db_prefix}log_reported
536 2
		WHERE id_msg = {int:id_msg}
537
			AND type = {string:type}
538
			AND (closed = {int:not_closed} OR ignore_all = {int:ignored})
539
		ORDER BY ignore_all DESC',
540
		array(
541
			'id_msg' => $message['id_msg'],
542
			'type' => $message['type'] ?? 'msg',
543
			'not_closed' => 0,
544
			'ignored' => 1,
545 2
		)
546 2
	);
547 2
	if ($request->num_rows() !== 0)
548 2
	{
549
		list ($id_report, $ignore_all) = $request->fetch_row();
550
	}
551 2
	$request->free_result();
552
553
	if (!empty($ignore_all))
554
	{
555 2
		return false;
556
	}
557 2
558
	// Already reported? My god, we could be dealing with a real rogue here...
559
	if (!empty($id_report))
560
	{
561
		$db->query('', '
562
			UPDATE {db_prefix}log_reported
563 2
			SET 
564
				num_reports = num_reports + 1, time_updated = {int:current_time}
565
			WHERE id_report = {int:id_report}',
566
			array(
567
				'current_time' => time(),
568
				'id_report' => $id_report,
569
			)
570
		);
571
	}
572
	// Otherwise, we shall make one!
573
	else
574
	{
575
		if (empty($message['real_name']))
576
		{
577
			$message['real_name'] = $message['poster_name'];
578
		}
579 2
580
		$db->insert('',
581 2
			'{db_prefix}log_reported',
582
			array(
583
				'id_msg' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'id_member' => 'int', 'membername' => 'string',
584 2
				'subject' => 'string', 'body' => 'string', 'time_started' => 'int', 'time_updated' => 'int',
585 2
				'num_reports' => 'int', 'closed' => 'int',
586
				'type' => 'string-5', 'time_message' => 'int'
587 2
			),
588
			array(
589
				$message['id_msg'], $message['id_topic'], $message['id_board'], $message['id_poster'], $message['real_name'],
590
				$message['subject'], $message['body'], time(), time(), 1, 0,
591
				$message['type'], $message['time_message'] ?? 0
592
			),
593 2
			array('id_report')
594 2
		);
595 2
		$id_report = $db->insert_id('{db_prefix}log_reported');
596
	}
597 2
598
	// Now just add our report...
599 2
	if (!empty($id_report))
600
	{
601
		$db->insert('',
602
			'{db_prefix}log_reported_comments',
603 2
			array(
604
				'id_report' => 'int', 'id_member' => 'int', 'membername' => 'string', 'email_address' => 'string',
605 2
				'member_ip' => 'string', 'comment' => 'string', 'time_sent' => 'int',
606 2
			),
607
			array(
608 2
				$id_report, User::$info->id, User::$info->name, User::$info->email,
0 ignored issues
show
Bug Best Practice introduced by
The property email 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 name 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...
609
				User::$info->ip, $poster_comment, time(),
0 ignored issues
show
Bug Best Practice introduced by
The property ip does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
610
			),
611
			array('id_comment')
612 2
		);
613 2
	}
614
615 2
	return $id_report;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_report does not seem to be defined for all execution paths leading up to this point.
Loading history...
616
}
617
618
/**
619 2
 * Count the new posts for a specific topic
620
 *
621
 * @param int $topic
622
 * @param array $topicinfo
623
 * @param int $timestamp
624
 * @return int
625
 */
626
function countNewPosts($topic, $topicinfo, $timestamp)
627
{
628
	global $modSettings;
629
630
	$db = database();
631
632
	// Find the number of messages posted before said time...
633
	$request = $db->query('', '
634
		SELECT 
635
			COUNT(*)
636
		FROM {db_prefix}messages
637
		WHERE poster_time < {int:timestamp}
638
			AND id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts') ? '
639
			AND (approved = {int:is_approved}' . (User::$info->is_guest ? '' : ' OR id_member = {int:current_member}') . ')' : ''),
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...
640
		array(
641
			'current_topic' => $topic,
642
			'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...
643
			'is_approved' => 1,
644
			'timestamp' => $timestamp,
645
		)
646
	);
647
	list ($start) = $request->fetch_row();
648
	$request->free_result();
649
650
	return $start;
651
}
652
653
/**
654
 * Loads the details from a message
655
 *
656
 * @param string[] $msg_selects
657
 * @param string[] $msg_tables
658
 * @param mixed[] $msg_parameters
659
 * @param mixed[] $optional
660
 *
661
 * @return resource A request object
662
 */
663
function loadMessageRequest($msg_selects, $msg_tables, $msg_parameters, $optional = array())
664
{
665
	$db = database();
666
667
	return $db->query('', '
0 ignored issues
show
Bug Best Practice introduced by
The expression return $db->query('', ' ..._msg', $msg_parameters) returns the type ElkArte\Database\AbstractResult|boolean which is incompatible with the documented return type resource.
Loading history...
668
		SELECT
669
			m.id_msg, m.icon, m.subject, m.poster_time, m.poster_ip, m.id_member,
670
			m.modified_time, m.modified_name, m.body, m.smileys_enabled,
671 2
			m.poster_name, m.poster_email, m.approved' . (isset($msg_parameters['new_from']) ? ',
672
			m.id_msg_modified < {int:new_from} AS is_read' : '') . '
673 2
			' . (!empty($msg_selects) ? ',' . implode(',', $msg_selects) : '') . '
674
		FROM {db_prefix}messages AS m
675
			' . (!empty($msg_tables) ? implode("\n\t\t\t", $msg_tables) : '') . '
676
		WHERE m.id_msg IN ({array_int:message_list})
677 2
			' . (!empty($optional['additional_conditions']) ? $optional['additional_conditions'] : '') . '
678 2
		ORDER BY m.id_msg',
679 2
		$msg_parameters
680
	);
681 2
}
682
683 2
/**
684
 * Returns the details from a message or several messages
685 1
 * Uses loadMessageRequest to query the database
686
 *
687
 * @param string[] $msg_selects
688
 * @param string[] $msg_tables
689
 * @param mixed[] $msg_parameters
690
 * @param mixed[] $optional
691
 * @return array
692
 */
693
function loadMessageDetails($msg_selects, $msg_tables, $msg_parameters, $optional = array())
694
{
695
	if (!is_array($msg_parameters['message_list']))
696
	{
697
		$single = true;
698
		$msg_parameters['message_list'] = array($msg_parameters['message_list']);
699
	}
700
	else
701
	{
702
		$single = false;
703
	}
704
705
	$request = loadMessageRequest($msg_selects, $msg_tables, $msg_parameters, $optional);
706
707
	$return = array();
708
	while (($row = $request->fetch_assoc()))
709
	{
710
		$return[] = $row;
711
	}
712
	$request->free_result();
713
714
	if ($single)
715
	{
716
		return $return[0];
717
	}
718
	else
719
	{
720
		return $return;
721
	}
722
}
723
724
/**
725
 * Checks, which messages can be removed from a certain member.
726
 *
727
 * @param int $topic
728
 * @param int[] $messages
729
 * @param bool $allowed_all
730
 * @return array
731
 */
732
function determineRemovableMessages($topic, $messages, $allowed_all)
733
{
734
	$db = database();
735
736
	// Allowed to remove which messages?
737
	$messages_list = array();
738
	$db->fetchQuery('
739
		SELECT 
740
			id_msg, subject, id_member, poster_time
741
		FROM {db_prefix}messages
742
		WHERE id_msg IN ({array_int:message_list})
743
			AND id_topic = {int:current_topic}' . (!$allowed_all ? '
744
			AND id_member = {int:current_member}' : '') . '
745
		LIMIT ' . count($messages),
746
		array(
747
			'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...
748
			'current_topic' => $topic,
749
			'message_list' => $messages,
750
		)
751
	)->fetch_callback(
752
		function ($row) use (&$messages_list, $allowed_all) {
753
			global $modSettings;
754
755
			if (!$allowed_all && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
756
			{
757
				return;
758
			}
759
760
			$messages_list[$row['id_msg']] = array($row['subject'], $row['id_member']);
761
		}
762
	);
763
764
	return $messages_list;
765
}
766
767
/**
768
 * Returns the number of messages that are being split to a new topic
769
 *
770
 * @param int $topic
771
 * @param bool $include_unapproved
772
 * @param int[] $selection
773
 *
774
 * @return array
775
 */
776
function countSplitMessages($topic, $include_unapproved, $selection = array())
777
{
778
	$db = database();
779
780
	$return = array('not_selected' => 0, 'selected' => 0);
781
	$db->fetchQuery('
782
		SELECT ' . (empty($selection) ? '0' : 'm.id_msg IN ({array_int:split_msgs})') . ' AS is_selected, COUNT(*) AS num_messages
783
		FROM {db_prefix}messages AS m
784
		WHERE m.id_topic = {int:current_topic}' . ($include_unapproved ? '' : '
785
			AND approved = {int:is_approved}') . (empty($selection) ? '' : '
786
		GROUP BY is_selected'),
787
		array(
788
			'current_topic' => $topic,
789
			'split_msgs' => $selection,
790
			'is_approved' => 1,
791
		)
792
	)->fetch_callback(
793
		function ($row) use (&$return) {
794
			$return[empty($row['is_selected']) || $row['is_selected'] == 'f' ? 'not_selected' : 'selected'] = $row['num_messages'];
795
		}
796
	);
797
798
	return $return;
799
}
800
801
/**
802
 * Returns an email (and few other things) associated with a message,
803
 * either the member's email or the poster_email (for example in case of guests)
804
 *
805
 * @param int $id_msg the id of a message
806
 * @return array
807
 * @todo very similar to posterDetails
808
 *
809
 */
810
function mailFromMessage($id_msg)
811
{
812
	$db = database();
813
814
	$request = $db->query('', '
815
		SELECT 
816
			COALESCE(mem.email_address, m.poster_email) AS email_address, COALESCE(mem.real_name, m.poster_name) AS real_name, COALESCE(mem.id_member, 0) AS id_member
817
		FROM {db_prefix}messages AS m
818
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
819
		WHERE m.id_msg = {int:id_msg}',
820
		array(
821
			'id_msg' => $id_msg,
822
		)
823
	);
824
	$row = $request->fetch_assoc();
825
	$request->free_result();
826
827
	return $row;
828
}
829
830
/**
831
 * This function changes the total number of messages,
832
 * and the highest message id by id_msg - which can be
833
 * parameters 1 and 2, respectively.
834
 *
835
 * @param bool|null $increment = null If true and $max_msg_id != null, then increment the total messages by one, otherwise recount all messages and get the max message id
836
 * @param int|null $max_msg_id = null, Only used if $increment === true
837
 */
838
function updateMessageStats($increment = null, $max_msg_id = null)
839
{
840
	global $modSettings;
841
842
	$db = database();
843
844
	if ($increment === true && $max_msg_id !== null)
845
	{
846
		updateSettings(array('totalMessages' => true, 'maxMsgID' => $max_msg_id), true);
847
	}
848
	else
849
	{
850
		// SUM and MAX on a smaller table is better for InnoDB tables.
851 26
		$request = $db->query('', '
852
			SELECT 
853 26
				SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
854
			FROM {db_prefix}boards
855 26
			WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
856
				AND id_board != {int:recycle_board}' : ''),
857 26
			array(
858
				'recycle_board' => $modSettings['recycle_board'] ?? 0,
859
				'blank_redirect' => '',
860
			)
861
		);
862 12
		$row = $request->fetch_assoc();
863
		$request->free_result();
864
865
		updateSettings(array(
866 12
			'totalMessages' => $row['total_messages'] ?? 0,
867 12
			'maxMsgID' => $row['max_msg_id'] ?? 0
868
		));
869 12
	}
870 12
}
871
872
/**
873 12
 * This function updates the log_search_subjects in the event of a topic being
874 12
 * moved, removed or split. It is being sent the topic id, and optionally
875
 * the new subject.
876 12
 *
877 12
 * @param int $id_topic
878 12
 * @param string|null $subject
879
 */
880
function updateSubjectStats($id_topic, $subject = null)
881 26
{
882
	$db = database();
883
884
	// Remove the previous subject (if any).
885
	$db->query('', '
886
		DELETE FROM {db_prefix}log_search_subjects
887
		WHERE id_topic = {int:id_topic}',
888
		array(
889
			'id_topic' => (int) $id_topic,
890
		)
891
	);
892
893
	// Insert the new subject.
894 26
	if ($subject !== null)
895
	{
896
		$id_topic = (int) $id_topic;
897 26
		$subject_words = text2words($subject);
898
899
		$inserts = array();
900
		foreach ($subject_words as $word)
901 26
		{
902
			$inserts[] = array($word, $id_topic);
903
		}
904
905
		if (!empty($inserts))
906 26
		{
907
			$db->insert('ignore',
908 26
				'{db_prefix}log_search_subjects',
909 26
				array('word' => 'string', 'id_topic' => 'int'),
910
				$inserts,
911 26
				array('word', 'id_topic')
912 26
			);
913
		}
914 26
	}
915
}
916