Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

RemoveTopic.php ➔ removeTopics()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 331
Code Lines 158

Duplication

Lines 46
Ratio 13.9 %

Importance

Changes 0
Metric Value
cc 31
eloc 158
nc 62281
nop 4
dl 46
loc 331
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * 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 http://www.simplemachines.org
11
 * @copyright 2017 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 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 View Code Duplication
	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 View Code Duplication
	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 View Code Duplication
			if ($starter == $user_info['id'] && !allowedTo('delete_any'))
127
				isAllowedTo('delete_replies');
128
			elseif (!allowedTo('delete_any'))
129
				isAllowedTo('delete_own');
130
		}
131 View Code Duplication
		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
	// Decrease the post counts.
259
	if ($decreasePostCount)
260
	{
261
		$requestMembers = $smcFunc['db_query']('', '
262
			SELECT m.id_member, COUNT(*) AS posts
263
			FROM {db_prefix}messages AS m
264
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
265
			WHERE m.id_topic IN ({array_int:topics})' . (!empty($recycle_board) ? '
266
				AND m.id_board != {int:recycled_board}' : '') . '
267
				AND b.count_posts = {int:do_count_posts}
268
				AND m.approved = {int:is_approved}
269
			GROUP BY m.id_member',
270
			array(
271
				'do_count_posts' => 0,
272
				'recycled_board' => $recycle_board,
273
				'topics' => $topics,
274
				'is_approved' => 1,
275
			)
276
		);
277
		if ($smcFunc['db_num_rows']($requestMembers) > 0)
278
		{
279
			while ($rowMembers = $smcFunc['db_fetch_assoc']($requestMembers))
280
				updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $rowMembers['posts']));
281
		}
282
		$smcFunc['db_free_result']($requestMembers);
283
	}
284
285
	// Recycle topics that aren't in the recycle board...
286
	if (!empty($recycle_board) && !$ignoreRecycling)
287
	{
288
		$request = $smcFunc['db_query']('', '
289
			SELECT id_topic, id_board, unapproved_posts, approved
290
			FROM {db_prefix}topics
291
			WHERE id_topic IN ({array_int:topics})
292
				AND id_board != {int:recycle_board}
293
			LIMIT {int:limit}',
294
			array(
295
				'recycle_board' => $recycle_board,
296
				'topics' => $topics,
297
				'limit' => count($topics),
298
			)
299
		);
300
		if ($smcFunc['db_num_rows']($request) > 0)
301
		{
302
			// Get topics that will be recycled.
303
			$recycleTopics = array();
304
			while ($row = $smcFunc['db_fetch_assoc']($request))
305
			{
306
				if (function_exists('apache_reset_timeout'))
307
					@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
308
309
				$recycleTopics[] = $row['id_topic'];
310
311
				// Set the id_previous_board for this topic - and make it not sticky.
312
				$smcFunc['db_query']('', '
313
					UPDATE {db_prefix}topics
314
					SET id_previous_board = {int:id_previous_board}, is_sticky = {int:not_sticky}
315
					WHERE id_topic = {int:id_topic}',
316
					array(
317
						'id_previous_board' => $row['id_board'],
318
						'id_topic' => $row['id_topic'],
319
						'not_sticky' => 0,
320
					)
321
				);
322
			}
323
			$smcFunc['db_free_result']($request);
324
325
			// Move the topics to the recycle board.
326
			require_once($sourcedir . '/MoveTopic.php');
327
			moveTopics($recycleTopics, $modSettings['recycle_board']);
328
329
			// Close reports that are being recycled.
330
			require_once($sourcedir . '/ModerationCenter.php');
331
332
			$smcFunc['db_query']('', '
333
				UPDATE {db_prefix}log_reported
334
				SET closed = {int:is_closed}
335
				WHERE id_topic IN ({array_int:recycle_topics})',
336
				array(
337
					'recycle_topics' => $recycleTopics,
338
					'is_closed' => 1,
339
				)
340
			);
341
342
			updateSettings(array('last_mod_report_action' => time()));
343
344
			require_once($sourcedir . '/Subs-ReportedContent.php');
345
			recountOpenReports('posts');
346
347
			// Topics that were recycled don't need to be deleted, so subtract them.
348
			$topics = array_diff($topics, $recycleTopics);
349
		}
350
		else
351
			$smcFunc['db_free_result']($request);
352
	}
353
354
	// Still topics left to delete?
355
	if (empty($topics))
356
		return;
357
358
	// Callback for search APIs to do their thing
359
	require_once($sourcedir . '/Search.php');
360
	$searchAPI = findSearchAPI();
361
	if ($searchAPI->supportsMethod('topicsRemoved'))
362
		$searchAPI->topicsRemoved($topics);
363
364
	$adjustBoards = array();
365
366
	// Find out how many posts we are deleting.
367
	$request = $smcFunc['db_query']('', '
368
		SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts,
369
			SUM(num_replies) AS num_replies
370
		FROM {db_prefix}topics
371
		WHERE id_topic IN ({array_int:topics})
372
		GROUP BY id_board, approved',
373
		array(
374
			'topics' => $topics,
375
		)
376
	);
377 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
378
	{
379
		if (!isset($adjustBoards[$row['id_board']]['num_posts']))
380
		{
381
			$adjustBoards[$row['id_board']] = array(
382
				'num_posts' => 0,
383
				'num_topics' => 0,
384
				'unapproved_posts' => 0,
385
				'unapproved_topics' => 0,
386
				'id_board' => $row['id_board']
387
			);
388
		}
389
		// Posts = (num_replies + 1) for each approved topic.
390
		$adjustBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0);
391
		$adjustBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
392
393
		// Add the topics to the right type.
