Issues (1061)

Sources/SplitTopics.php (17 issues)

1
<?php
2
3
/**
4
 * Handle merging and splitting of topics
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 *
15
 * Original module by Mach8 - We'll never forget you.
16
 */
17
18
if (!defined('SMF'))
19
	die('No direct access...');
20
21
/**
22
 * splits a topic into two topics.
23
 * delegates to the other functions (based on the URL parameter 'sa').
24
 * loads the SplitTopics template.
25
 * requires the split_any permission.
26
 * is accessed with ?action=splittopics.
27
 */
28
function SplitTopics()
29
{
30
	global $topic, $sourcedir;
31
32
	// And... which topic were you splitting, again?
33
	if (empty($topic))
34
		fatal_lang_error('numbers_one_to_nine', false);
35
36
	// Are you allowed to split topics?
37
	isAllowedTo('split_any');
38
39
	// Load up the "dependencies" - the template, getMsgMemberID(), and sendNotifications().
40
	if (!isset($_REQUEST['xml']))
41
		loadTemplate('SplitTopics');
42
	require_once($sourcedir . '/Subs-Boards.php');
43
	require_once($sourcedir . '/Subs-Post.php');
44
45
	$subActions = array(
46
		'selectTopics' => 'SplitSelectTopics',
47
		'execute' => 'SplitExecute',
48
		'index' => 'SplitIndex',
49
		'splitSelection' => 'SplitSelectionExecute',
50
	);
51
52
	// ?action=splittopics;sa=LETSBREAKIT won't work, sorry.
53
	if (empty($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
54
		SplitIndex();
55
56
	else
57
		call_helper($subActions[$_REQUEST['sa']]);
58
}
59
60
/**
61
 * screen shown before the actual split.
62
 * is accessed with ?action=splittopics;sa=index.
63
 * default sub action for ?action=splittopics.
64
 * uses 'ask' sub template of the SplitTopics template.
65
 * redirects to SplitSelectTopics if the message given turns out to be
66
 * the first message of a topic.
67
 * shows the user three ways to split the current topic.
68
 */
69
function SplitIndex()
70
{
71
	global $txt, $topic, $context, $smcFunc, $modSettings;
72
73
	// Validate "at".
74
	if (empty($_GET['at']))
75
		fatal_lang_error('numbers_one_to_nine', false);
76
	$_GET['at'] = (int) $_GET['at'];
77
78
	// Retrieve the subject and stuff of the specific topic/message.
79
	$request = $smcFunc['db_query']('', '
80
		SELECT m.subject, t.num_replies, t.unapproved_posts, t.id_first_msg, t.approved
81
		FROM {db_prefix}messages AS m
82
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
83
		WHERE m.id_msg = {int:split_at}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
84
			AND m.approved = 1') . '
85
			AND m.id_topic = {int:current_topic}
86
		LIMIT 1',
87
		array(
88
			'current_topic' => $topic,
89
			'split_at' => $_GET['at'],
90
		)
91
	);
92
	if ($smcFunc['db_num_rows']($request) == 0)
93
		fatal_lang_error('cant_find_messages');
94
95
	list ($_REQUEST['subname'], $num_replies, $unapproved_posts, $id_first_msg, $approved) = $smcFunc['db_fetch_row']($request);
96
	$smcFunc['db_free_result']($request);
97
98
	// If not approved validate they can see it.
99
	if ($modSettings['postmod_active'] && !$approved)
100
		isAllowedTo('approve_posts');
101
102
	// If this topic has unapproved posts, we need to count them too...
103
	if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
104
		$num_replies += $unapproved_posts - ($approved ? 0 : 1);
105
106
	// Check if there is more than one message in the topic.  (there should be.)
107
	if ($num_replies < 1)
108
		fatal_lang_error('topic_one_post', false);
109
110
	// Check if this is the first message in the topic (if so, the first and second option won't be available)
111
	if ($id_first_msg == $_GET['at'])
112
		return SplitSelectTopics();
0 ignored issues
show
Are you sure the usage of SplitSelectTopics() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
113
114
	// Basic template information....
115
	$context['message'] = array(
116
		'id' => $_GET['at'],
117
		'subject' => $_REQUEST['subname']
118
	);
119
	$context['sub_template'] = 'ask';
120
	$context['page_title'] = $txt['split'];
121
}
122
123
/**
124
 * do the actual split.
125
 * is accessed with ?action=splittopics;sa=execute.
126
 * uses the main SplitTopics template.
127
 * supports three ways of splitting:
128
 * (1) only one message is split off.
129
 * (2) all messages after and including a given message are split off.
130
 * (3) select topics to split (redirects to SplitSelectTopics()).
131
 * uses splitTopic function to do the actual splitting.
132
 */
133
function SplitExecute()
134
{
135
	global $txt, $topic, $context, $smcFunc;
136
137
	// Check the session to make sure they meant to do this.
138
	checkSession();
139
140
	// Clean up the subject.
141
	if (!isset($_POST['subname']) || $_POST['subname'] == '')
142
		$_POST['subname'] = $txt['new_topic'];
143
144
	// Redirect to the selector if they chose selective.
145
	if ($_POST['step2'] == 'selective')
146
		redirectexit ('action=splittopics;sa=selectTopics;subname=' . $_POST['subname'] . ';topic=' . $topic . '.0;start2=0');
147
148
	$_POST['at'] = (int) $_POST['at'];
149
	$messagesToBeSplit = array();
150
151
	if ($_POST['step2'] == 'afterthis')
152
	{
153
		// Fetch the message IDs of the topic that are at or after the message.
154
		$request = $smcFunc['db_query']('', '
155
			SELECT id_msg
156
			FROM {db_prefix}messages
157
			WHERE id_topic = {int:current_topic}
158
				AND id_msg >= {int:split_at}',
159
			array(
160
				'current_topic' => $topic,
161
				'split_at' => $_POST['at'],
162
			)
163
		);
164
		while ($row = $smcFunc['db_fetch_assoc']($request))
165
			$messagesToBeSplit[] = $row['id_msg'];
166
167
		$smcFunc['db_free_result']($request);
168
	}
169
	// Only the selected message has to be split. That should be easy.
170
	elseif ($_POST['step2'] == 'onlythis')
171
		$messagesToBeSplit[] = $_POST['at'];
172
	// There's another action?!
173
	else
174
		fatal_lang_error('no_access', false);
175
176
	$context['old_topic'] = $topic;
177
	$context['new_topic'] = splitTopic($topic, $messagesToBeSplit, $_POST['subname']);
178
	$context['page_title'] = $txt['split'];
179
}
180
181
/**
182
 * allows the user to select the messages to be split.
183
 * is accessed with ?action=splittopics;sa=selectTopics.
184
 * uses 'select' sub template of the SplitTopics template or (for
185
 * XMLhttp) the 'split' sub template of the Xml template.
186
 * supports XMLhttp for adding/removing a message to the selection.
187
 * uses a session variable to store the selected topics.
188
 * shows two independent page indexes for both the selected and
189
 * not-selected messages (;topic=1.x;start2=y).
190
 */
191
function SplitSelectTopics()
192
{
193
	global $txt, $scripturl, $topic, $context, $modSettings, $original_msgs, $smcFunc, $options;
194
195
	$context['page_title'] = $txt['split'] . ' - ' . $txt['select_split_posts'];
196
197
	// Haven't selected anything have we?
198
	$_SESSION['split_selection'][$topic] = empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic];
199
200
	// This is a special case for split topics from quick-moderation checkboxes
201
	if (isset($_REQUEST['subname_enc']))
202
		$_REQUEST['subname'] = urldecode($_REQUEST['subname_enc']);
203
204
	$context['not_selected'] = array(
205
		'num_messages' => 0,
206
		'start' => empty($_REQUEST['start']) ? 0 : (int) $_REQUEST['start'],
207
		'messages' => array(),
208
	);
209
210
	$context['selected'] = array(
211
		'num_messages' => 0,
212
		'start' => empty($_REQUEST['start2']) ? 0 : (int) $_REQUEST['start2'],
213
		'messages' => array(),
214
	);
215
216
	$context['topic'] = array(
217
		'id' => $topic,
218
		'subject' => urlencode($_REQUEST['subname']),
219
	);
220
221
	// Some stuff for our favorite template.
222
	$context['new_subject'] = $_REQUEST['subname'];
223
224
	// Using the "select" sub template.
225
	$context['sub_template'] = isset($_REQUEST['xml']) ? 'split' : 'select';
226
227
	// Are we using a custom messages per page?
228
	$context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
229
230
	// Get the message ID's from before the move.
231
	if (isset($_REQUEST['xml']))
232
	{
233
		$original_msgs = array(
234
			'not_selected' => array(),
235
			'selected' => array(),
236
		);
237
		$request = $smcFunc['db_query']('', '
238
			SELECT id_msg
239
			FROM {db_prefix}messages
240
			WHERE id_topic = {int:current_topic}' . (empty($_SESSION['split_selection'][$topic]) ? '' : '
241
				AND id_msg NOT IN ({array_int:no_split_msgs})') . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
242
				AND approved = {int:is_approved}') . '
243
				' . (empty($options['view_newest_first']) ? '' : 'ORDER BY id_msg DESC') . '
244
				LIMIT {int:start}, {int:messages_per_page}',
245
			array(
246
				'current_topic' => $topic,
247
				'no_split_msgs' => empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic],
248
				'is_approved' => 1,
249
				'start' => $context['not_selected']['start'],
250
				'messages_per_page' => $context['messages_per_page'],
251
			)
252
		);
253
		// You can't split the last message off.
254
		if (empty($context['not_selected']['start']) && $smcFunc['db_num_rows']($request) <= 1 && $_REQUEST['move'] == 'down')
255
			$_REQUEST['move'] = '';
256
		while ($row = $smcFunc['db_fetch_assoc']($request))
257
			$original_msgs['not_selected'][] = $row['id_msg'];
258
		$smcFunc['db_free_result']($request);
259
		if (!empty($_SESSION['split_selection'][$topic]))
260
		{
261
			$request = $smcFunc['db_query']('', '
262
				SELECT id_msg
263
				FROM {db_prefix}messages
264
				WHERE id_topic = {int:current_topic}
265
					AND id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
266
					AND approved = {int:is_approved}') . '
267
				' . (empty($options['view_newest_first']) ? '' : 'ORDER BY id_msg DESC') . '
268
				LIMIT {int:start}, {int:messages_per_page}',
269
				array(
270
					'current_topic' => $topic,
271
					'split_msgs' => $_SESSION['split_selection'][$topic],
272
					'is_approved' => 1,
273
					'start' => $context['selected']['start'],
274
					'messages_per_page' => $context['messages_per_page'],
275
				)
276
			);
277
			while ($row = $smcFunc['db_fetch_assoc']($request))
278
				$original_msgs['selected'][] = $row['id_msg'];
279
280
			$smcFunc['db_free_result']($request);
281
		}
282
	}
283
284
	// (De)select a message..
285
	if (!empty($_REQUEST['move']))
286
	{
287
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
288
289
		if ($_REQUEST['move'] == 'reset')
290
			$_SESSION['split_selection'][$topic] = array();
291
		elseif ($_REQUEST['move'] == 'up')
292
			$_SESSION['split_selection'][$topic] = array_diff($_SESSION['split_selection'][$topic], array($_REQUEST['msg']));
293
		else
294
			$_SESSION['split_selection'][$topic][] = $_REQUEST['msg'];
295
	}
296
297
	// Make sure the selection is still accurate.
298
	if (!empty($_SESSION['split_selection'][$topic]))
299
	{
300
		$request = $smcFunc['db_query']('', '
301
			SELECT id_msg
302
			FROM {db_prefix}messages
303
			WHERE id_topic = {int:current_topic}
304
				AND id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
305
				AND approved = {int:is_approved}'),
306
			array(
307
				'current_topic' => $topic,
308
				'split_msgs' => $_SESSION['split_selection'][$topic],
309
				'is_approved' => 1,
310
			)
311
		);
312
		$_SESSION['split_selection'][$topic] = array();
313
314
		while ($row = $smcFunc['db_fetch_assoc']($request))
315
			$_SESSION['split_selection'][$topic][] = $row['id_msg'];
316
317
		$smcFunc['db_free_result']($request);
318
	}
319
320
	// Get the number of messages (not) selected to be split.
321
	$request = $smcFunc['db_query']('', '
322
		SELECT ' . (empty($_SESSION['split_selection'][$topic]) ? '0' : 'm.id_msg IN ({array_int:split_msgs})') . ' AS is_selected, COUNT(*) AS num_messages
323
		FROM {db_prefix}messages AS m
324
		WHERE m.id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
325
			AND approved = {int:is_approved}') . (empty($_SESSION['split_selection'][$topic]) ? '' : '
326
		GROUP BY is_selected'),
327
		array(
328
			'current_topic' => $topic,
329
			'split_msgs' => !empty($_SESSION['split_selection'][$topic]) ? $_SESSION['split_selection'][$topic] : array(),
330
			'is_approved' => 1,
331
		)
332
	);
333
	while ($row = $smcFunc['db_fetch_assoc']($request))
334
		$context[empty($row['is_selected']) || $row['is_selected'] == 'f' ? 'not_selected' : 'selected']['num_messages'] = $row['num_messages'];
335
	$smcFunc['db_free_result']($request);
336
337
	// Fix an oversized starting page (to make sure both pageindexes are properly set).
338
	if ($context['selected']['start'] >= $context['selected']['num_messages'])
339
		$context['selected']['start'] = $context['selected']['num_messages'] <= $context['messages_per_page'] ? 0 : ($context['selected']['num_messages'] - (($context['selected']['num_messages'] % $context['messages_per_page']) == 0 ? $context['messages_per_page'] : ($context['selected']['num_messages'] % $context['messages_per_page'])));
340
341
	// Build a page list of the not-selected topics...
342
	$context['not_selected']['page_index'] = constructPageIndex($scripturl . '?action=splittopics;sa=selectTopics;subname=' . strtr(urlencode($_REQUEST['subname']), array('%' => '%%')) . ';topic=' . $topic . '.%1$d;start2=' . $context['selected']['start'], $context['not_selected']['start'], $context['not_selected']['num_messages'], $context['messages_per_page'], true);
343
	// ...and one of the selected topics.
344
	$context['selected']['page_index'] = constructPageIndex($scripturl . '?action=splittopics;sa=selectTopics;subname=' . strtr(urlencode($_REQUEST['subname']), array('%' => '%%')) . ';topic=' . $topic . '.' . $context['not_selected']['start'] . ';start2=%1$d', $context['selected']['start'], $context['selected']['num_messages'], $context['messages_per_page'], true);
345
346
	// Get the messages and stick them into an array.
347
	$request = $smcFunc['db_query']('', '
348
		SELECT m.subject, COALESCE(mem.real_name, m.poster_name) AS real_name, m.poster_time, m.body, m.id_msg, m.smileys_enabled
349
		FROM {db_prefix}messages AS m
350
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
351
		WHERE m.id_topic = {int:current_topic}' . (empty($_SESSION['split_selection'][$topic]) ? '' : '
352
			AND id_msg NOT IN ({array_int:no_split_msgs})') . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
353
			AND approved = {int:is_approved}') . '
354
			' . (empty($options['view_newest_first']) ? '' : 'ORDER BY m.id_msg DESC') . '
355
			LIMIT {int:start}, {int:messages_per_page}',
356
		array(
357
			'current_topic' => $topic,
358
			'no_split_msgs' => !empty($_SESSION['split_selection'][$topic]) ? $_SESSION['split_selection'][$topic] : array(),
359
			'is_approved' => 1,
360
			'start' => $context['not_selected']['start'],
361
			'messages_per_page' => $context['messages_per_page'],
362
		)
363
	);
364
	$context['messages'] = array();
365
	for ($counter = 0; $row = $smcFunc['db_fetch_assoc']($request); $counter++)
366
	{
367
		censorText($row['subject']);
368
		censorText($row['body']);
369
370
		$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
371
372
		$context['not_selected']['messages'][$row['id_msg']] = array(
373
			'id' => $row['id_msg'],
374
			'subject' => $row['subject'],
375
			'time' => timeformat($row['poster_time']),
376
			'timestamp' => forum_time(true, $row['poster_time']),
377
			'body' => $row['body'],
378
			'poster' => $row['real_name'],
379
		);
380
	}
381
	$smcFunc['db_free_result']($request);
382
383
	// Now get the selected messages.
384
	if (!empty($_SESSION['split_selection'][$topic]))
385
	{
386
		// Get the messages and stick them into an array.
387
		$request = $smcFunc['db_query']('', '
388
			SELECT m.subject, COALESCE(mem.real_name, m.poster_name) AS real_name,  m.poster_time, m.body, m.id_msg, m.smileys_enabled
389
			FROM {db_prefix}messages AS m
390
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
391
			WHERE m.id_topic = {int:current_topic}
392
				AND m.id_msg IN ({array_int:split_msgs})' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
393
				AND approved = {int:is_approved}') . '
394
			' . (empty($options['view_newest_first']) ? '' : 'ORDER BY m.id_msg DESC') . '
395
			LIMIT {int:start}, {int:messages_per_page}',
396
			array(
397
				'current_topic' => $topic,
398
				'split_msgs' => $_SESSION['split_selection'][$topic],
399
				'is_approved' => 1,
400
				'start' => $context['selected']['start'],
401
				'messages_per_page' => $context['messages_per_page'],
402
			)
403
		);
404
		$context['messages'] = array();
405
		for ($counter = 0; $row = $smcFunc['db_fetch_assoc']($request); $counter++)
406
		{
407
			censorText($row['subject']);
408
			censorText($row['body']);
409
410
			$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], $row['id_msg']);
411
412
			$context['selected']['messages'][$row['id_msg']] = array(
413
				'id' => $row['id_msg'],
414
				'subject' => $row['subject'],
415
				'time' => timeformat($row['poster_time']),
416
				'timestamp' => forum_time(true, $row['poster_time']),
417
				'body' => $row['body'],
418
				'poster' => $row['real_name']
419
			);
420
		}
