Issues (1014)

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 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.0
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' => $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' => $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' => $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' => $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
		// Replace tokens with links in the reason.
1403
		$reason_replacements = array(
1404
			$txt['movetopic_auto_topic'] => '[iurl="' . $scripturl . '?topic=' . $id_topic . '.0"]' . $target_subject . '[/iurl]',
1405
		);
1406
1407
		// Should be in the boardwide language.
1408
		if ($user_info['language'] != $language)
1409
		{
1410
			loadLanguage('index', $language);
1411
1412
			// Make sure we catch both languages in the reason.
1413
			$reason_replacements += array(
1414
				$txt['movetopic_auto_topic'] => '[iurl="' . $scripturl . '?topic=' . $id_topic . '.0"]' . $target_subject . '[/iurl]',
1415
			);
1416
		}
1417
1418
		$_POST['reason'] = $smcFunc['htmlspecialchars']($_POST['reason'], ENT_QUOTES);
1419
		preparsecode($_POST['reason']);
1420
1421
		// Add a URL onto the message.
1422
		$reason = strtr($_POST['reason'], $reason_replacements);
1423
1424
		// Automatically remove this MERGED redirection topic in the future?
1425
		$redirect_expires = !empty($_POST['redirect_expires']) ? ((int) ($_POST['redirect_expires'] * 60) + time()) : 0;
1426
1427
		// Redirect to the MERGED topic from topic list?
1428
		$redirect_topic = isset($_POST['redirect_topic']) ? $id_topic : 0;
1429
1430
		foreach ($deleted_topics as $this_old_topic)
1431
		{
1432
			$redirect_subject = sprintf($txt['merged_subject'], $topic_data[$this_old_topic]['subject']);
1433
1434
			$msgOptions = array(
1435
				'icon' => 'moved',
1436
				'subject' => $redirect_subject,
1437
				'body' => $reason,
1438
				'approved' => 1,
1439
			);
1440
			$topicOptions = array(
1441
				'id' => $this_old_topic,
1442
				'is_approved' => true,
1443
				'lock_mode' => 1,
1444
				'board' => $topic_data[$this_old_topic]['board'],
1445
				'mark_as_read' => true,
1446
			);
1447
1448
			// So we have to make the post. We need to do *this* here so we don't foul up indexes later
1449
			// and we have to fix them up later once everything else has happened.
1450
			if (createPost($msgOptions, $topicOptions, $posterOptions))
1451
			{
1452
				$updated_topics[$this_old_topic] = $msgOptions['id'];
1453
			}
1454
1455
			// Update subject search index
1456
			updateStats('subject', $this_old_topic, $redirect_subject);
1457
		}
1458
1459
		// Restore language strings to normal.
1460
		if ($user_info['language'] != $language)
1461
			loadLanguage('index');
1462
	}
1463
1464
	// Grab the response prefix (like 'Re: ') in the default forum language.
1465
	if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
1466
	{
1467
		if ($language === $user_info['language'])
1468
			$context['response_prefix'] = $txt['response_prefix'];
1469
		else
1470
		{
1471
			loadLanguage('index', $language, false);
1472
			$context['response_prefix'] = $txt['response_prefix'];
1473
			loadLanguage('index');
1474
		}
1475
		cache_put_data('response_prefix', $context['response_prefix'], 600);
1476
	}
1477
1478
	// Change the topic IDs of all messages that will be merged.  Also adjust subjects if 'enforce subject' was checked.
1479
	$smcFunc['db_query']('', '
1480
		UPDATE {db_prefix}messages
1481
		SET
1482
			id_topic = {int:id_topic},
1483
			id_board = {int:target_board}' . (empty($_POST['enforce_subject']) ? '' : ',
1484
			subject = {string:subject}') . '
1485
		WHERE id_topic IN ({array_int:topic_list})' . (!empty($updated_topics) ? '
1486
			AND id_msg NOT IN ({array_int:merge_msg})' : ''),
1487
		array(
1488
			'topic_list' => $topics,
1489
			'id_topic' => $id_topic,
1490
			'merge_msg' => $updated_topics,
1491
			'target_board' => $target_board,
1492
			'subject' => $context['response_prefix'] . $target_subject,
1493
		)
1494
	);
