Issues (1065)

Sources/RemoveTopic.php (1 issue)

1
<?php
2
3
/**
4
 * The contents of this file handle the deletion of topics, posts, and related
5
 * paraphernalia.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines https://www.simplemachines.org
11
 * @copyright 2023 Simple Machines and individual contributors
12
 * @license https://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1.4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/*	The contents of this file handle the deletion of topics, posts, and related
21
	paraphernalia.  It has the following functions:
22
23
*/
24
25
/**
26
 * Completely remove an entire topic.
27
 * Redirects to the board when completed.
28
 */
29
function RemoveTopic2()
30
{
31
	global $user_info, $topic, $board, $sourcedir, $smcFunc, $modSettings;
32
33
	// Make sure they aren't being lead around by someone. (:@)
34
	checkSession('get');
35
36
	// This file needs to be included for sendNotifications().
37
	require_once($sourcedir . '/Subs-Post.php');
38
39
	// Trying to fool us around, are we?
40
	if (empty($topic))
41
		redirectexit();
42
43
	removeDeleteConcurrence();
44
45
	$request = $smcFunc['db_query']('', '
46
		SELECT t.id_member_started, ms.subject, t.approved, t.locked
47
		FROM {db_prefix}topics AS t
48
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
49
		WHERE t.id_topic = {int:current_topic}
50
		LIMIT 1',
51
		array(
52
			'current_topic' => $topic,
53
		)
54
	);
55
	list ($starter, $subject, $approved, $locked) = $smcFunc['db_fetch_row']($request);
56
	$smcFunc['db_free_result']($request);
57
58
	if ($starter == $user_info['id'] && !allowedTo('remove_any'))
59
		isAllowedTo('remove_own');
60
	else
61
		isAllowedTo('remove_any');
62
63
	// Can they see the topic?
64
	if ($modSettings['postmod_active'] && !$approved && $starter != $user_info['id'])
65
		isAllowedTo('approve_posts');
66
67
	// Ok, we got that far, but is it locked?
68
	if ($locked)
69
	{
70
		if (!($locked == 1 && $starter == $user_info['id'] || allowedTo('lock_any')))
71
			fatal_lang_error('cannot_remove_locked', 'user');
72
	}
73
74
	// Notify people that this topic has been removed.
75
	sendNotifications($topic, 'remove');
76
77
	removeTopics($topic);
78
79
	// Note, only log topic ID in native form if it's not gone forever.
80
	if (allowedTo('remove_any') || (allowedTo('remove_own') && $starter == $user_info['id']))
81
		logAction('remove', array((empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $board ? 'topic' : 'old_topic_id') => $topic, 'subject' => $subject, 'member' => $starter, 'board' => $board));
82
83
	redirectexit('board=' . $board . '.0');
84
}
85
86
/**
87
 * Remove just a single post.
88
 * On completion redirect to the topic or to the board.
89
 */
90
function DeleteMessage()
91
{
92
	global $user_info, $topic, $board, $modSettings, $smcFunc;
93
94
	checkSession('get');
95
96
	$_REQUEST['msg'] = (int) $_REQUEST['msg'];
97
98
	// Is $topic set?
99
	if (empty($topic) && isset($_REQUEST['topic']))
100
		$topic = (int) $_REQUEST['topic'];
101
102
	removeDeleteConcurrence();
103
104
	$request = $smcFunc['db_query']('', '
105
		SELECT t.id_member_started, m.id_member, m.subject, m.poster_time, m.approved
106
		FROM {db_prefix}topics AS t
107
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = {int:id_msg} AND m.id_topic = {int:current_topic})
108
		WHERE t.id_topic = {int:current_topic}
109
		LIMIT 1',
110
		array(
111
			'current_topic' => $topic,
112
			'id_msg' => $_REQUEST['msg'],
113
		)
114
	);
115
	list ($starter, $poster, $subject, $post_time, $approved) = $smcFunc['db_fetch_row']($request);
116
	$smcFunc['db_free_result']($request);
117
118
	// Verify they can see this!
119
	if ($modSettings['postmod_active'] && !$approved && !empty($poster) && $poster != $user_info['id'])
120
		isAllowedTo('approve_posts');
121
122
	if ($poster == $user_info['id'])
123
	{
124
		if (!allowedTo('delete_own'))
125
		{
126
			if ($starter == $user_info['id'] && !allowedTo('delete_any'))
127
				isAllowedTo('delete_replies');
128
			elseif (!allowedTo('delete_any'))
129
				isAllowedTo('delete_own');
130
		}
131
		elseif (!allowedTo('delete_any') && ($starter != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $post_time + $modSettings['edit_disable_time'] * 60 < time())
132
			fatal_lang_error('modify_post_time_passed', false);
133
	}
134
	elseif ($starter == $user_info['id'] && !allowedTo('delete_any'))
135
		isAllowedTo('delete_replies');
136
	else
137
		isAllowedTo('delete_any');
138
139
	// If the full topic was removed go back to the board.
140
	$full_topic = removeMessage($_REQUEST['msg']);
141
142
	if (allowedTo('delete_any') && (!allowedTo('delete_own') || $poster != $user_info['id']))
143
		logAction('delete', array('topic' => $topic, 'subject' => $subject, 'member' => $poster, 'board' => $board));
144
145
	// We want to redirect back to recent action.
146
	if (isset($_REQUEST['modcenter']))
147
		redirectexit('action=moderate;area=reportedposts;done');
148
	elseif (isset($_REQUEST['recent']))
149
		redirectexit('action=recent');
150
	elseif (isset($_REQUEST['profile'], $_REQUEST['start'], $_REQUEST['u']))
151
		redirectexit('action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start']);
152
	elseif ($full_topic)
153
		redirectexit('board=' . $board . '.0');
154
	else
155
		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
156
}
157
158
/**
159
 * So long as you are sure... all old posts will be gone.
160
 * Used in ManageMaintenance.php to prune old topics.
161
 */