421
		$smcFunc['db_free_result']($request);
422
	}
423
424
	// The XMLhttp method only needs the stuff that changed, so let's compare.
425
	if (isset($_REQUEST['xml']))
426
	{
427
		$changes = array(
428
			'remove' => array(
429
				'not_selected' => array_diff($original_msgs['not_selected'], array_keys($context['not_selected']['messages'])),
430
				'selected' => array_diff($original_msgs['selected'], array_keys($context['selected']['messages'])),
431
			),
432
			'insert' => array(
433
				'not_selected' => array_diff(array_keys($context['not_selected']['messages']), $original_msgs['not_selected']),
434
				'selected' => array_diff(array_keys($context['selected']['messages']), $original_msgs['selected']),
435
			),
436
		);
437
438
		$context['changes'] = array();
439
		foreach ($changes as $change_type => $change_array)
440
			foreach ($change_array as $section => $msg_array)
441
			{
442
				if (empty($msg_array))
443
					continue;
444
445
				foreach ($msg_array as $id_msg)
446
				{
447
					$context['changes'][$change_type . $id_msg] = array(
448
						'id' => $id_msg,
449
						'type' => $change_type,
450
						'section' => $section,
451
					);
452
					if ($change_type == 'insert')
453
						$context['changes']['insert' . $id_msg]['insert_value'] = $context[$section]['messages'][$id_msg];
454
				}
455
			}
456
	}
