Issues (1686)

sources/ElkArte/Controller/SplitTopics.php (1 issue)

1
<?php
2
3
/**
4
 * Handle splitting of topics
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 * Original module by Mach8 - We'll never forget you.
16
 */
17
18
namespace ElkArte\Controller;
19
20
use ElkArte\AbstractController;
21
use ElkArte\Action;
22
use ElkArte\Exceptions\Exception;
23
use ElkArte\Helper\Util;
24
25
/**
26
 * Allows to take a topic and split at a point or select individual messages to
27
 * split to a new topic.
28
 *
29
 * - Requires the split_any permission
30
 */
31
class SplitTopics extends AbstractController
32
{
33
	/** @var string Holds the new subject for the split topic */
34
	private $_new_topic_subject;
35
36
	/**
37
	 * Intended entry point for this class.
38
	 *
39
	 * @see AbstractController::action_index
40
	 */
41
	public function action_index()
42
	{
43
		// Call the right method.
44
	}
45
46
	/**
47
	 * Splits a topic into two topics.
48
	 *
49
	 * What it does:
50
	 *
51
	 * - Delegates to the other functions (based on the URL parameter 'sa').
52
	 * - Loads the SplitTopics template.
53
	 * - Requires the split_any permission.
54
	 * - Accessed with ?action=splittopics.
55
	 */
56
	public function action_splittopics()
57
	{
58
		global $topic;
59
60
		// And... which topic were you splitting, again?
61
		if (empty($topic))
62
		{
63
			throw new Exception('numbers_one_to_nine', false);
64
		}
65
66
		// Load up the "dependencies" - the template, getMsgMemberID().
67
		if (!$this->getApi())
68
		{
69
			theme()->getTemplates()->load('SplitTopics');
70
		}
71
72
		// Need some utilities to deal with topics
73
		require_once(SUBSDIR . '/Boards.subs.php');
74
		require_once(SUBSDIR . '/Post.subs.php');
75
76
		// The things we know to do
77
		$subActions = array(
78
			'selectTopics' => array($this, 'action_splitSelectTopics', 'permission' => 'split_any'),
79
			'execute' => array($this, 'action_splitExecute', 'permission' => 'split_any'),
80
			'index' => array($this, 'action_splitIndex', 'permission' => 'split_any'),
81
			'splitSelection' => array($this, 'action_splitSelection', 'permission' => 'split_any'),
82
		);
83
84
		// To the right sub action or index if an invalid choice was submitted
85
		$action = new Action('split_topics');
86
		$subAction = $action->initialize($subActions, 'index');
87
		$action->dispatch($subAction);
88
	}
89
90
	/**
91
	 * Screen shown before the actual split.
92
	 *
93
	 * What it does:
94
	 *
95
	 * - Is accessed with ?action=splittopics or ?action=splittopics;sa=index
96
	 * - Default sub action for ?action=splittopics.
97
	 * - Redirects to action_splitSelectTopics if the message given turns out to be
98
	 * the first message of a topic.
99
	 * - Shows the user three ways to split the current topic.
100
	 *
101
	 * @uses template_ask() in SplitTopics.template
102
	 */
103
	public function action_splitIndex()
104
	{
105
		global $txt, $context, $modSettings;
106
107
		// Split at a specific topic
108
		$splitAt = $this->_req->getQuery('at', 'intval', 0);
109
110
		// Validate "at".
111
		if (empty($this->_req->query->at))
112
		{
113
			throw new Exception('numbers_one_to_nine', false);
114
		}
115
116
		// We deal with topics here.
117
		require_once(SUBSDIR . '/Boards.subs.php');
118
		require_once(SUBSDIR . '/Messages.subs.php');
119
120
		// Let's load up the boards in case they are useful.
121
		$context += getBoardList(array('not_redirection' => true));
122
123
		// Retrieve message info for the message at the split point.
124
		$messageInfo = basicMessageInfo($splitAt, false, true);
125
		if ($messageInfo === false)
126
		{
127
			throw new Exception('cant_find_messages');
128
		}
129
130
		// If not approved validate they can approve it.
131
		if ($modSettings['postmod_active'] && !$messageInfo['topic_approved'])
132
		{
133
			isAllowedTo('approve_posts');
134
		}
135
136
		// If this topic has unapproved posts, we need to count them too...
137
		if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
138
		{
139
			$messageInfo['num_replies'] += $messageInfo['unapproved_posts'] - ($messageInfo['topic_approved'] ? 0 : 1);
140
		}
141
142
		// If they can more it as well, allow the template to give them a move to board list
143
		$context['can_move'] = allowedTo('move_any') || allowedTo('move_own');
144
145
		// Check if there is more than one message in the topic.  (there should be.)
146
		if ($messageInfo['num_replies'] < 1)
147
		{
148
			throw new Exception('topic_one_post', false);
149
		}
150
151
		// Check if this is the first message in the topic (if so, the first and second option won't be available)
152
		if ($messageInfo['id_first_msg'] == $splitAt)
153
		{
154
			$this->_new_topic_subject = $messageInfo['subject'];
155
			$this->_set_session_values();
156
			$this->action_splitSelectTopics();
157
		}
158
		else
159
		{
160
			// Basic template information....
161
			$context['message'] = array(
162
				'id' => $splitAt,
163
				'subject' => $messageInfo['subject']
164
			);
165
			$context['sub_template'] = 'ask';
166
			$context['page_title'] = $txt['split_topic'];
167
		}
168
	}
169
170
	/**
171
	 * Set the values for this split session
172
	 */
173
	private function _set_session_values()
174
	{
175
		global $txt;
176
177
		// Clean up the subject.
178
		$subname = $this->_req->getPost('subname', 'trim', $this->_req->getQuery('subname', 'trim', null));
179
		if (isset($subname) && empty($this->_new_topic_subject))
180
		{
181
			$this->_new_topic_subject = Util::htmlspecialchars($subname);
182
		}
183
184
		if (empty($this->_new_topic_subject))
185
		{
186
			$this->_new_topic_subject = $txt['new_topic'];
187
		}
188
189
		// Save in session so its available across all the form pages
190
		if (empty($_SESSION['move_to_board']))
191
		{
192
			$_SESSION['move_to_board'] = (!empty($this->_req->post->move_new_topic) && !empty($this->_req->post->move_to_board)) ? (int) $this->_req->post->move_to_board : 0;
193
			$_SESSION['reason'] = empty($this->_req->post->reason) ? '' : trim(Util::htmlspecialchars($this->_req->post->reason, ENT_QUOTES));
194
			$_SESSION['messageRedirect'] = !empty($this->_req->post->messageRedirect);
195
			$_SESSION['new_topic_subject'] = $this->_new_topic_subject;
196
		}
197
	}
198
199
	/**
200
	 * Allows the user to select the messages to be split.
201
	 *
202
	 * What it does:
203
	 *
204
	 * - Is accessed with ?action=splittopics;sa=selectTopics.
205
	 * - Uses 'select' sub template of the SplitTopics template or (for
206
	 * XMLhttp) the 'split' sub template of the Xml template.
207
	 * - Supports XMLhttp for adding/removing a message to the selection.
208
	 * - Uses a session variable to store the selected topics.
209
	 * - Shows two independent page indexes for both the selected and
210
	 * not-selected messages (;topic=1.x;start2=y).
211
	 *
212
	 * @uses template_select() of SplitTopics.template
213
	 * @uses template_split() of SplitTopics.template
214
	 */
215
	public function action_splitSelectTopics()
216
	{
217
		global $txt, $topic, $context, $modSettings, $options;
218
219
		$context['page_title'] = $txt['split_topic'] . ' - ' . $txt['select_split_posts'];
220
		$context['destination_board'] = empty($this->_req->post->move_to_board) ? 0 : (int) $this->_req->post->move_to_board;
221
222
		// Haven't selected anything have we?
223
		$_SESSION['split_selection'][$topic] = empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic];
224
225
		// This is a special case for split topics from quick-moderation checkboxes
226
		if (isset($this->_req->query->subname_enc))
227
		{
228
			$this->_new_topic_subject = trim(Util::htmlspecialchars(urldecode($this->_req->query->subname_enc)));
229
			$this->_set_session_values();
230
		}
231
232
		require_once(SUBSDIR . '/Topic.subs.php');
233
		require_once(SUBSDIR . '/Messages.subs.php');
234
235
		$context['not_selected'] = array(
236
			'num_messages' => 0,
237
			'start' => $this->_req->getPost('start', 'intval', 0),
238
			'messages' => array(),
239
		);
240
241
		$context['selected'] = [
242
			'num_messages' => 0,
243
			'start' => $this->_req->getQuery('start2', 'intval', 0),
244
			'messages' => [],
245
		];
246
247
		$context['topic'] = [
248
			'id' => $topic,
249
			'subject' => urlencode($_SESSION['new_topic_subject']),
250
		];
251
252
		// Some stuff for our favorite template.
253
		$context['new_subject'] = $_SESSION['new_topic_subject'];
254
255
		// Using the "select" sub template.
256
		$context['sub_template'] = $this->getApi() ? 'split' : 'select';
257
258
		// All of the js for topic split selection is needed
259
		if (!$this->getApi())
260
		{
261
			loadJavascriptFile('topic.js');
262
		}
263
264
		// Are we using a custom messages per page?
265
		$context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
266
267
		// Get the message ID's from before the move.
268
		if ($this->getApi())
269
		{
270
			$original_msgs = [
271
				'not_selected' => messageAt($context['not_selected']['start'], $topic, [
272
					'not_in' => empty($_SESSION['split_selection'][$topic]) ? [] : $_SESSION['split_selection'][$topic],
273
					'only_approved' => !$modSettings['postmod_active'] || !allowedTo('approve_posts'),
274
					'limit' => $context['messages_per_page'],
275
				]),
276
				'selected' => [],
277
			];
278
279
			// You can't split the last message off.
280
			if (empty($context['not_selected']['start']) && count($original_msgs['not_selected']) <= 1 && $this->_req->query->move === 'down')
281
			{
282
				$this->_req->query->move = '';
283
			}
284
285
			if (!empty($_SESSION['split_selection'][$topic]))
286
			{
287
				$original_msgs['selected'] = messageAt($context['selected']['start'], $topic, array(
288
					'include' => empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic],
289
					'only_approved' => !$modSettings['postmod_active'] || !allowedTo('approve_posts'),
290
					'limit' => $context['messages_per_page'],
291
				));
292
			}
293
		}
