Passed
Push — development ( c29c1d...69387f )
by Spuds
01:04 queued 22s
created

basicMessageInfo()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 14.114

Importance

Changes 0
Metric Value
cc 13
eloc 25
nc 7
nop 4
dl 0
loc 46
ccs 13
cts 16
cp 0.8125
crap 14.114
rs 6.6166
c 0
b 0
f 0

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