457
}
458
459
/**
460
 * do the actual split of a selection of topics.
461
 * is accessed with ?action=splittopics;sa=splitSelection.
462
 * uses the main SplitTopics template.
463
 * uses splitTopic function to do the actual splitting.
464
 */
465
function SplitSelectionExecute()
466
{
467
	global $txt, $topic, $context;
468
469
	// Make sure the session id was passed with post.
470
	checkSession();
471
472
	// Default the subject in case it's blank.
473
	if (!isset($_POST['subname']) || $_POST['subname'] == '')
474
		$_POST['subname'] = $txt['new_topic'];
475
476
	// You must've selected some messages!  Can't split out none!
477
	if (empty($_SESSION['split_selection'][$topic]))
478
		fatal_lang_error('no_posts_selected', false);
479
480
	$context['old_topic'] = $topic;
481
	$context['new_topic'] = splitTopic($topic, $_SESSION['split_selection'][$topic], $_POST['subname']);
482
	$context['page_title'] = $txt['split'];
483
}
484
485
/**
486
 * general function to split off a topic.
487
 * creates a new topic and moves the messages with the IDs in
488
 * array messagesToBeSplit to the new topic.
489
 * the subject of the newly created topic is set to 'newSubject'.
490
 * marks the newly created message as read for the user splitting it.
491
 * updates the statistics to reflect a newly created topic.
492
 * logs the action in the moderation log.
493
 * a notification is sent to all users monitoring this topic.
494
 *
495
 * @param int $split1_ID_TOPIC The ID of the topic we're splitting
496
 * @param array $splitMessages The IDs of the messages being split
497
 * @param string $new_subject The subject of the new topic
498
 * @return int The ID of the new split topic.
499
 */
500
function splitTopic($split1_ID_TOPIC, $splitMessages, $new_subject)
501
{
502
	global $smcFunc, $txt, $sourcedir;
503
504
	// Nothing to split?
505
	if (empty($splitMessages))
506
		fatal_lang_error('no_posts_selected', false);
507
508
	// Get some board info.
509
	$request = $smcFunc['db_query']('', '
510
		SELECT id_board, approved
511
		FROM {db_prefix}topics
512
		WHERE id_topic = {int:id_topic}
513
		LIMIT 1',
514
		array(
515
			'id_topic' => $split1_ID_TOPIC,
516
		)
517
	);
518
	list ($id_board, $split1_approved) = $smcFunc['db_fetch_row']($request);
519
	$smcFunc['db_free_result']($request);
520
521
	// Find the new first and last not in the list. (old topic)
522
	$request = $smcFunc['db_query']('', '
523
		SELECT
524
			MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg, COUNT(*) AS message_count, m.approved
525
		FROM {db_prefix}messages AS m
526
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:id_topic})
527
		WHERE m.id_msg NOT IN ({array_int:no_msg_list})
528
			AND m.id_topic = {int:id_topic}
529
		GROUP BY m.approved
530
		ORDER BY m.approved DESC
531
		LIMIT 2',
532
		array(
533
			'id_topic' => $split1_ID_TOPIC,
534
			'no_msg_list' => $splitMessages,
535
		)
536
	);
537
	// You can't select ALL the messages!
538
	if ($smcFunc['db_num_rows']($request) == 0)
539
		fatal_lang_error('selected_all_posts', false);
540
541
	$split1_first_msg = null;
542
	$split1_last_msg = null;
543
544
	while ($row = $smcFunc['db_fetch_assoc']($request))
545
	{
546
		// Get the right first and last message dependant on approved state...
547
		if (empty($split1_first_msg) || $row['myid_first_msg'] < $split1_first_msg)
548
			$split1_first_msg = $row['myid_first_msg'];
549
		if (empty($split1_last_msg) || $row['approved'])
550
			$split1_last_msg = $row['myid_last_msg'];
551
552
		// Get the counts correct...
553
		if ($row['approved'])
554
		{
555
			$split1_replies = $row['message_count'] - 1;
556
			$split1_unapprovedposts = 0;
557
		}
558
		else
559
		{
560
			if (!isset($split1_replies))
561
				$split1_replies = 0;
562
			// If the topic isn't approved then num replies must go up by one... as first post wouldn't be counted.
563
			elseif (!$split1_approved)
564
				$split1_replies++;
565
566
			$split1_unapprovedposts = $row['message_count'];
567
		}
568
	}
569
	$smcFunc['db_free_result']($request);
570
	$split1_firstMem = getMsgMemberID($split1_first_msg);
571
	$split1_lastMem = getMsgMemberID($split1_last_msg);
572
573
	// Find the first and last in the list. (new topic)
574
	$request = $smcFunc['db_query']('', '
575
		SELECT MIN(id_msg) AS myid_first_msg, MAX(id_msg) AS myid_last_msg, COUNT(*) AS message_count, approved
576
		FROM {db_prefix}messages
577
		WHERE id_msg IN ({array_int:msg_list})
578
			AND id_topic = {int:id_topic}
579
		GROUP BY id_topic, approved
580
		ORDER BY approved DESC
581
		LIMIT 2',
582
		array(
583
			'msg_list' => $splitMessages,
584
			'id_topic' => $split1_ID_TOPIC,
585
		)
586
	);
587
	while ($row = $smcFunc['db_fetch_assoc']($request))
588
	{
589
		// As before get the right first and last message dependant on approved state...
590
		if (empty($split2_first_msg) || $row['myid_first_msg'] < $split2_first_msg)
591
			$split2_first_msg = $row['myid_first_msg'];
592
		if (empty($split2_last_msg) || $row['approved'])
593
			$split2_last_msg = $row['myid_last_msg'];
594
595
		// Then do the counts again...
596
		if ($row['approved'])
597
		{
598
			$split2_approved = true;
599
			$split2_replies = $row['message_count'] - 1;
600
			$split2_unapprovedposts = 0;
601
		}
602
		else
603
		{
604
			// Should this one be approved??
605
			if ($split2_first_msg == $row['myid_first_msg'])
606
				$split2_approved = false;
607
608
			if (!isset($split2_replies))
609
				$split2_replies = 0;
610
			// As before, fix number of replies.
611
			elseif (!$split2_approved)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $split2_approved does not seem to be defined for all execution paths leading up to this point.
Loading history...
612
				$split2_replies++;
613
614
			$split2_unapprovedposts = $row['message_count'];
615
		}
616
	}
617
	$smcFunc['db_free_result']($request);
618
	$split2_firstMem = getMsgMemberID($split2_first_msg);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $split2_first_msg does not seem to be defined for all execution paths leading up to this point.
Loading history...
619
	$split2_lastMem = getMsgMemberID($split2_last_msg);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $split2_last_msg does not seem to be defined for all execution paths leading up to this point.
Loading history...
620
621
	// No database changes yet, so let's double check to see if everything makes at least a little sense.
622
	if ($split1_first_msg <= 0 || $split1_last_msg <= 0 || $split2_first_msg <= 0 || $split2_last_msg <= 0 || $split1_replies < 0 || $split2_replies < 0 || $split1_unapprovedposts < 0 || $split2_unapprovedposts < 0 || !isset($split1_approved) || !isset($split2_approved))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $split2_replies does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $split1_replies does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $split2_unapprovedposts does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $split1_unapprovedposts does not seem to be defined for all execution paths leading up to this point.
Loading history...
623
		fatal_lang_error('cant_find_messages');
624
625
	// You cannot split off the first message of a topic.
626
	if ($split1_first_msg > $split2_first_msg)
627
		fatal_lang_error('split_first_post', false);
628
629
	// We're off to insert the new topic!  Use 0 for now to avoid UNIQUE errors.
630
	$split2_ID_TOPIC = $smcFunc['db_insert']('',
631
		'{db_prefix}topics',
632
		array(
633
			'id_board' => 'int',
634
			'id_member_started' => 'int',
635
			'id_member_updated' => 'int',
636
			'id_first_msg' => 'int',
637
			'id_last_msg' => 'int',
638
			'num_replies' => 'int',
639
			'unapproved_posts' => 'int',
640
			'approved' => 'int',
641
			'is_sticky' => 'int',
642
		),
643
		array(
644
			(int) $id_board, $split2_firstMem, $split2_lastMem, 0,
645
			0, $split2_replies, $split2_unapprovedposts, (int) $split2_approved, 0,
646
		),
647
		array('id_topic'),
648
		1
649
	);
650
	if ($split2_ID_TOPIC <= 0)
651
		fatal_lang_error('cant_insert_topic');
652
653
	// Move the messages over to the other topic.
654
	$new_subject = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($new_subject)), array("\r" => '', "\n" => '', "\t" => ''));
655
	// Check the subject length.
656
	if ($smcFunc['strlen']($new_subject) > 100)
657
		$new_subject = $smcFunc['substr']($new_subject, 0, 100);