394
		if ($row['approved'])
395
			$adjustBoards[$row['id_board']]['num_topics'] += $row['num_topics'];
396
		else
397
			$adjustBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics'];
398
	}
399
	$smcFunc['db_free_result']($request);
400
401
	if ($updateBoardCount)
402
	{
403
		// Decrease the posts/topics...
404
		foreach ($adjustBoards as $stats)
405
		{
406
			if (function_exists('apache_reset_timeout'))
407
				@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
408
409
			$smcFunc['db_query']('', '
410
				UPDATE {db_prefix}boards
411
				SET
412
					num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
413
					num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
414
					unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
415
					unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
416
				WHERE id_board = {int:id_board}',
417
				array(
418
					'id_board' => $stats['id_board'],
419
					'num_posts' => $stats['num_posts'],
420
					'num_topics' => $stats['num_topics'],
421
					'unapproved_posts' => $stats['unapproved_posts'],
422
					'unapproved_topics' => $stats['unapproved_topics'],
423
				)
424
			);
425
		}
426
	}
427
	// Remove Polls.
428
	$request = $smcFunc['db_query']('', '
429
		SELECT id_poll
430
		FROM {db_prefix}topics
431
		WHERE id_topic IN ({array_int:topics})
432
			AND id_poll > {int:no_poll}
433
		LIMIT {int:limit}',
434
		array(
435
			'no_poll' => 0,
436
			'topics' => $topics,
437
			'limit' => count($topics),
438
		)
439
	);
440
	$polls = array();
441
	while ($row = $smcFunc['db_fetch_assoc']($request))
442
		$polls[] = $row['id_poll'];
443
	$smcFunc['db_free_result']($request);
444
445 View Code Duplication
	if (!empty($polls))
446
	{
447
		$smcFunc['db_query']('', '
448
			DELETE FROM {db_prefix}polls
449
			WHERE id_poll IN ({array_int:polls})',
450
			array(
451
				'polls' => $polls,
452
			)
453
		);
454
		$smcFunc['db_query']('', '
455
			DELETE FROM {db_prefix}poll_choices
456
			WHERE id_poll IN ({array_int:polls})',
457
			array(
458
				'polls' => $polls,
459
			)
460
		);
461
		$smcFunc['db_query']('', '
462
			DELETE FROM {db_prefix}log_polls
463
			WHERE id_poll IN ({array_int:polls})',
464
			array(
465
				'polls' => $polls,
466
			)
467
		);
468
	}
469
470
	// Get rid of the attachment, if it exists.
471
	require_once($sourcedir . '/ManageAttachments.php');
472
	$attachmentQuery = array(
473
		'attachment_type' => 0,
474
		'id_topic' => $topics,
475
	);
476
	removeAttachments($attachmentQuery, 'messages');
477
478
	// Delete possible search index entries.
479
	if (!empty($modSettings['search_custom_index_config']))
480
	{
481
		$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
482
483
		$words = array();
484
		$messages = array();
485
		$request = $smcFunc['db_query']('', '
486
			SELECT id_msg, body
487
			FROM {db_prefix}messages
488
			WHERE id_topic IN ({array_int:topics})',
489
			array(
490
				'topics' => $topics,
491
			)
492
		);
493
		while ($row = $smcFunc['db_fetch_assoc']($request))
494
		{
495
			if (function_exists('apache_reset_timeout'))
496
				@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
497
498
			$words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true));
499
			$messages[] = $row['id_msg'];
500
		}
501
		$smcFunc['db_free_result']($request);
502
		$words = array_unique($words);
503
504
		if (!empty($words) && !empty($messages))
505
			$smcFunc['db_query']('', '
506
				DELETE FROM {db_prefix}log_search_words
507
				WHERE id_word IN ({array_int:word_list})
508
					AND id_msg IN ({array_int:message_list})',
509
				array(
510
					'word_list' => $words,
511
					'message_list' => $messages,
512
				)
513
			);
514
	}
515
516
	// Delete anything related to the topic.
517
	$smcFunc['db_query']('', '
518
		DELETE FROM {db_prefix}messages
519
		WHERE id_topic IN ({array_int:topics})',
520
		array(
521
			'topics' => $topics,
522
		)
523
	);
524
	$smcFunc['db_query']('', '
525
		DELETE FROM {db_prefix}calendar
526
		WHERE id_topic IN ({array_int:topics})',
527
		array(
528
			'topics' => $topics,
529
		)
530
	);
531
	$smcFunc['db_query']('', '
532
		DELETE FROM {db_prefix}log_topics
533
		WHERE id_topic IN ({array_int:topics})',
534
		array(
535
			'topics' => $topics,
536
		)
537
	);
538
	$smcFunc['db_query']('', '
539
		DELETE FROM {db_prefix}log_notify
540
		WHERE id_topic IN ({array_int:topics})',
541
		array(
542
			'topics' => $topics,
543
		)
544
	);
545
	$smcFunc['db_query']('', '
546
		DELETE FROM {db_prefix}topics
547
		WHERE id_topic IN ({array_int:topics})',
548
		array(
549
			'topics' => $topics,
550
		)
551
	);
552
	$smcFunc['db_query']('', '
553
		DELETE FROM {db_prefix}log_search_subjects
554
		WHERE id_topic IN ({array_int:topics})',
555
		array(
556
			'topics' => $topics,
557
		)
558
	);
559
560
	// Maybe there's a mod that wants to delete topic related data of its own
561
 	call_integration_hook('integrate_remove_topics', array($topics));
562
563
	// Update the totals...
564
	updateStats('message');
565
	updateStats('topic');
566
	updateSettings(array(
567
		'calendar_updated' => time(),
568
	));
