Issues (1061)

Sources/RemoveTopic.php (5 issues)

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 2020 Simple Machines and individual contributors
12
 * @license https://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 RC2
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
573
	require_once($sourcedir . '/Subs-Post.php');
574
	$updates = array();
575
	foreach ($adjustBoards as $stats)
576
		$updates[] = $stats['id_board'];
577
	updateLastMessages($updates);
578
}
579
580
/**
581
 * Remove a specific message (including permission checks).
582
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
583
 * - uses boardurl to determine these two things.
584
 *
585
 * @param int $message The message id
586
 * @param bool $decreasePostCount Whether to decrease users' post counts
587
 * @return bool Whether the operation succeeded
588
 */
589
function removeMessage($message, $decreasePostCount = true)
590
{
591
	global $board, $sourcedir, $modSettings, $user_info, $smcFunc;
592
593
	if (empty($message) || !is_numeric($message))
594
		return false;
595
596
	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
597
598
	$request = $smcFunc['db_query']('', '
599
		SELECT
600
			m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . '
601
			m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board,
602
			t.id_member_started AS id_member_poster,
603
			b.count_posts
604
		FROM {db_prefix}messages AS m
605
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
606
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
607
		WHERE m.id_msg = {int:id_msg}
608
		LIMIT 1',
609
		array(
610
			'id_msg' => $message,
611
		)
612
	);
613
	if ($smcFunc['db_num_rows']($request) == 0)
614
		return false;
615
616
	$row = $smcFunc['db_fetch_assoc']($request);
617
	$smcFunc['db_free_result']($request);
618
619
	if (empty($board) || $row['id_board'] != $board)
620
	{
621
		$delete_any = boardsAllowedTo('delete_any');
622
623
		if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any))
624
		{
625
			$delete_own = boardsAllowedTo('delete_own');
626
			$delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own);
0 ignored issues
show
$delete_own of type true is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

626
			$delete_own = in_array(0, /** @scrutinizer ignore-type */ $delete_own) || in_array($row['id_board'], $delete_own);
Loading history...
627
			$delete_replies = boardsAllowedTo('delete_replies');
628
			$delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies);
629
630
			if ($row['id_member'] == $user_info['id'])
631
			{
632
				if (!$delete_own)
633
				{
634
					if ($row['id_member_poster'] == $user_info['id'])
635
					{
636
						if (!$delete_replies)
637
							fatal_lang_error('cannot_delete_replies', 'permission');
638
					}
639
					else
640
						fatal_lang_error('cannot_delete_own', 'permission');
641
				}
642
				elseif (($row['id_member_poster'] != $user_info['id'] || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
643
					fatal_lang_error('modify_post_time_passed', false);
644
			}
645
			elseif ($row['id_member_poster'] == $user_info['id'])
646
			{
647
				if (!$delete_replies)
648
					fatal_lang_error('cannot_delete_replies', 'permission');
649
			}
650
			else
651
				fatal_lang_error('cannot_delete_any', 'permission');
652
		}
653
654
		// Can't delete an unapproved message, if you can't see it!
655
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !(in_array(0, $delete_any) || in_array($row['id_board'], $delete_any)))
656
		{
657
			$approve_posts = boardsAllowedTo('approve_posts');
658
			if (!in_array(0, $approve_posts) && !in_array($row['id_board'], $approve_posts))
659
				return false;
660
		}
661
	}
662
	else
663
	{
664
		// Check permissions to delete this message.
665
		if ($row['id_member'] == $user_info['id'])
666
		{
667
			if (!allowedTo('delete_own'))
668
			{
669
				if ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
670
					isAllowedTo('delete_replies');
671
				elseif (!allowedTo('delete_any'))
672
					isAllowedTo('delete_own');
673
			}
674
			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())
675
				fatal_lang_error('modify_post_time_passed', false);
676
		}
677
		elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
678
			isAllowedTo('delete_replies');
679
		else
680
			isAllowedTo('delete_any');
681
682
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !allowedTo('delete_own'))
683
			isAllowedTo('approve_posts');
684
	}
685
686
	// Delete the *whole* topic, but only if the topic consists of one message.
687
	if ($row['id_first_msg'] == $message)
688
	{
689
		if (empty($board) || $row['id_board'] != $board)
690
		{
691
			$remove_any = boardsAllowedTo('remove_any');
692
			$remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any);
693
			if (!$remove_any)
694
			{
695
				$remove_own = boardsAllowedTo('remove_own');
696
				$remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own);
697
			}