658
	// Valid subject?
659
	if ($new_subject != '')
660
	{
661
		$smcFunc['db_query']('', '
662
			UPDATE {db_prefix}messages
663
			SET
664
				id_topic = {int:id_topic},
665
				subject = CASE WHEN id_msg = {int:split_first_msg} THEN {string:new_subject} ELSE {string:new_subject_replies} END
666
			WHERE id_msg IN ({array_int:split_msgs})',
667
			array(
668
				'split_msgs' => $splitMessages,
669
				'id_topic' => $split2_ID_TOPIC,
670
				'new_subject' => $new_subject,
671
				'split_first_msg' => $split2_first_msg,
672
				'new_subject_replies' => $txt['response_prefix'] . $new_subject,
673
			)
674
		);
675
676
		// Cache the new topics subject... we can do it now as all the subjects are the same!
677
		updateStats('subject', $split2_ID_TOPIC, $new_subject);
678
	}
679
680
	// Any associated reported posts better follow...
681
	$smcFunc['db_query']('', '
682
		UPDATE {db_prefix}log_reported
683
		SET id_topic = {int:id_topic}
684
		WHERE id_msg IN ({array_int:split_msgs})',
685
		array(
686
			'split_msgs' => $splitMessages,
687
			'id_topic' => $split2_ID_TOPIC,
688
		)
689
	);
690
691
	// Mess with the old topic's first, last, and number of messages.
692
	$smcFunc['db_query']('', '
693
		UPDATE {db_prefix}topics
694
		SET
695
			num_replies = {int:num_replies},
696
			id_first_msg = {int:id_first_msg},
697
			id_last_msg = {int:id_last_msg},
698
			id_member_started = {int:id_member_started},
699
			id_member_updated = {int:id_member_updated},
700
			unapproved_posts = {int:unapproved_posts}
701
		WHERE id_topic = {int:id_topic}',
702
		array(
703
			'num_replies' => $split1_replies,
704
			'id_first_msg' => $split1_first_msg,
705
			'id_last_msg' => $split1_last_msg,
706
			'id_member_started' => $split1_firstMem,
707
			'id_member_updated' => $split1_lastMem,
708
			'unapproved_posts' => $split1_unapprovedposts,
709
			'id_topic' => $split1_ID_TOPIC,
710
		)
711
	);
712
713
	// Now, put the first/last message back to what they should be.
714
	$smcFunc['db_query']('', '
715
		UPDATE {db_prefix}topics
716
		SET
717
			id_first_msg = {int:id_first_msg},
718
			id_last_msg = {int:id_last_msg}
719
		WHERE id_topic = {int:id_topic}',
720
		array(
721
			'id_first_msg' => $split2_first_msg,
722
			'id_last_msg' => $split2_last_msg,
723
			'id_topic' => $split2_ID_TOPIC,
724
		)
725
	);
726
727
	// If the new topic isn't approved ensure the first message flags this just in case.
728
	if (!$split2_approved)
729
		$smcFunc['db_query']('', '
730
			UPDATE {db_prefix}messages
731
			SET approved = {int:approved}
732
			WHERE id_msg = {int:id_msg}
733
				AND id_topic = {int:id_topic}',
734
			array(
735
				'approved' => 0,
736
				'id_msg' => $split2_first_msg,
737
				'id_topic' => $split2_ID_TOPIC,
738
			)
739
		);
740
741
	// The board has more topics now (Or more unapproved ones!).
742
	$smcFunc['db_query']('', '
743
		UPDATE {db_prefix}boards
744
		SET ' . ($split2_approved ? '
745
			num_topics = num_topics + 1' : '
746
			unapproved_topics = unapproved_topics + 1') . '
747
		WHERE id_board = {int:id_board}',
748
		array(
749
			'id_board' => $id_board,
750
		)
751
	);
752
753
	// Copy log topic entries.
754
	// @todo This should really be chunked.
755
	$request = $smcFunc['db_query']('', '
756
		SELECT id_member, id_msg, unwatched
757
		FROM {db_prefix}log_topics
758
		WHERE id_topic = {int:id_topic}',
759
		array(
760
			'id_topic' => (int) $split1_ID_TOPIC,
761
		)
762
	);
763
	if ($smcFunc['db_num_rows']($request) > 0)
764
	{
765
		$replaceEntries = array();
766
		while ($row = $smcFunc['db_fetch_assoc']($request))
767
			$replaceEntries[] = array($row['id_member'], $split2_ID_TOPIC, $row['id_msg'], $row['unwatched']);
768
769
		$smcFunc['db_insert']('ignore',
770
			'{db_prefix}log_topics',
771
			array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
772
			$replaceEntries,
773
			array('id_member', 'id_topic')
774
		);
775
		unset($replaceEntries);
776
	}
777
	$smcFunc['db_free_result']($request);
778
779
	// Housekeeping.
780
	updateStats('topic');
781
	updateLastMessages($id_board);
782
783
	logAction('split', array('topic' => $split1_ID_TOPIC, 'new_topic' => $split2_ID_TOPIC, 'board' => $id_board));
784
785
	// Notify people that this topic has been split?
786
	sendNotifications($split1_ID_TOPIC, 'split');
0 ignored issues
show
$split1_ID_TOPIC of type integer is incompatible with the type array expected by parameter $topics of sendNotifications(). ( Ignorable by Annotation )

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

786
	sendNotifications(/** @scrutinizer ignore-type */ $split1_ID_TOPIC, 'split');
Loading history...
787
788
	// If there's a search index that needs updating, update it...
789
	require_once($sourcedir . '/Search.php');
790
	$searchAPI = findSearchAPI();
791
	if (is_callable(array($searchAPI, 'topicSplit')))
792
		$searchAPI->topicSplit($split2_ID_TOPIC, $splitMessages);
0 ignored issues
show
The method topicSplit() does not exist on search_api_interface. ( Ignorable by Annotation )

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

792
		$searchAPI->/** @scrutinizer ignore-call */ 
793
              topicSplit($split2_ID_TOPIC, $splitMessages);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
793
794
	// Maybe we want to let an external CMS know about this split
795
	$split1 = array(
796
		'num_replies' => $split1_replies,
797
		'id_first_msg' => $split1_first_msg,
798
		'id_last_msg' => $split1_last_msg,
799
		'id_member_started' => $split1_firstMem,
800
		'id_member_updated' => $split1_lastMem,
801
		'unapproved_posts' => $split1_unapprovedposts,
802
		'id_topic' => $split1_ID_TOPIC,
803
	);
804
	$split2 = array(
805
		'num_replies' => $split2_replies,
806
		'id_first_msg' => $split2_first_msg,
807
		'id_last_msg' => $split2_last_msg,
808
		'id_member_started' => $split2_firstMem,
809
		'id_member_updated' => $split2_lastMem,
810
		'unapproved_posts' => $split2_unapprovedposts,
811
		'id_topic' => $split2_ID_TOPIC,
812
	);
813
	call_integration_hook('integrate_split_topic', array($split1, $split2, $new_subject, $id_board));
814
815
	// Return the ID of the newly created topic.
816
	return $split2_ID_TOPIC;
817
}
818
819
/**
820
 * merges two or more topics into one topic.
821
 * delegates to the other functions (based on the URL parameter sa).
822
 * loads the SplitTopics template.
823
 * requires the merge_any permission.
824
 * is accessed with ?action=mergetopics.
825
 */
