Completed
Pull Request — master (#3325)
by Emanuele
11:19
created

Display_Controller::action_index()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
nc 1
nop 0
ccs 0
cts 2
cp 0
crap 2
1
<?php
2
3
/**
4
 * This controls topic display, with all related functions, its is the forum
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.1
15
 *
16
 */
17
18
/**
19
 * Display_Controller class.
20
 * This controller is the most important and probably most accessed of all.
21
 * It controls topic display, with all related.
22
 */
23
class Display_Controller extends Action_Controller
24
{
25
	/**
26
	 * The template layers object
27
	 * @var null|object
28
	 */
29
	protected $_template_layers = null;
30
31
	/**
32
	 * The message id when in the form msg123
33
	 * @var int
34
	 */
35
	protected $_virtual_msg = 0;
36
37
	/**
38
	 * The class that takes care of rendering the message icons (MessageTopicIcons)
39
	 * @var null|MessageTopicIcons
40
	 */
41
	protected $_icon_sources = null;
42
43
	/**
44
	 * Show signatures?
45
	 *
46
	 * @var int
47
	 */
48
	protected $_show_signatures = 0;
49
50
	/**
51
	 * Start viewing the topics from ... (page, all, other)
52
	 * @var int|string
53
	 */
54
	private $_start;
55
56
	/**
57
	 * Default action handler for this controller
58
	 */
59
	public function action_index()
60
	{
61
		// what to do... display things!
62
		$this->action_display();
63
	}
64
65
	/**
66
	 * If we are in a topic and don't have permission to approve it then duck out now.
67
	 * This is an abuse of the method, but it's easier that way.
68
	 *
69
	 * @param string $action the function name of the current action
70
	 *
71
	 * @return bool
72
	 * @throws Elk_Exception not_a_topic
73
	 */
74
	public function trackStats($action = '')
75
	{
76
		global $user_info, $topic, $board_info;
77
78
		if (!empty($topic) && empty($board_info['cur_topic_approved']) && !allowedTo('approve_posts') && ($user_info['id'] != $board_info['cur_topic_starter'] || $user_info['is_guest']))
79
		{
80
			throw new Elk_Exception('not_a_topic', false);
81
		}
82
83
		return parent::trackStats($action);
84
	}
85
86
	/**
87
	 * The central part of the board - topic display.
88
	 *
89
	 * What it does:
90
	 *
91
	 * - This function loads the posts in a topic up so they can be displayed.
92
	 * - It requires a topic, and can go to the previous or next topic from it.
93
	 * - It jumps to the correct post depending on a number/time/IS_MSG passed.
94
	 * - It depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
95
	 * - It is accessed by ?topic=id_topic.START.
96
	 *
97
	 * @uses the main sub template of the Display template.
98
	 */
99
	public function action_display()
100
	{
101
		global $scripturl, $txt, $modSettings, $context, $settings;
102
		global $options, $user_info, $board_info, $topic, $board;
103
		global $attachments, $messages_request;
104
105
		$this->_events->trigger('pre_load', array('_REQUEST' => &$_REQUEST, 'topic' => $topic, 'board' => &$board));
106
107
		// What are you gonna display if these are empty?!
108
		if (empty($topic))
109
			throw new Elk_Exception('no_board', false);
110
111
		// Load the template
112
		loadTemplate('Display');
113
		$context['sub_template'] = 'messages';
114
115
		// And the topic functions
116
		require_once(SUBSDIR . '/Topic.subs.php');
117
		require_once(SUBSDIR . '/Messages.subs.php');
118
119
		// Not only does a prefetch make things slower for the server, but it makes it impossible to know if they read it.
120
		stop_prefetching();
121
122
		// How much are we sticking on each page?
123
		$context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
124
		$this->_template_layers = Template_Layers::instance();
125
		$this->_template_layers->addEnd('messages_informations');
126
		$includeUnapproved = !$modSettings['postmod_active'] || allowedTo('approve_posts');
127
128
		// Let's do some work on what to search index.
129
		if (count((array) $this->_req->query) > 2)
130
		{
131
			foreach ($this->_req->query as $k => $v)
132
			{
133
				if (!in_array($k, array('topic', 'board', 'start', session_name())))
134
					$context['robot_no_index'] = true;
135
			}
136
		}
137
138
		$this->_start = $this->_req->getQuery('start');
139
		if (!empty($this->_start) && (!is_numeric($this->_start) || $this->_start % $context['messages_per_page'] !== 0))
140
			$context['robot_no_index'] = true;
141
142
		// Find the previous or next topic.  Make a fuss if there are no more.
143
		if ($this->_req->getQuery('prev_next') === 'prev' || $this->_req->getQuery('prev_next') === 'next')
144
		{
145
			// No use in calculating the next topic if there's only one.
146
			if ($board_info['num_topics'] > 1)
147
			{
148
				$topic = $this->_req->query->prev_next === 'prev'
149
					? previousTopic($topic, $board, $user_info['id'], $includeUnapproved)
150
					: nextTopic($topic, $board, $user_info['id'], $includeUnapproved);
151
				$context['current_topic'] = $topic;
152
			}
153
154
			// Go to the newest message on this topic.
155
			$this->_start = 'new';
156
		}
157
158
		// Add 1 to the number of views of this topic (except for robots).
159
		if (!$user_info['possibly_robot'] && (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] != $topic))
160
		{
161
			increaseViewCounter($topic);
162
			$_SESSION['last_read_topic'] = $topic;
163
		}
164
165
		$topic_selects = array();
166
		$topic_tables = array();
167
		$topic_parameters = array(
168
			'topic' => $topic,
169
			'member' => $user_info['id'],
170
			'board' => (int) $board,
171
		);
172
173
		// Allow addons to add additional details to the topic query
174
		call_integration_hook('integrate_topic_query', array(&$topic_selects, &$topic_tables, &$topic_parameters));
175
176
		// Load the topic details
177
		$topicinfo = getTopicInfo($topic_parameters, 'all', $topic_selects, $topic_tables);
178
		if (empty($topicinfo))
179
			throw new Elk_Exception('not_a_topic', false);
180
181
		// Is this a moved topic that we are redirecting to?
182
		if (!empty($topicinfo['id_redirect_topic']) && !isset($this->_req->query->noredir))
183
		{
184
			markTopicsRead(array($user_info['id'], $topic, $topicinfo['id_last_msg'], 0), $topicinfo['new_from'] !== 0);
185
			redirectexit('topic=' . $topicinfo['id_redirect_topic'] . '.0;redirfrom=' . $topicinfo['id_topic']);
186
		}
187
188
		$context['real_num_replies'] = $context['num_replies'] = $topicinfo['num_replies'];
189
		$context['topic_first_message'] = $topicinfo['id_first_msg'];
190
		$context['topic_last_message'] = $topicinfo['id_last_msg'];
191
		$context['topic_unwatched'] = isset($topicinfo['unwatched']) ? $topicinfo['unwatched'] : 0;
192
		if (isset($this->_req->query->redirfrom))
193
		{
194
			$redirfrom = $this->_req->getQuery('redirfrom', 'intval');
195
			$redir_topics = topicsList(array($redirfrom));
196
			if (!empty($redir_topics[$redirfrom]))
197
			{
198
				$context['topic_redirected_from'] = $redir_topics[$redirfrom];
199
				$context['topic_redirected_from']['redir_href'] = $scripturl . '?topic=' . $context['topic_redirected_from']['id_topic'] . '.0;noredir';
200
			}
201
		}
202
203
		// Did this user start the topic or not?
204
		$context['user']['started'] = $user_info['id'] == $topicinfo['id_member_started'] && !$user_info['is_guest'];
205
		$context['topic_starter_id'] = $topicinfo['id_member_started'];
206
207
		$this->_events->trigger('topicinfo', array('topicinfo' => &$topicinfo, 'includeUnapproved' => $includeUnapproved));
208
209
		// Add up unapproved replies to get real number of replies...
210
		if ($modSettings['postmod_active'] && allowedTo('approve_posts'))
211
			$context['real_num_replies'] += $topicinfo['unapproved_posts'] - ($topicinfo['approved'] ? 0 : 1);
212
213
		// If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing.
214
		if (!$includeUnapproved && $topicinfo['unapproved_posts'] && !$user_info['is_guest'])
215
		{
216
			$myUnapprovedPosts = unapprovedPosts($topic, $user_info['id']);
217
218
			$total_visible_posts = $context['num_replies'] + $myUnapprovedPosts + ($topicinfo['approved'] ? 1 : 0);
219
		}
220
		elseif ($user_info['is_guest'])
221
			$total_visible_posts = $context['num_replies'] + ($topicinfo['approved'] ? 1 : 0);
222
		else
223
			$total_visible_posts = $context['num_replies'] + $topicinfo['unapproved_posts'] + ($topicinfo['approved'] ? 1 : 0);
224
225
		// When was the last time this topic was replied to?  Should we warn them about it?
226
		if (!empty($modSettings['oldTopicDays']))
227
		{
228
			$mgsOptions = basicMessageInfo($topicinfo['id_last_msg'], true);
229
			$context['oldTopicError'] = $mgsOptions['poster_time'] + $modSettings['oldTopicDays'] * 86400 < time() && empty($topicinfo['is_sticky']);
230
		}
231
		else
232
			$context['oldTopicError'] = false;
233
234
		// The start isn't a number; it's information about what to do, where to go.
235
		if (!is_numeric($this->_start))
236
		{
237
			// Redirect to the page and post with new messages, originally by Omar Bazavilvazo.
238
			if ($this->_start === 'new')
239
			{
240
				// Guests automatically go to the last post.
241
				if ($user_info['is_guest'])
242
				{
243
					$context['start_from'] = $total_visible_posts - 1;
244
					$this->_start = $context['start_from'];
245
				}
246
				else
247
				{
248
					// Fall through to the next if statement.
249
					$this->_start = 'msg' . $topicinfo['new_from'];
250
				}
251
			}
252
253
			// Start from a certain time index, not a message.
254
			if (substr($this->_start, 0, 4) === 'from')
255
			{
256
				$timestamp = (int) substr($this->_start, 4);
257
				if ($timestamp === 0)
258
					$this->_start = 0;
259
				else
260
				{
261
					// Find the number of messages posted before said time...
262
					$context['start_from'] = countNewPosts($topic, $topicinfo, $timestamp);
263
					$this->_start = $context['start_from'];
264
				}
265
			}
266
			// Link to a message...
267
			elseif (substr($this->_start, 0, 3) === 'msg')
268
			{
269
				$this->_virtual_msg = (int) substr($this->_start, 3);
270
				if (!$topicinfo['unapproved_posts'] && $this->_virtual_msg >= $topicinfo['id_last_msg'])
271
					$context['start_from'] = $total_visible_posts - 1;
272
				elseif (!$topicinfo['unapproved_posts'] && $this->_virtual_msg <= $topicinfo['id_first_msg'])
273
					$context['start_from'] = 0;
274
				else
275
				{
276
					$only_approved = $modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts');
277
					$context['start_from'] = countMessagesBefore($topic, $this->_virtual_msg, false, $only_approved, !$user_info['is_guest']);
278
				}
279
280
				// We need to reverse the start as well in this case.
281
				$this->_start = $context['start_from'];
282
			}
283
		}
284
285
		// Create a previous next string if the selected theme has it as a selected option.
286
		if ($modSettings['enablePreviousNext'])
287
			$context['links'] += array(
288
				'go_prev' => $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new',
289
				'go_next' => $scripturl . '?topic=' . $topic . '.0;prev_next=next#new'
290
			);
291
292
		// Check if spellchecking is both enabled and actually working. (for quick reply.)
293
		$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
294
		if ($context['show_spellchecking'])
295
			loadJavascriptFile('spellcheck.js', array('defer' => true));
296
297
		// Are we showing signatures - or disabled fields?
298
		$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
299
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
300
301
		// Censor the title...
302
		$topicinfo['subject'] = censor($topicinfo['subject']);
303
		$context['page_title'] = $topicinfo['subject'];
304
305
		// Allow addons access to the topicinfo array
306
		call_integration_hook('integrate_display_topic', array($topicinfo));
307
308
		// Default this topic to not marked for notifications... of course...
309
		$context['is_marked_notify'] = false;
310
311
		// Did we report a post to a moderator just now?
312
		$context['report_sent'] = isset($this->_req->query->reportsent);
313
		if ($context['report_sent'])
314
			$this->_template_layers->add('report_sent');
315
316
		// Let's get nosey, who is viewing this topic?
317
		if (!empty($settings['display_who_viewing']))
318
		{
319
			require_once(SUBSDIR . '/Who.subs.php');
320
			formatViewers($topic, 'topic');
321
		}
322
323
		// If all is set, but not allowed... just unset it.
324
		$can_show_all = !empty($modSettings['enableAllMessages']) && $total_visible_posts > $context['messages_per_page'] && $total_visible_posts < $modSettings['enableAllMessages'];
325
		if (isset($this->_req->query->all) && !$can_show_all)
326
			unset($this->_req->query->all);
327
		// Otherwise, it must be allowed... so pretend start was -1.
328
		elseif (isset($this->_req->query->all))
329
			$this->_start = -1;
330
331
		// Construct the page index, allowing for the .START method...
332
		$context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $this->_start, $total_visible_posts, $context['messages_per_page'], true, array('all' => $can_show_all, 'all_selected' => isset($this->_req->query->all)));
333
		$context['start'] = $this->_start;
334
335
		// This is information about which page is current, and which page we're on - in case you don't like
336
		// the constructed page index. (again, wireless..)
337
		$context['page_info'] = array(
338
			'current_page' => $this->_start / $context['messages_per_page'] + 1,
339
			'num_pages' => floor(($total_visible_posts - 1) / $context['messages_per_page']) + 1,
340
		);
341
342
		// Figure out all the link to the next/prev
343
		$context['links'] += array(
344
			'prev' => $this->_start >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.' . ($this->_start - $context['messages_per_page']) : '',
345
			'next' => $this->_start + $context['messages_per_page'] < $total_visible_posts ? $scripturl . '?topic=' . $topic . '.' . ($this->_start + $context['messages_per_page']) : '',
346
		);
347
348
		// If they are viewing all the posts, show all the posts, otherwise limit the number.
349
		if ($can_show_all && isset($this->_req->query->all))
350
		{
351
			// No limit! (actually, there is a limit, but...)
352
			$context['messages_per_page'] = -1;
353
354
			// Set start back to 0...
355
			$this->_start = 0;
356
		}
357
358
		// Build the link tree.
359
		$context['linktree'][] = array(
360
			'url' => $scripturl . '?topic=' . $topic . '.0',
361
			'name' => $topicinfo['subject'],
362
		);
363
364
		// Build a list of this board's moderators.
365
		$context['moderators'] = &$board_info['moderators'];
366
		$context['link_moderators'] = array();
367
368
		// Information about the current topic...
369
		$context['is_locked'] = $topicinfo['locked'];
370
		$context['is_sticky'] = $topicinfo['is_sticky'];
371
		$context['is_very_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts'];
372
		$context['is_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicPosts'];
373
		$context['is_approved'] = $topicinfo['approved'];
374
375
		determineTopicClass($context);
376
377
		// Set the topic's information for the template.
378
		$context['subject'] = $topicinfo['subject'];
379
		$context['num_views'] = $topicinfo['num_views'];
380
		$context['num_views_text'] = $context['num_views'] == 1 ? $txt['read_one_time'] : sprintf($txt['read_many_times'], $context['num_views']);
381
		$context['mark_unread_time'] = !empty($this->_virtual_msg) ? $this->_virtual_msg : $topicinfo['new_from'];
382
383
		// Set a canonical URL for this page.
384
		$context['canonical_url'] = $scripturl . '?topic=' . $topic . '.' . $context['start'];
385
386
		// For quick reply we need a response prefix in the default forum language.
387
		$context['response_prefix'] = response_prefix();
388
389
		// Calculate the fastest way to get the messages!
390
		$ascending = true;
391
		$start = $this->_start;
392
		$limit = $context['messages_per_page'];
393
		$firstIndex = 0;
394
		if ($start >= $total_visible_posts / 2 && $context['messages_per_page'] != -1)
395
		{
396
			$ascending = !$ascending;
397
			$limit = $total_visible_posts <= $start + $limit ? $total_visible_posts - $start : $limit;
398
			$start = $total_visible_posts <= $start + $limit ? 0 : $total_visible_posts - $start - $limit;
399
			$firstIndex = $limit - 1;
400
		}
401
402
		// Taking care of member specific settings
403
		$limit_settings = array(
404
			'messages_per_page' => $context['messages_per_page'],
405
			'start' => $start,
406
			'offset' => $limit,
407
		);
408
409
		// Get each post and poster in this topic.
410
		$topic_details = getTopicsPostsAndPoster($topic, $limit_settings, $ascending);
411
		$messages = $topic_details['messages'];
412
		$posters = array_unique($topic_details['all_posters']);
413
		$all_posters = $topic_details['all_posters'];
414
		unset($topic_details);
415
416
		call_integration_hook('integrate_display_message_list', array(&$messages, &$posters));
417
418
		// Guests can't mark topics read or for notifications, just can't sorry.
419
		if (!$user_info['is_guest'] && !empty($messages))
420
		{
421
			$boardseen = isset($this->_req->query->boardseen);
422
423
			$mark_at_msg = max($messages);
424
			if ($mark_at_msg >= $topicinfo['id_last_msg'])
425
				$mark_at_msg = $modSettings['maxMsgID'];
426
			if ($mark_at_msg >= $topicinfo['new_from'])
427
			{
428
				markTopicsRead(array($user_info['id'], $topic, $mark_at_msg, $topicinfo['unwatched']), $topicinfo['new_from'] !== 0);
429
				$numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']);
430
431
				if (empty($numNewTopics))
432
					$boardseen = true;
433
			}
434
435
			updateReadNotificationsFor($topic, $board);
436
437
			// Mark board as seen if we came using last post link from BoardIndex. (or other places...)
438
			if ($boardseen)
439
			{
440
				require_once(SUBSDIR . '/Boards.subs.php');
441
				markBoardsRead($board, false, false);
442
			}
443
		}
444
445
		$attachments = array();
446
447
		// If there _are_ messages here... (probably an error otherwise :!)
448
		if (!empty($messages))
449
		{
450
			require_once(SUBSDIR . '/Attachments.subs.php');
451
452
			// Fetch attachments.
453
			if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments'))
454
				$attachments = getAttachments($messages, $includeUnapproved, 'filter_accessible_attachment', $all_posters);
455
456
			$msg_parameters = array(
457
				'message_list' => $messages,
458
				'new_from' => $topicinfo['new_from'],
459
			);
460
			$msg_selects = array();
461
			$msg_tables = array();
462
			call_integration_hook('integrate_message_query', array(&$msg_selects, &$msg_tables, &$msg_parameters));
463
464
			// What?  It's not like it *couldn't* be only guests in this topic...
465
			if (!empty($posters))
466
				loadMemberData($posters);
467
468
			// Load in the likes for this group of messages
469
			if (!empty($modSettings['likes_enabled']))
470
			{
471
				require_once(SUBSDIR . '/Likes.subs.php');
472
				$context['likes'] = loadLikes($messages, true);
473
474
				// ajax controller for likes
475
				loadJavascriptFile('like_posts.js', array('defer' => true));
476
				addJavascriptVar(array(
477
					'likemsg_are_you_sure' => JavaScriptEscape($txt['likemsg_are_you_sure']),
478
				));
479
				loadLanguage('Errors');
480
481
				// Initiate likes and the tooltips for likes
482
				addInlineJavascript('
483
				$(function() {
484
					var likePostInstance = likePosts.prototype.init({
485
						oTxt: ({
486
							btnText : ' . JavaScriptEscape($txt['ok_uppercase']) . ',
487
							likeHeadingError : ' . JavaScriptEscape($txt['like_heading_error']) . ',
488
							error_occurred : ' . JavaScriptEscape($txt['error_occurred']) . '
489
						}),
490
					});
491
492
					$(".like_button, .unlike_button, .likes_button").SiteTooltip({
493
						hoverIntent: {
494
							sensitivity: 10,
495
							interval: 150,
496
							timeout: 50
497
						}
498
					});
499
				});', true);
500
			}
501
502
			$messages_request = loadMessageRequest($msg_selects, $msg_tables, $msg_parameters);
503
504
			// Go to the last message if the given time is beyond the time of the last message.
505
			if (isset($context['start_from']) && $context['start_from'] >= $topicinfo['num_replies'])
506
				$context['start_from'] = $topicinfo['num_replies'];
507
508
			// Since the anchor information is needed on the top of the page we load these variables beforehand.
509
			$context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
510
			$context['first_new_message'] = isset($context['start_from']) && $this->_start == $context['start_from'];
511
		}
512
		else
513
		{
514
			$messages_request = false;
515
			$context['first_message'] = 0;
516
			$context['first_new_message'] = false;
517
		}
518
519
		$context['jump_to'] = array(
520
			'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
521
			'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), array('&amp;' => '&')), ENT_COMPAT, 'UTF-8'),
522
			'child_level' => $board_info['child_level'],
523
		);
524
525
		// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
526
		// This will be called from the template.
527
		$context['get_message'] = array($this, 'prepareDisplayContext_callback');
528
		$this->_icon_sources = new MessageTopicIcons(!empty($modSettings['messageIconChecks_enable']), $settings['theme_dir']);
529
		list ($sig_limits) = explode(':', $modSettings['signature_settings']);
530
		$signature_settings = explode(',', $sig_limits);
531
532
		if ($user_info['is_guest'])
533
		{
534
			$this->_show_signatures = !empty($signature_settings[8]) ? (int) $signature_settings[8] : 0;
535
		}
536
		else
537
		{
538
			$this->_show_signatures = !empty($signature_settings[9]) ? (int) $signature_settings[9] : 0;
539
		}
540
541
		// Now set all the wonderful, wonderful permissions... like moderation ones...
542
		$common_permissions = array(
543
			'can_approve' => 'approve_posts',
544
			'can_ban' => 'manage_bans',
545
			'can_sticky' => 'make_sticky',
546
			'can_merge' => 'merge_any',
547
			'can_split' => 'split_any',
548
			'can_mark_notify' => 'mark_any_notify',
549
			'can_send_topic' => 'send_topic',
550
			'can_send_pm' => 'pm_send',
551
			'can_send_email' => 'send_email_to_members',
552
			'can_report_moderator' => 'report_any',
553
			'can_moderate_forum' => 'moderate_forum',
554
			'can_issue_warning' => 'issue_warning',
555
			'can_restore_topic' => 'move_any',
556
			'can_restore_msg' => 'move_any',
557
		);
558
		foreach ($common_permissions as $contextual => $perm)
559
			$context[$contextual] = allowedTo($perm);
560
561
		// Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
562
		$anyown_permissions = array(
563
			'can_move' => 'move',
564
			'can_lock' => 'lock',
565
			'can_delete' => 'remove',
566
			'can_reply' => 'post_reply',
567
			'can_reply_unapproved' => 'post_unapproved_replies',
568
		);
569
		foreach ($anyown_permissions as $contextual => $perm)
570
			$context[$contextual] = allowedTo($perm . '_any') || ($context['user']['started'] && allowedTo($perm . '_own'));
571
572
		// Cleanup all the permissions with extra stuff...
573
		$context['can_mark_notify'] &= !$context['user']['is_guest'];
574
		$context['can_reply'] &= empty($topicinfo['locked']) || allowedTo('moderate_board');
575
		$context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($topicinfo['locked']) || allowedTo('moderate_board'));
576
		$context['can_issue_warning'] &= in_array('w', $context['admin_features']) && !empty($modSettings['warning_enable']);
577
578
		// Handle approval flags...
579
		$context['can_reply_approved'] = $context['can_reply'];
580
581
		// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
582
		if ($user_info['is_guest'] && allowedTo('post_unapproved_replies_any'))
583
		{
584
			$context['can_reply_approved'] = false;
585
		}
586
587
		$context['can_reply'] |= $context['can_reply_unapproved'];
588
		$context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
589
		$context['can_mark_unread'] = !$user_info['is_guest'] && $settings['show_mark_read'];
590
		$context['can_unwatch'] = !$user_info['is_guest'] && $modSettings['enable_unwatch'];
591
		$context['can_send_topic'] = (!$modSettings['postmod_active'] || $topicinfo['approved']) && allowedTo('send_topic');
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $context['can_send_topic...llowedTo('send_topic')), Probably Intended Meaning: $context['can_send_topic...llowedTo('send_topic'))
Loading history...
592
		$context['can_print'] = empty($modSettings['disable_print_topic']);
593
594
		// Start this off for quick moderation - it will be or'd for each post.
595
		$context['can_remove_post'] = allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']);
596
597
		// Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
598
		$context['can_restore_topic'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_board']);
599
		$context['can_restore_msg'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_topic']);
600
601
		// Load up the Quick ModifyTopic and Quick Reply scripts
602
		loadJavascriptFile('topic.js');
603
604
		// Auto video embedding enabled?
605
		if (!empty($modSettings['enableVideoEmbeding']))
606
		{
607
			addInlineJavascript('
608
		$(function() {
609
			$().linkifyvideo(oEmbedtext);
610
		});');
611
		}
612
613
		// Now create the editor.
614
		$editorOptions = array(
615
			'id' => 'message',
616
			'value' => '',
617
			'labels' => array(
618
				'post_button' => $txt['post'],
619
			),
620
			// add height and width for the editor
621
			'height' => '250px',
622
			'width' => '100%',
623
			// We do XML preview here.
624
			'preview_type' => 0,
625
		);
626
627
		// Trigger the prepare_context event for modules that have tied in to it
628
		$this->_events->trigger('prepare_context', array('editorOptions' => &$editorOptions, 'use_quick_reply' => !empty($options['display_quick_reply'])));
629
630
		// Load up the "double post" sequencing magic.
631
		if (!empty($options['display_quick_reply']))
632
		{
633
			checkSubmitOnce('register');
634
			$context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
635
			$context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
636
			if (!empty($options['use_editor_quick_reply']) && $context['can_reply'])
637
			{
638
				// Needed for the editor and message icons.
639
				require_once(SUBSDIR . '/Editor.subs.php');
640
641
				create_control_richedit($editorOptions);
642
			}
643
		}
644
645
		addJavascriptVar(array('notification_topic_notice' => $context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']), true);
646
647
		if ($context['can_send_topic'])
648
		{
649
			addJavascriptVar(array(
650
				'sendtopic_cancel' => $txt['modify_cancel'],
651
				'sendtopic_back' => $txt['back'],
652
				'sendtopic_close' => $txt['find_close'],
653
				'sendtopic_error' => $txt['send_error_occurred'],
654
				'required_field' => $txt['require_field']), true);
655
		}
656
657
		// Build the normal button array.
658
		$context['normal_buttons'] = array(
659
			'reply' => array('test' => 'can_reply', 'text' => 'reply', 'image' => 'reply.png', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message'], 'active' => true),
660
			'notify' => array('test' => 'can_mark_notify', 'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify', 'image' => ($context['is_marked_notify'] ? 'un' : '') . 'notify.png', 'lang' => true, 'custom' => 'onclick="return notifyButton(this);"', 'url' => $scripturl . '?action=notify;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
661
			'mark_unread' => array('test' => 'can_mark_unread', 'text' => 'mark_unread', 'image' => 'markunread.png', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
662
			'unwatch' => array('test' => 'can_unwatch', 'text' => ($context['topic_unwatched'] ? '' : 'un') . 'watch', 'image' => ($context['topic_unwatched'] ? '' : 'un') . 'watched.png', 'lang' => true, 'custom' => 'onclick="return unwatchButton(this);"', 'url' => $scripturl . '?action=unwatchtopic;topic=' . $context['current_topic'] . '.' . $context['start'] . ';sa=' . ($context['topic_unwatched'] ? 'off' : 'on') . ';' . $context['session_var'] . '=' . $context['session_id']),
663
			'send' => array('test' => 'can_send_topic', 'text' => 'send_topic', 'image' => 'sendtopic.png', 'lang' => true, 'url' => $scripturl . '?action=emailuser;sa=sendtopic;topic=' . $context['current_topic'] . '.0', 'custom' => 'onclick="return sendtopicOverlayDiv(this.href, \'' . $txt['send_topic'] . '\');"'),
664
			'print' => array('test' => 'can_print', 'text' => 'print', 'image' => 'print.png', 'lang' => true, 'custom' => 'rel="nofollow"', 'class' => 'new_win', 'url' => $scripturl . '?action=topic;sa=printpage;topic=' . $context['current_topic'] . '.0'),
665
		);
666
667
		// Build the mod button array
668
		$context['mod_buttons'] = array(
669
			'move' => array('test' => 'can_move', 'text' => 'move_topic', 'image' => 'admin_move.png', 'lang' => true, 'url' => $scripturl . '?action=movetopic;current_board=' . $context['current_board'] . ';topic=' . $context['current_topic'] . '.0'),
670
			'delete' => array('test' => 'can_delete', 'text' => 'remove_topic', 'image' => 'admin_rem.png', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"', 'url' => $scripturl . '?action=removetopic2;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id']),
671
			'lock' => array('test' => 'can_lock', 'text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.png', 'lang' => true, 'url' => $scripturl . '?action=topic;sa=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
672
			'sticky' => array('test' => 'can_sticky', 'text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.png', 'lang' => true, 'url' => $scripturl . '?action=topic;sa=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']),
673
			'merge' => array('test' => 'can_merge', 'text' => 'merge', 'image' => 'merge.png', 'lang' => true, 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']),
674
		);
675
676
		// Restore topic. eh?  No monkey business.
677
		if ($context['can_restore_topic'])
678
			$context['mod_buttons'][] = array('text' => 'restore_topic', 'image' => '', 'lang' => true, 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']);
679
680
		// Quick reply & modify enabled?
681
		if ($context['can_reply'] && !empty($options['display_quick_reply']))
682
			$this->_template_layers->add('quickreply');
683
684
		$this->_template_layers->add('pages_and_buttons');
685
686
		// Allow adding new buttons easily.
687
		call_integration_hook('integrate_display_buttons');
688
		call_integration_hook('integrate_mod_buttons');
689
	}
690
691
	/**
692
	 * In-topic quick moderation.
693
	 *
694
	 * Accessed by ?action=quickmod2
695
	 */
696
	public function action_quickmod2()
697
	{
698
		global $topic, $board, $user_info, $context, $modSettings;
699
700
		// Check the session = get or post.
701
		checkSession('request');
702
703
		require_once(SUBSDIR . '/Messages.subs.php');
704
705
		if (empty($this->_req->post->msgs))
706
			redirectexit('topic=' . $topic . '.' . $this->_req->getQuery('start', 'intval'));
707
708
		$messages = array_map('intval', $this->_req->post->msgs);
709
710
		// We are restoring messages. We handle this in another place.
711
		if (isset($this->_req->query->restore_selected))
712
			redirectexit('action=restoretopic;msgs=' . implode(',', $messages) . ';' . $context['session_var'] . '=' . $context['session_id']);
713
714
		if (isset($this->_req->query->split_selection))
715
		{
716
			$mgsOptions = basicMessageInfo(min($messages), true);
717
718
			$_SESSION['split_selection'][$topic] = $messages;
719
			redirectexit('action=splittopics;sa=selectTopics;topic=' . $topic . '.0;subname_enc=' . urlencode($mgsOptions['subject']) . ';' . $context['session_var'] . '=' . $context['session_id']);
720
		}
721
722
		require_once(SUBSDIR . '/Topic.subs.php');
723
		$topic_info = getTopicInfo($topic);
724
725
		// Allowed to delete any message?
726
		if (allowedTo('delete_any'))
727
			$allowed_all = true;
728
		// Allowed to delete replies to their messages?
729
		elseif (allowedTo('delete_replies'))
730
			$allowed_all = $topic_info['id_member_started'] == $user_info['id'];
731
		else
732
			$allowed_all = false;
733
734
		// Make sure they're allowed to delete their own messages, if not any.
735
		if (!$allowed_all)
736
			isAllowedTo('delete_own');
737
738
		// Allowed to remove which messages?
739
		$messages = determineRemovableMessages($topic, $messages, $allowed_all);
740
741
		// Get the first message in the topic - because you can't delete that!
742
		$first_message = $topic_info['id_first_msg'];
743
		$last_message = $topic_info['id_last_msg'];
744
		$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
745
746
		// Delete all the messages we know they can delete. ($messages)
747
		foreach ($messages as $message => $info)
748
		{
749
			// Just skip the first message - if it's not the last.
750
			if ($message == $first_message && $message != $last_message)
751
				continue;
752
			// If the first message is going then don't bother going back to the topic as we're effectively deleting it.
753
			elseif ($message == $first_message)
754
				$topicGone = true;
755
756
			$remover->removeMessage($message);
757
		}
758
759
		redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . (int) $this->_req->query->start);
760
	}
761
762
	/**
763
	 * Callback for the message display.
764
	 * It actually gets and prepares the message context.
765
	 * This method will start over from the beginning if reset is set to true, which is
766
	 * useful for showing an index before or after the posts.
767
	 *
768
	 * @param bool $reset default false.
769
	 */
770
	public function prepareDisplayContext_callback($reset = false)
771
	{
772
		global $settings, $txt, $modSettings, $scripturl, $user_info;
773
		global $memberContext, $context, $messages_request, $topic;
774
		static $counter = null;
775
		static $signature_shown = null;
776
777
		// If the query returned false, bail.
778
		if ($messages_request === false)
779
			return false;
780
781
		// Remember which message this is.  (ie. reply #83)
782
		if ($counter === null || $reset)
783
			$counter = $context['start'];
784
785
		// Start from the beginning...
786
		if ($reset)
787
			return currentContext($messages_request, $reset);
788
789
		// Attempt to get the next message.
790
		$message = currentContext($messages_request);
791
		if (!$message)
792
			return false;
793
794
		// If you're a lazy bum, you probably didn't give a subject...
795
		$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
796
797
		// Are you allowed to remove at least a single reply?
798
		$context['can_remove_post'] |= allowedTo('delete_own') && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()) && $message['id_member'] == $user_info['id'];
799
800
		// Have you liked this post, can you?
801
		$message['you_liked'] = !empty($context['likes'][$message['id_msg']]['member'])
802
			&& isset($context['likes'][$message['id_msg']]['member'][$user_info['id']]);
803
		$message['use_likes'] = allowedTo('like_posts') && empty($context['is_locked'])
804
			&& ($message['id_member'] != $user_info['id'] || !empty($modSettings['likeAllowSelf']))
805
			&& (empty($modSettings['likeMinPosts']) ? true : $modSettings['likeMinPosts'] <= $user_info['posts']);
806
		$message['like_count'] = !empty($context['likes'][$message['id_msg']]['count']) ? $context['likes'][$message['id_msg']]['count'] : 0;
807
808
		// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
809
		if (!loadMemberContext($message['id_member'], true))
810
		{
811
			// Notice this information isn't used anywhere else....
812
			$memberContext[$message['id_member']]['name'] = $message['poster_name'];
813
			$memberContext[$message['id_member']]['id'] = 0;
814
			$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
815
			$memberContext[$message['id_member']]['link'] = $message['poster_name'];
816
			$memberContext[$message['id_member']]['email'] = $message['poster_email'];
817
			$memberContext[$message['id_member']]['show_email'] = showEmailAddress(true, 0);
818
			$memberContext[$message['id_member']]['is_guest'] = true;
819
		}
820
		else
821
		{
822
			$memberContext[$message['id_member']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member'] == $user_info['id'] && allowedTo('profile_view_own'));
823
			$memberContext[$message['id_member']]['is_topic_starter'] = $message['id_member'] == $context['topic_starter_id'];
824
			$memberContext[$message['id_member']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member']]['warning_status'] && ($context['user']['can_mod'] || (!$user_info['is_guest'] && !empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member'] == $user_info['id'])));
825
826
			if ($this->_show_signatures === 1)
827
			{
828
				if (empty($signature_shown[$message['id_member']]))
829
				{
830
					$signature_shown[$message['id_member']] = true;
831
				}
832
				else
833
				{
834
					$memberContext[$message['id_member']]['signature'] = '';
835
				}
836
			}
837
			elseif ($this->_show_signatures === 2)
838
			{
839
				$memberContext[$message['id_member']]['signature'] = '';
840
			}
841
		}
842
843
		$memberContext[$message['id_member']]['ip'] = $message['poster_ip'];
844
		$memberContext[$message['id_member']]['show_profile_buttons'] = $settings['show_profile_buttons'] && (!empty($memberContext[$message['id_member']]['can_view_profile']) || (!empty($memberContext[$message['id_member']]['website']['url']) && !isset($context['disabled_fields']['website'])) || (in_array($memberContext[$message['id_member']]['show_email'], array('yes', 'yes_permission_override', 'no_through_forum'))) || $context['can_send_pm']);
845
846
		// Do the censor thang.
847
		$message['body'] = censor($message['body']);
848
		$message['subject'] = censor($message['subject']);
849
850
		// Run BBC interpreter on the message.
851
		$context['id_msg'] = $message['id_msg'];
852
		$bbc_wrapper = \BBC\ParserWrapper::instance();
853
		$message['body'] = $bbc_wrapper->parseMessage($message['body'], $message['smileys_enabled']);
854
855
		call_integration_hook('integrate_before_prepare_display_context', array(&$message));
856
857
		// Compose the memory eat- I mean message array.
858
		require_once(SUBSDIR . '/Attachments.subs.php');
859
		$output = array(
860
			'attachment' => loadAttachmentContext($message['id_msg']),
861
			'alternate' => $counter % 2,
862
			'id' => $message['id_msg'],
863
			'href' => $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'],
864
			'link' => '<a href="' . $scripturl . '?topic=' . $topic . '.msg' . $message['id_msg'] . '#msg' . $message['id_msg'] . '" rel="nofollow">' . $message['subject'] . '</a>',
865
			'member' => &$memberContext[$message['id_member']],
866
			'icon' => $message['icon'],
867
			'icon_url' => $this->_icon_sources->{$message['icon']},
868
			'subject' => $message['subject'],
869
			'time' => standardTime($message['poster_time']),
870
			'html_time' => htmlTime($message['poster_time']),
871
			'timestamp' => forum_time(true, $message['poster_time']),
872
			'counter' => $counter,
873
			'modified' => array(
874
				'time' => standardTime($message['modified_time']),
875
				'html_time' => htmlTime($message['modified_time']),
876
				'timestamp' => forum_time(true, $message['modified_time']),
877
				'name' => $message['modified_name']
878
			),
879
			'body' => $message['body'],
880
			'new' => empty($message['is_read']),
881
			'approved' => $message['approved'],
882
			'first_new' => isset($context['start_from']) && $context['start_from'] == $counter,
883
			'is_ignored' => !empty($modSettings['enable_buddylist']) && in_array($message['id_member'], $context['user']['ignoreusers']),
884
			'is_message_author' => $message['id_member'] == $user_info['id'],
885
			'can_approve' => !$message['approved'] && $context['can_approve'],
886
			'can_unapprove' => !empty($modSettings['postmod_active']) && $context['can_approve'] && $message['approved'],
887
			'can_modify' => (!$context['is_locked'] || allowedTo('moderate_board')) && (allowedTo('modify_any') || (allowedTo('modify_replies') && $context['user']['started']) || (allowedTo('modify_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || !$message['approved'] || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time()))),
888
			'can_remove' => allowedTo('delete_any') || (allowedTo('delete_replies') && $context['user']['started']) || (allowedTo('delete_own') && $message['id_member'] == $user_info['id'] && (empty($modSettings['edit_disable_time']) || $message['poster_time'] + $modSettings['edit_disable_time'] * 60 > time())),
889
			'can_see_ip' => allowedTo('moderate_forum') || ($message['id_member'] == $user_info['id'] && !empty($user_info['id'])),
890
			'can_like' => $message['use_likes'] && !$message['you_liked'],
891
			'can_unlike' => $message['use_likes'] && $message['you_liked'],
892
			'like_counter' => $message['like_count'],
893
			'likes_enabled' => !empty($modSettings['likes_enabled']) && ($message['use_likes'] || ($message['like_count'] != 0)),
894
			'classes' => array(),
895
		);
896
897
		if (!empty($output['modified']['name']))
898
			$output['modified']['last_edit_text'] = sprintf($txt['last_edit_by'], $output['modified']['time'], $output['modified']['name'], standardTime($output['modified']['timestamp']));
899
900
		if (!empty($output['member']['karma']['allow']))
901
		{
902
			$output['member']['karma'] += array(
903
				'applaud_url' => $scripturl . '?action=karma;sa=applaud;uid=' . $output['member']['id'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';m=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id'],
904
				'smite_url' => $scripturl . '?action=karma;sa=smite;uid=' . $output['member']['id'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';m=' . $output['id'] . ';' . $context['session_var'] . '=' . $context['session_id']
905
			);
906
		}
907
908
		call_integration_hook('integrate_prepare_display_context', array(&$output, &$message));
909
910
		$output['classes'] = implode(' ', $output['classes']);
911
912
		$counter++;
913
914
		return $output;
915
	}
916
}
917