698
699
			if ($row['id_member'] != $user_info['id'] && !$remove_any)
700
				fatal_lang_error('cannot_remove_any', 'permission');
701
			elseif (!$remove_any && !$remove_own)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $remove_own does not seem to be defined for all execution paths leading up to this point.
Loading history...
702
				fatal_lang_error('cannot_remove_own', 'permission');
703
		}
704
		else
705
		{
706
			// Check permissions to delete a whole topic.
707
			if ($row['id_member'] != $user_info['id'])
708
				isAllowedTo('remove_any');
709
			elseif (!allowedTo('remove_any'))
710
				isAllowedTo('remove_own');
711
		}
712
713
		// ...if there is only one post.
714
		if (!empty($row['num_replies']))
715
			fatal_lang_error('delFirstPost', false);
716
717
		removeTopics($row['id_topic']);
718
		return true;
719
	}
720
721
	// Deleting a recycled message can not lower anyone's post count.
722
	if (!empty($recycle_board) && $row['id_board'] == $recycle_board)
723
		$decreasePostCount = false;
724
725
	// This is the last post, update the last post on the board.
726
	if ($row['id_last_msg'] == $message)
727
	{
728
		// Find the last message, set it, and decrease the post count.
729
		$request = $smcFunc['db_query']('', '
730
			SELECT id_msg, id_member
731
			FROM {db_prefix}messages
732
			WHERE id_topic = {int:id_topic}
733
				AND id_msg != {int:id_msg}
734
			ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC
735
			LIMIT 1',
736
			array(
737
				'id_topic' => $row['id_topic'],
738
				'id_msg' => $message,
739
			)
740
		);
741
		$row2 = $smcFunc['db_fetch_assoc']($request);
742
		$smcFunc['db_free_result']($request);
743
744
		$smcFunc['db_query']('', '
745
			UPDATE {db_prefix}topics
746
			SET
747
				id_last_msg = {int:id_last_msg},
748
				id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ',
749
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ',
750
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
751
			WHERE id_topic = {int:id_topic}',
752
			array(
753
				'id_last_msg' => $row2['id_msg'],
754
				'id_member_updated' => $row2['id_member'],
755
				'no_replies' => 0,
756
				'no_unapproved' => 0,
757
				'id_topic' => $row['id_topic'],
758
			)
759
		);
760
	}
761
	// Only decrease post counts.
762
	else
763
		$smcFunc['db_query']('', '
764
			UPDATE {db_prefix}topics
765
			SET ' . ($row['approved'] ? '
766
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : '
767
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
768
			WHERE id_topic = {int:id_topic}',
769
			array(
770
				'no_replies' => 0,
771
				'no_unapproved' => 0,
772
				'id_topic' => $row['id_topic'],
773
			)
774
		);
775
776
	// Default recycle to false.
777
	$recycle = false;
778
779
	// If recycle topics has been set, make a copy of this message in the recycle board.
780
	// Make sure we're not recycling messages that are already on the recycle board.
781
	if (!empty($modSettings['recycle_enable']) && $row['id_board'] != $modSettings['recycle_board'] && $row['icon'] != 'recycled')