826
function MergeTopics()
827
{
828
	// Load the template....
829
	loadTemplate('MoveTopic');
830
831
	$subActions = array(
832
		'done' => 'MergeDone',
833
		'execute' => 'MergeExecute',
834
		'index' => 'MergeIndex',
835
		'options' => 'MergeExecute',
836
	);
837
838
	// ?action=mergetopics;sa=LETSBREAKIT won't work, sorry.
839
	if (empty($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
840
		MergeIndex();
841
842
	else
843
		call_helper($subActions[$_REQUEST['sa']]);
844
}
845
846
/**
847
 * allows to pick a topic to merge the current topic with.
848
 * is accessed with ?action=mergetopics;sa=index
849
 * default sub action for ?action=mergetopics.
850
 * uses 'merge' sub template of the MoveTopic template.
851
 * allows to set a different target board.
852
 */
853
function MergeIndex()
854
{
855
	global $txt, $board, $context, $smcFunc, $sourcedir;
856
	global $scripturl, $modSettings;
857
858
	if (!isset($_GET['from']))
859
		fatal_lang_error('no_access', false);
860
861
	$_GET['from'] = (int) $_GET['from'];
862
863
	$_REQUEST['targetboard'] = isset($_REQUEST['targetboard']) ? (int) $_REQUEST['targetboard'] : $board;
864
	$context['target_board'] = $_REQUEST['targetboard'];
865
866
	// Prepare a handy query bit for approval...
867
	if ($modSettings['postmod_active'])
868
	{
869
		$can_approve_boards = boardsAllowedTo('approve_posts');
870
		$onlyApproved = $can_approve_boards !== array(0) && !in_array($_REQUEST['targetboard'], $can_approve_boards);
871
	}
872
873
	else
874
		$onlyApproved = false;
875
876
	// How many topics are on this board?  (used for paging.)
877
	$request = $smcFunc['db_query']('', '
878
		SELECT COUNT(*)
879
		FROM {db_prefix}topics AS t
880
		WHERE t.id_board = {int:id_board}' . ($onlyApproved ? '
881
			AND t.approved = {int:is_approved}' : ''),
882
		array(
883
			'id_board' => $_REQUEST['targetboard'],
884
			'is_approved' => 1,
885
		)
886
	);
887
888
	list ($topiccount) = $smcFunc['db_fetch_row']($request);
889
	$smcFunc['db_free_result']($request);
890
891
	// Make the page list.
892
	$context['page_index'] = constructPageIndex($scripturl . '?action=mergetopics;from=' . $_GET['from'] . ';targetboard=' . $_REQUEST['targetboard'] . ';board=' . $board . '.%1$d', $_REQUEST['start'], $topiccount, $modSettings['defaultMaxTopics'], true);
893
894
	// Get the topic's subject.
895
	$request = $smcFunc['db_query']('', '
896
		SELECT m.subject
897
		FROM {db_prefix}topics AS t
898
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
899
		WHERE t.id_topic = {int:id_topic}
900
			AND t.id_board = {int:current_board}' . ($onlyApproved ? '
901
			AND t.approved = {int:is_approved}' : '') . '
902
		LIMIT 1',
903
		array(
904
			'current_board' => $board,
905
			'id_topic' => $_GET['from'],
906
			'is_approved' => 1,
907
		)
908
	);
909
910
	if ($smcFunc['db_num_rows']($request) == 0)
911
		fatal_lang_error('no_board');
912
913
	list ($subject) = $smcFunc['db_fetch_row']($request);
914
	$smcFunc['db_free_result']($request);
915
916
	// Tell the template a few things..
917
	$context['origin_topic'] = $_GET['from'];
918
	$context['origin_subject'] = $subject;
919
	$context['origin_js_subject'] = addcslashes(addslashes($subject), '/');
920
	$context['page_title'] = $txt['merge'];
921
922
	// Check which boards you have merge permissions on.
923
	$merge_boards = boardsAllowedTo('merge_any');
924
925
	if (empty($merge_boards))
926
		fatal_lang_error('cannot_merge_any', 'user');
927
928
	// No sense in loading this if you can only merge on this board
929
	if (count($merge_boards) > 1 || in_array(0, $merge_boards))
930
	{
931
		require_once($sourcedir . '/Subs-MessageIndex.php');
932
933
		// Set up a couple of options for our board list
934
		$options = array(
935
			'not_redirection' => true,
936
			'selected_board' => $context['target_board'],
937
		);
938
939
		// Only include these boards in the list (0 means you're an admin')
940
		if (!in_array(0, $merge_boards))
941
			$options['included_boards'] = $merge_boards;
942
943
		$context['merge_categories'] = getBoardList($options);
944
	}
945
946
	// Get some topics to merge it with.
947
	$request = $smcFunc['db_query']('', '
948
		SELECT t.id_topic, m.subject, m.id_member, COALESCE(mem.real_name, m.poster_name) AS poster_name
949
		FROM {db_prefix}topics AS t
950
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
951
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
952
		WHERE t.id_board = {int:id_board}
953
			AND t.id_topic != {int:id_topic}
954
			AND t.id_redirect_topic = {int:not_redirect}' . ($onlyApproved ? '
955
			AND t.approved = {int:is_approved}' : '') . '
956
		ORDER BY {raw:sort}
957
		LIMIT {int:offset}, {int:limit}',
958
		array(
959
			'id_board' => $_REQUEST['targetboard'],
960
			'id_topic' => $_GET['from'],
961
			'sort' => 't.is_sticky DESC, t.id_last_msg DESC',
962
			'offset' => $_REQUEST['start'],
963
			'limit' => $modSettings['defaultMaxTopics'],
964
			'is_approved' => 1,
965
			'not_redirect' => 0,
966
		)
967
	);
968
	$context['topics'] = array();
969
	while ($row = $smcFunc['db_fetch_assoc']($request))
970
	{
971
		censorText($row['subject']);
972
973
		$context['topics'][] = array(
974
			'id' => $row['id_topic'],
975
			'poster' => array(
976
				'id' => $row['id_member'],
977
				'name' => $row['poster_name'],
978
				'href' => empty($row['id_member']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member'],
979
				'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '" target="_blank" rel="noopener">' . $row['poster_name'] . '</a>'
980
			),
981
			'subject' => $row['subject'],
982
			'js_subject' => addcslashes(addslashes($row['subject']), '/')
983
		);
984
	}
985
	$smcFunc['db_free_result']($request);
986
987
	if (empty($context['topics']) && count($merge_boards) <= 1 && !in_array(0, $merge_boards))
988
		fatal_lang_error('merge_need_more_topics');
989
990
	$context['sub_template'] = 'merge';
991
}
992
993
/**
994
 * set merge options and do the actual merge of two or more topics.
995
 *
996
 * the merge options screen:
997
 * * shows topics to be merged and allows to set some merge options.
998
 * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by QuickModeration() (Subs-Boards.php).
999
 * * uses 'merge_extra_options' sub template of the MoveTopic template.
1000
 *
1001
 * the actual merge:
1002
 * * is accessed with ?action=mergetopics;sa=execute.
1003
 * * updates the statistics to reflect the merge.
1004
 * * logs the action in the moderation log.
1005
 * * sends a notification is sent to all users monitoring this topic.
1006
 * * redirects to ?action=mergetopics;sa=done.
1007
 *
1008
 * @param array $topics The IDs of the topics to merge
1009
 */
1010
function MergeExecute($topics = array())
1011
{
1012
	global $user_info, $txt, $context, $scripturl, $sourcedir;
1013
	global $smcFunc, $language, $modSettings;
1014
1015
	// Check the session.
1016
	checkSession('request');
1017
1018
	// Handle URLs from MergeIndex.
1019
	if (!empty($_GET['from']) && !empty($_GET['to']))
1020
		$topics = array((int) $_GET['from'], (int) $_GET['to']);
1021
1022
	// If we came from a form, the topic IDs came by post.
1023
	if (!empty($_POST['topics']) && is_array($_POST['topics']))
1024
		$topics = $_POST['topics'];
1025
1026
	// There's nothing to merge with just one topic...
1027
	if (empty($topics) || !is_array($topics) || count($topics) == 1)
1028
		fatal_lang_error('merge_need_more_topics');
1029
1030
	// Make sure every topic is numeric, or some nasty things could be done with the DB.
1031
	foreach ($topics as $id => $topic)
1032
		$topics[$id] = (int) $topic;
1033
1034
	// Joy of all joys, make sure they're not messing about with unapproved topics they can't see :P
1035
	if ($modSettings['postmod_active'])
1036
		$can_approve_boards = boardsAllowedTo('approve_posts');
1037
1038
	// Get info about the topics and polls that will be merged.
1039
	$request = $smcFunc['db_query']('', '
1040
		SELECT
1041
			t.id_topic, t.id_board, t.id_poll, t.num_views, t.is_sticky, t.approved, t.num_replies, t.unapproved_posts, t.id_redirect_topic,
1042
			m1.subject, m1.poster_time AS time_started, COALESCE(mem1.id_member, 0) AS id_member_started, COALESCE(mem1.real_name, m1.poster_name) AS name_started,
1043
			m2.poster_time AS time_updated, COALESCE(mem2.id_member, 0) AS id_member_updated, COALESCE(mem2.real_name, m2.poster_name) AS name_updated
1044
		FROM {db_prefix}topics AS t
1045
			INNER JOIN {db_prefix}messages AS m1 ON (m1.id_msg = t.id_first_msg)
1046
			INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_last_msg)
1047
			LEFT JOIN {db_prefix}members AS mem1 ON (mem1.id_member = m1.id_member)
1048
			LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member)
1049
		WHERE t.id_topic IN ({array_int:topic_list})
1050
		ORDER BY t.id_first_msg
1051
		LIMIT {int:limit}',
1052
		array(
1053
			'topic_list' => $topics,
1054
			'limit' => count($topics),
1055
		)
1056
	);
1057
	if ($smcFunc['db_num_rows']($request) < 2)
1058
		fatal_lang_error('no_topic_id');
1059
1060
	$num_views = 0;
1061
	$is_sticky = 0;
1062
	$boardTotals = array();
1063
	$boards = array();
1064
	$polls = array();
1065
	$firstTopic = 0;
1066
	$context['is_approved'] = 1;
1067
	$lowestTopicId = 0;
1068
	$lowestTopicBoard = 0;
1069
1070
	while ($row = $smcFunc['db_fetch_assoc']($request))
1071
	{
1072
		// Sorry, redirection topics can't be merged
1073
		if (!empty($row['id_redirect_topic']))
1074
			fatal_lang_error('cannot_merge_redirect', false);
1075
1076
		// Make a note for the board counts...
1077
		if (!isset($boardTotals[$row['id_board']]))
1078
			$boardTotals[$row['id_board']] = array(
1079
				'posts' => 0,
1080
				'topics' => 0,
1081
				'unapproved_posts' => 0,
1082
				'unapproved_topics' => 0
1083
			);
1084
1085
		// We can't see unapproved topics here?
1086
		if ($modSettings['postmod_active'] && !$row['approved'] && $can_approve_boards != array(0) && in_array($row['id_board'], $can_approve_boards))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $can_approve_boards does not seem to be defined for all execution paths leading up to this point.
Loading history...
1087
		{
1088
			unset($topics[$row['id_topic']]); // If we can't see it, we should not merge it and not adjust counts! Instead skip it.
1089
			continue;
1090
		}
1091
		elseif (!$row['approved'])
1092
			$boardTotals[$row['id_board']]['unapproved_topics']++;
1093
		else
1094
			$boardTotals[$row['id_board']]['topics']++;
1095
1096
		$boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
1097
		$boardTotals[$row['id_board']]['posts'] += $row['num_replies'] + ($row['approved'] ? 1 : 0);
1098
1099
		// In the case of making a redirect, the topic count goes up by one due to the redirect topic.
1100
		if (isset($_POST['postRedirect']))
1101
			$boardTotals[$row['id_board']]['topics']--;
1102
1103
		$topic_data[$row['id_topic']] = array(
1104
			'id' => $row['id_topic'],
1105
			'board' => $row['id_board'],
1106
			'poll' => $row['id_poll'],
1107
			'num_views' => $row['num_views'],
1108
			'subject' => $row['subject'],
1109
			'started' => array(
1110
				'time' => timeformat($row['time_started']),
1111
				'timestamp' => forum_time(true, $row['time_started']),
1112
				'href' => empty($row['id_member_started']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_started'],
1113
				'link' => empty($row['id_member_started']) ? $row['name_started'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_started'] . '">' . $row['name_started'] . '</a>'
1114
			),
1115
			'updated' => array(
1116
				'time' => timeformat($row['time_updated']),
1117
				'timestamp' => forum_time(true, $row['time_updated']),
1118
				'href' => empty($row['id_member_updated']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_updated'],
1119
				'link' => empty($row['id_member_updated']) ? $row['name_updated'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['name_updated'] . '</a>'
1120
			),
1121
			'approved' => $row['approved']
1122
		);
1123
		$num_views += $row['num_views'];
1124
		$boards[] = $row['id_board'];
1125
1126
		// If there's no poll, id_poll == 0...
1127
		if ($row['id_poll'] > 0)
1128
			$polls[] = $row['id_poll'];
1129
		// Store the id_topic with the lowest id_first_msg.
1130
		if (empty($firstTopic))
1131
			$firstTopic = $row['id_topic'];
1132
1133
		// Lowest topic id gets selected as surviving topic id. We need to store this board so we can adjust the topic count (This one will not have a redirect topic)
1134
		if ($row['id_topic'] < $lowestTopicId || empty($lowestTopicId))
1135
		{
1136
			$lowestTopicId = $row['id_topic'];
1137
			$lowestTopicBoard = $row['id_board'];
1138
		}
1139
1140
		$is_sticky = max($is_sticky, $row['is_sticky']);
1141
	}
1142
	$smcFunc['db_free_result']($request);
1143
1144
	// If we didn't get any topics then they've been messing with unapproved stuff.
1145
	if (empty($topic_data))
1146
		fatal_lang_error('no_topic_id');
1147
1148
	if (isset($_POST['postRedirect']) && !empty($lowestTopicBoard))
1149
		$boardTotals[$lowestTopicBoard]['topics']++;
1150
1151
	// Will this be approved?
1152
	$context['is_approved'] = $topic_data[$firstTopic]['approved'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $topic_data does not seem to be defined for all execution paths leading up to this point.
Loading history...
1153
1154
	$boards = array_values(array_unique($boards));
1155
1156
	// The parameters of MergeExecute were set, so this must've been an internal call.
1157
	if (!empty($topics))
1158
	{
1159
		isAllowedTo('merge_any', $boards);
1160
		loadTemplate('MoveTopic');
1161
	}
1162
1163
	// Get the boards a user is allowed to merge in.
1164
	$merge_boards = boardsAllowedTo('merge_any');
1165
	if (empty($merge_boards))
1166
		fatal_lang_error('cannot_merge_any', 'user');
1167
1168
	// Make sure they can see all boards....
1169
	$request = $smcFunc['db_query']('', '
1170
		SELECT b.id_board
1171
		FROM {db_prefix}boards AS b
1172
		WHERE b.id_board IN ({array_int:boards})
1173
			AND {query_see_board}' . (!in_array(0, $merge_boards) ? '
1174
			AND b.id_board IN ({array_int:merge_boards})' : '') . '
1175
		LIMIT {int:limit}',
1176
		array(
1177
			'boards' => $boards,
1178
			'merge_boards' => $merge_boards,
1179
			'limit' => count($boards),
1180
		)
1181
	);
1182
	// If the number of boards that's in the output isn't exactly the same as we've put in there, you're in trouble.
1183
	if ($smcFunc['db_num_rows']($request) != count($boards))
1184
		fatal_lang_error('no_board');
1185
	$smcFunc['db_free_result']($request);
1186
1187
	if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options')
1188
	{
1189
		if (count($polls) > 1)
1190
		{
1191
			$request = $smcFunc['db_query']('', '
1192
				SELECT t.id_topic, t.id_poll, m.subject, p.question
1193
				FROM {db_prefix}polls AS p
1194
					INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll)
1195
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1196
				WHERE p.id_poll IN ({array_int:polls})
1197
				LIMIT {int:limit}',
1198
				array(
1199
					'polls' => $polls,
1200
					'limit' => count($polls),
1201
				)
1202
			);
1203
			while ($row = $smcFunc['db_fetch_assoc']($request))
1204
				$context['polls'][] = array(
1205
					'id' => $row['id_poll'],
1206
					'topic' => array(
1207
						'id' => $row['id_topic'],
1208
						'subject' => $row['subject']
1209
					),
1210
					'question' => $row['question'],
1211
					'selected' => $row['id_topic'] == $firstTopic
1212
				);
1213
			$smcFunc['db_free_result']($request);
1214
		}
1215
		if (count($boards) > 1)
1216
		{
1217
			$request = $smcFunc['db_query']('', '
1218
				SELECT id_board, name
1219
				FROM {db_prefix}boards
1220
				WHERE id_board IN ({array_int:boards})
1221
				ORDER BY name
1222
				LIMIT {int:limit}',
1223
				array(
1224
					'boards' => $boards,
1225
					'limit' => count($boards),
1226
				)
1227
			);
1228
			while ($row = $smcFunc['db_fetch_assoc']($request))
1229
				$context['boards'][] = array(
1230
					'id' => $row['id_board'],
1231
					'name' => $row['name'],
1232
					'selected' => $row['id_board'] == $topic_data[$firstTopic]['board']
1233
				);
1234
			$smcFunc['db_free_result']($request);
1235
		}
1236
1237
		$context['topics'] = $topic_data;
1238
		foreach ($topic_data as $id => $topic)
1239
			$context['topics'][$id]['selected'] = $topic['id'] == $firstTopic;
1240
1241
		$context['page_title'] = $txt['merge'];
1242
		$context['sub_template'] = 'merge_extra_options';
1243
		return;
1244
	}
1245
1246
	// Determine target board.
1247
	$target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0];
1248
	if (!in_array($target_board, $boards))
1249
		fatal_lang_error('no_board');
1250
1251
	// Determine which poll will survive and which polls won't.
1252
	$target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0);
1253
	if ($target_poll > 0 && !in_array($target_poll, $polls))
1254
		fatal_lang_error('no_access', false);
1255
	$deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll));
1256
1257
	// Determine the subject of the newly merged topic - was a custom subject specified?
1258
	if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '')
1259
	{
1260
		$target_subject = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => ''));