1495
1496
	// Any reported posts should reflect the new board.
1497
	$smcFunc['db_query']('', '
1498
		UPDATE {db_prefix}log_reported
1499
		SET
1500
			id_topic = {int:id_topic},
1501
			id_board = {int:target_board}
1502
		WHERE id_topic IN ({array_int:topics_list})',
1503
		array(
1504
			'topics_list' => $topics,
1505
			'id_topic' => $id_topic,
1506
			'target_board' => $target_board,
1507
		)
1508
	);
1509
1510
	// Change the subject of the first message...
1511
	$smcFunc['db_query']('', '
1512
		UPDATE {db_prefix}messages
1513
		SET subject = {string:target_subject}
1514
		WHERE id_msg = {int:first_msg}',
1515
		array(
1516
			'first_msg' => $first_msg,
1517
			'target_subject' => $target_subject,
1518
		)
1519
	);
1520
1521
	// Adjust all calendar events to point to the new topic.
1522
	$smcFunc['db_query']('', '
1523
		UPDATE {db_prefix}calendar
1524
		SET
1525
			id_topic = {int:id_topic},
1526
			id_board = {int:target_board}
1527
		WHERE id_topic IN ({array_int:deleted_topics})',
1528
		array(
1529
			'deleted_topics' => $deleted_topics,
1530
			'id_topic' => $id_topic,
1531
			'target_board' => $target_board,
1532
		)
1533
	);
1534
1535
	// Merge log topic entries.
1536
	// The unwatch setting comes from the oldest topic
1537
	$request = $smcFunc['db_query']('', '
1538
		SELECT id_member, MIN(id_msg) AS new_id_msg, unwatched
1539
		FROM {db_prefix}log_topics
1540
		WHERE id_topic IN ({array_int:topics})
1541
		GROUP BY id_member, unwatched',
1542
		array(
1543
			'topics' => $topics,
1544
		)
1545
	);
1546
	if ($smcFunc['db_num_rows']($request) > 0)
1547
	{
1548
		$replaceEntries = array();
1549
		while ($row = $smcFunc['db_fetch_assoc']($request))
1550
			$replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg'], $row['unwatched']);
1551
1552
		$smcFunc['db_insert']('replace',
1553
			'{db_prefix}log_topics',
1554
			array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
1555
			$replaceEntries,
1556
			array('id_member', 'id_topic')
1557
		);
1558
		unset($replaceEntries);
1559
1560
		// Get rid of the old log entries.
1561
		$smcFunc['db_query']('', '
1562
			DELETE FROM {db_prefix}log_topics
1563
			WHERE id_topic IN ({array_int:deleted_topics})',
1564
			array(
1565
				'deleted_topics' => $deleted_topics,
1566
			)
1567
		);
1568
	}
1569
	$smcFunc['db_free_result']($request);
1570
1571
	// Merge topic notifications.
1572
	$notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array();
1573
	if (!empty($notifications))
1574
	{
1575
		$request = $smcFunc['db_query']('', '
1576
			SELECT id_member, MAX(sent) AS sent
1577
			FROM {db_prefix}log_notify
1578
			WHERE id_topic IN ({array_int:topics_list})
1579
			GROUP BY id_member',
1580
			array(
1581
				'topics_list' => $notifications,
1582
			)
1583
		);
1584
		if ($smcFunc['db_num_rows']($request) > 0)
1585
		{
1586
			$replaceEntries = array();
1587
			while ($row = $smcFunc['db_fetch_assoc']($request))
1588
				$replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']);
1589
1590
			$smcFunc['db_insert']('replace',
1591
				'{db_prefix}log_notify',
1592
				array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'sent' => 'int'),
1593
				$replaceEntries,
1594
				array('id_member', 'id_topic', 'id_board')
1595
			);
1596
			unset($replaceEntries);
1597
1598
			$smcFunc['db_query']('', '
1599
				DELETE FROM {db_prefix}log_topics
1600
				WHERE id_topic IN ({array_int:deleted_topics})',
1601
				array(
1602
					'deleted_topics' => $deleted_topics,
1603
				)
1604
			);
1605
		}