162
function RemoveOldTopics2()
163
{
164
	global $smcFunc;
165
166
	isAllowedTo('admin_forum');
167
	checkSession('post', 'admin');
168
169
	// No boards at all?  Forget it then :/.
170
	if (empty($_POST['boards']))
171
		redirectexit('action=admin;area=maintain;sa=topics');
172
173
	// This should exist, but we can make sure.
174
	$_POST['delete_type'] = isset($_POST['delete_type']) ? $_POST['delete_type'] : 'nothing';
175
176
	// Custom conditions.
177
	$condition = '';
178
	$condition_params = array(
179
		'boards' => array_keys($_POST['boards']),
180
		'poster_time' => time() - 3600 * 24 * $_POST['maxdays'],
181
	);
182
183
	// Just moved notice topics?
184
	// Note that this ignores redirection topics unless it's a non-expiring one
185
	if ($_POST['delete_type'] == 'moved')
186
	{
187
		$condition .= '
188
			AND m.icon = {string:icon}
189
			AND t.locked = {int:locked}
190
			AND t.redirect_expires = {int:not_expiring}';
191
		$condition_params['icon'] = 'moved';
192
		$condition_params['locked'] = 1;
193
		$condition_params['not_expiring'] = 0;
194
	}
195
	// Otherwise, maybe locked topics only?
196
	elseif ($_POST['delete_type'] == 'locked')
197
	{
198
		// Exclude moved/merged notices since we have another option for those...
199
		$condition .= '
200
			AND t.icon != {string:icon}
201
			AND t.locked = {int:locked}';
202
		$condition_params['icon'] = 'moved';
203
		$condition_params['locked'] = 1;
204
	}
205
206
	// Exclude stickies?
207
	if (isset($_POST['delete_old_not_sticky']))
208
	{
209
		$condition .= '
210
			AND t.is_sticky = {int:is_sticky}';
211
		$condition_params['is_sticky'] = 0;
212
	}
213
214
	// All we're gonna do here is grab the id_topic's and send them to removeTopics().
215
	$request = $smcFunc['db_query']('', '
216
		SELECT t.id_topic
217
		FROM {db_prefix}topics AS t
218
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
219
		WHERE
220
			m.poster_time < {int:poster_time}' . $condition . '
221
			AND t.id_board IN ({array_int:boards})',
222
		$condition_params
223
	);
224
	$topics = array();
225
	while ($row = $smcFunc['db_fetch_assoc']($request))
226
		$topics[] = $row['id_topic'];
227
	$smcFunc['db_free_result']($request);
228
229
	removeTopics($topics, false, true);
230
231
	// Log an action into the moderation log.
232
	logAction('pruned', array('days' => $_POST['maxdays']));
233
234
	redirectexit('action=admin;area=maintain;sa=topics;done=purgeold');
235
}
236
237
/**
238
 * Removes the passed id_topic's. (permissions are NOT checked here!).
239
 *
240
 * @param array|int $topics The topics to remove (can be an id or an array of ids).
241
 * @param bool $decreasePostCount Whether to decrease the users' post counts
242
 * @param bool $ignoreRecycling Whether to ignore recycling board settings
243
 * @param bool $updateBoardCount Whether to adjust topic counts for the boards
244
 */
245
function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false, $updateBoardCount = true)
246
{
247
	global $sourcedir, $modSettings, $smcFunc;
248
249
	// Nothing to do?
250
	if (empty($topics))
251
		return;
252
	// Only a single topic.
253
	if (is_numeric($topics))
254
		$topics = array($topics);
255
256
	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
257
258
	// Do something before?
259
	call_integration_hook('integrate_remove_topics_before', array($topics, $recycle_board));
260
261
	// Decrease the post counts.
262
	if ($decreasePostCount)
263
	{
264
		$requestMembers = $smcFunc['db_query']('', '
265
			SELECT m.id_member, COUNT(*) AS posts
266
			FROM {db_prefix}messages AS m
267
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
268
			WHERE m.id_topic IN ({array_int:topics})' . (!empty($recycle_board) ? '
269
				AND m.id_board != {int:recycled_board}' : '') . '
270
				AND b.count_posts = {int:do_count_posts}
271
				AND m.approved = {int:is_approved}
272
			GROUP BY m.id_member',
273
			array(
274
				'do_count_posts' => 0,
275
				'recycled_board' => $recycle_board,
276
				'topics' => $topics,
277
				'is_approved' => 1,
278
			)
279
		);
280
		if ($smcFunc['db_num_rows']($requestMembers) > 0)
281
		{
282
			while ($rowMembers = $smcFunc['db_fetch_assoc']($requestMembers))
283
				updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $rowMembers['posts']));
284
		}
285
		$smcFunc['db_free_result']($requestMembers);
286
	}
287
288
	// Recycle topics that aren't in the recycle board...
289
	if (!empty($recycle_board) && !$ignoreRecycling)
290
	{
291
		$request = $smcFunc['db_query']('', '
292
			SELECT id_topic, id_board, unapproved_posts, approved
293
			FROM {db_prefix}topics
294
			WHERE id_topic IN ({array_int:topics})
295
				AND id_board != {int:recycle_board}
296
			LIMIT {int:limit}',
297
			array(
298
				'recycle_board' => $recycle_board,
299
				'topics' => $topics,
300
				'limit' => count($topics),
301
			)
302
		);
303
		if ($smcFunc['db_num_rows']($request) > 0)
304
		{
305
			// Get topics that will be recycled.
306
			$recycleTopics = array();
307
			while ($row = $smcFunc['db_fetch_assoc']($request))
308
			{
309
				if (function_exists('apache_reset_timeout'))
310
					@apache_reset_timeout();
311
312
				$recycleTopics[] = $row['id_topic'];
313
314
				// Set the id_previous_board for this topic - and make it not sticky.
315
				$smcFunc['db_query']('', '
316
					UPDATE {db_prefix}topics
317
					SET id_previous_board = {int:id_previous_board}, is_sticky = {int:not_sticky}
318
					WHERE id_topic = {int:id_topic}',
319
					array(
320
						'id_previous_board' => $row['id_board'],
321
						'id_topic' => $row['id_topic'],
322
						'not_sticky' => 0,
323
					)
324
				);
325
			}
326
			$smcFunc['db_free_result']($request);
327
328
			// Move the topics to the recycle board.
329
			require_once($sourcedir . '/MoveTopic.php');
330
			moveTopics($recycleTopics, $modSettings['recycle_board']);
331
332
			// Close reports that are being recycled.
333
			require_once($sourcedir . '/ModerationCenter.php');
334
335
			$smcFunc['db_query']('', '
336
				UPDATE {db_prefix}log_reported
337
				SET closed = {int:is_closed}
338
				WHERE id_topic IN ({array_int:recycle_topics})',
339
				array(
340
					'recycle_topics' => $recycleTopics,
341
					'is_closed' => 1,
342
				)
343
			);
344
345
			updateSettings(array('last_mod_report_action' => time()));
346
347
			require_once($sourcedir . '/Subs-ReportedContent.php');
348
			recountOpenReports('posts');
349
350
			// Topics that were recycled don't need to be deleted, so subtract them.
351
			$topics = array_diff($topics, $recycleTopics);
352
		}
353
		else
354
			$smcFunc['db_free_result']($request);
355
	}
356
357
	// Still topics left to delete?
358
	if (empty($topics))
359
		return;
360
361
	// Callback for search APIs to do their thing
362
	require_once($sourcedir . '/Search.php');
363
	$searchAPI = findSearchAPI();
364
	if ($searchAPI->supportsMethod('topicsRemoved'))
365
		$searchAPI->topicsRemoved($topics);
366
367
	$adjustBoards = array();
368
369
	// Find out how many posts we are deleting.