294
295
		// (De)select a message..
296
		if (!empty($this->_req->query->move))
297
		{
298
			$_id_msg = $this->_req->getQuery('msg', 'intval');
299
300
			if ($this->_req->query->move === 'reset')
301
			{
302
				$_SESSION['split_selection'][$topic] = array();
303
			}
304
			elseif ($this->_req->query->move === 'up')
305
			{
306
				$_SESSION['split_selection'][$topic] = array_diff($_SESSION['split_selection'][$topic], array($_id_msg));
307
			}
308
			else
309
			{
310
				$_SESSION['split_selection'][$topic][] = $_id_msg;
311
			}
312
		}
313
314
		// Make sure the selection is still accurate.
315
		if (!empty($_SESSION['split_selection'][$topic]))
316
		{
317
			$_SESSION['split_selection'][$topic] = messageAt(0, $topic, array(
318
				'include' => empty($_SESSION['split_selection'][$topic]) ? array() : $_SESSION['split_selection'][$topic],
319
				'only_approved' => !$modSettings['postmod_active'] || !allowedTo('approve_posts'),
320
				'limit' => false,
321
			));
322
			$selection = $_SESSION['split_selection'][$topic];
323
		}
324
		else
325
		{
326
			$selection = array();
327
		}
328
329
		// Get the number of messages (not) selected to be split.