782
	{
783
		// Check if the recycle board exists and if so get the read status.
784
		$request = $smcFunc['db_query']('', '
785
			SELECT (COALESCE(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg
786
			FROM {db_prefix}boards AS b
787
				LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
788
			WHERE b.id_board = {int:recycle_board}',
789
			array(
790
				'current_member' => $user_info['id'],
791
				'recycle_board' => $modSettings['recycle_board'],
792
			)
793
		);
794
		if ($smcFunc['db_num_rows']($request) == 0)
795
			fatal_lang_error('recycle_no_valid_board');
796
		list ($isRead, $last_board_msg) = $smcFunc['db_fetch_row']($request);
797
		$smcFunc['db_free_result']($request);
798
799
		// Is there an existing topic in the recycle board to group this post with?
800
		$request = $smcFunc['db_query']('', '
801
			SELECT id_topic, id_first_msg, id_last_msg
802
			FROM {db_prefix}topics
803
			WHERE id_previous_topic = {int:id_previous_topic}
804
				AND id_board = {int:recycle_board}',
805
			array(
806
				'id_previous_topic' => $row['id_topic'],
807
				'recycle_board' => $modSettings['recycle_board'],
808
			)
809
		);
810
		list ($id_recycle_topic, $first_topic_msg, $last_topic_msg) = $smcFunc['db_fetch_row']($request);
811
		$smcFunc['db_free_result']($request);
812
813
		// Insert a new topic in the recycle board if $id_recycle_topic is empty.
814
		if (empty($id_recycle_topic))
815
			$id_topic = $smcFunc['db_insert']('',
816
				'{db_prefix}topics',
817
				array(
818
					'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
819
					'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int',
820
				),
821
				array(
822
					$modSettings['recycle_board'], $row['id_member'], $row['id_member'], $message,
823
					$message, 0, 1, $row['id_topic'],
824
				),
825
				array('id_topic'),
826
				1
827
			);
828
829
		// Capture the ID of the new topic...
830
		$topicID = empty($id_recycle_topic) ? $id_topic : $id_recycle_topic;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_topic does not seem to be defined for all execution paths leading up to this point.
Loading history...
831
832
		// If the topic creation went successful, move the message.
833
		if ($topicID > 0)
834
		{
835
			$smcFunc['db_query']('', '
836
				UPDATE {db_prefix}messages
837
				SET
838
					id_topic = {int:id_topic},
839
					id_board = {int:recycle_board},
840
					approved = {int:is_approved}
841
				WHERE id_msg = {int:id_msg}',
842
				array(
843
					'id_topic' => $topicID,
844
					'recycle_board' => $modSettings['recycle_board'],
845
					'id_msg' => $message,
846
					'is_approved' => 1,
847
				)
848
			);
849
850
			// Take any reported posts with us...
851
			$smcFunc['db_query']('', '
852
				UPDATE {db_prefix}log_reported
853
				SET
854
					id_topic = {int:id_topic},
855
					id_board = {int:recycle_board}
856
				WHERE id_msg = {int:id_msg}',
857
				array(
858
					'id_topic' => $topicID,
859
					'recycle_board' => $modSettings['recycle_board'],
860
					'id_msg' => $message,
861
				)
862
			);
863
864
			// Mark recycled topic as read.
865
			if (!$user_info['is_guest'])
866
				$smcFunc['db_insert']('replace',
867
					'{db_prefix}log_topics',
868
					array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
869
					array($topicID, $user_info['id'], $modSettings['maxMsgID'], 0),
870
					array('id_topic', 'id_member')
871
				);
872
873
			// Mark recycle board as seen, if it was marked as seen before.
874
			if (!empty($isRead) && !$user_info['is_guest'])
875
				$smcFunc['db_insert']('replace',
876
					'{db_prefix}log_boards',
877
					array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
878
					array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']),
879
					array('id_board', 'id_member')
880
				);
881
882
			// Add one topic and post to the recycle bin board.
883
			$smcFunc['db_query']('', '
884
				UPDATE {db_prefix}boards
885
				SET
886
					num_topics = num_topics + {int:num_topics_inc},
887
					num_posts = num_posts + 1' .
888
						($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . '
889
				WHERE id_board = {int:recycle_board}',
890
				array(
891
					'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0,
892
					'recycle_board' => $modSettings['recycle_board'],
893
					'id_merged_msg' => $message,
894
				)
895
			);
896
897
			// Lets increase the num_replies, and the first/last message ID as appropriate.
898
			if (!empty($id_recycle_topic))
899
				$smcFunc['db_query']('', '
900
					UPDATE {db_prefix}topics
901
					SET num_replies = num_replies + 1' .
902
						($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') .
903
						($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . '
904
					WHERE id_topic = {int:id_recycle_topic}',
905
					array(
906
						'id_recycle_topic' => $id_recycle_topic,
907
						'id_merged_msg' => $message,
908
					)
909
				);
910
911
			// Make sure this message isn't getting deleted later on.
912
			$recycle = true;
913
914
			// Make sure we update the search subject index.
915
			updateStats('subject', $topicID, $row['subject']);
916
		}
917
918
		// If it wasn't approved don't keep it in the queue.
919
		if (!$row['approved'])
920
			$smcFunc['db_query']('', '
921
				DELETE FROM {db_prefix}approval_queue
922
				WHERE id_msg = {int:id_msg}
923
					AND id_attach = {int:id_attach}',
924
				array(
925
					'id_msg' => $message,
926
					'id_attach' => 0,
927
				)
928
			);
929
	}
930
931
	$smcFunc['db_query']('', '
932
		UPDATE {db_prefix}boards
933
		SET ' . ($row['approved'] ? '
934
			num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : '
935
			unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
936
		WHERE id_board = {int:id_board}',
937
		array(
938
			'no_posts' => 0,
939
			'no_unapproved' => 0,
940
			'id_board' => $row['id_board'],
941
		)
942
	);
943
944
	// If the poster was registered and the board this message was on incremented
945
	// the member's posts when it was posted, decrease his or her post count.
946
	if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved'])
947
		updateMemberData($row['id_member'], array('posts' => '-'));
948
949
	// Only remove posts if they're not recycled.
950
	if (!$recycle)
951
	{
952
		// Callback for search APIs to do their thing
953
		require_once($sourcedir . '/Search.php');
954
		$searchAPI = findSearchAPI();
955
		if ($searchAPI->supportsMethod('postRemoved'))
956
			$searchAPI->postRemoved($message);
957
958
		// Remove the message!
959
		$smcFunc['db_query']('', '
960
			DELETE FROM {db_prefix}messages
961
			WHERE id_msg = {int:id_msg}',
962
			array(
963
				'id_msg' => $message,
964
			)
965
		);
966
967
		if (!empty($modSettings['search_custom_index_config']))
968
		{
969
			$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
970
			$words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
971
			if (!empty($words))
972
				$smcFunc['db_query']('', '
973
					DELETE FROM {db_prefix}log_search_words
974
					WHERE id_word IN ({array_int:word_list})
975
						AND id_msg = {int:id_msg}',
976
					array(
977
						'word_list' => $words,
978
						'id_msg' => $message,
979
					)
980
				);
981
		}
982
983
		// Delete attachment(s) if they exist.
984
		require_once($sourcedir . '/ManageAttachments.php');
985
		$attachmentQuery = array(
986
			'attachment_type' => 0,
987
			'id_msg' => $message,
988
		);
989
		removeAttachments($attachmentQuery);
990
	}
991
992
	// Allow mods to remove message related data of their own (likes, maybe?)
993
	call_integration_hook('integrate_remove_message', array($message, $row, $recycle));
994
995
	// Update the pesky statistics.
996
	updateStats('message');
997
	updateStats('topic');
998
	updateSettings(array(
999
		'calendar_updated' => time(),
1000
	));
1001
1002
	// And now to update the last message of each board we messed with.
1003
	require_once($sourcedir . '/Subs-Post.php');
1004
	if ($recycle)
1005
		updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
1006
	else
1007
		updateLastMessages($row['id_board']);
1008
1009
	// Close any moderation reports for this message.
1010
	$smcFunc['db_query']('', '
1011
		UPDATE {db_prefix}log_reported
1012
		SET closed = {int:is_closed}
1013
		WHERE id_msg = {int:id_msg}',
1014
		array(
1015
			'is_closed' => 1,
1016
			'id_msg' => $message,
1017
		)
1018
	);
1019
	if ($smcFunc['db_affected_rows']() != 0)
1020
	{
1021
		require_once($sourcedir . '/Subs-ReportedContent.php');
1022
		updateSettings(array('last_mod_report_action' => time()));
1023
		recountOpenReports('posts');
1024
	}
1025
1026
	return false;
1027
}
1028
1029
/**
1030
 * Move back a topic from the recycle board to its original board.
1031
 */
1032
function RestoreTopic()
1033
{
1034
	global $smcFunc, $modSettings, $sourcedir;
1035
1036
	// Check session.
1037
	checkSession('get');
1038
1039
	// Is recycled board enabled?
1040
	if (empty($modSettings['recycle_enable']))
1041
		fatal_lang_error('restored_disabled', 'critical');
1042
1043
	// Can we be in here?
1044
	isAllowedTo('move_any', $modSettings['recycle_board']);
1045
1046
	// We need this file.
1047
	require_once($sourcedir . '/MoveTopic.php');
1048
1049
	$unfound_messages = array();
1050
	$topics_to_restore = array();
1051
1052
	// Restoring messages?
1053
	if (!empty($_REQUEST['msgs']))
1054
	{
1055
		$msgs = explode(',', $_REQUEST['msgs']);
1056
		foreach ($msgs as $k => $msg)
1057
			$msgs[$k] = (int) $msg;
1058
1059
		// Get the id_previous_board and id_previous_topic.
1060
		$request = $smcFunc['db_query']('', '
1061
			SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic,
1062
				t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board
1063
			FROM {db_prefix}messages AS m
1064
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1065
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1066
				LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic)
1067
			WHERE m.id_msg IN ({array_int:messages})',
1068
			array(
1069
				'messages' => $msgs,
1070
			)
1071
		);
1072
1073
		$actioned_messages = array();
1074
		$previous_topics = array();
1075
		while ($row = $smcFunc['db_fetch_assoc']($request))
1076
		{
1077
			// Restoring the first post means topic.
1078
			if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic'])
1079
			{
1080
				$topics_to_restore[] = $row['id_topic'];
1081
				continue;
1082
			}
1083
			// Don't know where it's going?
1084
			if (empty($row['id_previous_topic']))
1085
			{
1086
				$unfound_messages[$row['id_msg']] = $row['subject'];
1087
				continue;
1088
			}
1089
1090
			$previous_topics[] = $row['id_previous_topic'];
1091
			if (empty($actioned_messages[$row['id_previous_topic']]))
1092
				$actioned_messages[$row['id_previous_topic']] = array(
1093
					'msgs' => array(),
1094
					'count_posts' => $row['count_posts'],
1095
					'subject' => $row['subject'],
1096
					'previous_board' => $row['id_previous_board'],
1097
					'possible_prev_board' => $row['possible_prev_board'],
1098
					'current_topic' => $row['id_topic'],
1099
					'current_board' => $row['id_board'],
1100
					'members' => array(),
1101
				);
1102
1103
			$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject'];
1104
			if ($row['id_member'])
1105
				$actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member'];
1106
		}
1107
		$smcFunc['db_free_result']($request);
1108
1109
		// Check for topics we are going to fully restore.
1110
		foreach ($actioned_messages as $topic => $data)
1111
			if (in_array($topic, $topics_to_restore))
1112
				unset($actioned_messages[$topic]);
1113
1114
		// Load any previous topics to check they exist.
1115
		if (!empty($previous_topics))
1116
		{
1117
			$request = $smcFunc['db_query']('', '
1118
				SELECT t.id_topic, t.id_board, m.subject
1119
				FROM {db_prefix}topics AS t
1120
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1121
				WHERE t.id_topic IN ({array_int:previous_topics})',
1122
				array(
1123
					'previous_topics' => $previous_topics,
1124
				)
1125
			);
1126
			$previous_topics = array();
1127
			while ($row = $smcFunc['db_fetch_assoc']($request))
1128
				$previous_topics[$row['id_topic']] = array(
1129
					'board' => $row['id_board'],
1130
					'subject' => $row['subject'],
1131
				);
1132
			$smcFunc['db_free_result']($request);
1133
		}
1134
1135
		// Restore each topic.
1136
		$messages = array();
1137
		foreach ($actioned_messages as $topic => $data)
1138
		{
1139
			// If we have topics we are going to restore the whole lot ignore them.
1140
			if (in_array($topic, $topics_to_restore))
1141
			{
1142
				unset($actioned_messages[$topic]);
1143
				continue;
1144
			}
1145
1146
			// Move the posts back then!
1147
			if (isset($previous_topics[$topic]))
1148
			{
1149
				mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic);
1150
				// Log em.
1151
				logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board']));
1152
				$messages = array_merge(array_keys($data['msgs']), $messages);
1153
			}
1154
			else
1155
			{
1156
				foreach ($data['msgs'] as $msg)
1157
					$unfound_messages[$msg['id']] = $msg['subject'];
1158
			}
1159
		}
1160
	}
1161
1162
	// Now any topics?
1163
	if (!empty($_REQUEST['topics']))
1164
	{
1165
		$topics = explode(',', $_REQUEST['topics']);
1166
		foreach ($topics as $id)
1167
			$topics_to_restore[] = (int) $id;
1168
	}
1169
1170
	if (!empty($topics_to_restore))
1171
	{
1172
		// Lets get the data for these topics.
1173
		$request = $smcFunc['db_query']('', '
1174
			SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject
1175
			FROM {db_prefix}topics AS t
1176
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1177
			WHERE t.id_topic IN ({array_int:topics})',
1178
			array(
1179
				'topics' => $topics_to_restore,
1180
			)
1181
		);
1182
		while ($row = $smcFunc['db_fetch_assoc']($request))
1183
		{
1184
			// We can only restore if the previous board is set.
1185
			if (empty($row['id_previous_board']))
1186
			{
1187
				$unfound_messages[$row['id_first_msg']] = $row['subject'];
1188
				continue;
1189
			}
1190
1191
			// Ok we got here so me move them from here to there.
1192
			moveTopics($row['id_topic'], $row['id_previous_board']);
1193
1194
			// Lets see if the board that we are returning to has post count enabled.
1195
			$request2 = $smcFunc['db_query']('', '
1196
				SELECT count_posts
1197
				FROM {db_prefix}boards
1198
				WHERE id_board = {int:board}',
1199
				array(
1200
					'board' => $row['id_previous_board'],
1201
				)
1202
			);
1203
			list ($count_posts) = $smcFunc['db_fetch_row']($request2);
1204
			$smcFunc['db_free_result']($request2);
1205
1206
			if (empty($count_posts))
1207
			{
1208
				// Lets get the members that need their post count restored.
1209
				$request2 = $smcFunc['db_query']('', '
1210
					SELECT id_member, COUNT(id_msg) AS post_count
1211
					FROM {db_prefix}messages
1212
					WHERE id_topic = {int:topic}
1213
						AND approved = {int:is_approved}
1214
					GROUP BY id_member',
1215
					array(
1216
						'topic' => $row['id_topic'],
1217
						'is_approved' => 1,
1218
					)
1219
				);
1220
1221
				while ($member = $smcFunc['db_fetch_assoc']($request2))
1222
					updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count']));
1223
				$smcFunc['db_free_result']($request2);
1224
			}
1225
1226
			// Log it.
1227
			logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board']));
1228
		}
1229
		$smcFunc['db_free_result']($request);
1230
	}
1231
1232
	// Didn't find some things?
1233
	if (!empty($unfound_messages))
1234
		fatal_lang_error('restore_not_found', false, array(implode('<br>', $unfound_messages)));
1235
1236
	// Just send them to the index if they get here.
1237
	redirectexit();
1238
}
1239
1240
/**
1241
 * Take a load of messages from one place and stick them in a topic
1242
 *
1243
 * @param array $msgs The IDs of the posts to merge
1244
 * @param integer $from_topic The ID of the topic the messages were originally in
1245
 * @param integer $target_topic The ID of the topic the messages are being merged into
1246
 */
1247
function mergePosts($msgs, $from_topic, $target_topic)
1248
{
1249
	global $smcFunc, $sourcedir;
1250
1251
	//!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient.
1252
1253
	// Is it an array?
1254
	if (!is_array($msgs))
0 ignored issues
show
The condition is_array($msgs) is always true.
Loading history...
1255
		$msgs = array($msgs);
1256
1257
	// Lets make sure they are int.
1258
	foreach ($msgs as $key => $msg)
1259
		$msgs[$key] = (int) $msg;
1260
1261
	// Get the source information.
1262
	$request = $smcFunc['db_query']('', '
1263
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts
1264
		FROM {db_prefix}topics AS t
1265
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1266
		WHERE t.id_topic = {int:from_topic}',
1267
		array(
1268
			'from_topic' => $from_topic,
1269
		)
1270
	);
1271
	list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request);
1272
	$smcFunc['db_free_result']($request);
1273
1274
	// Get some target topic and board stats.
1275
	$request = $smcFunc['db_query']('', '
1276
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts
1277
		FROM {db_prefix}topics AS t
1278
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1279
		WHERE t.id_topic = {int:target_topic}',
1280
		array(
1281
			'target_topic' => $target_topic,
1282
		)
1283
	);
1284
	list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request);
1285
	$smcFunc['db_free_result']($request);
1286
1287
	// Lets see if the board that we are returning to has post count enabled.
1288
	if (empty($count_posts))
1289
	{
1290
		// Lets get the members that need their post count restored.
1291
		$request = $smcFunc['db_query']('', '
1292
			SELECT id_member
1293
			FROM {db_prefix}messages
1294
			WHERE id_msg IN ({array_int:messages})
1295
				AND approved = {int:is_approved}',
1296
			array(
1297
				'messages' => $msgs,
1298
				'is_approved' => 1,
1299
			)
1300
		);
1301
1302
		while ($row = $smcFunc['db_fetch_assoc']($request))
1303
			updateMemberData($row['id_member'], array('posts' => '+'));
1304
	}
1305
1306
	// Time to move the messages.
1307
	$smcFunc['db_query']('', '
1308
		UPDATE {db_prefix}messages
1309
		SET
1310
			id_topic = {int:target_topic},
1311
			id_board = {int:target_board}
1312
		WHERE id_msg IN({array_int:msgs})',
1313
		array(
1314
			'target_topic' => $target_topic,
1315
			'target_board' => $target_board,
1316
			'msgs' => $msgs,
1317
		)
1318
	);
1319
1320
	// Fix the id_first_msg and id_last_msg for the target topic.
1321
	$target_topic_data = array(
1322
		'num_replies' => 0,
1323
		'unapproved_posts' => 0,
1324
		'id_first_msg' => 9999999999,
1325
	);
1326
	$request = $smcFunc['db_query']('', '
1327
		SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved
1328
		FROM {db_prefix}messages
1329
		WHERE id_topic = {int:target_topic}
1330
		GROUP BY id_topic, approved
1331
		ORDER BY approved ASC
1332
		LIMIT 2',
1333
		array(
1334
			'target_topic' => $target_topic,
1335
		)
1336
	);
1337
	while ($row = $smcFunc['db_fetch_assoc']($request))
1338
	{
1339
		if ($row['id_first_msg'] < $target_topic_data['id_first_msg'])
1340
			$target_topic_data['id_first_msg'] = $row['id_first_msg'];
1341
		$target_topic_data['id_last_msg'] = $row['id_last_msg'];
1342
		if (!$row['approved'])
1343
			$target_topic_data['unapproved_posts'] = $row['message_count'];
1344
		else
1345
			$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1346
	}
1347
	$smcFunc['db_free_result']($request);
1348
1349
	// We have a new post count for the board.
1350
	$smcFunc['db_query']('', '
1351
		UPDATE {db_prefix}boards
1352
		SET
1353
			num_posts = num_posts + {int:diff_replies},
1354
			unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1355
		WHERE id_board = {int:target_board}',
1356
		array(
1357
			'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.
1358
			'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts,
1359
			'target_board' => $target_board,
1360
		)
1361
	);
1362
1363
	// In some cases we merged the only post in a topic so the topic data is left behind in the topic table.
1364
	$request = $smcFunc['db_query']('', '
1365
		SELECT id_topic
1366
		FROM {db_prefix}messages
1367
		WHERE id_topic = {int:from_topic}',
1368
		array(
1369
			'from_topic' => $from_topic,
1370
		)
1371
	);
1372
1373
	// Remove the topic if it doesn't have any messages.
1374
	$topic_exists = true;
1375
	if ($smcFunc['db_num_rows']($request) == 0)
1376
	{
1377
		removeTopics($from_topic, false, true);
1378
		$topic_exists = false;
1379
	}
1380
	$smcFunc['db_free_result']($request);
1381
1382
	// Recycled topic.
1383
	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...
1384
	{
1385
		// Fix the id_first_msg and id_last_msg for the source topic.
1386
		$source_topic_data = array(
1387
			'num_replies' => 0,
1388
			'unapproved_posts' => 0,
1389
			'id_first_msg' => 9999999999,
1390
		);
1391
		$request = $smcFunc['db_query']('', '
1392
			SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject
1393
			FROM {db_prefix}messages
1394
			WHERE id_topic = {int:from_topic}
1395
			GROUP BY id_topic, approved
1396
			ORDER BY approved ASC
1397
			LIMIT 2',
1398
			array(
1399
				'from_topic' => $from_topic,
1400
			)
1401
		);
1402
		while ($row = $smcFunc['db_fetch_assoc']($request))
1403
		{
1404
			if ($row['id_first_msg'] < $source_topic_data['id_first_msg'])
1405
				$source_topic_data['id_first_msg'] = $row['id_first_msg'];
1406
			$source_topic_data['id_last_msg'] = $row['id_last_msg'];
1407
			if (!$row['approved'])
1408
				$source_topic_data['unapproved_posts'] = $row['message_count'];
1409
			else
1410
				$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1411
		}
1412
		$smcFunc['db_free_result']($request);
1413
1414
		// Update the topic details for the source topic.
1415
		$smcFunc['db_query']('', '
1416
			UPDATE {db_prefix}topics
1417
			SET
1418
				id_first_msg = {int:id_first_msg},
1419
				id_last_msg = {int:id_last_msg},
1420
				num_replies = {int:num_replies},
1421
				unapproved_posts = {int:unapproved_posts}
1422
			WHERE id_topic = {int:from_topic}',
1423
			array(
1424
				'id_first_msg' => $source_topic_data['id_first_msg'],
1425
				'id_last_msg' => $source_topic_data['id_last_msg'],
1426
				'num_replies' => $source_topic_data['num_replies'],
1427
				'unapproved_posts' => $source_topic_data['unapproved_posts'],
1428
				'from_topic' => $from_topic,
1429
			)
1430
		);
1431
1432
		// We have a new post count for the source board.
1433
		$smcFunc['db_query']('', '
1434
			UPDATE {db_prefix}boards
1435
			SET
1436
				num_posts = num_posts + {int:diff_replies},
1437
				unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1438
			WHERE id_board = {int:from_board}',
1439
			array(
1440
				'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.
1441
				'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts,
1442
				'from_board' => $from_board,
1443
			)
1444
		);
1445
	}
1446
1447
	// Finally get around to updating the destination topic, now all indexes etc on the source are fixed.
1448
	$smcFunc['db_query']('', '
1449
		UPDATE {db_prefix}topics
1450
		SET
1451
			id_first_msg = {int:id_first_msg},
1452
			id_last_msg = {int:id_last_msg},
1453
			num_replies = {int:num_replies},
1454
			unapproved_posts = {int:unapproved_posts}
1455
		WHERE id_topic = {int:target_topic}',
1456
		array(
1457
			'id_first_msg' => $target_topic_data['id_first_msg'],
1458
			'id_last_msg' => $target_topic_data['id_last_msg'],
1459
			'num_replies' => $target_topic_data['num_replies'],
1460
			'unapproved_posts' => $target_topic_data['unapproved_posts'],
1461
			'target_topic' => $target_topic,
1462
		)
1463
	);
1464
1465
	// Need it to update some stats.
1466
	require_once($sourcedir . '/Subs-Post.php');
1467
1468
	// Update stats.
1469
	updateStats('topic');
1470
	updateStats('message');
1471
1472
	// Subject cache?
1473
	$cache_updates = array();
1474
	if ($target_first_msg != $target_topic_data['id_first_msg'])
1475
		$cache_updates[] = $target_topic_data['id_first_msg'];
1476
	if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg'])
1477
		$cache_updates[] = $source_topic_data['id_first_msg'];
1478
1479
	if (!empty($cache_updates))
1480
	{
1481
		$request = $smcFunc['db_query']('', '
1482
			SELECT id_topic, subject
1483
			FROM {db_prefix}messages
1484
			WHERE id_msg IN ({array_int:first_messages})',
1485
			array(
1486
				'first_messages' => $cache_updates,
1487
			)
1488
		);
1489
		while ($row = $smcFunc['db_fetch_assoc']($request))
1490
			updateStats('subject', $row['id_topic'], $row['subject']);
1491
		$smcFunc['db_free_result']($request);
1492
	}
1493
1494
	updateLastMessages(array($from_board, $target_board));
1495
}
1496
1497
/**
1498
 * Try to determine if the topic has already been deleted by another user.
1499
 *
1500
 * @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.
1501
 */
1502
function removeDeleteConcurrence()
1503
{
1504
	global $modSettings, $board, $scripturl, $context;
1505
1506
	// No recycle no need to go further
1507
	if (empty($modSettings['recycle_enable']) || empty($modSettings['recycle_board']))
1508
		return false;
1509
1510
	// If it's confirmed go on and delete (from recycle)
1511
	if (isset($_GET['confirm_delete']))
1512
		return true;
1513
1514
	if (empty($board))
1515
		return false;
1516
1517
	if ($modSettings['recycle_board'] != $board)
1518
		return true;
1519
	elseif (isset($_REQUEST['msg']))
1520
		$confirm_url = $scripturl . '?action=deletemsg;confirm_delete;topic=' . $context['current_topic'] . '.0;msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1521
	else
1522
		$confirm_url = $scripturl . '?action=removetopic2;confirm_delete;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id'];
1523
1524
	fatal_lang_error('post_already_deleted', false, array($confirm_url));
1525
}
1526
1527
?>