370
	$request = $smcFunc['db_query']('', '
371
		SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts,
372
			SUM(num_replies) AS num_replies
373
		FROM {db_prefix}topics
374
		WHERE id_topic IN ({array_int:topics})
375
		GROUP BY id_board, approved',
376
		array(
377
			'topics' => $topics,
378
		)
379
	);
380
	while ($row = $smcFunc['db_fetch_assoc']($request))
381
	{
382
		if (!isset($adjustBoards[$row['id_board']]['num_posts']))
383
		{
384
			$adjustBoards[$row['id_board']] = array(
385
				'num_posts' => 0,
386
				'num_topics' => 0,
387
				'unapproved_posts' => 0,
388
				'unapproved_topics' => 0,
389
				'id_board' => $row['id_board']
390
			);
391
		}
392
		// Posts = (num_replies + 1) for each approved topic.
393
		$adjustBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0);
394
		$adjustBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
395
396
		// Add the topics to the right type.
397
		if ($row['approved'])
398
			$adjustBoards[$row['id_board']]['num_topics'] += $row['num_topics'];
399
		else
400
			$adjustBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics'];
401
	}
402
	$smcFunc['db_free_result']($request);
403
404
	if ($updateBoardCount)
405
	{
406
		// Decrease the posts/topics...
407
		foreach ($adjustBoards as $stats)
408
		{
409
			if (function_exists('apache_reset_timeout'))
410
				@apache_reset_timeout();
411
412
			$smcFunc['db_query']('', '
413
				UPDATE {db_prefix}boards
414
				SET
415
					num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
416
					num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
417
					unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
418
					unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
419
				WHERE id_board = {int:id_board}',
420
				array(
421
					'id_board' => $stats['id_board'],
422
					'num_posts' => $stats['num_posts'],
423
					'num_topics' => $stats['num_topics'],
424
					'unapproved_posts' => $stats['unapproved_posts'],
425
					'unapproved_topics' => $stats['unapproved_topics'],
426
				)
427
			);
428
		}
429
	}
430
	// Remove Polls.
431
	$request = $smcFunc['db_query']('', '
432
		SELECT id_poll
433
		FROM {db_prefix}topics
434
		WHERE id_topic IN ({array_int:topics})
435
			AND id_poll > {int:no_poll}
436
		LIMIT {int:limit}',
437
		array(
438
			'no_poll' => 0,
439
			'topics' => $topics,
440
			'limit' => count($topics),
441
		)
442
	);
443
	$polls = array();
444
	while ($row = $smcFunc['db_fetch_assoc']($request))
445
		$polls[] = $row['id_poll'];
446
	$smcFunc['db_free_result']($request);
447
448
	if (!empty($polls))
449
	{
450
		$smcFunc['db_query']('', '
451
			DELETE FROM {db_prefix}polls
452
			WHERE id_poll IN ({array_int:polls})',
453
			array(
454
				'polls' => $polls,
455
			)
456
		);
457
		$smcFunc['db_query']('', '
458
			DELETE FROM {db_prefix}poll_choices
459
			WHERE id_poll IN ({array_int:polls})',
460
			array(
461
				'polls' => $polls,
462
			)
463
		);
464
		$smcFunc['db_query']('', '
465
			DELETE FROM {db_prefix}log_polls
466
			WHERE id_poll IN ({array_int:polls})',
467
			array(
468
				'polls' => $polls,
469
			)
470
		);
471
	}
472
473
	// Get rid of the attachment, if it exists.
474
	require_once($sourcedir . '/ManageAttachments.php');
475
	$attachmentQuery = array(
476
		'attachment_type' => 0,
477
		'id_topic' => $topics,
478
	);
479
	removeAttachments($attachmentQuery, 'messages');
480
481
	// Delete possible search index entries.
482
	if (!empty($modSettings['search_custom_index_config']))
483
	{
484
		$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
485
486
		$words = array();
487
		$messages = array();
488
		$request = $smcFunc['db_query']('', '
489
			SELECT id_msg, body
490
			FROM {db_prefix}messages
491
			WHERE id_topic IN ({array_int:topics})',
492
			array(
493
				'topics' => $topics,
494
			)
495
		);
496
		while ($row = $smcFunc['db_fetch_assoc']($request))
497
		{
498
			if (function_exists('apache_reset_timeout'))
499
				@apache_reset_timeout();
500
501
			$words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true));
502
			$messages[] = $row['id_msg'];
503
		}
504
		$smcFunc['db_free_result']($request);
505
		$words = array_unique($words);
506
507
		if (!empty($words) && !empty($messages))
508
			$smcFunc['db_query']('', '
509
				DELETE FROM {db_prefix}log_search_words
510
				WHERE id_word IN ({array_int:word_list})
511
					AND id_msg IN ({array_int:message_list})',
512
				array(
513
					'word_list' => $words,
514
					'message_list' => $messages,
515
				)
516
			);
517
	}
518
519
	// Delete anything related to the topic.
520
	$smcFunc['db_query']('', '
521
		DELETE FROM {db_prefix}messages
522
		WHERE id_topic IN ({array_int:topics})',
523
		array(
524
			'topics' => $topics,
525
		)
526
	);
527
	$smcFunc['db_query']('', '
528
		DELETE FROM {db_prefix}calendar
529
		WHERE id_topic IN ({array_int:topics})',
530
		array(
531
			'topics' => $topics,
532
		)
533
	);
534
	$smcFunc['db_query']('', '
535
		DELETE FROM {db_prefix}log_topics
536
		WHERE id_topic IN ({array_int:topics})',
537
		array(
538
			'topics' => $topics,
539
		)
540
	);
541
	$smcFunc['db_query']('', '
542
		DELETE FROM {db_prefix}log_notify
543
		WHERE id_topic IN ({array_int:topics})',
544
		array(
545
			'topics' => $topics,
546
		)
547
	);
548
	$smcFunc['db_query']('', '
549
		DELETE FROM {db_prefix}topics
550
		WHERE id_topic IN ({array_int:topics})',
551
		array(
552
			'topics' => $topics,
553
		)
554
	);
555
	$smcFunc['db_query']('', '
556
		DELETE FROM {db_prefix}log_search_subjects
557
		WHERE id_topic IN ({array_int:topics})',
558
		array(
559
			'topics' => $topics,
560
		)
561
	);
562
563
	// Maybe there's a mod that wants to delete topic related data of its own
564
	call_integration_hook('integrate_remove_topics', array($topics));
565
566
	// Update the totals...
567
	updateStats('message');
568
	updateStats('topic');
569
	updateSettings(array(
570
		'calendar_updated' => time(),
571
	));
572
	if (!empty($cache_enable) && $cache_enable >= 3) {
573
		cache_put_data('board-' . $recycle_board, null);
574
		foreach ($adjustBoards as $stats) {
575
			cache_put_data('board-' . $stats['id_board'], null);
576
		}
577
	}
578
579
	require_once($sourcedir . '/Subs-Post.php');