569
570
	require_once($sourcedir . '/Subs-Post.php');
571
	$updates = array();
572
	foreach ($adjustBoards as $stats)
573
		$updates[] = $stats['id_board'];
574
	updateLastMessages($updates);
575
}
576
577
/**
578
 * Remove a specific message (including permission checks).
579
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
580
 * - uses boardurl to determine these two things.
581
 *
582
 * @param int $message The message id
583
 * @param bool $decreasePostCount Whether to decrease users' post counts
584
 * @return bool Whether the operation succeeded
585
 */
586
function removeMessage($message, $decreasePostCount = true)
587
{
588
	global $board, $sourcedir, $modSettings, $user_info, $smcFunc;
589
590
	if (empty($message) || !is_numeric($message))
591
		return false;
592
593
	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
594
595
	$request = $smcFunc['db_query']('', '
596
		SELECT
597
			m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . '
598
			m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board,
599
			t.id_member_started AS id_member_poster,
600
			b.count_posts
601
		FROM {db_prefix}messages AS m
602
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
603
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
604
		WHERE m.id_msg = {int:id_msg}
605
		LIMIT 1',
606
		array(
607
			'id_msg' => $message,
608
		)
609
	);
610
	if ($smcFunc['db_num_rows']($request) == 0)
611
		return false;
612
	$row = $smcFunc['db_fetch_assoc']($request);
613
	$smcFunc['db_free_result']($request);
614
615
	if (empty($board) || $row['id_board'] != $board)
616
	{
617
		$delete_any = boardsAllowedTo('delete_any');
618
619
		if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any))
620
		{
621
			$delete_own = boardsAllowedTo('delete_own');
622
			$delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own);
623
			$delete_replies = boardsAllowedTo('delete_replies');
624
			$delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies);
625
626
			if ($row['id_member'] == $user_info['id'])
627
			{
628
				if (!$delete_own)
629
				{
630
					if ($row['id_member_poster'] == $user_info['id'])
631
					{
632
						if (!$delete_replies)
633
							fatal_lang_error('cannot_delete_replies', 'permission');
634
					}
635
					else
636
						fatal_lang_error('cannot_delete_own', 'permission');
637
				}
638
				elseif (($row['id_member_poster'] != $user_info['id'] || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time())
639
					fatal_lang_error('modify_post_time_passed', false);
640
			}
641
			elseif ($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_any', 'permission');
648
		}
649
650
		// Can't delete an unapproved message, if you can't see it!
651
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !(in_array(0, $delete_any) || in_array($row['id_board'], $delete_any)))
652
		{
653
			$approve_posts = boardsAllowedTo('approve_posts');
654
			if (!in_array(0, $approve_posts) && !in_array($row['id_board'], $approve_posts))
655
				return false;
656
		}
657
	}
658
	else
659
	{
660
		// Check permissions to delete this message.
661
		if ($row['id_member'] == $user_info['id'])
662
		{
663
			if (!allowedTo('delete_own'))
664
			{
665 View Code Duplication
				if ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
666
					isAllowedTo('delete_replies');
667
				elseif (!allowedTo('delete_any'))
668
					isAllowedTo('delete_own');
669
			}
670 View Code Duplication
			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())
671
				fatal_lang_error('modify_post_time_passed', false);
672
		}
673
		elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any'))
674
			isAllowedTo('delete_replies');
675
		else
676
			isAllowedTo('delete_any');
677
678
		if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !allowedTo('delete_own'))
679
			isAllowedTo('approve_posts');
680
	}
681
682
	// Delete the *whole* topic, but only if the topic consists of one message.
683
	if ($row['id_first_msg'] == $message)
684
	{
685
		if (empty($board) || $row['id_board'] != $board)
686
		{
687
			$remove_any = boardsAllowedTo('remove_any');
688
			$remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any);
689
			if (!$remove_any)
690
			{
691
				$remove_own = boardsAllowedTo('remove_own');
692
				$remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own);
693
			}
694
695
			if ($row['id_member'] != $user_info['id'] && !$remove_any)
696
				fatal_lang_error('cannot_remove_any', 'permission');
697
			elseif (!$remove_any && !$remove_own)
0 ignored issues
show
Bug introduced by
The variable $remove_own does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
698
				fatal_lang_error('cannot_remove_own', 'permission');
699
		}
700
		else
701
		{
702
			// Check permissions to delete a whole topic.
703
			if ($row['id_member'] != $user_info['id'])
704
				isAllowedTo('remove_any');
705
			elseif (!allowedTo('remove_any'))
706
				isAllowedTo('remove_own');
707
		}
708
709
		// ...if there is only one post.
710
		if (!empty($row['num_replies']))
711
			fatal_lang_error('delFirstPost', false);
712
713
		removeTopics($row['id_topic']);
714
		return true;
715
	}
716
717
	// Deleting a recycled message can not lower anyone's post count.
718
	if (!empty($recycle_board) && $row['id_board'] == $recycle_board)
719
		$decreasePostCount = false;
720
721
	// This is the last post, update the last post on the board.
722
	if ($row['id_last_msg'] == $message)
