Completed
Push — release-2.1 ( 5876a2...f30e04 )
by Mert
06:50
created

RemoveTopic.php ➔ removeTopics()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 331
Code Lines 158

Duplication

Lines 46
Ratio 13.9 %
Metric Value
cc 31
eloc 158
nc 62281
nop 4
dl 46
loc 331
rs 2

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 2016 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 3
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'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
			fatal_lang_error('modify_post_time_passed', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 = 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));
0 ignored issues
show
Unused Code introduced by
The call to text2words() has too many arguments starting with $customIndexSettings['bytes_per_word'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
671
				fatal_lang_error('modify_post_time_passed', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
			$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
			);
823
824
		// Capture the ID of the new topic...
825
		$topicID = empty($id_recycle_topic) ? $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic') : $id_recycle_topic;
826
827
		// If the topic creation went successful, move the message.
828
		if ($topicID > 0)
829
		{
830
			$smcFunc['db_query']('', '
831
				UPDATE {db_prefix}messages
832
				SET
833
					id_topic = {int:id_topic},
834
					id_board = {int:recycle_board},
835
					approved = {int:is_approved}
836
				WHERE id_msg = {int:id_msg}',
837
				array(
838
					'id_topic' => $topicID,
839
					'recycle_board' => $modSettings['recycle_board'],
840
					'id_msg' => $message,
841
					'is_approved' => 1,
842
				)
843
			);
844
845
			// Take any reported posts with us...
846
			$smcFunc['db_query']('', '
847
				UPDATE {db_prefix}log_reported
848
				SET
849
					id_topic = {int:id_topic},
850
					id_board = {int:recycle_board}
851
				WHERE id_msg = {int:id_msg}',
852
				array(
853
					'id_topic' => $topicID,
854
					'recycle_board' => $modSettings['recycle_board'],
855
					'id_msg' => $message,
856
				)
857
			);
858
859
			// Mark recycled topic as read.
860
			if (!$user_info['is_guest'])
861
				$smcFunc['db_insert']('replace',
862
					'{db_prefix}log_topics',
863
					array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
864
					array($topicID, $user_info['id'], $modSettings['maxMsgID'], 0),
865
					array('id_topic', 'id_member')
866
				);
867
868
			// Mark recycle board as seen, if it was marked as seen before.
869
			if (!empty($isRead) && !$user_info['is_guest'])
870
				$smcFunc['db_insert']('replace',
871
					'{db_prefix}log_boards',
872
					array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
873
					array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']),
874
					array('id_board', 'id_member')
875
				);
876
877
			// Add one topic and post to the recycle bin board.
878
			$smcFunc['db_query']('', '
879
				UPDATE {db_prefix}boards
880
				SET
881
					num_topics = num_topics + {int:num_topics_inc},
882
					num_posts = num_posts + 1' .
883
						($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . '
884
				WHERE id_board = {int:recycle_board}',
885
				array(
886
					'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0,
887
					'recycle_board' => $modSettings['recycle_board'],
888
					'id_merged_msg' => $message,
889
				)
890
			);
891
892
			// Lets increase the num_replies, and the first/last message ID as appropriate.
893
			if (!empty($id_recycle_topic))
894
				$smcFunc['db_query']('', '
895
					UPDATE {db_prefix}topics
896
					SET num_replies = num_replies + 1' .
897
						($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') .
898
						($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . '
899
					WHERE id_topic = {int:id_recycle_topic}',
900
					array(
901
						'id_recycle_topic' => $id_recycle_topic,
902
						'id_merged_msg' => $message,
903
					)
904
				);
905
906
			// Make sure this message isn't getting deleted later on.
907
			$recycle = true;
908
909
			// Make sure we update the search subject index.
910
			updateStats('subject', $topicID, $row['subject']);
911
		}
912
913
		// If it wasn't approved don't keep it in the queue.
914
		if (!$row['approved'])
915
			$smcFunc['db_query']('', '
916
				DELETE FROM {db_prefix}approval_queue
917
				WHERE id_msg = {int:id_msg}
918
					AND id_attach = {int:id_attach}',
919
				array(
920
					'id_msg' => $message,
921
					'id_attach' => 0,
922
				)
923
			);
924
	}
925
926
	$smcFunc['db_query']('', '
927
		UPDATE {db_prefix}boards
928
		SET ' . ($row['approved'] ? '
929
			num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : '
930
			unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
931
		WHERE id_board = {int:id_board}',
932
		array(
933
			'no_posts' => 0,
934
			'no_unapproved' => 0,
935
			'id_board' => $row['id_board'],
936
		)
937
	);
938
939
	// If the poster was registered and the board this message was on incremented
940
	// the member's posts when it was posted, decrease his or her post count.
941
	if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved'])
942
		updateMemberData($row['id_member'], array('posts' => '-'));
943
944
	// Only remove posts if they're not recycled.
945
	if (!$recycle)
946
	{
947
		// Callback for search APIs to do their thing
948
		require_once($sourcedir . '/Search.php');
949
		$searchAPI = findSearchAPI();
950
		if ($searchAPI->supportsMethod('postRemoved'))
951
			$searchAPI->postRemoved($message);
952
953
		// Remove the message!
954
		$smcFunc['db_query']('', '
955
			DELETE FROM {db_prefix}messages
956
			WHERE id_msg = {int:id_msg}',
957
			array(
958
				'id_msg' => $message,
959
			)
960
		);
961
962
		if (!empty($modSettings['search_custom_index_config']))
963
		{
964
			$customIndexSettings = json_decode($modSettings['search_custom_index_config'], true);
965
			$words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
0 ignored issues
show
Unused Code introduced by
The call to text2words() has too many arguments starting with $customIndexSettings['bytes_per_word'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
966
			if (!empty($words))
967
				$smcFunc['db_query']('', '
968
					DELETE FROM {db_prefix}log_search_words
969
					WHERE id_word IN ({array_int:word_list})
970
						AND id_msg = {int:id_msg}',
971
					array(
972
						'word_list' => $words,
973
						'id_msg' => $message,
974
					)
975
				);
976
		}
977
978
		// Delete attachment(s) if they exist.
979
		require_once($sourcedir . '/ManageAttachments.php');
980
		$attachmentQuery = array(
981
			'attachment_type' => 0,
982
			'id_msg' => $message,
983
		);
984
		removeAttachments($attachmentQuery);
985
986
		// Allow mods to remove message related data of their own (likes, maybe?)
987
		call_integration_hook('integrate_remove_message', array($message));
988
	}
989
990
	// Update the pesky statistics.
991
	updateStats('message');
992
	updateStats('topic');
993
	updateSettings(array(
994
		'calendar_updated' => time(),
995
	));
996
997
	// And now to update the last message of each board we messed with.
998
	require_once($sourcedir . '/Subs-Post.php');
999
	if ($recycle)
1000
		updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
1001
	else
1002
		updateLastMessages($row['id_board']);
1003
1004
	// Close any moderation reports for this message.
1005
	$smcFunc['db_query']('', '
1006
		UPDATE {db_prefix}log_reported
1007
		SET closed = {int:is_closed}
1008
		WHERE id_msg = {int:id_msg}',
1009
		array(
1010
			'is_closed' => 1,
1011
			'id_msg' => $message,
1012
		)
1013
	);
1014 View Code Duplication
	if ($smcFunc['db_affected_rows']() != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1015
	{
1016
		require_once($sourcedir . '/ModerationCenter.php');
1017
		updateSettings(array('last_mod_report_action' => time()));
1018
		recountOpenReports('posts');
1019
	}
1020
1021
	return false;
1022
}
1023
1024
/**
1025
 * Move back a topic from the recycle board to its original board.
1026
 */
1027
function RestoreTopic()
1028
{
1029
	global $smcFunc, $modSettings, $sourcedir;
1030
1031
	// Check session.
1032
	checkSession('get');
1033
1034
	// Is recycled board enabled?
1035
	if (empty($modSettings['recycle_enable']))
1036
		fatal_lang_error('restored_disabled', 'critical');
1037
1038
	// Can we be in here?
1039
	isAllowedTo('move_any', $modSettings['recycle_board']);
1040
1041
	// We need this file.
1042
	require_once($sourcedir . '/MoveTopic.php');
1043
1044
	$unfound_messages = array();
1045
	$topics_to_restore = array();
1046
1047
	// Restoring messages?
1048
	if (!empty($_REQUEST['msgs']))
1049
	{
1050
		$msgs = explode(',', $_REQUEST['msgs']);
1051
		foreach ($msgs as $k => $msg)
1052
			$msgs[$k] = (int) $msg;
1053
1054
		// Get the id_previous_board and id_previous_topic.
1055
		$request = $smcFunc['db_query']('', '
1056
			SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic,
1057
				t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board
1058
			FROM {db_prefix}messages AS m
1059
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1060
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1061
				LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic)
1062
			WHERE m.id_msg IN ({array_int:messages})',
1063
			array(
1064
				'messages' => $msgs,
1065
			)
1066
		);
1067
1068
		$actioned_messages = array();
1069
		$previous_topics = array();
1070
		while ($row = $smcFunc['db_fetch_assoc']($request))
1071
		{
1072
			// Restoring the first post means topic.
1073
			if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic'])
1074
			{
1075
				$topics_to_restore[] = $row['id_topic'];
1076
				continue;
1077
			}
1078
			// Don't know where it's going?
1079
			if (empty($row['id_previous_topic']))
1080
			{
1081
				$unfound_messages[$row['id_msg']] = $row['subject'];
1082
				continue;
1083
			}
1084
1085
			$previous_topics[] = $row['id_previous_topic'];
1086
			if (empty($actioned_messages[$row['id_previous_topic']]))
1087
				$actioned_messages[$row['id_previous_topic']] = array(
1088
					'msgs' => array(),
1089
					'count_posts' => $row['count_posts'],
1090
					'subject' => $row['subject'],
1091
					'previous_board' => $row['id_previous_board'],
1092
					'possible_prev_board' => $row['possible_prev_board'],
1093
					'current_topic' => $row['id_topic'],
1094
					'current_board' => $row['id_board'],
1095
					'members' => array(),
1096
				);
1097
1098
			$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject'];
1099
			if ($row['id_member'])
1100
				$actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member'];
1101
		}
1102
		$smcFunc['db_free_result']($request);
1103
1104
		// Check for topics we are going to fully restore.
1105
		foreach ($actioned_messages as $topic => $data)
1106
			if (in_array($topic, $topics_to_restore))
1107
				unset($actioned_messages[$topic]);
1108
1109
		// Load any previous topics to check they exist.
1110
		if (!empty($previous_topics))
1111
		{
1112
			$request = $smcFunc['db_query']('', '
1113
				SELECT t.id_topic, t.id_board, m.subject
1114
				FROM {db_prefix}topics AS t
1115
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1116
				WHERE t.id_topic IN ({array_int:previous_topics})',
1117
				array(
1118
					'previous_topics' => $previous_topics,
1119
				)
1120
			);
1121
			$previous_topics = array();
1122
			while ($row = $smcFunc['db_fetch_assoc']($request))
1123
				$previous_topics[$row['id_topic']] = array(
1124
					'board' => $row['id_board'],
1125
					'subject' => $row['subject'],
1126
				);
1127
			$smcFunc['db_free_result']($request);
1128
		}
1129
1130
		// Restore each topic.
1131
		$messages = array();
1132
		foreach ($actioned_messages as $topic => $data)
1133
		{
1134
			// If we have topics we are going to restore the whole lot ignore them.
1135
			if (in_array($topic, $topics_to_restore))
1136
			{
1137
				unset($actioned_messages[$topic]);
1138
				continue;
1139
			}
1140
1141
			// Move the posts back then!
1142
			if (isset($previous_topics[$topic]))
1143
			{
1144
				mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic);
1145
				// Log em.
1146
				logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board']));
1147
				$messages = array_merge(array_keys($data['msgs']), $messages);
1148
			}
1149
			else
1150
			{
1151
				foreach ($data['msgs'] as $msg)
1152
					$unfound_messages[$msg['id']] = $msg['subject'];
1153
			}
1154
		}
1155
	}
1156
1157
	// Now any topics?
1158
	if (!empty($_REQUEST['topics']))
1159
	{
1160
		$topics = explode(',', $_REQUEST['topics']);
1161
		foreach ($topics as $id)
1162
			$topics_to_restore[] = (int) $id;
1163
	}
1164
1165
	if (!empty($topics_to_restore))
1166
	{
1167
		// Lets get the data for these topics.
1168
		$request = $smcFunc['db_query']('', '
1169
			SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject
1170
			FROM {db_prefix}topics AS t
1171
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1172
			WHERE t.id_topic IN ({array_int:topics})',
1173
			array(
1174
				'topics' => $topics_to_restore,
1175
			)
1176
		);
1177
		while ($row = $smcFunc['db_fetch_assoc']($request))
1178
		{
1179
			// We can only restore if the previous board is set.
1180
			if (empty($row['id_previous_board']))
1181
			{
1182
				$unfound_messages[$row['id_first_msg']] = $row['subject'];
1183
				continue;
1184
			}
1185
1186
			// Ok we got here so me move them from here to there.
1187
			moveTopics($row['id_topic'], $row['id_previous_board']);
1188
1189
			// Lets see if the board that we are returning to has post count enabled.
1190
			$request2 = $smcFunc['db_query']('', '
1191
				SELECT count_posts
1192
				FROM {db_prefix}boards
1193
				WHERE id_board = {int:board}',
1194
				array(
1195
					'board' => $row['id_previous_board'],
1196
				)
1197
			);
1198
			list ($count_posts) = $smcFunc['db_fetch_row']($request2);
1199
			$smcFunc['db_free_result']($request2);
1200
1201 View Code Duplication
			if (empty($count_posts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1202
			{
1203
				// Lets get the members that need their post count restored.
1204
				$request2 = $smcFunc['db_query']('', '
1205
					SELECT id_member, COUNT(id_msg) AS post_count
1206
					FROM {db_prefix}messages
1207
					WHERE id_topic = {int:topic}
1208
						AND approved = {int:is_approved}
1209
					GROUP BY id_member',
1210
					array(
1211
						'topic' => $row['id_topic'],
1212
						'is_approved' => 1,
1213
					)
1214
				);
1215
1216
				while ($member = $smcFunc['db_fetch_assoc']($request2))
1217
					updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count']));
1218
				$smcFunc['db_free_result']($request2);
1219
			}
1220
1221
			// Log it.
1222
			logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board']));
1223
		}
1224
		$smcFunc['db_free_result']($request);
1225
	}
1226
1227
	// Didn't find some things?
1228
	if (!empty($unfound_messages))
1229
		fatal_lang_error('restore_not_found', false, array(implode('<br>', $unfound_messages)));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1230
1231
	// Just send them to the index if they get here.
1232
	redirectexit();
1233
}
1234
1235
/**
1236
 * Take a load of messages from one place and stick them in a topic
1237
 *
1238
 * @param array $msgs The IDs of the posts to merge
1239
 * @param integer $from_topic The ID of the topic the messages were originally in
1240
 * @param integer $target_topic The ID of the topic the messages are being merged into
1241
 */
1242
function mergePosts($msgs, $from_topic, $target_topic)
1243
{
1244
	global $smcFunc, $sourcedir;
1245
1246
	//!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient.
1247
1248
	// Is it an array?
1249
	if (!is_array($msgs))
1250
		$msgs = array($msgs);
1251
1252
	// Lets make sure they are int.
1253
	foreach ($msgs as $key => $msg)
1254
		$msgs[$key] = (int) $msg;
1255
1256
	// Get the source information.
1257
	$request = $smcFunc['db_query']('', '
1258
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts
1259
		FROM {db_prefix}topics AS t
1260
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1261
		WHERE t.id_topic = {int:from_topic}',
1262
		array(
1263
			'from_topic' => $from_topic,
1264
		)
1265
	);
1266
	list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request);
1267
	$smcFunc['db_free_result']($request);
1268
1269
	// Get some target topic and board stats.
1270
	$request = $smcFunc['db_query']('', '
1271
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts
1272
		FROM {db_prefix}topics AS t
1273
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1274
		WHERE t.id_topic = {int:target_topic}',
1275
		array(
1276
			'target_topic' => $target_topic,
1277
		)
1278
	);
1279
	list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request);
1280
	$smcFunc['db_free_result']($request);
1281
1282
	// Lets see if the board that we are returning to has post count enabled.
1283
	if (empty($count_posts))
1284
	{
1285
		// Lets get the members that need their post count restored.
1286
		$request = $smcFunc['db_query']('', '
1287
			SELECT id_member
1288
			FROM {db_prefix}messages
1289
			WHERE id_msg IN ({array_int:messages})
1290
				AND approved = {int:is_approved}',
1291
			array(
1292
				'messages' => $msgs,
1293
				'is_approved' => 1,
1294
			)
1295
		);
1296
1297
		while ($row = $smcFunc['db_fetch_assoc']($request))
1298
			updateMemberData($row['id_member'], array('posts' => '+'));
1299
	}
1300
1301
	// Time to move the messages.
1302
	$smcFunc['db_query']('', '
1303
		UPDATE {db_prefix}messages
1304
		SET
1305
			id_topic = {int:target_topic},
1306
			id_board = {int:target_board}
1307
		WHERE id_msg IN({array_int:msgs})',
1308
		array(
1309
			'target_topic' => $target_topic,
1310
			'target_board' => $target_board,
1311
			'msgs' => $msgs,
1312
		)
1313
	);
1314
1315
	// Fix the id_first_msg and id_last_msg for the target topic.
1316
	$target_topic_data = array(
1317
		'num_replies' => 0,
1318
		'unapproved_posts' => 0,
1319
		'id_first_msg' => 9999999999,
1320
	);
1321
	$request = $smcFunc['db_query']('', '
1322
		SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved
1323
		FROM {db_prefix}messages
1324
		WHERE id_topic = {int:target_topic}
1325
		GROUP BY id_topic, approved
1326
		ORDER BY approved ASC
1327
		LIMIT 2',
1328
		array(
1329
			'target_topic' => $target_topic,
1330
		)
1331
	);
1332 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1333
	{
1334
		if ($row['id_first_msg'] < $target_topic_data['id_first_msg'])
1335
			$target_topic_data['id_first_msg'] = $row['id_first_msg'];
1336
		$target_topic_data['id_last_msg'] = $row['id_last_msg'];
1337
		if (!$row['approved'])
1338
			$target_topic_data['unapproved_posts'] = $row['message_count'];
1339
		else
1340
			$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1341
	}
1342
	$smcFunc['db_free_result']($request);
1343
1344
	// We have a new post count for the board.
1345
	$smcFunc['db_query']('', '
1346
		UPDATE {db_prefix}boards
1347
		SET
1348
			num_posts = num_posts + {int:diff_replies},
1349
			unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1350
		WHERE id_board = {int:target_board}',
1351
		array(
1352
			'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.
1353
			'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts,
1354
			'target_board' => $target_board,
1355
		)
1356
	);
1357
1358
	// In some cases we merged the only post in a topic so the topic data is left behind in the topic table.
1359
	$request = $smcFunc['db_query']('', '
1360
		SELECT id_topic
1361
		FROM {db_prefix}messages
1362
		WHERE id_topic = {int:from_topic}',
1363
		array(
1364
			'from_topic' => $from_topic,
1365
		)
1366
	);
1367
1368
	// Remove the topic if it doesn't have any messages.
1369
	$topic_exists = true;
1370
	if ($smcFunc['db_num_rows']($request) == 0)
1371
	{
1372
		removeTopics($from_topic, false, true);
1373
		$topic_exists = false;
1374
	}
1375
	$smcFunc['db_free_result']($request);
1376
1377
	// Recycled topic.
1378
	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...
1379
	{
1380
		// Fix the id_first_msg and id_last_msg for the source topic.
1381
		$source_topic_data = array(
1382
			'num_replies' => 0,
1383
			'unapproved_posts' => 0,
1384
			'id_first_msg' => 9999999999,
1385
		);
1386
		$request = $smcFunc['db_query']('', '
1387
			SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject
1388
			FROM {db_prefix}messages
1389
			WHERE id_topic = {int:from_topic}
1390
			GROUP BY id_topic, approved
1391
			ORDER BY approved ASC
1392
			LIMIT 2',
1393
			array(
1394
				'from_topic' => $from_topic,
1395
			)
1396
		);
1397 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1398
		{
1399
			if ($row['id_first_msg'] < $source_topic_data['id_first_msg'])
1400
				$source_topic_data['id_first_msg'] = $row['id_first_msg'];
1401
			$source_topic_data['id_last_msg'] = $row['id_last_msg'];
1402
			if (!$row['approved'])
1403
				$source_topic_data['unapproved_posts'] = $row['message_count'];
1404
			else
1405
				$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
1406
		}
1407
		$smcFunc['db_free_result']($request);
1408
1409
		// Update the topic details for the source topic.
1410
		$smcFunc['db_query']('', '
1411
			UPDATE {db_prefix}topics
1412
			SET
1413
				id_first_msg = {int:id_first_msg},
1414
				id_last_msg = {int:id_last_msg},
1415
				num_replies = {int:num_replies},
1416
				unapproved_posts = {int:unapproved_posts}
1417
			WHERE id_topic = {int:from_topic}',
1418
			array(
1419
				'id_first_msg' => $source_topic_data['id_first_msg'],
1420
				'id_last_msg' => $source_topic_data['id_last_msg'],
1421
				'num_replies' => $source_topic_data['num_replies'],
1422
				'unapproved_posts' => $source_topic_data['unapproved_posts'],
1423
				'from_topic' => $from_topic,
1424
			)
1425
		);
1426
1427
		// We have a new post count for the source board.
1428
		$smcFunc['db_query']('', '
1429
			UPDATE {db_prefix}boards
1430
			SET
1431
				num_posts = num_posts + {int:diff_replies},
1432
				unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
1433
			WHERE id_board = {int:from_board}',
1434
			array(
1435
				'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.
1436
				'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts,
1437
				'from_board' => $from_board,
1438
			)
1439
		);
1440
	}
1441
1442
	// Finally get around to updating the destination topic, now all indexes etc on the source are fixed.
1443
	$smcFunc['db_query']('', '
1444
		UPDATE {db_prefix}topics
1445
		SET
1446
			id_first_msg = {int:id_first_msg},
1447
			id_last_msg = {int:id_last_msg},
1448
			num_replies = {int:num_replies},
1449
			unapproved_posts = {int:unapproved_posts}
1450
		WHERE id_topic = {int:target_topic}',
1451
		array(
1452
			'id_first_msg' => $target_topic_data['id_first_msg'],
1453
			'id_last_msg' => $target_topic_data['id_last_msg'],
1454
			'num_replies' => $target_topic_data['num_replies'],
1455
			'unapproved_posts' => $target_topic_data['unapproved_posts'],
1456
			'target_topic' => $target_topic,
1457
		)
1458
	);
1459
1460
	// Need it to update some stats.
1461
	require_once($sourcedir . '/Subs-Post.php');
1462
1463
	// Update stats.
1464
	updateStats('topic');
1465
	updateStats('message');
1466
1467
	// Subject cache?
1468
	$cache_updates = array();
1469
	if ($target_first_msg != $target_topic_data['id_first_msg'])
1470
		$cache_updates[] = $target_topic_data['id_first_msg'];
1471
	if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg'])
1472
		$cache_updates[] = $source_topic_data['id_first_msg'];
1473
1474 View Code Duplication
	if (!empty($cache_updates))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1475
	{
1476
		$request = $smcFunc['db_query']('', '
1477
			SELECT id_topic, subject
1478
			FROM {db_prefix}messages
1479
			WHERE id_msg IN ({array_int:first_messages})',
1480
			array(
1481
				'first_messages' => $cache_updates,
1482
			)
1483
		);
1484
		while ($row = $smcFunc['db_fetch_assoc']($request))
1485
			updateStats('subject', $row['id_topic'], $row['subject']);
1486
		$smcFunc['db_free_result']($request);
1487
	}
1488
1489
	updateLastMessages(array($from_board, $target_board));
1490
}
1491
1492
/**
1493
 * Try to determine if the topic has already been deleted by another user.
1494
 * @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.
1495
 */
1496
function removeDeleteConcurrence()
1497
{
1498
	global $modSettings, $board, $scripturl, $context;
1499
1500
	// No recycle no need to go further
1501
	if (empty($modSettings['recycle_enable']) || empty($modSettings['recycle_board']))
1502
		return false;
1503
1504
	// If it's confirmed go on and delete (from recycle)
1505
	if (isset($_GET['confirm_delete']))
1506
		return true;
1507
1508
	if (empty($board))
1509
		return false;
1510
1511
	if ($modSettings['recycle_board'] != $board)
1512
		return true;
1513
	elseif (isset($_REQUEST['msg']))
1514
		$confirm_url = $scripturl . '?action=deletemsg;confirm_delete;topic=' . $context['current_topic'] . '.0;msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1515
	else
1516
		$confirm_url = $scripturl . '?action=removetopic2;confirm_delete;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id'];
1517
1518
	fatal_lang_error('post_already_deleted', false, array($confirm_url));
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1519
}
1520
1521
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...