1261
		// Keep checking the length.
1262
		if ($smcFunc['strlen']($target_subject) > 100)
1263
			$target_subject = $smcFunc['substr']($target_subject, 0, 100);
1264
1265
		// Nothing left - odd but pick the first topics subject.
1266
		if ($target_subject == '')
1267
			$target_subject = $topic_data[$firstTopic]['subject'];
1268
	}
1269
	// A subject was selected from the list.
1270
	elseif (!empty($topic_data[(int) $_POST['subject']]['subject']))
1271
		$target_subject = $topic_data[(int) $_POST['subject']]['subject'];
1272
	// Nothing worked? Just take the subject of the first message.
1273
	else
1274
		$target_subject = $topic_data[$firstTopic]['subject'];
1275
1276
	// Get the first and last message and the number of messages....
1277
	$request = $smcFunc['db_query']('', '
1278
		SELECT approved, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg, COUNT(*) AS message_count
1279
		FROM {db_prefix}messages
1280
		WHERE id_topic IN ({array_int:topics})
1281
		GROUP BY approved
1282
		ORDER BY approved DESC',
1283
		array(
1284
			'topics' => $topics,
1285
		)
1286
	);
1287
	$topic_approved = 1;
1288
	$first_msg = 0;
1289
	while ($row = $smcFunc['db_fetch_assoc']($request))
1290
	{
1291
		// If this is approved, or is fully unapproved.
1292
		if ($row['approved'] || !empty($first_msg))
1293
		{
1294
			$first_msg = $row['first_msg'];
1295
			$last_msg = $row['last_msg'];
1296
			if ($row['approved'])
1297
			{
1298
				$num_replies = $row['message_count'] - 1;
1299
				$num_unapproved = 0;
1300
			}
1301
			else
1302
			{
1303
				$topic_approved = 0;
1304
				$num_replies = 0;
1305
				$num_unapproved = $row['message_count'];
1306
			}
1307
		}
1308
		else
1309
		{
1310
			// If this has a lower first_msg then the first post is not approved and hence the number of replies was wrong!
1311
			if ($first_msg > $row['first_msg'])
1312
			{
1313
				$first_msg = $row['first_msg'];
1314
				$num_replies++;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $num_replies does not seem to be defined for all execution paths leading up to this point.
Loading history...
1315
				$topic_approved = 0;
1316
			}
1317
			$num_unapproved = $row['message_count'];
1318
		}
1319
	}