723
	{
724
		// Find the last message, set it, and decrease the post count.
725
		$request = $smcFunc['db_query']('', '
726
			SELECT id_msg, id_member
727
			FROM {db_prefix}messages
728
			WHERE id_topic = {int:id_topic}
729
				AND id_msg != {int:id_msg}
730
			ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC
731
			LIMIT 1',
732
			array(
733
				'id_topic' => $row['id_topic'],
734
				'id_msg' => $message,
735
			)
736
		);
737
		$row2 = $smcFunc['db_fetch_assoc']($request);
738
		$smcFunc['db_free_result']($request);
739
740
		$smcFunc['db_query']('', '
741
			UPDATE {db_prefix}topics
742
			SET
743
				id_last_msg = {int:id_last_msg},
744
				id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ',
745
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ',
746
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
747
			WHERE id_topic = {int:id_topic}',
748
			array(
749
				'id_last_msg' => $row2['id_msg'],
750
				'id_member_updated' => $row2['id_member'],
751
				'no_replies' => 0,
752
				'no_unapproved' => 0,
753
				'id_topic' => $row['id_topic'],
754
			)
755
		);
756
	}
757
	// Only decrease post counts.
758
	else
759
		$smcFunc['db_query']('', '
760
			UPDATE {db_prefix}topics
761
			SET ' . ($row['approved'] ? '
762
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : '
763
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
764
			WHERE id_topic = {int:id_topic}',
765
			array(
766
				'no_replies' => 0,
767
				'no_unapproved' => 0,
768
				'id_topic' => $row['id_topic'],
769
			)
770
		);
771
772
	// Default recycle to false.
773
	$recycle = false;
774
775
	// If recycle topics has been set, make a copy of this message in the recycle board.
776
	// Make sure we're not recycling messages that are already on the recycle board.
777
	if (!empty($modSettings['recycle_enable']) && $row['id_board'] != $modSettings['recycle_board'] && $row['icon'] != 'recycled')