330
		$split_counts = countSplitMessages($topic, !$modSettings['postmod_active'] || allowedTo('approve_posts'), $selection);
331
		foreach ($split_counts as $key => $num_messages)
332
		{
333
			$context[$key]['num_messages'] = $num_messages;
334
		}
335
336
		// Fix an oversize starting page (to make sure both pageindexes are properly set).
337
		if ($context['selected']['start'] >= $context['selected']['num_messages'])
338
		{
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
342
		$page_index_url = '{scripturl}?action=splittopics;sa=selectTopics;subname=' . strtr(urlencode($_SESSION['new_topic_subject']), array('%' => '%%')) . ';topic=' . $topic;
343
344
		// Build a page list of the not-selected topics...
345
		$context['not_selected']['page_index'] = constructPageIndex($page_index_url . '.%1$d;start2=' . $context['selected']['start'], $context['not_selected']['start'], $context['not_selected']['num_messages'], $context['messages_per_page'], true);
346
347
		// ...and one of the selected topics.
348
		$context['selected']['page_index'] = constructPageIndex($page_index_url . '.' . $context['not_selected']['start'] . ';start2=%1$d', $context['selected']['start'], $context['selected']['num_messages'], $context['messages_per_page'], true);
349
350
		// Retrieve the unselected messages.
351
		$context['not_selected']['messages'] = selectMessages($topic, $context['not_selected']['start'], $context['messages_per_page'], empty($_SESSION['split_selection'][$topic]) ? array() : array('excluded' => $_SESSION['split_selection'][$topic]), $modSettings['postmod_active'] && !allowedTo('approve_posts'));
352
353
		// Now retrieve the selected messages.
354
		if (!empty($_SESSION['split_selection'][$topic]))
355
		{
356
			$context['selected']['messages'] = selectMessages($topic, $context['selected']['start'], $context['messages_per_page'], array('included' => $_SESSION['split_selection'][$topic]), $modSettings['postmod_active'] && !allowedTo('approve_posts'));
357
		}
358
359
		// The XMLhttp method only needs the stuff that changed, so let's compare.
360
		if ($this->getApi())
361
		{
362
			$changes = [
363
				'remove' => [
364
					'not_selected' => array_diff($original_msgs['not_selected'], array_keys($context['not_selected']['messages'])),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $original_msgs does not seem to be defined for all execution paths leading up to this point.
Loading history...
365
					'selected' => array_diff($original_msgs['selected'], array_keys($context['selected']['messages'])),
366
				],
367
				'insert' => [
368
					'not_selected' => array_diff(array_keys($context['not_selected']['messages']), $original_msgs['not_selected']),
369
					'selected' => array_diff(array_keys($context['selected']['messages']), $original_msgs['selected']),
370
				],
371
			];
372
373
			$context['changes'] = [];
374
			foreach ($changes as $change_type => $change_array)
375
			{
376
				foreach ($change_array as $section => $msg_array)
377
				{
378
					if (empty($msg_array))
379
					{
380
						continue;
381
					}
382
383
					foreach ($msg_array as $id_msg)
384
					{
385
						$context['changes'][$change_type . $id_msg] = array(
386
							'id' => $id_msg,
387
							'type' => $change_type,
388
							'section' => $section,
389
						);
390
391
						if ($change_type === 'insert')
392
						{
393
							$context['changes']['insert' . $id_msg]['insert_value'] = $context[$section]['messages'][$id_msg];
394
						}
395
					}
396
				}
397
			}
398
		}
399
	}