580
	$updates = array();
581
	foreach ($adjustBoards as $stats)
582
		$updates[] = $stats['id_board'];
583
	updateLastMessages($updates);
584
}
585
586
/**
587
 * Remove a specific message (including permission checks).
588
 *
589
 * @param int $message The message id
590
 * @param bool $decreasePostCount Whether to decrease users' post counts
591
 * @return bool Whether the operation succeeded
592
 */
593
function removeMessage($message, $decreasePostCount = true)
594
{
595
	global $board, $sourcedir, $modSettings, $user_info, $smcFunc;
596
597
	if (empty($message) || !is_numeric($message))
598
		return false;
599
600
	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
601
602
	$request = $smcFunc['db_query']('', '
603
		SELECT
604
			m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . '
605
			m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board,
606
			t.id_member_started AS id_member_poster,
607
			b.count_posts
608
		FROM {db_prefix}messages AS m
609
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
610
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
611
		WHERE m.id_msg = {int:id_msg}
612
		LIMIT 1',
613
		array(
614
			'id_msg' => $message,
615
		)
616
	);
617
	if ($smcFunc['db_num_rows']($request) == 0)
618
		return false;
619
620
	$row = $smcFunc['db_fetch_assoc']($request);
621
	$smcFunc['db_free_result']($request);
622
623
	// Give mods a heads-up before we do anything.
624
	call_integration_hook('integrate_pre_remove_message', array($message, $decreasePostCount, $row));
625
626
	if (empty($board) || $row['id_board'] != $board)
627
	{
628
		$delete_any = boardsAllowedTo('delete_any');
629
630
		if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any))
631
		{
632
			$delete_own = boardsAllowedTo('delete_own');
633
			$delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own);
634
			$delete_replies = boardsAllowedTo('delete_replies');
635
			$delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies);
636
637
			if ($row['id_member'] == $user_info['id'])
638
			{
639
				if (!$delete_own)
640
				{
641
					if ($row['id_member_poster'] == $user_info['id'])
642
					{
643
						if (!$delete_replies)
644
							fatal_lang_error('cannot_delete_replies', 'permission');
645
					}
646
					else
647
						fatal_lang_error('cannot_delete_own', 'permission');
648
				}
649
				elseif (($row['id_member_poster'] != $user_info['id'] || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
650
					fatal_lang_error('modify_post_time_passed', false);
651
			}
652
			elseif ($row['id_member_poster'] == $user_info['id'])
653
			{
654
				if (!$delete_replies)
655
					fatal_lang_error('cannot_delete_replies', 'permission');
656
			}
657
			else
658
				fatal_lang_error('cannot_delete_any', 'permission');
659
		}
660
661
		// Can't delete an unapproved message, if you can't see it!
662
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !(in_array(0, $delete_any) || in_array($row['id_board'], $delete_any)))
663
		{
664
			$approve_posts = boardsAllowedTo('approve_posts');
665
			if (!in_array(0, $approve_posts) && !in_array($row['id_board'], $approve_posts))
666
				return false;
667
		}
668
	}
669
	else
670
	{
671
		// Check permissions to delete this message.
672
		if ($row['id_member'] == $user_info['id'])
673
		{
674
			if (!allowedTo('delete_own'))
675
			{
676
				if ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
677
					isAllowedTo('delete_replies');
678
				elseif (!allowedTo('delete_any'))
679
					isAllowedTo('delete_own');
680
			}
681
			elseif (!allowedTo('delete_any') && ($row['id_member_poster'] != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
682
				fatal_lang_error('modify_post_time_passed', false);
683
		}
684
		elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
685
			isAllowedTo('delete_replies');
686
		else
687
			isAllowedTo('delete_any');
688
689
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !allowedTo('delete_own'))
690
			isAllowedTo('approve_posts');
691
	}
692
693
	// Delete the *whole* topic, but only if the topic consists of one message.
694
	if ($row['id_first_msg'] == $message)
695
	{
696
		if (empty($board) || $row['id_board'] != $board)
697
		{
698
			$remove_any = boardsAllowedTo('remove_any');
699
			$remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any);
700
			if (!$remove_any)
701
			{
702
				$remove_own = boardsAllowedTo('remove_own');
703
				$remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own);
704
			}
705
706
			if ($row['id_member'] != $user_info['id'] && !$remove_any)
707
				fatal_lang_error('cannot_remove_any', 'permission');
708
			elseif (!$remove_any && !$remove_own)
709
				fatal_lang_error('cannot_remove_own', 'permission');
710
		}
711
		else
712
		{
713
			// Check permissions to delete a whole topic.
714
			if ($row['id_member'] != $user_info['id'])
715
				isAllowedTo('remove_any');
716
			elseif (!allowedTo('remove_any'))
717
				isAllowedTo('remove_own');
718
		}
719
720
		// ...if there is only one post.
721
		if (!empty($row['num_replies']))
722
			fatal_lang_error('delFirstPost', false);
723
724
		removeTopics($row['id_topic']);
725
		return true;
726
	}
727
728
	// Deleting a recycled message can not lower anyone's post count.
729
	if (!empty($recycle_board) && $row['id_board'] == $recycle_board)
730
		$decreasePostCount = false;
731
732
	// This is the last post, update the last post on the board.
733
	if ($row['id_last_msg'] == $message)
734
	{
735
		// Find the last message, set it, and decrease the post count.
736
		$request = $smcFunc['db_query']('', '
737
			SELECT id_msg, id_member
738
			FROM {db_prefix}messages
739
			WHERE id_topic = {int:id_topic}
740
				AND id_msg != {int:id_msg}
741
			ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC
742
			LIMIT 1',
743
			array(
744
				'id_topic' => $row['id_topic'],
745
				'id_msg' => $message,
746
			)
747
		);
748
		$row2 = $smcFunc['db_fetch_assoc']($request);
749
		$smcFunc['db_free_result']($request);
750
751
		$smcFunc['db_query']('', '
752
			UPDATE {db_prefix}topics
753
			SET
754
				id_last_msg = {int:id_last_msg},
755
				id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ',
756
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ',
757
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
758
			WHERE id_topic = {int:id_topic}',
759
			array(
760
				'id_last_msg' => $row2['id_msg'],
761
				'id_member_updated' => $row2['id_member'],
762
				'no_replies' => 0,
763
				'no_unapproved' => 0,
764
				'id_topic' => $row['id_topic'],
765
			)
766
		);
767
	}
768
	// Only decrease post counts.
769
	else
770
		$smcFunc['db_query']('', '
771
			UPDATE {db_prefix}topics
772
			SET ' . ($row['approved'] ? '
773
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : '
774
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
775
			WHERE id_topic = {int:id_topic}',
776
			array(
777
				'no_replies' => 0,
778
				'no_unapproved' => 0,
779
				'id_topic' => $row['id_topic'],
780
			)
781
		);
782
783
	// Default recycle to false.