1320
	$smcFunc['db_free_result']($request);
1321
1322
	// Ensure we have a board stat for the target board.
1323
	if (!isset($boardTotals[$target_board]))
1324
	{
1325
		$boardTotals[$target_board] = array(
1326
			'posts' => 0,
1327
			'topics' => 0,
1328
			'unapproved_posts' => 0,
1329
			'unapproved_topics' => 0
1330
		);
1331
	}
1332
1333
	// Fix the topic count stuff depending on what the new one counts as.
1334
	$boardTotals[$target_board][(!$topic_approved) ? 'unapproved_topics' : 'topics']--;
1335
1336
	$boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $num_unapproved does not seem to be defined for all execution paths leading up to this point.
Loading history...
1337
	$boardTotals[$target_board]['posts'] -= $topic_approved ? $num_replies + 1 : $num_replies;
1338
1339
	// Get the member ID of the first and last message.
1340
	$request = $smcFunc['db_query']('', '
1341
		SELECT id_member
1342
		FROM {db_prefix}messages
1343
		WHERE id_msg IN ({int:first_msg}, {int:last_msg})
1344
		ORDER BY id_msg
1345
		LIMIT 2',
1346
		array(
1347
			'first_msg' => $first_msg,
1348
			'last_msg' => $last_msg,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last_msg does not seem to be defined for all execution paths leading up to this point.
Loading history...
1349
		)
1350
	);
1351
	list ($member_started) = $smcFunc['db_fetch_row']($request);
1352
	list ($member_updated) = $smcFunc['db_fetch_row']($request);
1353
1354
	// First and last message are the same, so only row was returned.
1355
	if ($member_updated === null)
1356
		$member_updated = $member_started;
1357
1358
	$smcFunc['db_free_result']($request);
1359
1360
	// Obtain all the message ids we are going to affect.
1361
	$affected_msgs = array();
1362
	$request = $smcFunc['db_query']('', '
1363
		SELECT id_msg
1364
		FROM {db_prefix}messages
1365
		WHERE id_topic IN ({array_int:topic_list})',
1366
		array(
1367
			'topic_list' => $topics,
1368
		)
1369
	);
1370
	while ($row = $smcFunc['db_fetch_row']($request))
1371
		$affected_msgs[] = $row[0];
1372
	$smcFunc['db_free_result']($request);
1373
1374
	// Assign the first topic ID to be the merged topic.
1375
	$id_topic = min($topics);
1376
1377
	$deleted_topics = array_diff($topics, array($id_topic));
1378
	$updated_topics = array();
1379
1380
	// Create stub topics out of the remaining topics.
1381
	// We don't want the search index data though (For non-redirect merges).
1382
	if (!isset($_POST['postRedirect']))
1383
	{
1384
		$smcFunc['db_query']('', '
1385
			DELETE FROM {db_prefix}log_search_subjects
1386
			WHERE id_topic IN ({array_int:deleted_topics})',
1387
			array(
1388
				'deleted_topics' => $deleted_topics,
1389
			)
1390
		);
1391
	}
1392
1393
	require_once($sourcedir . '/Subs-Post.php');
1394
	$posterOptions = array(
1395
		'id' => $user_info['id'],
1396
		'update_post_count' => false,
1397
	);
1398
1399
	// We only need to do this if we're posting redirection topics...
1400
	if (isset($_POST['postRedirect']))
1401
	{
1402
		$_POST['reason'] = $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES);
1403
		preparsecode($_POST['reason']);
1404
1405
		// Add a URL onto the message.
1406
		$reason = strtr($_POST['reason'], array(
1407
			$txt['movetopic_auto_topic'] => '[iurl=' . $scripturl . '?topic=' . $id_topic . '.0]' . $target_subject . '[/iurl]'
1408
		));
1409
1410
		// Automatically remove this MERGED redirection topic in the future?
1411
		$redirect_expires = !empty($_POST['redirect_expires']) ? ((int) ($_POST['redirect_expires'] * 60) + time()) : 0;
1412
1413
		// Redirect to the MERGED topic from topic list?
1414
		$redirect_topic = isset($_POST['redirect_topic']) ? $id_topic : 0;
1415
1416
		foreach ($deleted_topics as $this_old_topic)
1417
		{
1418
			$redirect_subject = sprintf($txt['merged_subject'], $topic_data[$this_old_topic]['subject']);
1419
1420
			$msgOptions = array(
1421
				'icon' => 'moved',
1422
				'subject' => $redirect_subject,
1423
				'body' => $reason,
1424
				'approved' => 1,
1425
			);
1426
			$topicOptions = array(
1427
				'id' => $this_old_topic,
1428
				'is_approved' => true,
1429
				'lock_mode' => 1,
1430
				'board' => $topic_data[$this_old_topic]['board'],
1431
				'mark_as_read' => true,
1432
			);
1433
1434
			// So we have to make the post. We need to do *this* here so we don't foul up indexes later
1435
			// and we have to fix them up later once everything else has happened.
1436
			if (createPost($msgOptions, $topicOptions, $posterOptions))
1437
			{
1438
				$updated_topics[$this_old_topic] = $msgOptions['id'];
1439
			}
1440
1441
			// Update subject search index
1442
			updateStats('subject', $this_old_topic, $redirect_subject);
1443
		}
1444
	}
1445
1446
	// Grab the response prefix (like 'Re: ') in the default forum language.
1447
	if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1448
	{
1449
		if ($language === $user_info['language'])
1450
			$context['response_prefix'] = $txt['response_prefix'];
1451
		else
1452
		{
1453
			loadLanguage('index', $language, false);
1454
			$context['response_prefix'] = $txt['response_prefix'];
1455
			loadLanguage('index');
1456
		}
1457
		cache_put_data('response_prefix', $context['response_prefix'], 600);
1458
	}
1459
1460
	// Change the topic IDs of all messages that will be merged.  Also adjust subjects if 'enforce subject' was checked.
1461
	$smcFunc['db_query']('', '
1462
		UPDATE {db_prefix}messages
1463
		SET
1464
			id_topic = {int:id_topic},
1465
			id_board = {int:target_board}' . (empty($_POST['enforce_subject']) ? '' : ',
1466
			subject = {string:subject}') . '
1467
		WHERE id_topic IN ({array_int:topic_list})' . (!empty($updated_topics) ? '
1468
			AND id_msg NOT IN ({array_int:merge_msg})' : ''),
1469
		array(
1470
			'topic_list' => $topics,
1471
			'id_topic' => $id_topic,
1472
			'merge_msg' => $updated_topics,
1473
			'target_board' => $target_board,
1474
			'subject' => $context['response_prefix'] . $target_subject,
1475
		)
1476
	);
1477
1478
	// Any reported posts should reflect the new board.
1479
	$smcFunc['db_query']('', '
1480
		UPDATE {db_prefix}log_reported
1481
		SET
1482
			id_topic = {int:id_topic},
1483
			id_board = {int:target_board}
1484
		WHERE id_topic IN ({array_int:topics_list})',
1485
		array(
1486
			'topics_list' => $topics,
1487
			'id_topic' => $id_topic,
1488
			'target_board' => $target_board,
1489
		)
1490
	);
1491
1492
	// Change the subject of the first message...
1493
	$smcFunc['db_query']('', '
1494
		UPDATE {db_prefix}messages
1495
		SET subject = {string:target_subject}
1496
		WHERE id_msg = {int:first_msg}',
1497
		array(
1498
			'first_msg' => $first_msg,
1499
			'target_subject' => $target_subject,
1500
		)
1501
	);
1502
1503
	// Adjust all calendar events to point to the new topic.
1504
	$smcFunc['db_query']('', '
1505
		UPDATE {db_prefix}calendar
1506
		SET
1507
			id_topic = {int:id_topic},
1508
			id_board = {int:target_board}
1509
		WHERE id_topic IN ({array_int:deleted_topics})',
1510
		array(
1511
			'deleted_topics' => $deleted_topics,
1512
			'id_topic' => $id_topic,
1513
			'target_board' => $target_board,
1514
		)
1515
	);
1516
1517
	// Merge log topic entries.
1518
	// The unwatch setting comes from the oldest topic
1519
	$request = $smcFunc['db_query']('', '
1520
		SELECT id_member, MIN(id_msg) AS new_id_msg, unwatched
1521
		FROM {db_prefix}log_topics
1522
		WHERE id_topic IN ({array_int:topics})
1523
		GROUP BY id_member, unwatched',
1524
		array(
1525
			'topics' => $topics,
1526
		)
1527
	);
1528
	if ($smcFunc['db_num_rows']($request) > 0)
1529
	{
1530
		$replaceEntries = array();
1531
		while ($row = $smcFunc['db_fetch_assoc']($request))
1532
			$replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg'], $row['unwatched']);
1533
1534
		$smcFunc['db_insert']('replace',
1535
			'{db_prefix}log_topics',
1536
			array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
1537
			$replaceEntries,
1538
			array('id_member', 'id_topic')
1539
		);
1540
		unset($replaceEntries);
1541
1542
		// Get rid of the old log entries.
1543
		$smcFunc['db_query']('', '
1544
			DELETE FROM {db_prefix}log_topics
1545
			WHERE id_topic IN ({array_int:deleted_topics})',
1546
			array(
1547
				'deleted_topics' => $deleted_topics,
1548
			)
1549
		);
1550
	}
1551
	$smcFunc['db_free_result']($request);
1552
1553
	// Merge topic notifications.
1554
	$notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array();
1555
	if (!empty($notifications))