400
401
	/**
402
	 * Do the actual split.
403
	 *
404
	 * What it does:
405
	 *
406
	 * - Is accessed with ?action=splittopics;sa=execute.
407
	 * - Supports three ways of splitting:
408
	 *   (1) only one message is split off.
409
	 *   (2) all messages after and including a given message are split off.
410
	 *   (3) select topics to split (redirects to action_splitSelectTopics()).
411
	 * - Uses splitTopic function to do the actual splitting.
412
	 *
413
	 * @uses template_split_successful() in SplitTopics.template
414
	 */
415
	public function action_splitExecute()
416
	{
417
		global $txt, $context, $topic;
418
419
		// Check the session to make sure they meant to do this.
420
		checkSession();
421
422
		// Set the form options in to session
423
		$this->_set_session_values();
424
425
		// Cant post an empty redirect topic
426
		if (!empty($_SESSION['messageRedirect']) && empty($_SESSION['reason']))
427
		{
428
			$this->_unset_session_values();
429
			throw new Exception('splittopic_no_reason', false);
430
		}
431
432
		// Redirect to the selector if they chose selective.
433
		if ($this->_req->post->step2 === 'selective')
434
		{
435
			if (!empty($this->_req->post->at))
436
			{
437
				$_SESSION['split_selection'][$topic][] = (int) $this->_req->post->at;
438
			}
439
440
			$this->action_splitSelectTopics();
441
442
			return true;
443
		}
444
445
		// We work with them topics.
446
		require_once(SUBSDIR . '/Topic.subs.php');
447
		require_once(SUBSDIR . '/Boards.subs.php');
448
449
		// Make sure they can see the board they are trying to move to
450
		// (and get whether posts count in the target board).
451
		// Before the actual split because of the fatal_lang_errors
452
		$boards = splitDestinationBoard($_SESSION['move_to_board']);
453
454
		$splitAt = $this->_req->getPost('at', 'intval', 0);
455
		$messagesToBeSplit = array();
456
457
		// Fetch the message IDs of the topic that are at or after the message.
458
		if ($this->_req->post->step2 === 'afterthis')
459
		{
460
			$messagesToBeSplit = messagesSince($topic, $splitAt, true);
461
		}
462
		// Only the selected message has to be split. That should be easy.
463
		elseif ($this->_req->post->step2 === 'onlythis')
464
		{
465
			$messagesToBeSplit[] = $splitAt;
466
		}
467
		// There's another action?!
468
		else
469
		{
470
			$this->_unset_session_values();
471
			throw new Exception('no_access', false);
472
		}
473
474
		$context['old_topic'] = $topic;
475
		$context['new_topic'] = splitTopic($topic, $messagesToBeSplit, $_SESSION['new_topic_subject']);
476
		$context['page_title'] = $txt['split_topic'];
477
		$context['sub_template'] = 'split_successful';
478
479
		splitAttemptMove($boards, $context['new_topic']);
480
481
		// Create a link to this in the old topic.
482
		// @todo Does this make sense if the topic was unapproved before? We are not yet sure if the resulting topic is unapproved.
483
		if ($_SESSION['messageRedirect'])
484
		{
485
			postSplitRedirect($_SESSION['reason'], $_SESSION['new_topic_subject'], $boards['destination'], $context['new_topic']);
486
		}
487
488
		$this->_unset_session_values();
489
490
		return true;
491
	}