1606
		$smcFunc['db_free_result']($request);
1607
	}
1608
1609
	// Get rid of the redundant polls.
1610
	if (!empty($deleted_polls))
1611
	{
1612
		$smcFunc['db_query']('', '
1613
			DELETE FROM {db_prefix}polls
1614
			WHERE id_poll IN ({array_int:deleted_polls})',
1615
			array(
1616
				'deleted_polls' => $deleted_polls,
1617
			)
1618
		);
1619
		$smcFunc['db_query']('', '
1620
			DELETE FROM {db_prefix}poll_choices
1621
			WHERE id_poll IN ({array_int:deleted_polls})',
1622
			array(
1623
				'deleted_polls' => $deleted_polls,
1624
			)
1625
		);
1626
		$smcFunc['db_query']('', '
1627
			DELETE FROM {db_prefix}log_polls
1628
			WHERE id_poll IN ({array_int:deleted_polls})',
1629
			array(
1630
				'deleted_polls' => $deleted_polls,
1631
			)
1632
		);
1633
	}
1634
1635
	// Cycle through each board...
1636
	foreach ($boardTotals as $id_board => $stats)
1637
	{
1638
		$smcFunc['db_query']('', '
1639
			UPDATE {db_prefix}boards
1640
			SET
1641
				num_topics = CASE WHEN {int:topics} > num_topics THEN 0 ELSE num_topics - {int:topics} END,
1642
				unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END,
1643
				num_posts = CASE WHEN {int:posts} > num_posts THEN 0 ELSE num_posts - {int:posts} END,
1644
				unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END
1645
			WHERE id_board = {int:id_board}',
1646
			array(
1647
				'id_board' => $id_board,
1648
				'topics' => $stats['topics'],
1649
				'unapproved_topics' => $stats['unapproved_topics'],
1650
				'posts' => $stats['posts'],
1651
				'unapproved_posts' => $stats['unapproved_posts'],
1652
			)
1653
		);
1654
	}
1655
1656
	// Determine the board the final topic resides in
1657
	$request = $smcFunc['db_query']('', '
1658
		SELECT id_board
1659
		FROM {db_prefix}topics
1660
		WHERE id_topic = {int:id_topic}
1661
		LIMIT 1',
1662
		array(
1663
			'id_topic' => $id_topic,
1664
		)
1665
	);
1666
	list($id_board) = $smcFunc['db_fetch_row']($request);
1667
	$smcFunc['db_free_result']($request);
1668
1669
	// Again, only do this if we're redirecting - otherwise delete
1670
	if (isset($_POST['postRedirect']))
1671
	{
1672
		// Having done all that, now make sure we fix the merge/redirect topics upp before we
1673
		// leave here. Specifically: that there are no replies, no unapproved stuff, that the first
1674
		// and last posts are the same and so on and so forth.
1675
		foreach ($updated_topics as $old_topic => $id_msg)
1676
		{
1677
			$smcFunc['db_query']('', '
1678
				UPDATE {db_prefix}topics
1679
				SET id_first_msg = id_last_msg,
1680
					id_member_started = {int:current_user},
1681
					id_member_updated = {int:current_user},
1682
					id_poll = 0,
1683
					approved = 1,
1684
					num_replies = 0,
1685
					unapproved_posts = 0,
1686
					id_redirect_topic = {int:redirect_topic},
1687
					redirect_expires = {int:redirect_expires}
1688
				WHERE id_topic = {int:old_topic}',
1689
				array(
1690
					'current_user' => $user_info['id'],
1691
					'old_topic' => $old_topic,
1692
					'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...
1693
					'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...
1694
				)
1695
			);
1696
		}
1697
	}
1698
1699
	// Ensure we don't accidentally delete the poll we want to keep...
1700
	$smcFunc['db_query']('', '
1701
		UPDATE {db_prefix}topics
1702
		SET id_poll = 0
1703
		WHERE id_topic IN ({array_int:deleted_topics})',
1704
		array(
1705
			'deleted_topics' => $deleted_topics
1706
		)
1707
	);