784
	$recycle = false;
785
786
	// If recycle topics has been set, make a copy of this message in the recycle board.
787
	// Make sure we're not recycling messages that are already on the recycle board.
788
	if (!empty($modSettings['recycle_enable']) && $row['id_board'] != $modSettings['recycle_board'] && $row['icon'] != 'recycled')
789
	{
790
		// Check if the recycle board exists and if so get the read status.
791
		$request = $smcFunc['db_query']('', '
792
			SELECT (COALESCE(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg
793
			FROM {db_prefix}boards AS b
794
				LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
795
			WHERE b.id_board = {int:recycle_board}',
796
			array(
797
				'current_member' => $user_info['id'],
798
				'recycle_board' => $modSettings['recycle_board'],
799
			)
800
		);
801
		if ($smcFunc['db_num_rows']($request) == 0)
802
			fatal_lang_error('recycle_no_valid_board');
803
		list ($isRead, $last_board_msg) = $smcFunc['db_fetch_row']($request);
804
		$smcFunc['db_free_result']($request);
805
806
		// Is there an existing topic in the recycle board to group this post with?
807
		$request = $smcFunc['db_query']('', '
808
			SELECT id_topic, id_first_msg, id_last_msg
809
			FROM {db_prefix}topics
810
			WHERE id_previous_topic = {int:id_previous_topic}
811
				AND id_board = {int:recycle_board}',
812
			array(
813
				'id_previous_topic' => $row['id_topic'],
814
				'recycle_board' => $modSettings['recycle_board'],
815
			)
816
		);
817
		list ($id_recycle_topic, $first_topic_msg, $last_topic_msg) = $smcFunc['db_fetch_row']($request);
818
		$smcFunc['db_free_result']($request);
819
820
		// Insert a new topic in the recycle board if $id_recycle_topic is empty.
821
		if (empty($id_recycle_topic))
822
			$id_topic = $smcFunc['db_insert']('',
823
				'{db_prefix}topics',
824
				array(
825
					'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
826
					'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int',
827
				),
828
				array(
829
					$modSettings['recycle_board'], $row['id_member'], $row['id_member'], $message,
830
					$message, 0, 1, $row['id_topic'],
831
				),
832
				array('id_topic'),
833
				1
834
			);
835
836
		// Capture the ID of the new topic...
837
		$topicID = empty($id_recycle_topic) ? $id_topic : $id_recycle_topic;
838
839
		// If the topic creation went successful, move the message.
840
		if ($topicID > 0)
841
		{
842
			$smcFunc['db_query']('', '
843
				UPDATE {db_prefix}messages
844
				SET
845
					id_topic = {int:id_topic},
846
					id_board = {int:recycle_board},
847
					approved = {int:is_approved}
848
				WHERE id_msg = {int:id_msg}',
849
				array(
850
					'id_topic' => $topicID,
851
					'recycle_board' => $modSettings['recycle_board'],
852
					'id_msg' => $message,
853
					'is_approved' => 1,
854
				)
855
			);
856
857
			// Take any reported posts with us...
858
			$smcFunc['db_query']('', '
859
				UPDATE {db_prefix}log_reported
860
				SET
861
					id_topic = {int:id_topic},
862
					id_board = {int:recycle_board}
863
				WHERE id_msg = {int:id_msg}',
864
				array(
865
					'id_topic' => $topicID,
866
					'recycle_board' => $modSettings['recycle_board'],
867
					'id_msg' => $message,
868
				)
869
			);
870
871
			// Mark recycled topic as read.
872
			if (!$user_info['is_guest'])
873
				$smcFunc['db_insert']('replace',
874
					'{db_prefix}log_topics',
875
					array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
876
					array($topicID, $user_info['id'], $modSettings['maxMsgID'], 0),
877
					array('id_topic', 'id_member')
878
				);
879
880
			// Mark recycle board as seen, if it was marked as seen before.
881
			if (!empty($isRead) && !$user_info['is_guest'])
882
				$smcFunc['db_insert']('replace',
883
					'{db_prefix}log_boards',
884
					array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
885
					array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']),
886
					array('id_board', 'id_member')
887
				);
888
889
			// Add one topic and post to the recycle bin board.
890
			$smcFunc['db_query']('', '
891
				UPDATE {db_prefix}boards
892
				SET
893
					num_topics = num_topics + {int:num_topics_inc},
894
					num_posts = num_posts + 1' .
895
						($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . '
896
				WHERE id_board = {int:recycle_board}',
897
				array(
898
					'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0,
899
					'recycle_board' => $modSettings['recycle_board'],
900
					'id_merged_msg' => $message,
901
				)
902
			);
903
904
			// Lets increase the num_replies, and the first/last message ID as appropriate.
905
			if (!empty($id_recycle_topic))
906
				$smcFunc['db_query']('', '
907
					UPDATE {db_prefix}topics
908
					SET num_replies = num_replies + 1' .
909
						($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') .
910
						($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . '
911
					WHERE id_topic = {int:id_recycle_topic}',
912
					array(
913
						'id_recycle_topic' => $id_recycle_topic,
914
						'id_merged_msg' => $message,
915
					)
916
				);
917
918
			// Make sure this message isn't getting deleted later on.
919
			$recycle = true;
920
921
			// Make sure we update the search subject index.
922
			updateStats('subject', $topicID, $row['subject']);
923
		}
924
925
		// If it wasn't approved don't keep it in the queue.
926
		if (!$row['approved'])
927
			$smcFunc['db_query']('', '
928
				DELETE FROM {db_prefix}approval_queue
929
				WHERE id_msg = {int:id_msg}
930
					AND id_attach = {int:id_attach}',
931
				array(
932
					'id_msg' => $message,
933
					'id_attach' => 0,
934
				)
935
			);
936
	}
937
938
	$smcFunc['db_query']('', '
939
		UPDATE {db_prefix}boards
940
		SET ' . ($row['approved'] ? '
941
			num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : '
942
			unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
943
		WHERE id_board = {int:id_board}',
944
		array(
945
			'no_posts' => 0,
946
			'no_unapproved' => 0,
947
			'id_board' => $row['id_board'],
948
		)
949
	);
950
951
	// If the poster was registered and the board this message was on incremented
952
	// the member's posts when it was posted, decrease his or her post count.
953
	if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved'])
954
		updateMemberData($row['id_member'], array('posts' => '-'));
955
956
	// Only remove posts if they're not recycled.
957
	if (!$recycle)
958
	{
959
		// Callback for search APIs to do their thing
960
		require_once($sourcedir . '/Search.php');
961
		$searchAPI = findSearchAPI();
962
		if ($searchAPI->supportsMethod('postRemoved'))
963
			$searchAPI->postRemoved($message);
964
965
		// Remove the message!
966
		$smcFunc['db_query']('', '
967
			DELETE FROM {db_prefix}messages
968
			WHERE id_msg = {int:id_msg}',
969
			array(
970
				'id_msg' => $message,
971
			)
972
		);
973
974
		if (!empty($modSettings['search_custom_index_config']))
975
		{
976
			$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
977
			$words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
978
			if (!empty($words))
979
				$smcFunc['db_query']('', '
980
					DELETE FROM {db_prefix}log_search_words
981
					WHERE id_word IN ({array_int:word_list})
982
						AND id_msg = {int:id_msg}',
983
					array(
984
						'word_list' => $words,
985
						'id_msg' => $message,
986
					)
987
				);
988
		}
989
990
		// Delete attachment(s) if they exist.
991
		require_once($sourcedir . '/ManageAttachments.php');
992
		$attachmentQuery = array(
993
			'attachment_type' => 0,
994
			'id_msg' => $message,
995
		);
996
		removeAttachments($attachmentQuery);
997
	}
998
999
	// Allow mods to remove message related data of their own (likes, maybe?)
1000
	call_integration_hook('integrate_remove_message', array($message, $row, $recycle));
1001
1002
	// Update the pesky statistics.
1003
	updateStats('message');
1004
	updateStats('topic');
1005
	updateSettings(array(
1006
		'calendar_updated' => time(),
1007
	));
1008
1009
	// And now to update the last message of each board we messed with.
1010
	require_once($sourcedir . '/Subs-Post.php');
1011
	if ($recycle)
1012
		updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
1013
	else
1014
		updateLastMessages($row['id_board']);
1015
1016
	// Close any moderation reports for this message.
1017
	$smcFunc['db_query']('', '
1018
		UPDATE {db_prefix}log_reported
1019
		SET closed = {int:is_closed}
1020
		WHERE id_msg = {int:id_msg}',
1021
		array(
1022
			'is_closed' => 1,
1023
			'id_msg' => $message,
1024
		)
1025
	);
1026
	if ($smcFunc['db_affected_rows']() != 0)
1027
	{
1028
		require_once($sourcedir . '/Subs-ReportedContent.php');
1029
		updateSettings(array('last_mod_report_action' => time()));
1030
		recountOpenReports('posts');
1031
	}
1032
1033
	return false;
1034
}
1035
1036
/**
1037
 * Move back a topic from the recycle board to its original board.
1038
 */
1039
function RestoreTopic()
1040
{
1041
	global $smcFunc, $modSettings, $sourcedir;
1042
1043
	// Check session.
1044
	checkSession('get');
1045
1046
	// Is recycled board enabled?
1047
	if (empty($modSettings['recycle_enable']))
1048
		fatal_lang_error('restored_disabled', 'critical');
1049
1050
	// Can we be in here?
1051
	isAllowedTo('move_any', $modSettings['recycle_board']);
1052
1053
	// We need this file.
1054
	require_once($sourcedir . '/MoveTopic.php');
1055
1056
	$unfound_messages = array();
1057
	$topics_to_restore = array();
1058
1059
	// Restoring messages?
1060
	if (!empty($_REQUEST['msgs']))
1061
	{
1062
		$msgs = explode(',', $_REQUEST['msgs']);
1063
		foreach ($msgs as $k => $msg)
1064
			$msgs[$k] = (int) $msg;
1065
1066
		// Get the id_previous_board and id_previous_topic.
1067
		$request = $smcFunc['db_query']('', '
1068
			SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic,
1069
				t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board
1070
			FROM {db_prefix}messages AS m
1071
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1072
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1073
				LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic)
1074
			WHERE m.id_msg IN ({array_int:messages})',
1075
			array(
1076
				'messages' => $msgs,
1077
			)
1078
		);
1079
1080
		$actioned_messages = array();
1081
		$previous_topics = array();
1082
		while ($row = $smcFunc['db_fetch_assoc']($request))
1083
		{
1084
			// Restoring the first post means topic.
1085
			if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic'])
1086
			{
1087
				$topics_to_restore[] = $row['id_topic'];
1088
				continue;
1089
			}
1090
			// Don't know where it's going?
1091
			if (empty($row['id_previous_topic']))
1092
			{
1093
				$unfound_messages[$row['id_msg']] = $row['subject'];
1094
				continue;
1095
			}
1096
1097
			$previous_topics[] = $row['id_previous_topic'];
1098
			if (empty($actioned_messages[$row['id_previous_topic']]))
1099
				$actioned_messages[$row['id_previous_topic']] = array(
1100
					'msgs' => array(),
1101
					'count_posts' => $row['count_posts'],
1102
					'subject' => $row['subject'],
1103
					'previous_board' => $row['id_previous_board'],
1104
					'possible_prev_board' => $row['possible_prev_board'],
1105
					'current_topic' => $row['id_topic'],
1106
					'current_board' => $row['id_board'],
1107
					'members' => array(),
1108
				);
1109
1110
			$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject'];
1111
			if ($row['id_member'])
1112
				$actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member'];
1113
		}
1114
		$smcFunc['db_free_result']($request);
1115
1116
		// Check for topics we are going to fully restore.
1117
		foreach ($actioned_messages as $topic => $data)
1118
			if (in_array($topic, $topics_to_restore))
1119
				unset($actioned_messages[$topic]);
1120
1121
		// Load any previous topics to check they exist.
1122
		if (!empty($previous_topics))
1123
		{
1124
			$request = $smcFunc['db_query']('', '
1125
				SELECT t.id_topic, t.id_board, m.subject
1126
				FROM {db_prefix}topics AS t
1127
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1128
				WHERE t.id_topic IN ({array_int:previous_topics})',
1129
				array(
1130
					'previous_topics' => $previous_topics,
1131
				)
1132
			);
1133
			$previous_topics = array();
1134
			while ($row = $smcFunc['db_fetch_assoc']($request))
1135
				$previous_topics[$row['id_topic']] = array(
1136
					'board' => $row['id_board'],
1137
					'subject' => $row['subject'],
1138
				);
1139
			$smcFunc['db_free_result']($request);
1140
		}
1141
1142
		// Restore each topic.
1143
		$messages = array();
1144
		foreach ($actioned_messages as $topic => $data)
1145
		{
1146
			// If we have topics we are going to restore the whole lot ignore them.
1147
			if (in_array($topic, $topics_to_restore))
1148
			{
1149
				unset($actioned_messages[$topic]);
1150
				continue;
1151
			}
1152
1153
			// Move the posts back then!
1154
			if (isset($previous_topics[$topic]))
1155
			{
1156
				mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic);
1157
				// Log em.
1158
				logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board']));
1159
				$messages = array_merge(array_keys($data['msgs']), $messages);
1160
			}
1161
			else
1162
			{
1163
				foreach ($data['msgs'] as $msg)
1164
					$unfound_messages[$msg['id']] = $msg['subject'];
1165
			}
1166
		}
1167
	}
1168
1169
	// Now any topics?
1170
	if (!empty($_REQUEST['topics']))
1171
	{
1172
		$topics = explode(',', $_REQUEST['topics']);
1173
		foreach ($topics as $id)
1174
			$topics_to_restore[] = (int) $id;
1175
	}
1176
1177
	if (!empty($topics_to_restore))
1178
	{
1179
		// Lets get the data for these topics.
1180
		$request = $smcFunc['db_query']('', '
1181
			SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject
1182
			FROM {db_prefix}topics AS t
1183
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1184
			WHERE t.id_topic IN ({array_int:topics})',
1185
			array(
1186
				'topics' => $topics_to_restore,
1187
			)
1188
		);
1189
		while ($row = $smcFunc['db_fetch_assoc']($request))
1190
		{
1191
			// We can only restore if the previous board is set.
1192
			if (empty($row['id_previous_board']))
1193
			{
1194
				$unfound_messages[$row['id_first_msg']] = $row['subject'];
1195
				continue;
1196
			}
1197
1198
			// Ok we got here so me move them from here to there.
1199
			moveTopics($row['id_topic'], $row['id_previous_board']);
1200
1201
			// Lets see if the board that we are returning to has post count enabled.
1202
			$request2 = $smcFunc['db_query']('', '
1203
				SELECT count_posts
1204
				FROM {db_prefix}boards
1205
				WHERE id_board = {int:board}',
1206
				array(
1207
					'board' => $row['id_previous_board'],
1208
				)
1209
			);
1210
			list ($count_posts) = $smcFunc['db_fetch_row']($request2);
1211
			$smcFunc['db_free_result']($request2);
1212
1213
			if (empty($count_posts))
1214
			{
1215
				// Lets get the members that need their post count restored.
1216
				$request2 = $smcFunc['db_query']('', '
1217
					SELECT id_member, COUNT(*) AS post_count
1218
					FROM {db_prefix}messages
1219
					WHERE id_topic = {int:topic}
1220
						AND approved = {int:is_approved}
1221
					GROUP BY id_member',
1222
					array(
1223
						'topic' => $row['id_topic'],
1224
						'is_approved' => 1,
1225
					)
1226
				);
1227
1228
				while ($member = $smcFunc['db_fetch_assoc']($request2))
1229
					updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count']));
1230
				$smcFunc['db_free_result']($request2);
1231
			}
1232
1233
			// Log it.
1234
			logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board']));
1235
		}
1236
		$smcFunc['db_free_result']($request);
1237
	}
1238
1239
	// Didn't find some things?
1240
	if (!empty($unfound_messages))
1241
		fatal_lang_error('restore_not_found', false, array(implode('<br>', $unfound_messages)));
1242
1243
	// Just send them to the index if they get here.
1244
	redirectexit();
1245
}
1246
1247
/**
1248
 * Take a load of messages from one place and stick them in a topic
1249
 *
1250
 * @param array $msgs The IDs of the posts to merge
1251
 * @param integer $from_topic The ID of the topic the messages were originally in
1252
 * @param integer $target_topic The ID of the topic the messages are being merged into
1253
 */
1254
function mergePosts($msgs, $from_topic, $target_topic)
1255
{
1256
	global $smcFunc, $sourcedir;
1257
1258
	//!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient.
1259
1260
	// Is it an array?
1261
	if (!is_array($msgs))
1262
		$msgs = array($msgs);
1263
1264
	// Lets make sure they are int.
1265
	foreach ($msgs as $key => $msg)
1266
		$msgs[$key] = (int) $msg;
1267
1268
	// Get the source information.
1269
	$request = $smcFunc['db_query']('', '
1270
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts
1271
		FROM {db_prefix}topics AS t
1272
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1273
		WHERE t.id_topic = {int:from_topic}',
1274
		array(
1275
			'from_topic' => $from_topic,
1276
		)
1277
	);
1278
	list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request);
1279
	$smcFunc['db_free_result']($request);
1280
1281
	// Get some target topic and board stats.
1282
	$request = $smcFunc['db_query']('', '
1283
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts
1284
		FROM {db_prefix}topics AS t
1285
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1286
		WHERE t.id_topic = {int:target_topic}',
1287
		array(
1288
			'target_topic' => $target_topic,
1289
		)
1290
	);
1291
	list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request);
1292
	$smcFunc['db_free_result']($request);
1293
1294
	// Lets see if the board that we are returning to has post count enabled.
1295
	if (empty($count_posts))
1296
	{
1297
		// Lets get the members that need their post count restored.
1298
		$request = $smcFunc['db_query']('', '
1299
			SELECT id_member
1300
			FROM {db_prefix}messages
1301
			WHERE id_msg IN ({array_int:messages})
1302
				AND approved = {int:is_approved}',
1303
			array(
1304
				'messages' => $msgs,
1305
				'is_approved' => 1,
1306
			)
1307
		);
1308
1309
		while ($row = $smcFunc['db_fetch_assoc']($request))
1310
			updateMemberData($row['id_member'], array('posts' => '+'));
1311
	}
1312
1313
	// Time to move the messages.
1314
	$smcFunc['db_query']('', '
1315
		UPDATE {db_prefix}messages
1316
		SET
1317
			id_topic = {int:target_topic},
1318
			id_board = {int:target_board}
1319
		WHERE id_msg IN({array_int:msgs})',
1320
		array(
1321
			'target_topic' => $target_topic,
1322
			'target_board' => $target_board,
1323
			'msgs' => $msgs,
1324
		)
1325
	);
1326
1327
	// Fix the id_first_msg and id_last_msg for the target topic.
1328
	$target_topic_data = array(
1329
		'num_replies' => 0,
1330
		'unapproved_posts' => 0,
1331
		'id_first_msg' => 9999999999,
1332
	);
1333
	$request = $smcFunc['db_query']('', '
1334
		SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved
1335
		FROM {db_prefix}messages
1336
		WHERE id_topic = {int:target_topic}
1337
		GROUP BY id_topic, approved
1338
		ORDER BY approved ASC
1339
		LIMIT 2',
1340
		array(
1341
			'target_topic' => $target_topic,
1342
		)
1343
	);
1344
	while ($row = $smcFunc['db_fetch_assoc']($request))
1345
	{
1346
		if ($row['id_first_msg'] < $target_topic_data['id_first_msg'])
1347
			$target_topic_data['id_first_msg'] = $row['id_first_msg'];
1348
		$target_topic_data['id_last_msg'] = $row['id_last_msg'];
1349
		if (!$row['approved'])
1350
			$target_topic_data['unapproved_posts'] = $row['message_count'];
1351
		else
1352
			$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1353
	}
1354
	$smcFunc['db_free_result']($request);
1355
1356
	// We have a new post count for the board.
1357
	$smcFunc['db_query']('', '
1358
		UPDATE {db_prefix}boards
1359
		SET
1360
			num_posts = num_posts + {int:diff_replies},
1361
			unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1362
		WHERE id_board = {int:target_board}',
1363
		array(
1364
			'diff_replies' => $target_topic_data['num_replies'] - $target_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board.
1365
			'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts,
1366
			'target_board' => $target_board,
1367
		)
1368
	);
1369
1370
	// In some cases we merged the only post in a topic so the topic data is left behind in the topic table.
1371
	$request = $smcFunc['db_query']('', '
1372
		SELECT id_topic
1373
		FROM {db_prefix}messages
1374
		WHERE id_topic = {int:from_topic}',
1375
		array(
1376
			'from_topic' => $from_topic,
1377
		)
1378
	);
1379
1380
	// Remove the topic if it doesn't have any messages.
1381
	$topic_exists = true;
1382
	if ($smcFunc['db_num_rows']($request) == 0)
1383
	{
1384
		removeTopics($from_topic, false, true);
1385
		$topic_exists = false;
1386
	}
1387
	$smcFunc['db_free_result']($request);
1388
1389
	// Recycled topic.
1390
	if ($topic_exists == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1391
	{
1392
		// Fix the id_first_msg and id_last_msg for the source topic.
1393
		$source_topic_data = array(
1394
			'num_replies' => 0,
1395
			'unapproved_posts' => 0,
1396
			'id_first_msg' => 9999999999,
1397
		);
1398
		$request = $smcFunc['db_query']('', '
1399
			SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject
1400
			FROM {db_prefix}messages
1401
			WHERE id_topic = {int:from_topic}
1402
			GROUP BY id_topic, approved, subject
1403
			ORDER BY approved ASC
1404
			LIMIT 2',
1405
			array(
1406
				'from_topic' => $from_topic,
1407
			)
1408
		);
1409
		while ($row = $smcFunc['db_fetch_assoc']($request))
1410
		{
1411
			if ($row['id_first_msg'] < $source_topic_data['id_first_msg'])
1412
				$source_topic_data['id_first_msg'] = $row['id_first_msg'];
1413
			$source_topic_data['id_last_msg'] = $row['id_last_msg'];
1414
			if (!$row['approved'])
1415
				$source_topic_data['unapproved_posts'] = $row['message_count'];
1416
			else
1417
				$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1418
		}
1419
		$smcFunc['db_free_result']($request);
1420
1421
		// Update the topic details for the source topic.
1422
		$smcFunc['db_query']('', '
1423
			UPDATE {db_prefix}topics
1424
			SET
1425
				id_first_msg = {int:id_first_msg},
1426
				id_last_msg = {int:id_last_msg},
1427
				num_replies = {int:num_replies},
1428
				unapproved_posts = {int:unapproved_posts}
1429
			WHERE id_topic = {int:from_topic}',
1430
			array(
1431
				'id_first_msg' => $source_topic_data['id_first_msg'],
1432
				'id_last_msg' => $source_topic_data['id_last_msg'],
1433
				'num_replies' => $source_topic_data['num_replies'],
1434
				'unapproved_posts' => $source_topic_data['unapproved_posts'],
1435
				'from_topic' => $from_topic,
1436
			)
1437
		);
1438
1439
		// We have a new post count for the source board.
1440
		$smcFunc['db_query']('', '
1441
			UPDATE {db_prefix}boards
1442
			SET
1443
				num_posts = num_posts + {int:diff_replies},
1444
				unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1445
			WHERE id_board = {int:from_board}',
1446
			array(
1447
				'diff_replies' => $source_topic_data['num_replies'] - $from_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board.
1448
				'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts,
1449
				'from_board' => $from_board,
1450
			)
1451
		);
1452
	}
1453
1454
	// Finally get around to updating the destination topic, now all indexes etc on the source are fixed.
1455
	$smcFunc['db_query']('', '
1456
		UPDATE {db_prefix}topics
1457
		SET
1458
			id_first_msg = {int:id_first_msg},
1459
			id_last_msg = {int:id_last_msg},
1460
			num_replies = {int:num_replies},
1461
			unapproved_posts = {int:unapproved_posts}
1462
		WHERE id_topic = {int:target_topic}',
1463
		array(
1464
			'id_first_msg' => $target_topic_data['id_first_msg'],
1465
			'id_last_msg' => $target_topic_data['id_last_msg'],
1466
			'num_replies' => $target_topic_data['num_replies'],
1467
			'unapproved_posts' => $target_topic_data['unapproved_posts'],
1468
			'target_topic' => $target_topic,
1469
		)
1470
	);
1471
1472
	// Need it to update some stats.
1473
	require_once($sourcedir . '/Subs-Post.php');
1474
1475
	// Update stats.
1476
	updateStats('topic');
1477
	updateStats('message');
1478
1479
	// Subject cache?
1480
	$cache_updates = array();
1481
	if ($target_first_msg != $target_topic_data['id_first_msg'])
1482
		$cache_updates[] = $target_topic_data['id_first_msg'];
1483
	if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg'])
1484
		$cache_updates[] = $source_topic_data['id_first_msg'];
1485
1486
	if (!empty($cache_updates))
1487
	{
1488
		$request = $smcFunc['db_query']('', '
1489
			SELECT id_topic, subject
1490
			FROM {db_prefix}messages
1491
			WHERE id_msg IN ({array_int:first_messages})',
1492
			array(
1493
				'first_messages' => $cache_updates,
1494
			)
1495
		);
1496
		while ($row = $smcFunc['db_fetch_assoc']($request))
1497
			updateStats('subject', $row['id_topic'], $row['subject']);
1498
		$smcFunc['db_free_result']($request);
1499
	}
1500
1501
	updateLastMessages(array($from_board, $target_board));
1502
}
1503
1504
/**
1505
 * Try to determine if the topic has already been deleted by another user.
1506
 *
1507
 * @return bool False if it can't be deleted (recycling not enabled or no recycling board set), true if we've confirmed it can be deleted. Dies with an error if it's already been deleted.
1508
 */
1509
function removeDeleteConcurrence()
1510
{
1511
	global $modSettings, $board, $scripturl, $context;
1512
1513
	// No recycle no need to go further
1514
	if (empty($modSettings['recycle_enable']) || empty($modSettings['recycle_board']))
1515
		return false;
1516
1517
	// If it's confirmed go on and delete (from recycle)
1518
	if (isset($_GET['confirm_delete']))
1519
		return true;
1520
1521
	if (empty($board))
1522
		return false;
1523
1524
	if ($modSettings['recycle_board'] != $board)
1525
		return true;
1526
	elseif (isset($_REQUEST['msg']))
1527
		$confirm_url = $scripturl . '?action=deletemsg;confirm_delete;topic=' . $context['current_topic'] . '.0;msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1528
	else
1529
		$confirm_url = $scripturl . '?action=removetopic2;confirm_delete;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id'];
1530
1531
	fatal_lang_error('post_already_deleted', false, array($confirm_url));
1532
}
1533
1534
?>