492
493
	/**
494
	 * Clear out this split session
495
	 */
496
	private function _unset_session_values()
497
	{
498
		unset(
499
			$_SESSION['move_to_board'],
500
			$_SESSION['reason'],
501
			$_SESSION['messageRedirect'],
502
			$_SESSION['split_selection'],
503
			$_SESSION['new_topic_subject']
504
		);
505
	}
506
507
	/**
508
	 * Do the actual split of a selection of topics.
509
	 *
510
	 * What it does:
511
	 *
512
	 * - Is accessed with ?action=splittopics;sa=splitSelection.
513
	 * - Uses the main SplitTopics template.
514
	 *
515
	 * @uses splitTopic() function to do the actual splitting.
516
	 * @uses template_split_successful() of SplitTopics.template
517
	 */
518
	public function action_splitSelection()
519
	{
520
		global $txt, $topic, $context;
521
522
		// Make sure the session id was passed with post.
523
		checkSession();
524
525
		require_once(SUBSDIR . '/Topic.subs.php');
526
527
		// You must've selected some messages!  Can't split out none!
528
		if (empty($_SESSION['split_selection'][$topic]))
529
		{
530
			$this->_unset_session_values();
531
			throw new Exception('no_posts_selected', false);
532
		}
533
534
		// This is here because there are two fatal_lang_errors in there
535
		$boards = splitDestinationBoard($_SESSION['move_to_board']);
536
537
		$context['old_topic'] = $topic;
538
		$context['new_topic'] = splitTopic($topic, $_SESSION['split_selection'][$topic], $_SESSION['new_topic_subject']);
539
		$context['page_title'] = $txt['split_topic'];
540
		$context['sub_template'] = 'split_successful';
541
542
		splitAttemptMove($boards, $context['new_topic']);
543
544
		// Create a link to this in the old topic.
545
		// @todo Does this make sense if the topic was unapproved before? We are not yet sure if the resulting topic is unapproved.
546
		if ($_SESSION['messageRedirect'])
547
		{
548
			postSplitRedirect($_SESSION['reason'], $_SESSION['new_topic_subject'], $boards['destination'], $context['new_topic']);
549
		}
550
551
		$this->_unset_session_values();
552
	}
553
}
554