1556
	{
1557
		$request = $smcFunc['db_query']('', '
1558
			SELECT id_member, MAX(sent) AS sent
1559
			FROM {db_prefix}log_notify
1560
			WHERE id_topic IN ({array_int:topics_list})
1561
			GROUP BY id_member',
1562
			array(
1563
				'topics_list' => $notifications,
1564
			)
1565
		);
1566
		if ($smcFunc['db_num_rows']($request) > 0)
1567
		{
1568
			$replaceEntries = array();
1569
			while ($row = $smcFunc['db_fetch_assoc']($request))
1570
				$replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']);
1571
1572
			$smcFunc['db_insert']('replace',
1573
				'{db_prefix}log_notify',
1574
				array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'sent' => 'int'),
1575
				$replaceEntries,
1576
				array('id_member', 'id_topic', 'id_board')
1577
			);
1578
			unset($replaceEntries);
1579
1580
			$smcFunc['db_query']('', '
1581
				DELETE FROM {db_prefix}log_topics
1582
				WHERE id_topic IN ({array_int:deleted_topics})',
1583
				array(
1584
					'deleted_topics' => $deleted_topics,
1585
				)
1586
			);
1587
		}
1588
		$smcFunc['db_free_result']($request);
1589
	}
1590
1591
	// Get rid of the redundant polls.
1592
	if (!empty($deleted_polls))
1593
	{
1594
		$smcFunc['db_query']('', '
1595
			DELETE FROM {db_prefix}polls
1596
			WHERE id_poll IN ({array_int:deleted_polls})',
1597
			array(
1598
				'deleted_polls' => $deleted_polls,
1599
			)
1600
		);
1601
		$smcFunc['db_query']('', '
1602
			DELETE FROM {db_prefix}poll_choices
1603
			WHERE id_poll IN ({array_int:deleted_polls})',
1604
			array(
1605
				'deleted_polls' => $deleted_polls,
1606
			)
1607
		);
1608
		$smcFunc['db_query']('', '
1609
			DELETE FROM {db_prefix}log_polls
1610
			WHERE id_poll IN ({array_int:deleted_polls})',
1611
			array(
1612
				'deleted_polls' => $deleted_polls,
1613
			)
1614
		);
1615
	}
1616
1617
	// Cycle through each board...
1618
	foreach ($boardTotals as $id_board => $stats)
1619
	{
1620
		$smcFunc['db_query']('', '
1621
			UPDATE {db_prefix}boards
1622
			SET
1623
				num_topics = CASE WHEN {int:topics} > num_topics THEN 0 ELSE num_topics - {int:topics} END,
1624
				unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END,
1625
				num_posts = CASE WHEN {int:posts} > num_posts THEN 0 ELSE num_posts - {int:posts} END,
1626
				unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END
1627
			WHERE id_board = {int:id_board}',
1628
			array(
1629
				'id_board' => $id_board,
1630
				'topics' => $stats['topics'],
1631
				'unapproved_topics' => $stats['unapproved_topics'],
1632
				'posts' => $stats['posts'],
1633
				'unapproved_posts' => $stats['unapproved_posts'],
1634
			)
1635
		);
1636
	}
1637
1638
	// Determine the board the final topic resides in
1639
	$request = $smcFunc['db_query']('', '
1640
		SELECT id_board
1641
		FROM {db_prefix}topics
1642
		WHERE id_topic = {int:id_topic}
1643
		LIMIT 1',
1644
		array(
1645
			'id_topic' => $id_topic,
1646
		)
1647
	);
1648
	list($id_board) = $smcFunc['db_fetch_row']($request);
1649
	$smcFunc['db_free_result']($request);
1650
1651
	// Again, only do this if we're redirecting - otherwise delete
1652
	if (isset($_POST['postRedirect']))
1653
	{
1654
		// Having done all that, now make sure we fix the merge/redirect topics upp before we
1655
		// leave here. Specifically: that there are no replies, no unapproved stuff, that the first
1656
		// and last posts are the same and so on and so forth.
1657
		foreach ($updated_topics as $old_topic => $id_msg)
1658
		{
1659
			$smcFunc['db_query']('', '
1660
				UPDATE {db_prefix}topics
1661
				SET id_first_msg = id_last_msg,
1662
					id_member_started = {int:current_user},
1663
					id_member_updated = {int:current_user},
1664
					id_poll = 0,
1665
					approved = 1,
1666
					num_replies = 0,
1667
					unapproved_posts = 0,
1668
					id_redirect_topic = {int:redirect_topic},
1669
					redirect_expires = {int:redirect_expires}
1670
				WHERE id_topic = {int:old_topic}',
1671
				array(
1672
					'current_user' => $user_info['id'],
1673
					'old_topic' => $old_topic,
1674
					'redirect_topic' => $redirect_topic,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $redirect_topic does not seem to be defined for all execution paths leading up to this point.
Loading history...
1675
					'redirect_expires' => $redirect_expires
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $redirect_expires does not seem to be defined for all execution paths leading up to this point.
Loading history...
1676
				)
1677
			);
1678
		}
1679
	}
1680
1681
	// Ensure we don't accidentally delete the poll we want to keep...
1682
	$smcFunc['db_query']('', '
1683
		UPDATE {db_prefix}topics
1684
		SET id_poll = 0
1685
		WHERE id_topic IN ({array_int:deleted_topics})',
1686
		array(
1687
			'deleted_topics' => $deleted_topics
1688
		)
1689
	);
1690
1691
	// Delete any remaining data regarding these topics, this is done before changing the properties of the merged topic (else we get duplicate keys)...
1692
	if (!isset($_POST['postRedirect']))
1693
	{
1694
		// Remove any remaining info about these topics...
1695
		include_once($sourcedir . '/RemoveTopic.php');
1696
		// We do not need to remove the counts of the deleted topics, as we already removed these.
1697
		removeTopics($deleted_topics, false, true, false);
1698
	}
1699
1700
	// Asssign the properties of the newly merged topic.
1701
	$smcFunc['db_query']('', '
1702
		UPDATE {db_prefix}topics
1703
		SET
1704
			id_board = {int:id_board},
1705
			id_member_started = {int:id_member_started},
1706
			id_member_updated = {int:id_member_updated},
1707
			id_first_msg = {int:id_first_msg},
1708
			id_last_msg = {int:id_last_msg},
1709
			id_poll = {int:id_poll},
1710
			num_replies = {int:num_replies},
1711
			unapproved_posts = {int:unapproved_posts},
1712
			num_views = {int:num_views},
1713
			is_sticky = {int:is_sticky},
1714
			approved = {int:approved}
1715
		WHERE id_topic = {int:id_topic}',
1716
		array(
1717
			'id_board' => $target_board,
1718
			'is_sticky' => $is_sticky,
1719
			'approved' => $topic_approved,
1720
			'id_topic' => $id_topic,
1721
			'id_member_started' => $member_started,
1722
			'id_member_updated' => $member_updated,
1723
			'id_first_msg' => $first_msg,
1724
			'id_last_msg' => $last_msg,
1725
			'id_poll' => $target_poll,
1726
			'num_replies' => $num_replies,
1727
			'unapproved_posts' => $num_unapproved,
1728
			'num_views' => $num_views,
1729
		)
1730
	);
1731
1732
	// Update all the statistics.
1733
	updateStats('topic');
1734
	updateStats('subject', $id_topic, $target_subject);
1735
	updateLastMessages($boards);
1736
1737
	logAction('merge', array('topic' => $id_topic, 'board' => $id_board));
1738
1739
	// Notify people that these topics have been merged?
1740
	sendNotifications($id_topic, 'merge');
1741
1742
	// If there's a search index that needs updating, update it...
1743
	require_once($sourcedir . '/Search.php');
1744
	$searchAPI = findSearchAPI();
1745
	if (is_callable(array($searchAPI, 'topicMerge')))
1746
		$searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($_POST['enforce_subject']) ? null : array($context['response_prefix'], $target_subject));
1747
1748
	// Merging is the sort of thing an external CMS might want to know about
1749
	$merged_topic = array(
1750
		'id_board' => $target_board,
1751
		'is_sticky' => $is_sticky,
1752
		'approved' => $topic_approved,
1753
		'id_topic' => $id_topic,
1754
		'id_member_started' => $member_started,
1755
		'id_member_updated' => $member_updated,
1756
		'id_first_msg' => $first_msg,
1757
		'id_last_msg' => $last_msg,
1758
		'id_poll' => $target_poll,
1759
		'num_replies' => $num_replies,
1760
		'unapproved_posts' => $num_unapproved,
1761
		'num_views' => $num_views,
1762
		'subject' => $target_subject,
1763
	);
1764
	call_integration_hook('integrate_merge_topic', array($merged_topic, $updated_topics, $deleted_topics, $deleted_polls));
1765
1766
	// Send them to the all done page.
1767
	redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board);
1768
}
1769
1770
/**
1771
 * Shows a 'merge completed' screen.
1772
 * is accessed with ?action=mergetopics;sa=done.
1773
 * uses 'merge_done' sub template of the SplitTopics template.
1774
 */
1775
function MergeDone()
1776
{
1777
	global $txt, $context;
1778
1779
	// Make sure the template knows everything...
1780
	$context['target_board'] = (int) $_GET['targetboard'];
1781
	$context['target_topic'] = (int) $_GET['to'];
1782
1783
	$context['page_title'] = $txt['merge'];
1784
	$context['sub_template'] = 'merge_done';
1785
}
1786
1787
?>