1708
1709
	// Delete any remaining data regarding these topics, this is done before changing the properties of the merged topic (else we get duplicate keys)...
1710
	if (!isset($_POST['postRedirect']))
1711
	{
1712
		// Remove any remaining info about these topics...
1713
		include_once($sourcedir . '/RemoveTopic.php');
1714
		// We do not need to remove the counts of the deleted topics, as we already removed these.
1715
		removeTopics($deleted_topics, false, true, false);
1716
	}
1717
1718
	// Asssign the properties of the newly merged topic.
1719
	$smcFunc['db_query']('', '
1720
		UPDATE {db_prefix}topics
1721
		SET
1722
			id_board = {int:id_board},
1723
			id_member_started = {int:id_member_started},
1724
			id_member_updated = {int:id_member_updated},
1725
			id_first_msg = {int:id_first_msg},
1726
			id_last_msg = {int:id_last_msg},
1727
			id_poll = {int:id_poll},
1728
			num_replies = {int:num_replies},
1729
			unapproved_posts = {int:unapproved_posts},
1730
			num_views = {int:num_views},
1731
			is_sticky = {int:is_sticky},
1732
			approved = {int:approved}
1733
		WHERE id_topic = {int:id_topic}',
1734
		array(
1735
			'id_board' => $target_board,
1736
			'is_sticky' => $is_sticky,
1737
			'approved' => $topic_approved,
1738
			'id_topic' => $id_topic,
1739
			'id_member_started' => $member_started,
1740
			'id_member_updated' => $member_updated,
1741
			'id_first_msg' => $first_msg,
1742
			'id_last_msg' => $last_msg,
1743
			'id_poll' => $target_poll,
1744
			'num_replies' => $num_replies,
1745
			'unapproved_posts' => $num_unapproved,
1746
			'num_views' => $num_views,
1747
		)
1748
	);
1749
1750
	// Update all the statistics.
1751
	updateStats('topic');
1752
	updateStats('subject', $id_topic, $target_subject);
1753
	updateLastMessages($boards);
1754
1755
	logAction('merge', array('topic' => $id_topic, 'board' => $id_board));
1756
1757
	// Notify people that these topics have been merged?
1758
	sendNotifications($id_topic, 'merge');
1759
1760
	// If there's a search index that needs updating, update it...
1761
	require_once($sourcedir . '/Search.php');
1762
	$searchAPI = findSearchAPI();
1763
	if (is_callable(array($searchAPI, 'topicMerge')))
1764
		$searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($_POST['enforce_subject']) ? null : array($context['response_prefix'], $target_subject));
1765
1766
	// Merging is the sort of thing an external CMS might want to know about
1767
	$merged_topic = array(
1768
		'id_board' => $target_board,
1769
		'is_sticky' => $is_sticky,
1770
		'approved' => $topic_approved,
1771
		'id_topic' => $id_topic,
1772
		'id_member_started' => $member_started,
1773
		'id_member_updated' => $member_updated,
1774
		'id_first_msg' => $first_msg,
1775
		'id_last_msg' => $last_msg,
1776
		'id_poll' => $target_poll,
1777
		'num_replies' => $num_replies,
1778
		'unapproved_posts' => $num_unapproved,
1779
		'num_views' => $num_views,
1780
		'subject' => $target_subject,
1781
	);
1782
	call_integration_hook('integrate_merge_topic', array($merged_topic, $updated_topics, $deleted_topics, $deleted_polls));
1783
1784
	// Send them to the all done page.
1785
	redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board);
1786
}
1787
1788
/**
1789
 * Shows a 'merge completed' screen.
1790
 * is accessed with ?action=mergetopics;sa=done.
1791
 * uses 'merge_done' sub template of the SplitTopics template.
1792
 */
1793
function MergeDone()
1794
{
1795
	global $txt, $context;
1796
1797
	// Make sure the template knows everything...
1798
	$context['target_board'] = (int) $_GET['targetboard'];
1799
	$context['target_topic'] = (int) $_GET['to'];
1800
1801
	$context['page_title'] = $txt['merge'];
1802
	$context['sub_template'] = 'merge_done';
1803
}
1804
1805
?>