778
	{
779
		// Check if the recycle board exists and if so get the read status.
780
		$request = $smcFunc['db_query']('', '
781
			SELECT (COALESCE(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg
782
			FROM {db_prefix}boards AS b
783
				LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
784
			WHERE b.id_board = {int:recycle_board}',
785
			array(
786
				'current_member' => $user_info['id'],
787
				'recycle_board' => $modSettings['recycle_board'],
788
			)
789
		);
790
		if ($smcFunc['db_num_rows']($request) == 0)
791
			fatal_lang_error('recycle_no_valid_board');
792
		list ($isRead, $last_board_msg) = $smcFunc['db_fetch_row']($request);
793
		$smcFunc['db_free_result']($request);
794
795
		// Is there an existing topic in the recycle board to group this post with?
796
		$request = $smcFunc['db_query']('', '
797
			SELECT id_topic, id_first_msg, id_last_msg
798
			FROM {db_prefix}topics
799
			WHERE id_previous_topic = {int:id_previous_topic}
800
				AND id_board = {int:recycle_board}',
801
			array(
802
				'id_previous_topic' => $row['id_topic'],
803
				'recycle_board' => $modSettings['recycle_board'],
804
			)
805
		);
806
		list ($id_recycle_topic, $first_topic_msg, $last_topic_msg) = $smcFunc['db_fetch_row']($request);
807
		$smcFunc['db_free_result']($request);
808
809
		// Insert a new topic in the recycle board if $id_recycle_topic is empty.
810
		if (empty($id_recycle_topic))
811
			$id_topic = $smcFunc['db_insert']('',
812
				'{db_prefix}topics',
813
				array(
814
					'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
815
					'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int',
816
				),
817
				array(
818
					$modSettings['recycle_board'], $row['id_member'], $row['id_member'], $message,
819
					$message, 0, 1, $row['id_topic'],
820
				),
821
				array('id_topic'),
822
				1
823
			);
824
825
		// Capture the ID of the new topic...
826
		$topicID = empty($id_recycle_topic) ? $id_topic : $id_recycle_topic;
0 ignored issues
show
Bug introduced by
The variable $id_topic does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
827
828
		// If the topic creation went successful, move the message.
829
		if ($topicID > 0)
830
		{
831
			$smcFunc['db_query']('', '
832
				UPDATE {db_prefix}messages
833
				SET
834
					id_topic = {int:id_topic},
835
					id_board = {int:recycle_board},
836
					approved = {int:is_approved}
837
				WHERE id_msg = {int:id_msg}',
838
				array(
839
					'id_topic' => $topicID,
840
					'recycle_board' => $modSettings['recycle_board'],
841
					'id_msg' => $message,
842
					'is_approved' => 1,
843
				)
844
			);
845
846
			// Take any reported posts with us...
847
			$smcFunc['db_query']('', '
848
				UPDATE {db_prefix}log_reported
849
				SET
850
					id_topic = {int:id_topic},
851
					id_board = {int:recycle_board}
852
				WHERE id_msg = {int:id_msg}',
853
				array(
854
					'id_topic' => $topicID,
855
					'recycle_board' => $modSettings['recycle_board'],
856
					'id_msg' => $message,
857
				)
858
			);
859
860
			// Mark recycled topic as read.
861
			if (!$user_info['is_guest'])
862
				$smcFunc['db_insert']('replace',
863
					'{db_prefix}log_topics',
864
					array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
865
					array($topicID, $user_info['id'], $modSettings['maxMsgID'], 0),
866
					array('id_topic', 'id_member')
867
				);
868
869
			// Mark recycle board as seen, if it was marked as seen before.
870
			if (!empty($isRead) && !$user_info['is_guest'])
871
				$smcFunc['db_insert']('replace',
872
					'{db_prefix}log_boards',
873
					array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
874
					array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']),
875
					array('id_board', 'id_member')
876
				);
877
878
			// Add one topic and post to the recycle bin board.
879
			$smcFunc['db_query']('', '
880
				UPDATE {db_prefix}boards
881
				SET
882
					num_topics = num_topics + {int:num_topics_inc},
883
					num_posts = num_posts + 1' .
884
						($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . '
885
				WHERE id_board = {int:recycle_board}',
886
				array(
887
					'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0,
888
					'recycle_board' => $modSettings['recycle_board'],
889
					'id_merged_msg' => $message,
890
				)
891
			);
892
893
			// Lets increase the num_replies, and the first/last message ID as appropriate.
894
			if (!empty($id_recycle_topic))
895
				$smcFunc['db_query']('', '
896
					UPDATE {db_prefix}topics
897
					SET num_replies = num_replies + 1' .
898
						($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') .
899
						($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . '
900
					WHERE id_topic = {int:id_recycle_topic}',
901
					array(
902
						'id_recycle_topic' => $id_recycle_topic,
903
						'id_merged_msg' => $message,
904
					)
905
				);
906
907
			// Make sure this message isn't getting deleted later on.
908
			$recycle = true;
909
910
			// Make sure we update the search subject index.
911
			updateStats('subject', $topicID, $row['subject']);
912
		}
913
914
		// If it wasn't approved don't keep it in the queue.
915
		if (!$row['approved'])
916
			$smcFunc['db_query']('', '
917
				DELETE FROM {db_prefix}approval_queue
918
				WHERE id_msg = {int:id_msg}
919
					AND id_attach = {int:id_attach}',
920
				array(
921
					'id_msg' => $message,
922
					'id_attach' => 0,
923
				)
924
			);
925
	}
926
927
	$smcFunc['db_query']('', '
928
		UPDATE {db_prefix}boards
929
		SET ' . ($row['approved'] ? '
930
			num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : '
931
			unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
932
		WHERE id_board = {int:id_board}',
933
		array(
934
			'no_posts' => 0,
935
			'no_unapproved' => 0,
936
			'id_board' => $row['id_board'],
937
		)
938
	);
939
940
	// If the poster was registered and the board this message was on incremented
941
	// the member's posts when it was posted, decrease his or her post count.
942
	if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved'])
943
		updateMemberData($row['id_member'], array('posts' => '-'));
944
945
	// Only remove posts if they're not recycled.
946
	if (!$recycle)
947
	{
948
		// Callback for search APIs to do their thing
949
		require_once($sourcedir . '/Search.php');
950
		$searchAPI = findSearchAPI();
951
		if ($searchAPI->supportsMethod('postRemoved'))
952
			$searchAPI->postRemoved($message);
953
954
		// Remove the message!
955
		$smcFunc['db_query']('', '
956
			DELETE FROM {db_prefix}messages
957
			WHERE id_msg = {int:id_msg}',
958
			array(
959
				'id_msg' => $message,
960
			)
961
		);
962
963
		if (!empty($modSettings['search_custom_index_config']))
964
		{
965
			$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
966
			$words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
967
			if (!empty($words))
968
				$smcFunc['db_query']('', '
969
					DELETE FROM {db_prefix}log_search_words
970
					WHERE id_word IN ({array_int:word_list})
971
						AND id_msg = {int:id_msg}',
972
					array(
973
						'word_list' => $words,
974
						'id_msg' => $message,
975
					)
976
				);
977
		}
978
979
		// Delete attachment(s) if they exist.
980
		require_once($sourcedir . '/ManageAttachments.php');
981
		$attachmentQuery = array(
982
			'attachment_type' => 0,
983
			'id_msg' => $message,
984
		);
985
		removeAttachments($attachmentQuery);
986
987
		// Allow mods to remove message related data of their own (likes, maybe?)
988
		call_integration_hook('integrate_remove_message', array($message));
989
	}
990
991
	// Update the pesky statistics.
992
	updateStats('message');
993
	updateStats('topic');
994
	updateSettings(array(
995
		'calendar_updated' => time(),
996
	));
997
998
	// And now to update the last message of each board we messed with.
999
	require_once($sourcedir . '/Subs-Post.php');
1000
	if ($recycle)
1001
		updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
1002
	else
1003
		updateLastMessages($row['id_board']);
1004
1005
	// Close any moderation reports for this message.
1006
	$smcFunc['db_query']('', '
1007
		UPDATE {db_prefix}log_reported
1008
		SET closed = {int:is_closed}
1009
		WHERE id_msg = {int:id_msg}',
1010
		array(
1011
			'is_closed' => 1,
1012
			'id_msg' => $message,
1013
		)
1014
	);
1015 View Code Duplication
	if ($smcFunc['db_affected_rows']() != 0)
1016
	{
1017
		require_once($sourcedir . '/ModerationCenter.php');
1018
		updateSettings(array('last_mod_report_action' => time()));
1019
		recountOpenReports('posts');
1020
	}
1021
1022
	return false;
1023
}
1024
1025
/**
1026
 * Move back a topic from the recycle board to its original board.
1027
 */
1028
function RestoreTopic()
1029
{
1030
	global $smcFunc, $modSettings, $sourcedir;
1031
1032
	// Check session.
1033
	checkSession('get');
1034
1035
	// Is recycled board enabled?
1036
	if (empty($modSettings['recycle_enable']))
1037
		fatal_lang_error('restored_disabled', 'critical');
1038
1039
	// Can we be in here?
1040
	isAllowedTo('move_any', $modSettings['recycle_board']);
1041
1042
	// We need this file.
1043
	require_once($sourcedir . '/MoveTopic.php');
1044
1045
	$unfound_messages = array();
1046
	$topics_to_restore = array();
1047
1048
	// Restoring messages?
1049
	if (!empty($_REQUEST['msgs']))
1050
	{
1051
		$msgs = explode(',', $_REQUEST['msgs']);
1052
		foreach ($msgs as $k => $msg)
1053
			$msgs[$k] = (int) $msg;
1054
1055
		// Get the id_previous_board and id_previous_topic.
1056
		$request = $smcFunc['db_query']('', '
1057
			SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic,
1058
				t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board
1059
			FROM {db_prefix}messages AS m
1060
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1061
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1062
				LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic)
1063
			WHERE m.id_msg IN ({array_int:messages})',
1064
			array(
1065
				'messages' => $msgs,
1066
			)
1067
		);
1068
1069
		$actioned_messages = array();
1070
		$previous_topics = array();
1071
		while ($row = $smcFunc['db_fetch_assoc']($request))
1072
		{
1073
			// Restoring the first post means topic.
1074
			if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic'])
1075
			{
1076
				$topics_to_restore[] = $row['id_topic'];
1077
				continue;
1078
			}
1079
			// Don't know where it's going?
1080
			if (empty($row['id_previous_topic']))
1081
			{
1082
				$unfound_messages[$row['id_msg']] = $row['subject'];
1083
				continue;
1084
			}
1085
1086
			$previous_topics[] = $row['id_previous_topic'];
1087
			if (empty($actioned_messages[$row['id_previous_topic']]))
1088
				$actioned_messages[$row['id_previous_topic']] = array(
1089
					'msgs' => array(),
1090
					'count_posts' => $row['count_posts'],
1091
					'subject' => $row['subject'],
1092
					'previous_board' => $row['id_previous_board'],
1093
					'possible_prev_board' => $row['possible_prev_board'],
1094
					'current_topic' => $row['id_topic'],
1095
					'current_board' => $row['id_board'],
1096
					'members' => array(),
1097
				);
1098
1099
			$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject'];
1100
			if ($row['id_member'])
1101
				$actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member'];
1102
		}
1103
		$smcFunc['db_free_result']($request);
1104
1105
		// Check for topics we are going to fully restore.
1106
		foreach ($actioned_messages as $topic => $data)
1107
			if (in_array($topic, $topics_to_restore))
1108
				unset($actioned_messages[$topic]);
1109
1110
		// Load any previous topics to check they exist.
1111
		if (!empty($previous_topics))
1112
		{
1113
			$request = $smcFunc['db_query']('', '
1114
				SELECT t.id_topic, t.id_board, m.subject
1115
				FROM {db_prefix}topics AS t
1116
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1117
				WHERE t.id_topic IN ({array_int:previous_topics})',
1118
				array(
1119
					'previous_topics' => $previous_topics,
1120
				)
1121
			);
1122
			$previous_topics = array();
1123
			while ($row = $smcFunc['db_fetch_assoc']($request))
1124
				$previous_topics[$row['id_topic']] = array(
1125
					'board' => $row['id_board'],
1126
					'subject' => $row['subject'],
1127
				);
1128
			$smcFunc['db_free_result']($request);
1129
		}
1130
1131
		// Restore each topic.
1132
		$messages = array();
1133
		foreach ($actioned_messages as $topic => $data)
1134
		{
1135
			// If we have topics we are going to restore the whole lot ignore them.
1136
			if (in_array($topic, $topics_to_restore))
1137
			{
1138
				unset($actioned_messages[$topic]);
1139
				continue;
1140
			}
1141
1142
			// Move the posts back then!
1143
			if (isset($previous_topics[$topic]))
1144
			{
1145
				mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic);
1146
				// Log em.
1147
				logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board']));
1148
				$messages = array_merge(array_keys($data['msgs']), $messages);
1149
			}
1150
			else
1151
			{
1152
				foreach ($data['msgs'] as $msg)
1153
					$unfound_messages[$msg['id']] = $msg['subject'];
1154
			}
1155
		}
1156
	}
1157
1158
	// Now any topics?
1159
	if (!empty($_REQUEST['topics']))
1160
	{
1161
		$topics = explode(',', $_REQUEST['topics']);
1162
		foreach ($topics as $id)
1163
			$topics_to_restore[] = (int) $id;
1164
	}
1165
1166
	if (!empty($topics_to_restore))
1167
	{
1168
		// Lets get the data for these topics.
1169
		$request = $smcFunc['db_query']('', '
1170
			SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject
1171
			FROM {db_prefix}topics AS t
1172
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1173
			WHERE t.id_topic IN ({array_int:topics})',
1174
			array(
1175
				'topics' => $topics_to_restore,
1176
			)
1177
		);
1178
		while ($row = $smcFunc['db_fetch_assoc']($request))
1179
		{
1180
			// We can only restore if the previous board is set.
1181
			if (empty($row['id_previous_board']))
1182
			{
1183
				$unfound_messages[$row['id_first_msg']] = $row['subject'];
1184
				continue;
1185
			}
1186
1187
			// Ok we got here so me move them from here to there.
1188
			moveTopics($row['id_topic'], $row['id_previous_board']);
1189
1190
			// Lets see if the board that we are returning to has post count enabled.
1191
			$request2 = $smcFunc['db_query']('', '
1192
				SELECT count_posts
1193
				FROM {db_prefix}boards
1194
				WHERE id_board = {int:board}',
1195
				array(
1196
					'board' => $row['id_previous_board'],
1197
				)
1198
			);
1199
			list ($count_posts) = $smcFunc['db_fetch_row']($request2);
1200
			$smcFunc['db_free_result']($request2);
1201
1202 View Code Duplication
			if (empty($count_posts))
1203
			{
1204
				// Lets get the members that need their post count restored.
1205
				$request2 = $smcFunc['db_query']('', '
1206
					SELECT id_member, COUNT(id_msg) AS post_count
1207
					FROM {db_prefix}messages
1208
					WHERE id_topic = {int:topic}
1209
						AND approved = {int:is_approved}
1210
					GROUP BY id_member',
1211
					array(
1212
						'topic' => $row['id_topic'],
1213
						'is_approved' => 1,
1214
					)
1215
				);
1216
1217
				while ($member = $smcFunc['db_fetch_assoc']($request2))
1218
					updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count']));
1219
				$smcFunc['db_free_result']($request2);
1220
			}
1221
1222
			// Log it.
1223
			logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board']));
1224
		}
1225
		$smcFunc['db_free_result']($request);
1226
	}
1227
1228
	// Didn't find some things?
1229
	if (!empty($unfound_messages))
1230
		fatal_lang_error('restore_not_found', false, array(implode('<br>', $unfound_messages)));
1231
1232
	// Just send them to the index if they get here.
1233
	redirectexit();
1234
}
1235
1236
/**
1237
 * Take a load of messages from one place and stick them in a topic
1238
 *
1239
 * @param array $msgs The IDs of the posts to merge
1240
 * @param integer $from_topic The ID of the topic the messages were originally in
1241
 * @param integer $target_topic The ID of the topic the messages are being merged into
1242
 */
1243
function mergePosts($msgs, $from_topic, $target_topic)
1244
{
1245
	global $smcFunc, $sourcedir;
1246
1247
	//!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient.
1248
1249
	// Is it an array?
1250
	if (!is_array($msgs))
1251
		$msgs = array($msgs);
1252
1253
	// Lets make sure they are int.
1254
	foreach ($msgs as $key => $msg)
1255
		$msgs[$key] = (int) $msg;
1256
1257
	// Get the source information.
1258
	$request = $smcFunc['db_query']('', '
1259
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts
1260
		FROM {db_prefix}topics AS t
1261
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1262
		WHERE t.id_topic = {int:from_topic}',
1263
		array(
1264
			'from_topic' => $from_topic,
1265
		)
1266
	);
1267
	list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request);
1268
	$smcFunc['db_free_result']($request);
1269
1270
	// Get some target topic and board stats.
1271
	$request = $smcFunc['db_query']('', '
1272
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts
1273
		FROM {db_prefix}topics AS t
1274
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1275
		WHERE t.id_topic = {int:target_topic}',
1276
		array(
1277
			'target_topic' => $target_topic,
1278
		)
1279
	);
1280
	list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request);
1281
	$smcFunc['db_free_result']($request);
1282
1283
	// Lets see if the board that we are returning to has post count enabled.
1284
	if (empty($count_posts))
1285
	{
1286
		// Lets get the members that need their post count restored.
1287
		$request = $smcFunc['db_query']('', '
1288
			SELECT id_member
1289
			FROM {db_prefix}messages
1290
			WHERE id_msg IN ({array_int:messages})
1291
				AND approved = {int:is_approved}',
1292
			array(
1293
				'messages' => $msgs,
1294
				'is_approved' => 1,
1295
			)
1296
		);
1297
1298
		while ($row = $smcFunc['db_fetch_assoc']($request))
1299
			updateMemberData($row['id_member'], array('posts' => '+'));
1300
	}
1301
1302
	// Time to move the messages.
1303
	$smcFunc['db_query']('', '
1304
		UPDATE {db_prefix}messages
1305
		SET
1306
			id_topic = {int:target_topic},
1307
			id_board = {int:target_board}
1308
		WHERE id_msg IN({array_int:msgs})',
1309
		array(
1310
			'target_topic' => $target_topic,
1311
			'target_board' => $target_board,
1312
			'msgs' => $msgs,
1313
		)
1314
	);
1315
1316
	// Fix the id_first_msg and id_last_msg for the target topic.
1317
	$target_topic_data = array(
1318
		'num_replies' => 0,
1319
		'unapproved_posts' => 0,
1320
		'id_first_msg' => 9999999999,
1321
	);
1322
	$request = $smcFunc['db_query']('', '
1323
		SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved
1324
		FROM {db_prefix}messages
1325
		WHERE id_topic = {int:target_topic}
1326
		GROUP BY id_topic, approved
1327
		ORDER BY approved ASC
1328
		LIMIT 2',
1329
		array(
1330
			'target_topic' => $target_topic,
1331
		)
1332
	);
1333 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
1334
	{
1335
		if ($row['id_first_msg'] < $target_topic_data['id_first_msg'])
1336
			$target_topic_data['id_first_msg'] = $row['id_first_msg'];
1337
		$target_topic_data['id_last_msg'] = $row['id_last_msg'];
1338
		if (!$row['approved'])
1339
			$target_topic_data['unapproved_posts'] = $row['message_count'];
1340
		else
1341
			$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1342
	}
1343
	$smcFunc['db_free_result']($request);
1344
1345
	// We have a new post count for the board.
1346
	$smcFunc['db_query']('', '
1347
		UPDATE {db_prefix}boards
1348
		SET
1349
			num_posts = num_posts + {int:diff_replies},
1350
			unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1351
		WHERE id_board = {int:target_board}',
1352
		array(
1353
			'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.
1354
			'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts,
1355
			'target_board' => $target_board,
1356
		)
1357
	);
1358
1359
	// In some cases we merged the only post in a topic so the topic data is left behind in the topic table.
1360
	$request = $smcFunc['db_query']('', '
1361
		SELECT id_topic
1362
		FROM {db_prefix}messages
1363
		WHERE id_topic = {int:from_topic}',
1364
		array(
1365
			'from_topic' => $from_topic,
1366
		)
1367
	);
1368
1369
	// Remove the topic if it doesn't have any messages.
1370
	$topic_exists = true;
1371
	if ($smcFunc['db_num_rows']($request) == 0)
1372
	{
1373
		removeTopics($from_topic, false, true);
1374
		$topic_exists = false;
1375
	}
1376
	$smcFunc['db_free_result']($request);
1377
1378
	// Recycled topic.
1379
	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...
1380
	{
1381
		// Fix the id_first_msg and id_last_msg for the source topic.
1382
		$source_topic_data = array(
1383
			'num_replies' => 0,
1384
			'unapproved_posts' => 0,
1385
			'id_first_msg' => 9999999999,
1386
		);
1387
		$request = $smcFunc['db_query']('', '
1388
			SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject
1389
			FROM {db_prefix}messages
1390
			WHERE id_topic = {int:from_topic}
1391
			GROUP BY id_topic, approved
1392
			ORDER BY approved ASC
1393
			LIMIT 2',
1394
			array(
1395
				'from_topic' => $from_topic,
1396
			)
1397
		);
1398 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1399
		{
1400
			if ($row['id_first_msg'] < $source_topic_data['id_first_msg'])
1401
				$source_topic_data['id_first_msg'] = $row['id_first_msg'];
1402
			$source_topic_data['id_last_msg'] = $row['id_last_msg'];
1403
			if (!$row['approved'])
1404
				$source_topic_data['unapproved_posts'] = $row['message_count'];
1405
			else
1406
				$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1407
		}
1408
		$smcFunc['db_free_result']($request);
1409
1410
		// Update the topic details for the source topic.
1411
		$smcFunc['db_query']('', '
1412
			UPDATE {db_prefix}topics
1413
			SET
1414
				id_first_msg = {int:id_first_msg},
1415
				id_last_msg = {int:id_last_msg},
1416
				num_replies = {int:num_replies},
1417
				unapproved_posts = {int:unapproved_posts}
1418
			WHERE id_topic = {int:from_topic}',
1419
			array(
1420
				'id_first_msg' => $source_topic_data['id_first_msg'],
1421
				'id_last_msg' => $source_topic_data['id_last_msg'],
1422
				'num_replies' => $source_topic_data['num_replies'],
1423
				'unapproved_posts' => $source_topic_data['unapproved_posts'],
1424
				'from_topic' => $from_topic,
1425
			)
1426
		);
1427
1428
		// We have a new post count for the source board.
1429
		$smcFunc['db_query']('', '
1430
			UPDATE {db_prefix}boards
1431
			SET
1432
				num_posts = num_posts + {int:diff_replies},
1433
				unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1434
			WHERE id_board = {int:from_board}',
1435
			array(
1436
				'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.
1437
				'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts,
1438
				'from_board' => $from_board,
1439
			)
1440
		);
1441
	}
1442
1443
	// Finally get around to updating the destination topic, now all indexes etc on the source are fixed.
1444
	$smcFunc['db_query']('', '
1445
		UPDATE {db_prefix}topics
1446
		SET
1447
			id_first_msg = {int:id_first_msg},
1448
			id_last_msg = {int:id_last_msg},
1449
			num_replies = {int:num_replies},
1450
			unapproved_posts = {int:unapproved_posts}
1451
		WHERE id_topic = {int:target_topic}',
1452
		array(
1453
			'id_first_msg' => $target_topic_data['id_first_msg'],
1454
			'id_last_msg' => $target_topic_data['id_last_msg'],
1455
			'num_replies' => $target_topic_data['num_replies'],
1456
			'unapproved_posts' => $target_topic_data['unapproved_posts'],
1457
			'target_topic' => $target_topic,
1458
		)
1459
	);
1460
1461
	// Need it to update some stats.
1462
	require_once($sourcedir . '/Subs-Post.php');
1463
1464
	// Update stats.
1465
	updateStats('topic');
1466
	updateStats('message');
1467
1468
	// Subject cache?
1469
	$cache_updates = array();
1470
	if ($target_first_msg != $target_topic_data['id_first_msg'])
1471
		$cache_updates[] = $target_topic_data['id_first_msg'];
1472
	if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg'])
1473
		$cache_updates[] = $source_topic_data['id_first_msg'];
1474
1475 View Code Duplication
	if (!empty($cache_updates))
1476
	{
1477
		$request = $smcFunc['db_query']('', '
1478
			SELECT id_topic, subject
1479
			FROM {db_prefix}messages
1480
			WHERE id_msg IN ({array_int:first_messages})',
1481
			array(
1482
				'first_messages' => $cache_updates,
1483
			)
1484
		);
1485
		while ($row = $smcFunc['db_fetch_assoc']($request))
1486
			updateStats('subject', $row['id_topic'], $row['subject']);
1487
		$smcFunc['db_free_result']($request);
1488
	}
1489
1490
	updateLastMessages(array($from_board, $target_board));
1491
}
1492
1493
/**
1494
 * Try to determine if the topic has already been deleted by another user.
1495
 * @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.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1496
 */
1497
function removeDeleteConcurrence()
1498
{
1499
	global $modSettings, $board, $scripturl, $context;
1500
1501
	// No recycle no need to go further
1502
	if (empty($modSettings['recycle_enable']) || empty($modSettings['recycle_board']))
1503
		return false;
1504
1505
	// If it's confirmed go on and delete (from recycle)
1506
	if (isset($_GET['confirm_delete']))
1507
		return true;
1508
1509
	if (empty($board))
1510
		return false;
1511
1512
	if ($modSettings['recycle_board'] != $board)
1513
		return true;
1514
	elseif (isset($_REQUEST['msg']))
1515
		$confirm_url = $scripturl . '?action=deletemsg;confirm_delete;topic=' . $context['current_topic'] . '.0;msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1516
	else
1517
		$confirm_url = $scripturl . '?action=removetopic2;confirm_delete;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id'];
1518
1519
	fatal_lang_error('post_already_deleted', false, array($confirm_url));
1520
}
1521
1522
?>