Completed
Pull Request — development (#2332)
by Joshua
10:27
created

Post_Controller   F

Complexity

Total Complexity 391

Size/Duplication

Total Lines 1443
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%
Metric Value
wmc 391
lcom 1
cbo 12
dl 0
loc 1443
ccs 0
cts 850
cp 0
rs 3.4839

8 Methods

Rating   Name   Duplication   Size   Complexity  
A pre_dispatch() 0 9 1
A action_index() 0 6 1
F action_post() 0 505 134
F action_post2() 0 483 154
C action_quotefast() 0 68 9
F action_jsmodify() 0 231 75
A action_spellcheck() 0 20 1
C _checkLocked() 0 40 16

How to fix   Complexity   

Complex Class

Complex classes like Post_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Post_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * The job of this file is to handle everything related to posting replies,
5
 * new topics, quotes, and modifications to existing posts.  It also handles
6
 * quoting posts by way of javascript.
7
 *
8
 * @name      ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
11
 *
12
 * This software is a derived product, based on:
13
 *
14
 * Simple Machines Forum (SMF)
15
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
16
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
17
 *
18
 * @version 1.1 dev
19
 *
20
 */
21
22 1
if (!defined('ELK'))
23 1
	die('No access...');
24
25
/**
26
 * Post Controller
27
 */
28
class Post_Controller extends Action_Controller
29
{
30
	/**
31
	 * The post (messages) errors object
32
	 * @var null|object
33
	 */
34
	protected $_post_errors = null;
35
36
	/**
37
	 * The template layers object
38
	 * @var null|object
39
	 */
40
	protected $_template_layers = null;
41
42
	/**
43
	 * An array of attributes of the topic (if not new)
44
	 * @var mixed[]
45
	 */
46
	protected $_topic_attributes = array();
47
48
	/**
49
	 * Sets up common stuff for all or most of the actions.
50
	 */
51
	public function pre_dispatch()
52
	{
53
		$this->_post_errors = Error_Context::context('post', 1);
54
		$this->_template_layers = Template_Layers::getInstance();
55
56
		require_once(SUBSDIR . '/Post.subs.php');
57
		require_once(SUBSDIR . '/Messages.subs.php');
58
		require_once(SUBSDIR . '/Topic.subs.php');
59
	}
60
61
	/**
62
	 * Dispatch to the right action method for the request.
63
	 *
64
	 * @see Action_Controller::action_index()
65
	 */
66
	public function action_index()
67
	{
68
		// Figure out the right action to do.
69
		// hint: I'm post controller. :P
70
		$this->action_post();
71
	}
72
73
	/**
74
	 * Handles showing the post screen, loading the post to be modified, loading any post quoted, previews,
75
	 * display of errors and polls.
76
	 *
77
	 * What it does:
78
	 * - Validates that we're posting in a board.
79
	 * - Find the topic id if a message id is passed, else assume it's a new message
80
	 * - Get the response prefix in the default forum language.
81
	 * - Triggers events associated with posting.
82
	 * - Additionally handles previews of posts.
83
	 * - Requires different permissions depending on the actions, but most notably post_new, post_reply_own, and post_reply_any.
84
	 * - Shows options for the editing and posting of calendar events and attachments, and as the posting of polls (using modules).
85
	 * - Accessed from ?action=post or called from action_post2 in the event of errors or preview from quick reply.
86
	 *
87
	 * @uses the Post template and language file, main sub template.
88
	 * @uses Errors language
89
	 */
90
	public function action_post()
0 ignored issues
show
Coding Style introduced by
action_post uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
91
	{
92
		global $txt, $scripturl, $topic, $modSettings, $board, $user_info, $context, $options;
93
94
		loadLanguage('Post');
95
		loadLanguage('Errors');
96
97
		$context['robot_no_index'] = true;
98
		$this->_template_layers->add('postarea');
99
		$this->_topic_attributes = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('locked' => false,... 'last_post_time' => 0) of type array<string,false|integ..._post_time":"integer"}> is incompatible with the declared type array<integer,*> of property $_topic_attributes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
100
			'locked' => false,
101
			'notify' => false,
102
			'is_sticky' => false,
103
			'id_last_msg' => 0,
104
			'id_member' => 0,
105
			'id_first_msg' => 0,
106
			'subject' => '',
107
			'last_post_time' => 0
108
		);
109
110
		// Trigger the prepare_post event
111
		$this->_events->trigger('prepare_post', array('topic_attributes' => &$this->_topic_attributes));
112
113
		// You must be posting to *some* board.
114
		if (empty($board) && !$context['make_event'])
115
			Errors::instance()->fatal_lang_error('no_board', false);
116
117
		// All those wonderful modifiers and attachments
118
		$this->_template_layers->add('additional_options', 200);
119
120
		if (isset($_REQUEST['xml']))
121
		{
122
			$context['sub_template'] = 'post';
123
124
			// Just in case of an earlier error...
125
			$context['preview_message'] = '';
126
			$context['preview_subject'] = '';
127
		}
128
129
		// No message is complete without a topic.
130
		if (empty($topic) && !empty($_REQUEST['msg']))
131
		{
132
			$topic = associatedTopic((int) $_REQUEST['msg']);
133
			if (empty($topic))
134
				unset($_REQUEST['msg'], $_POST['msg'], $_GET['msg']);
135
		}
136
137
		// Check if it's locked. It isn't locked if no topic is specified.
138
		if (!empty($topic))
139
		{
140
			$this->_topic_attributes = topicUserAttributes($topic, $user_info['id']);
141
			$context['notify'] = $this->_topic_attributes['notify'];
142
			$context['topic_last_message'] = $this->_topic_attributes['id_last_msg'];
143
144
			if (empty($_REQUEST['msg']))
145
			{
146
				if ($user_info['is_guest'] && !allowedTo('post_reply_any') && (!$modSettings['postmod_active'] || !allowedTo('post_unapproved_replies_any')))
147
					is_not_guest();
148
149
				// By default the reply will be approved...
150
				$context['becomes_approved'] = true;
151
				if ($this->_topic_attributes['id_member'] != $user_info['id'])
152
				{
153
					if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any') && !allowedTo('post_reply_any'))
154
						$context['becomes_approved'] = false;
155
					else
156
						isAllowedTo('post_reply_any');
157
				}
158
				elseif (!allowedTo('post_reply_any'))
159
				{
160
					if ($modSettings['postmod_active'])
161
					{
162
						if (allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own'))
163
							$context['becomes_approved'] = false;
164
						// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
165
						elseif ($user_info['is_guest'] && allowedTo('post_unapproved_replies_any'))
166
							$context['becomes_approved'] = false;
167
						else
168
							isAllowedTo('post_reply_own');
169
					}
170
					else
171
						isAllowedTo('post_reply_own');
172
				}
173
			}
174
			else
175
				$context['becomes_approved'] = true;
176
177
			$context['can_lock'] = allowedTo('lock_any') || ($user_info['id'] == $this->_topic_attributes['id_member'] && allowedTo('lock_own'));
178
			$context['can_sticky'] = allowedTo('make_sticky');
179
			$context['notify'] = !empty($context['notify']);
180
			$context['sticky'] = isset($_REQUEST['sticky']) ? !empty($_REQUEST['sticky']) : $this->_topic_attributes['is_sticky'];
181
		}
182
		else
183
		{
184
			$this->_topic_attributes['id_member'] = 0;
185
			$context['becomes_approved'] = true;
186
			if (empty($context['make_event']) || !empty($board))
187
			{
188
				if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics'))
189
					$context['becomes_approved'] = false;
190
				else
191
					isAllowedTo('post_new');
192
			}
193
194
			$this->_topic_attributes['locked'] = 0;
195
196
			// @todo These won't work if you're making an event.
197
			$context['can_lock'] = allowedTo(array('lock_any', 'lock_own'));
198
			$context['can_sticky'] = allowedTo('make_sticky');
199
200
			$context['notify'] = !empty($context['notify']);
201
			$context['sticky'] = !empty($_REQUEST['sticky']);
202
		}
203
204
		// @todo These won't work if you're posting an event!
205
		$context['can_notify'] = allowedTo('mark_any_notify');
206
		$context['can_move'] = allowedTo('move_any');
207
		$context['move'] = !empty($_REQUEST['move']);
208
		$context['announce'] = !empty($_REQUEST['announce']);
209
210
		// You can only announce topics that will get approved...
211
		$context['can_announce'] = allowedTo('announce_topic') && $context['becomes_approved'];
212
		$context['locked'] = !empty($this->_topic_attributes['locked']) || !empty($_REQUEST['lock']);
213
		$context['can_quote'] = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
214
215
		// Generally don't show the approval box... (Assume we want things approved)
216
		$context['show_approval'] = allowedTo('approve_posts') && $context['becomes_approved'] ? 2 : (allowedTo('approve_posts') ? 1 : 0);
217
218
		// Don't allow a post if it's locked and you aren't all powerful.
219
		if ($this->_topic_attributes['locked'] && !allowedTo('moderate_board'))
220
			Errors::instance()->fatal_lang_error('topic_locked', false);
221
222
		try
223
		{
224
			$this->_events->trigger('prepare_context', array('id_member_poster' => $this->_topic_attributes['id_member']));
225
		}
226
		catch (Controller_Redirect_Exception $e)
227
		{
228
			return $e->doRedirect($this);
229
		}
230
231
		// See if any new replies have come along.
232
		if (empty($_REQUEST['msg']) && !empty($topic))
233
		{
234
			if (empty($options['no_new_reply_warning']) && isset($_REQUEST['last_msg']) && $context['topic_last_message'] > $_REQUEST['last_msg'])
235
			{
236
				$context['new_replies'] = countMessagesSince($topic, (int) $_REQUEST['last_msg'], false, $modSettings['postmod_active'] && !allowedTo('approve_posts'));
237
238
				if (!empty($context['new_replies']))
239
				{
240
					if ($context['new_replies'] == 1)
241
						$txt['error_new_replies'] = isset($_GET['last_msg']) ? $txt['error_new_reply_reading'] : $txt['error_new_reply'];
242
					else
243
						$txt['error_new_replies'] = sprintf(isset($_GET['last_msg']) ? $txt['error_new_replies_reading'] : $txt['error_new_replies'], $context['new_replies']);
244
245
					$this->_post_errors->addError('new_replies', 0);
246
247
					$modSettings['topicSummaryPosts'] = $context['new_replies'] > $modSettings['topicSummaryPosts'] ? max($modSettings['topicSummaryPosts'], 5) : $modSettings['topicSummaryPosts'];
248
				}
249
			}
250
		}
251
252
		// Get a response prefix (like 'Re:') in the default forum language.
253
		$context['response_prefix'] = response_prefix();
254
		$context['destination'] = 'post2;start=' . $_REQUEST['start'];
255
256
		// Previewing, modifying, or posting?
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
257
		// Do we have a body, but an error happened.
258
		if (isset($_REQUEST['message']) || $this->_post_errors->hasErrors())
259
		{
260
			// Validate inputs.
261
			if (!$this->_post_errors->hasErrors())
262
			{
263
				// This means they didn't click Post and get an error.
264
				$really_previewing = true;
265
			}
266
			else
267
			{
268
				if (!isset($_REQUEST['subject']))
269
					$_REQUEST['subject'] = '';
270
271
				if (!isset($_REQUEST['message']))
272
					$_REQUEST['message'] = '';
273
274
				if (!isset($_REQUEST['icon']))
275
					$_REQUEST['icon'] = 'xx';
276
277
				// They are previewing if they asked to preview (i.e. came from quick reply).
278
				$really_previewing = !empty($_REQUEST['preview']) || isset($_REQUEST['xml']);
279
			}
280
281
			// Trigger the prepare_modifying event
282
			$this->_events->trigger('prepare_modifying', array('post_errors' => $this->_post_errors, 'really_previewing' => &$really_previewing));
283
284
			// In order to keep the approval status flowing through, we have to pass it through the form...
285
			$context['becomes_approved'] = empty($_REQUEST['not_approved']);
286
			$context['show_approval'] = isset($_REQUEST['approve']) ? ($_REQUEST['approve'] ? 2 : 1) : 0;
287
			$context['can_announce'] &= $context['becomes_approved'];
288
289
			// Set up the inputs for the form.
290
			$form_subject = strtr(Util::htmlspecialchars($_REQUEST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
291
			$form_message = Util::htmlspecialchars($_REQUEST['message'], ENT_QUOTES, 'UTF-8', true);
292
293
			// Make sure the subject isn't too long - taking into account special characters.
294
			if (Util::strlen($form_subject) > 100)
295
				$form_subject = Util::substr($form_subject, 0, 100);
296
297
			// Are you... a guest?
298
			if ($user_info['is_guest'])
299
			{
300
				$context['name'] = !isset($_REQUEST['guestname']) ? '' : Util::htmlspecialchars(trim($_REQUEST['guestname']));
301
				$context['email'] = !isset($_REQUEST['email']) ? '' : Util::htmlspecialchars(trim($_REQUEST['email']));
302
				$user_info['name'] = $context['name'];
303
			}
304
305
			// Only show the preview stuff if they hit Preview.
306
			if ($really_previewing === true)
307
			{
308
				// Set up the preview message and subject
309
				$context['preview_message'] = $form_message;
310
				preparsecode($form_message, true);
311
312
				// Do all bulletin board code thing on the message
313
				$bbc_parser = \BBC\ParserWrapper::getInstance();
314
				preparsecode($context['preview_message']);
315
				$context['preview_message'] = $bbc_parser->parseMessage($context['preview_message'], isset($_REQUEST['ns']) ? 0 : 1);
0 ignored issues
show
Documentation introduced by
isset($_REQUEST['ns']) ? 0 : 1 is of type integer, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
316
				censorText($context['preview_message']);
317
318
				// Don't forget the subject
319
				$context['preview_subject'] = $form_subject;
320
				censorText($context['preview_subject']);
321
322
				// Any errors we should tell them about?
323
				if ($form_subject === '')
324
				{
325
					$this->_post_errors->addError('no_subject');
326
					$context['preview_subject'] = '<em>' . $txt['no_subject'] . '</em>';
327
				}
328
329
				if ($context['preview_message'] === '')
330
					$this->_post_errors->addError('no_message');
331
				elseif (!empty($modSettings['max_messageLength']) && Util::strlen($form_message) > $modSettings['max_messageLength'])
332
					$this->_post_errors->addError(array('long_message', array($modSettings['max_messageLength'])));
333
334
				// Protect any CDATA blocks.
335
				if (isset($_REQUEST['xml']))
336
					$context['preview_message'] = strtr($context['preview_message'], array(']]>' => ']]]]><![CDATA[>'));
337
			}
338
339
			// Set up the checkboxes.
340
			$context['notify'] = !empty($_REQUEST['notify']);
341
			$context['use_smileys'] = !isset($_REQUEST['ns']);
342
			$context['icon'] = isset($_REQUEST['icon']) ? preg_replace('~[\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : 'xx';
343
344
			// Set the destination action for submission.
345
			$context['destination'] .= isset($_REQUEST['msg']) ? ';msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'] : '';
346
			$context['submit_label'] = isset($_REQUEST['msg']) ? $txt['save'] : $txt['post'];
347
348
			// Previewing an edit?
349
			if (isset($_REQUEST['msg']) && !empty($topic))
350
			{
351
				$msg_id = (int) $_REQUEST['msg'];
352
353
				// Get the existing message.
354
				$message = messageDetails((int) $msg_id, $topic);
355
356
				// The message they were trying to edit was most likely deleted.
357
				// @todo Change this error message?
358
				if ($message === false)
359
					Errors::instance()->fatal_lang_error('no_board', false);
360
361
				$errors = checkMessagePermissions($message['message']);
362
				if (!empty($errors))
363
					foreach ($errors as $error)
364
						$this->_post_errors->addError($error);
365
366
				prepareMessageContext($message);
0 ignored issues
show
Documentation introduced by
$message is of type false|array<string,?,{"m...chment_stuff":"array"}>, but the function expects a array<integer,*>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
367
			}
368
			elseif (isset($_REQUEST['last_msg']))
369
			{
370
				// @todo: sort out what kind of combinations are actually possible
371
				// Posting a quoted reply?
372
				if ((!empty($topic) && !empty($_REQUEST['quote'])) || (!empty($modSettings['enableFollowup']) && !empty($_REQUEST['followup'])))
373
					$case = 2;
374
				// Posting a reply without a quote?
375
				elseif (!empty($topic) && empty($_REQUEST['quote']))
376
					$case = 3;
377
				else
378
					$case = 4;
379
380
				list ($form_subject,) = getFormMsgSubject($case, $topic, $this->_topic_attributes['subject']);
381
			}
382
383
			// No check is needed, since nothing is really posted.
384
			checkSubmitOnce('free');
385
		}
386
		// Editing a message...
387
		elseif (isset($_REQUEST['msg']) && !empty($topic))
388
		{
389
			$msg_id = (int) $_REQUEST['msg'];
390
391
			$message = getFormMsgSubject(1, $topic, '', $msg_id);
392
393
			// The message they were trying to edit was most likely deleted.
394
			if ($message === false)
395
				Errors::instance()->fatal_lang_error('no_message', false);
396
397
			$this->_events->trigger('prepare_editing', array('topic' => $topic, 'message' => &$message));
398
399
			if (!empty($message['errors']))
400
				foreach ($message['errors'] as $error)
401
					$this->_post_errors->addError($error);
402
403
			// Get the stuff ready for the form.
404
			$form_subject = $message['message']['subject'];
405
			$form_message = un_preparsecode($message['message']['body']);
406
407
			censorText($form_message);
408
			censorText($form_subject);
409
410
			// Check the boxes that should be checked.
411
			$context['use_smileys'] = !empty($message['message']['smileys_enabled']);
412
			$context['icon'] = $message['message']['icon'];
413
414
			// Set the destination.
415
			$context['destination'] .= ';msg=' . $msg_id . ';' . $context['session_var'] . '=' . $context['session_id'];
416
			$context['submit_label'] = $txt['save'];
417
		}
418
		// Posting...
419
		else
420
		{
421
			// By default....
422
			$context['use_smileys'] = true;
423
			$context['icon'] = 'xx';
424
425
			if ($user_info['is_guest'])
426
			{
427
				$context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
428
				$context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
429
			}
430
431
			$this->_events->trigger('prepare_posting');
432
433
			$context['submit_label'] = $txt['post'];
434
435
			// @todo: sort out what kind of combinations are actually possible
436
			// Posting a quoted reply?
437
			if ((!empty($topic) && !empty($_REQUEST['quote'])) || (!empty($modSettings['enableFollowup']) && !empty($_REQUEST['followup'])))
438
				$case = 2;
439
			// Posting a reply without a quote?
440
			elseif (!empty($topic) && empty($_REQUEST['quote']))
441
				$case = 3;
442
			else
443
				$case = 4;
444
445
			list ($form_subject, $form_message) = getFormMsgSubject($case, $topic, $this->_topic_attributes['subject']);
446
		}
447
448
		// Check whether this is a really old post being bumped...
449
		if (!empty($topic) && !empty($modSettings['oldTopicDays']) && $this->_topic_attributes['last_post_time'] + $modSettings['oldTopicDays'] * 86400 < time() && empty($this->_topic_attributes['is_sticky']) && !isset($_REQUEST['subject']))
450
			$this->_post_errors->addError(array('old_topic', array($modSettings['oldTopicDays'])), 0);
451
452
		$this->_events->trigger('post_errors');
453
454
		// Any errors occurred?
455
		$context['post_error'] = array(
456
			'errors' => $this->_post_errors->prepareErrors(),
457
			'type' => $this->_post_errors->getErrorType() == 0 ? 'minor' : 'serious',
458
			'title' => $this->_post_errors->getErrorType() == 0 ? $txt['warning_while_submitting'] : $txt['error_while_submitting'],
459
		);
460
461
		// What are you doing? Posting, modifying, previewing, new post, or reply...
462
		if (empty($context['page_title']))
463
		{
464
			if (isset($_REQUEST['msg']))
465
				$context['page_title'] = $txt['modify_msg'];
466
			elseif (isset($_REQUEST['subject'], $context['preview_subject']))
467
				$context['page_title'] = $txt['post_reply'];
468
			elseif (empty($topic))
469
				$context['page_title'] = $txt['start_new_topic'];
470
			else
471
				$context['page_title'] = $txt['post_reply'];
472
		}
473
474
		// Update the topic summary, needed to show new posts in a preview
475
		if (!empty($topic) && !empty($modSettings['topicSummaryPosts']))
476
		{
477
			$only_approved = $modSettings['postmod_active'] && !allowedTo('approve_posts');
478
479
			if (isset($_REQUEST['xml']))
480
				$limit = empty($context['new_replies']) ? 0 : (int) $context['new_replies'];
481
			else
482
				$limit = $modSettings['topicSummaryPosts'];
483
484
			$before = isset($_REQUEST['msg']) ? array('before' => (int) $_REQUEST['msg']) : array();
485
486
			$counter = 0;
487
			$context['previous_posts'] = empty($limit) ? array() : selectMessages($topic, 0, $limit, $before, $only_approved);
488
			foreach ($context['previous_posts'] as &$post)
489
			{
490
				$post['is_new'] = !empty($context['new_replies']);
491
				$post['counter'] = $counter++;
492
				$post['is_ignored'] = !empty($modSettings['enable_buddylist']) && in_array($post['id_poster'], $user_info['ignoreusers']);
493
494
				if (!empty($context['new_replies']))
495
					$context['new_replies']--;
496
			}
497
		}
498
499
		// Just ajax previewing then lets stop now
500
		if (isset($_REQUEST['xml']))
501
			obExit();
502
503
		$context['subject'] = addcslashes($form_subject, '"');
504
		$context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
505
506
		// Needed for the editor and message icons.
507
		require_once(SUBSDIR . '/Editor.subs.php');
508
509
		// Now create the editor.
510
		$editorOptions = array(
511
			'id' => 'message',
512
			'value' => $context['message'],
513
			'labels' => array(
514
				'post_button' => $context['submit_label'],
515
			),
516
			// add height and width for the editor
517
			'height' => '275px',
518
			'width' => '100%',
519
			// We do XML preview here.
520
			'preview_type' => 2
521
		);
522
523
		// Message icons - customized or not, retrieve them...
524
		$context['icons'] = getMessageIcons($board);
525
526
		$context['icon_url'] = '';
527
528
		if (!empty($context['icons']))
529
		{
530
			$context['icons'][count($context['icons']) - 1]['is_last'] = true;
531
			$context['icons'][0]['selected'] = true;
532
533
			// $context['icon'] is set when editing a message
534
			if (!isset($context['icon']))
535
				$context['icon'] = $context['icons'][0]['value'];
536
			$found = false;
537
			foreach ($context['icons'] as $icon)
538
			{
539
				if ($icon['value'] === $context['icon'])
540
				{
541
					$found = true;
542
					$context['icon_url'] = $icon['url'];
543
					break;
544
				}
545
			}
546
			// Fail safe
547
			if (!$found)
548
			{
549
				$context['icon'] = $context['icons'][0]['value'];
550
				$context['icon_url'] = $context['icons'][0]['url'];
551
			}
552
		}
553
554
		$context['show_additional_options'] = !empty($_POST['additional_options']) || isset($_GET['additionalOptions']);
555
556
		// Trigger the finalize_post_form event
557
		$this->_events->trigger('finalize_post_form', array('destination' => &$context['destination'], 'page_title' => &$context['page_title'], 'show_additional_options' => &$context['show_additional_options'], 'editorOptions' => &$editorOptions));
558
559
		// Initialize the editor
560
		create_control_richedit($editorOptions);
561
562
		// Build the link tree.
563
		if (empty($topic))
564
		{
565
			$context['linktree'][] = array(
566
				'name' => '<em>' . $txt['start_new_topic'] . '</em>'
567
			);
568
		}
569
		else
570
		{
571
			$context['linktree'][] = array(
572
				'url' => $scripturl . '?topic=' . $topic . '.' . $_REQUEST['start'],
573
				'name' => $form_subject,
574
				'extra_before' => '<span><strong class="nav">' . $context['page_title'] . ' ( </strong></span>',
575
				'extra_after' => '<span><strong class="nav"> )</strong></span>'
576
			);
577
		}
578
579
		$context['back_to_topic'] = isset($_REQUEST['goback']) || (isset($_REQUEST['msg']) && !isset($_REQUEST['subject']));
580
		$context['is_new_topic'] = empty($topic);
581
		$context['is_new_post'] = !isset($_REQUEST['msg']);
582
		$context['is_first_post'] = $context['is_new_topic'] || (isset($_REQUEST['msg']) && $_REQUEST['msg'] == $this->_topic_attributes['id_first_msg']);
583
		$context['current_action'] = 'post';
584
585
		// Register this form in the session variables.
586
		checkSubmitOnce('register');
587
588
		// Finally, load the template.
589
		if (!isset($_REQUEST['xml']))
590
		{
591
			loadTemplate('Post');
592
			$context['sub_template'] = 'post_page';
593
		}
594
	}
595
596
	/**
597
	 * Posts or saves the message composed with Post().
598
	 *
599
	 * requires various permissions depending on the action.
600
	 * handles attachment, post, and calendar saving.
601
	 * sends off notifications, and allows for announcements and moderation.
602
	 * accessed from ?action=post2.
603
	 */
604
	public function action_post2()
0 ignored issues
show
Coding Style introduced by
action_post2 uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post2 uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post2 uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post2 uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_post2 uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
605
	{
606
		global $board, $topic, $txt, $modSettings, $context, $user_settings;
607
		global $user_info, $board_info, $options;
608
609
		// Sneaking off, are we?
610
		if (empty($_POST) && empty($topic))
611
		{
612
			if (empty($_SERVER['CONTENT_LENGTH']))
613
				redirectexit('action=post;board=' . $board . '.0');
614
			else
615
				Errors::instance()->fatal_lang_error('post_upload_error', false);
616
		}
617
		elseif (empty($_POST) && !empty($topic))
618
			redirectexit('action=post;topic=' . $topic . '.0');
619
620
		// No need!
621
		$context['robot_no_index'] = true;
622
623
		// We are now in post2 action
624
		$context['current_action'] = 'post2';
625
626
		// If the session has timed out, let the user re-submit their form.
627
		if (checkSession('post', '', false) != '')
628
		{
629
			$this->_post_errors->addError('session_timeout');
630
631
			// Disable the preview so that any potentially malicious code is not executed
632
			$_REQUEST['preview'] = false;
633
634
			return $this->action_post();
635
		}
636
637
		$topic_info = array();
638
639
		// Previewing? Go back to start.
640
		if (isset($_REQUEST['preview']))
641
			return $this->action_post();
642
643
		require_once(SUBSDIR . '/Boards.subs.php');
644
		loadLanguage('Post');
645
646
		$this->_events->trigger('prepare_save_post', array('topic_info' => &$topic_info));
647
648
		// Prevent double submission of this form.
649
		checkSubmitOnce('check');
650
651
		// If this isn't a new topic load the topic info that we need.
652
		if (!empty($topic))
653
		{
654
			$topic_info = getTopicInfo($topic);
655
656
			// Though the topic should be there, it might have vanished.
657
			if (empty($topic_info))
658
				Errors::instance()->fatal_lang_error('topic_doesnt_exist');
659
660
			// Did this topic suddenly move? Just checking...
661
			if ($topic_info['id_board'] != $board)
662
				Errors::instance()->fatal_lang_error('not_a_topic');
663
		}
664
665
		// Replying to a topic?
666
		if (!empty($topic) && !isset($_REQUEST['msg']))
667
		{
668
			// Don't allow a post if it's locked.
669
			if ($topic_info['locked'] != 0 && !allowedTo('moderate_board'))
670
				Errors::instance()->fatal_lang_error('topic_locked', false);
671
672
			// Do the permissions and approval stuff...
673
			$becomesApproved = true;
674
			if ($topic_info['id_member_started'] != $user_info['id'])
675
			{
676
				if ($modSettings['postmod_active'] && allowedTo('post_unapproved_replies_any') && !allowedTo('post_reply_any'))
677
					$becomesApproved = false;
678
				else
679
					isAllowedTo('post_reply_any');
680
			}
681
			elseif (!allowedTo('post_reply_any'))
682
			{
683
				if ($modSettings['postmod_active'])
684
				{
685
					if (allowedTo('post_unapproved_replies_own') && !allowedTo('post_reply_own'))
686
						$becomesApproved = false;
687
					// Guests do not have post_unapproved_replies_own permission, so it's always post_unapproved_replies_any
688
					elseif ($user_info['is_guest'] && allowedTo('post_unapproved_replies_any'))
689
						$becomesApproved = false;
690
					else
691
						isAllowedTo('post_reply_own');
692
				}
693
			}
694
695
			if (isset($_POST['lock']))
696
			{
697
				$_POST['lock'] = $this->_checkLocked($_POST['lock'], $topic_info);
698
			}
699
700
			// So you wanna (un)sticky this...let's see.
701
			if (isset($_POST['sticky']) && ($_POST['sticky'] == $topic_info['is_sticky'] || !allowedTo('make_sticky')))
702
				unset($_POST['sticky']);
703
704
			$this->_events->trigger('save_replying', array('topic_info' => &$topic_info));
705
706
			// If the number of replies has changed, if the setting is enabled, go back to action_post() - which handles the error.
707
			if (empty($options['no_new_reply_warning']) && isset($_POST['last_msg']) && $topic_info['id_last_msg'] > $_POST['last_msg'])
708
			{
709
				addInlineJavascript('
710
					$(document).ready(function () {
711
						$("html,body").scrollTop($(\'.category_header:visible:first\').offset().top);
712
					});'
713
				);
714
715
				return $this->action_post();
716
			}
717
718
			$posterIsGuest = $user_info['is_guest'];
719
		}
720
		// Posting a new topic.
721
		elseif (empty($topic))
722
		{
723
			// Now don't be silly, new topics will get their own id_msg soon enough.
724
			unset($_REQUEST['msg'], $_POST['msg'], $_GET['msg']);
725
726
			// Do like, the permissions, for safety and stuff...
727
			$becomesApproved = true;
728
			if ($modSettings['postmod_active'] && !allowedTo('post_new') && allowedTo('post_unapproved_topics'))
729
				$becomesApproved = false;
730
			else
731
				isAllowedTo('post_new');
732
733
			$this->_events->trigger('save_new_topic', array('becomesApproved' => &$becomesApproved));
734
735
			if (isset($_POST['lock']))
736
			{
737
				$_POST['lock'] = $this->_checkLocked($_POST['lock']);
738
			}
739
740
			if (isset($_POST['sticky']) && (empty($_POST['sticky']) || !allowedTo('make_sticky')))
741
				unset($_POST['sticky']);
742
743
			$posterIsGuest = $user_info['is_guest'];
744
		}
745
		// Modifying an existing message?
746
		elseif (isset($_REQUEST['msg']) && !empty($topic))
747
		{
748
			$_REQUEST['msg'] = (int) $_REQUEST['msg'];
749
750
			$msgInfo = basicMessageInfo($_REQUEST['msg'], true);
751
752
			if (empty($msgInfo))
753
				Errors::instance()->fatal_lang_error('cant_find_messages', false);
754
755
			$this->_events->trigger('save_modify', array('msgInfo' => &$msgInfo));
756
757
			if (!empty($topic_info['locked']) && !allowedTo('moderate_board'))
758
				Errors::instance()->fatal_lang_error('topic_locked', false);
759
760
			if (isset($_POST['lock']))
761
			{
762
				$_POST['lock'] = $this->_checkLocked($_POST['lock'], $topic_info);
763
			}
764
765
			// Change the sticky status of this topic?
766
			if (isset($_POST['sticky']) && (!allowedTo('make_sticky') || $_POST['sticky'] == $topic_info['is_sticky']))
767
				unset($_POST['sticky']);
768
769
			if ($msgInfo['id_member'] == $user_info['id'] && !allowedTo('modify_any'))
770
			{
771
				if ((!$modSettings['postmod_active'] || $msgInfo['approved']) && !empty($modSettings['edit_disable_time']) && $msgInfo['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time())
772
					Errors::instance()->fatal_lang_error('modify_post_time_passed', false);
773
				elseif ($topic_info['id_member_started'] == $user_info['id'] && !allowedTo('modify_own'))
774
					isAllowedTo('modify_replies');
775
				else
776
					isAllowedTo('modify_own');
777
			}
778
			elseif ($topic_info['id_member_started'] == $user_info['id'] && !allowedTo('modify_any'))
779
			{
780
				isAllowedTo('modify_replies');
781
782
				// If you're modifying a reply, I say it better be logged...
783
				$moderationAction = true;
784
			}
785
			else
786
			{
787
				isAllowedTo('modify_any');
788
789
				// Log it, assuming you're not modifying your own post.
790
				if ($msgInfo['id_member'] != $user_info['id'])
791
					$moderationAction = true;
792
			}
793
794
			$posterIsGuest = empty($msgInfo['id_member']);
795
796
			// Can they approve it?
797
			$can_approve = allowedTo('approve_posts');
798
			$becomesApproved = $modSettings['postmod_active'] ? ($can_approve && !$msgInfo['approved'] ? (!empty($_REQUEST['approve']) ? 1 : 0) : $msgInfo['approved']) : 1;
799
			$approve_has_changed = $msgInfo['approved'] != $becomesApproved;
800
801
			if (!allowedTo('moderate_forum') || !$posterIsGuest)
802
			{
803
				$_POST['guestname'] = $msgInfo['poster_name'];
804
				$_POST['email'] = $msgInfo['poster_email'];
805
			}
806
		}
807
808
		// In case we want to override
809
		if (allowedTo('approve_posts'))
810
		{
811
			$becomesApproved = !isset($_REQUEST['approve']) || !empty($_REQUEST['approve']) ? 1 : 0;
812
			$approve_has_changed = isset($msgInfo['approved']) ? $msgInfo['approved'] != $becomesApproved : false;
813
		}
814
815
		// If the poster is a guest evaluate the legality of name and email.
816
		if ($posterIsGuest)
0 ignored issues
show
Bug introduced by
The variable $posterIsGuest does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
817
		{
818
			$_POST['guestname'] = !isset($_POST['guestname']) ? '' : Util::htmlspecialchars(trim($_POST['guestname']));
819
			$_POST['email'] = !isset($_POST['email']) ? '' : Util::htmlspecialchars(trim($_POST['email']));
820
821
			if ($_POST['guestname'] == '' || $_POST['guestname'] == '_')
822
				$this->_post_errors->addError('no_name');
823
824
			if (Util::strlen($_POST['guestname']) > 25)
825
				$this->_post_errors->addError('long_name');
826
827
			if (empty($modSettings['guest_post_no_email']))
828
			{
829
				// Only check if they changed it!
830
				if (!isset($msgInfo) || $msgInfo['poster_email'] != $_POST['email'])
831
				{
832
					if (!allowedTo('moderate_forum') && !Data_Validator::is_valid($_POST, array('email' => 'valid_email|required'), array('email' => 'trim')))
833
						empty($_POST['email']) ? $this->_post_errors->addError('no_email') : $this->_post_errors->addError('bad_email');
834
				}
835
836
				// Now make sure this email address is not banned from posting.
837
				isBannedEmail($_POST['email'], 'cannot_post', sprintf($txt['you_are_post_banned'], $txt['guest_title']));
838
			}
839
840
			// In case they are making multiple posts this visit, help them along by storing their name.
841
			if (!$this->_post_errors->hasErrors())
842
			{
843
				$_SESSION['guest_name'] = $_POST['guestname'];
844
				$_SESSION['guest_email'] = $_POST['email'];
845
			}
846
		}
847
848
		try
849
		{
850
			$this->_events->trigger('before_save_post', array('post_errors' => $this->_post_errors, 'topic_info' => $topic_info));
851
		}
852
		catch (Controller_Redirect_Exception $e)
853
		{
854
			return $e->doRedirect($this);
855
		}
856
857
		// Check the subject and message.
858
		if (!isset($_POST['subject']) || Util::htmltrim(Util::htmlspecialchars($_POST['subject'])) === '')
859
			$this->_post_errors->addError('no_subject');
860
861
		if (!isset($_POST['message']) || Util::htmltrim(Util::htmlspecialchars($_POST['message'], ENT_QUOTES)) === '')
862
			$this->_post_errors->addError('no_message');
863
		elseif (!empty($modSettings['max_messageLength']) && Util::strlen($_POST['message']) > $modSettings['max_messageLength'])
864
			$this->_post_errors->addError(array('long_message', array($modSettings['max_messageLength'])));
865
		else
866
		{
867
			// Prepare the message a bit for some additional testing.
868
			$_POST['message'] = Util::htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8', true);
869
870
			// Preparse code. (Zef)
871
			if ($user_info['is_guest'])
872
				$user_info['name'] = $_POST['guestname'];
873
			preparsecode($_POST['message']);
874
875
			$bbc_parser = \BBC\ParserWrapper::getInstance();
876
877
			// Let's see if there's still some content left without the tags.
878
			if (Util::htmltrim(strip_tags($bbc_parser->parseMessage($_POST['message'], false), '<img>')) === '' && (!allowedTo('admin_forum') || strpos($_POST['message'], '[html]') === false))
879
				$this->_post_errors->addError('no_message');
880
		}
881
882
		if ($posterIsGuest)
883
		{
884
			// If user is a guest, make sure the chosen name isn't taken.
885
			require_once(SUBSDIR . '/Members.subs.php');
886
			if (isReservedName($_POST['guestname'], 0, true, false) && (!isset($msgInfo['poster_name']) || $_POST['guestname'] != $msgInfo['poster_name']))
0 ignored issues
show
Bug introduced by
The variable $msgInfo does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
887
				$this->_post_errors->addError('bad_name');
888
		}
889
		// If the user isn't a guest, get his or her name and email.
890
		elseif (!isset($_REQUEST['msg']))
891
		{
892
			$_POST['guestname'] = $user_info['username'];
893
			$_POST['email'] = $user_info['email'];
894
		}
895
896
		// Posting somewhere else? Are we sure you can?
897
		if (!empty($_REQUEST['post_in_board']))
898
		{
899
			$new_board = (int) $_REQUEST['post_in_board'];
900
			if (!allowedTo('post_new', $new_board))
901
			{
902
				$post_in_board = boardInfo($new_board);
903
904
				if (!empty($post_in_board))
905
					$this->_post_errors->addError(array('post_new_board', array($post_in_board['name'])));
906
				else
907
					$this->_post_errors->addError('post_new');
908
			}
909
		}
910
911
		// Any mistakes?
912
		if ($this->_post_errors->hasErrors())
913
		{
914
			addInlineJavascript('
915
				$(document).ready(function () {
916
					$("html,body").scrollTop($(\'.category_header:visible:first\').offset().top);
917
				});'
918
			);
919
920
			return $this->action_post();
921
		}
922
923
		// Make sure the user isn't spamming the board.
924
		if (!isset($_REQUEST['msg']))
925
			spamProtection('post');
926
927
		// At about this point, we're posting and that's that.
928
		ignore_user_abort(true);
929
		setTimeLimit(300);
930
931
		// Add special html entities to the subject, name, and email.
932
		$_POST['subject'] = strtr(Util::htmlspecialchars($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
933
		$_POST['guestname'] = htmlspecialchars($_POST['guestname'], ENT_COMPAT, 'UTF-8');
934
		$_POST['email'] = htmlspecialchars($_POST['email'], ENT_COMPAT, 'UTF-8');
935
936
		// At this point, we want to make sure the subject isn't too long.
937
		if (Util::strlen($_POST['subject']) > 100)
938
			$_POST['subject'] = Util::substr($_POST['subject'], 0, 100);
939
940
		// Creating a new topic?
941
		$newTopic = empty($_REQUEST['msg']) && empty($topic);
942
943
		// Collect all parameters for the creation or modification of a post.
944
		$msgOptions = array(
945
			'id' => empty($_REQUEST['msg']) ? 0 : (int) $_REQUEST['msg'],
946
			'subject' => $_POST['subject'],
947
			'body' => $_POST['message'],
948
			'icon' => preg_replace('~[\./\\\\*:"\'<>]~', '', $_POST['icon']),
949
			'smileys_enabled' => !isset($_POST['ns']),
950
			'approved' => $becomesApproved,
0 ignored issues
show
Bug introduced by
The variable $becomesApproved does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
951
		);
952
953
		$topicOptions = array(
954
			'id' => empty($topic) ? 0 : $topic,
955
			'board' => $board,
956
			'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null,
957
			'sticky_mode' => isset($_POST['sticky']) ? (int) $_POST['sticky'] : null,
958
			'mark_as_read' => true,
959
			'is_approved' => !$modSettings['postmod_active'] || empty($topic) || !empty($board_info['cur_topic_approved']),
960
		);
961
962
		$posterOptions = array(
963
			'id' => $user_info['id'],
964
			'name' => $_POST['guestname'],
965
			'email' => $_POST['email'],
966
			'update_post_count' => !$user_info['is_guest'] && !isset($_REQUEST['msg']) && $board_info['posts_count'],
967
		);
968
969
		$this->_events->trigger('pre_save_post', array('msgOptions' => &$msgOptions, 'topicOptions' => &$topicOptions, 'posterOptions' => &$posterOptions));
970
971
		// This is an already existing message. Edit it.
972
		if (!empty($_REQUEST['msg']))
973
		{
974
			// Have admins allowed people to hide their screwups?
975
			if (time() - $msgInfo['poster_time'] > $modSettings['edit_wait_time'] || $user_info['id'] != $msgInfo['id_member'])
976
			{
977
				$msgOptions['modify_time'] = time();
978
				$msgOptions['modify_name'] = $user_info['name'];
979
			}
980
981
			// This will save some time...
982
			if (empty($approve_has_changed))
983
				unset($msgOptions['approved']);
984
985
			modifyPost($msgOptions, $topicOptions, $posterOptions);
986
		}
987
		// This is a new topic or an already existing one. Save it.
988
		else
989
		{
990
			// We also have to fake the board:
991
			// if it's valid and it's not the current, let's forget about the "current" and load the new one
992
			if (!empty($new_board) && $board !== $new_board)
993
			{
994
				$board = $new_board;
995
				loadBoard();
996
997
				// Some details changed
998
				$topicOptions['board'] = $board;
999
				$topicOptions['is_approved'] = !$modSettings['postmod_active'] || empty($topic) || !empty($board_info['cur_topic_approved']);
1000
				$posterOptions['update_post_count'] = !$user_info['is_guest'] && !isset($_REQUEST['msg']) && $board_info['posts_count'];
1001
			}
1002
1003
			createPost($msgOptions, $topicOptions, $posterOptions);
1004
1005
			if (isset($topicOptions['id']))
1006
				$topic = $topicOptions['id'];
1007
		}
1008
1009
		$this->_events->trigger('after_save_post', array('board' => $board, 'topic' => $topic, 'msgOptions' => $msgOptions, 'topicOptions' => $topicOptions, 'becomesApproved' => $becomesApproved, 'posterOptions' => $posterOptions));
1010
1011
		// Marking boards as read.
1012
		// (You just posted and they will be unread.)
1013
		if (!$user_info['is_guest'])
1014
		{
1015
			$board_list = !empty($board_info['parent_boards']) ? array_keys($board_info['parent_boards']) : array();
1016
1017
			// Returning to the topic?
1018
			if (!empty($_REQUEST['goback']))
1019
				$board_list[] = $board;
1020
1021
			if (!empty($board_list))
1022
				markBoardsRead($board_list, false, false);
1023
		}
1024
1025
		// Turn notification on or off.
1026
		if (!empty($_POST['notify']) && allowedTo('mark_any_notify'))
1027
			setTopicNotification($user_info['id'], $topic, true);
1028
		elseif (!$newTopic)
1029
			setTopicNotification($user_info['id'], $topic, false);
1030
1031
		// Log an act of moderation - modifying.
1032
		if (!empty($moderationAction))
1033
			logAction('modify', array('topic' => $topic, 'message' => (int) $_REQUEST['msg'], 'member' => $msgInfo['id_member'], 'board' => $board));
1034
1035
		if (isset($_POST['lock']) && $_POST['lock'] != 2)
1036
			logAction(empty($_POST['lock']) ? 'unlock' : 'lock', array('topic' => $topicOptions['id'], 'board' => $topicOptions['board']));
1037
1038
		if (isset($_POST['sticky']))
1039
			logAction(empty($_POST['sticky']) ? 'unsticky' : 'sticky', array('topic' => $topicOptions['id'], 'board' => $topicOptions['board']));
1040
1041
		// Notify any members who have notification turned on for this topic/board - only do this if it's going to be approved(!)
1042
		if ($becomesApproved)
1043
		{
1044
			require_once(SUBSDIR . '/Notification.subs.php');
1045
			if ($newTopic)
1046
			{
1047
				$notifyData = array(
1048
					'body' => $_POST['message'],
1049
					'subject' => $_POST['subject'],
1050
					'name' => $user_info['name'],
1051
					'poster' => $user_info['id'],
1052
					'msg' => $msgOptions['id'],
1053
					'board' => $board,
1054
					'topic' => $topic,
1055
					'signature' => (isset($user_settings['signature']) ? $user_settings['signature'] : ''),
1056
				);
1057
				sendBoardNotifications($notifyData);
1058
			}
1059
			elseif (empty($_REQUEST['msg']))
1060
			{
1061
				// Only send it to everyone if the topic is approved, otherwise just to the topic starter if they want it.
1062
				if ($topic_info['approved'])
1063
					sendNotifications($topic, 'reply');
1064
				else
1065
					sendNotifications($topic, 'reply', array(), $topic_info['id_member_started']);
1066
			}
1067
		}
1068
1069
		if ($board_info['num_topics'] == 0)
1070
			Cache::instance()->remove('board-' . $board);
1071
1072
		if (!empty($_POST['announce_topic']))
1073
			redirectexit('action=announce;sa=selectgroup;topic=' . $topic . (!empty($_POST['move']) && allowedTo('move_any') ? ';move' : '') . (empty($_REQUEST['goback']) ? '' : ';goback'));
1074
1075
		if (!empty($_POST['move']) && allowedTo('move_any'))
1076
			redirectexit('action=movetopic;topic=' . $topic . '.0' . (empty($_REQUEST['goback']) ? '' : ';goback'));
1077
1078
		// Return to post if the mod is on.
1079
		if (isset($_REQUEST['msg']) && !empty($_REQUEST['goback']))
1080
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'], isBrowser('ie'));
1081
		elseif (!empty($_REQUEST['goback']))
1082
			redirectexit('topic=' . $topic . '.new#new', isBrowser('ie'));
1083
		// Dut-dut-duh-duh-DUH-duh-dut-duh-duh!  *dances to the Final Fantasy Fanfare...*
1084
		else
1085
			redirectexit('board=' . $board . '.0');
1086
	}
1087
1088
	/**
1089
	 * Loads a post and inserts it into the current editing text box.
1090
	 * Used to quick edit a post as well as to quote a post and place it in the quick reply box
1091
	 * Can be used to quick edit just the subject from the topic listing
1092
	 *
1093
	 * uses the Post language file.
1094
	 * uses special (sadly browser dependent) javascript to parse entities for internationalization reasons.
1095
	 * accessed with ?action=quotefast and ?action=quotefast;modify
1096
	 */
1097
	public function action_quotefast()
0 ignored issues
show
Coding Style introduced by
action_quotefast uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_quotefast uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1098
	{
1099
		global $user_info, $context;
1100
1101
		loadLanguage('Post');
1102
1103
		// Where we going if we need to?
1104
		$context['post_box_name'] = isset($_GET['pb']) ? $_GET['pb'] : '';
1105
1106
		$row = quoteMessageInfo((int) $_REQUEST['quote'], isset($_REQUEST['modify']));
1107
1108
		$context['sub_template'] = 'quotefast';
1109
		if (!empty($row))
1110
			$can_view_post = $row['approved'] || ($row['id_member'] != 0 && $row['id_member'] == $user_info['id']) || allowedTo('approve_posts', $row['id_board']);
1111
1112
		if (!empty($can_view_post))
1113
		{
1114
			// Remove special formatting we don't want anymore.
1115
			$row['body'] = un_preparsecode($row['body']);
1116
1117
			// Censor the message!
1118
			censorText($row['body']);
1119
1120
			$row['body'] = preg_replace('~<br ?/?' . '>~i', "\n", $row['body']);
1121
1122
			// Want to modify a single message by double clicking it?
1123
			if (isset($_REQUEST['modify']))
1124
			{
1125
				censorText($row['subject']);
1126
1127
				$context['sub_template'] = 'modifyfast';
1128
				$context['message'] = array(
1129
					'id' => $_REQUEST['quote'],
1130
					'body' => $row['body'],
1131
					'subject' => addcslashes($row['subject'], '"'),
1132
				);
1133
1134
				return;
1135
			}
1136
1137
			// Remove any nested quotes.
1138
			$row['body'] = removeNestedQuotes($row['body']);
1139
1140
			// Add a quote string on the front and end.
1141
			$context['quote']['xml'] = '[quote author=' . $row['poster_name'] . ' link=msg=' . (int) $_REQUEST['quote'] . ' date=' . $row['poster_time'] . "]\n" . $row['body'] . "\n[/quote]";
1142
			$context['quote']['text'] = strtr(un_htmlspecialchars($context['quote']['xml']), array('\'' => '\\\'', '\\' => '\\\\', "\n" => '\\n', '</script>' => '</\' + \'script>'));
1143
			$context['quote']['xml'] = strtr($context['quote']['xml'], array('&nbsp;' => '&#160;', '<' => '&lt;', '>' => '&gt;'));
1144
1145
			$context['quote']['mozilla'] = strtr(Util::htmlspecialchars($context['quote']['text']), array('&quot;' => '"'));
1146
		}
1147
		//@todo Needs a nicer interface.
1148
		// In case our message has been removed in the meantime.
1149
		elseif (isset($_REQUEST['modify']))
1150
		{
1151
			$context['sub_template'] = 'modifyfast';
1152
			$context['message'] = array(
1153
				'id' => 0,
1154
				'body' => '',
1155
				'subject' => '',
1156
			);
1157
		}
1158
		else
1159
			$context['quote'] = array(
1160
				'xml' => '',
1161
				'mozilla' => '',
1162
				'text' => '',
1163
			);
1164
	}
1165
1166
	/**
1167
	 * Used to edit the body or subject of a message inline
1168
	 * called from action=jsmodify from script and topic js
1169
	 */
1170
	public function action_jsmodify()
0 ignored issues
show
Coding Style introduced by
action_jsmodify uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
action_jsmodify uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1171
	{
1172
		global $modSettings, $board, $topic;
1173
		global $user_info, $context;
1174
1175
		// We have to have a topic!
1176
		if (empty($topic))
1177
			obExit(false);
1178
1179
		checkSession('get');
1180
1181
		$row = getTopicInfoByMsg($topic, empty($_REQUEST['msg']) ? 0 : (int) $_REQUEST['msg']);
1182
1183
		if (empty($row))
1184
			Errors::instance()->fatal_lang_error('no_board', false);
1185
1186
		// Change either body or subject requires permissions to modify messages.
1187
		if (isset($_POST['message']) || isset($_POST['subject']) || isset($_REQUEST['icon']))
1188
		{
1189
			if (!empty($row['locked']))
1190
				isAllowedTo('moderate_board');
1191
1192
			if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any'))
1193
			{
1194
				if ((!$modSettings['postmod_active'] || $row['approved']) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time())
1195
					Errors::instance()->fatal_lang_error('modify_post_time_passed', false);
1196
				elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_own'))
1197
					isAllowedTo('modify_replies');
1198
				else
1199
					isAllowedTo('modify_own');
1200
			}
1201
			// Otherwise, they're locked out; someone who can modify the replies is needed.
1202
			elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_any'))
1203
				isAllowedTo('modify_replies');
1204
			else
1205
				isAllowedTo('modify_any');
1206
1207
			// Only log this action if it wasn't your message.
1208
			$moderationAction = $row['id_member'] != $user_info['id'];
1209
		}
1210
1211
		if (isset($_POST['subject']) && Util::htmltrim(Util::htmlspecialchars($_POST['subject'])) !== '')
1212
		{
1213
			$_POST['subject'] = strtr(Util::htmlspecialchars($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
1214
1215
			// Maximum number of characters.
1216
			if (Util::strlen($_POST['subject']) > 100)
1217
				$_POST['subject'] = Util::substr($_POST['subject'], 0, 100);
1218
		}
1219
		elseif (isset($_POST['subject']))
1220
		{
1221
			$this->_post_errors->addError('no_subject');
1222
			unset($_POST['subject']);
1223
		}
1224
1225
		if (isset($_POST['message']))
1226
		{
1227
			if (Util::htmltrim(Util::htmlspecialchars($_POST['message'])) === '')
1228
			{
1229
				$this->_post_errors->addError('no_message');
1230
				unset($_POST['message']);
1231
			}
1232
			elseif (!empty($modSettings['max_messageLength']) && Util::strlen($_POST['message']) > $modSettings['max_messageLength'])
1233
			{
1234
				$this->_post_errors->addError(array('long_message', array($modSettings['max_messageLength'])));
1235
				unset($_POST['message']);
1236
			}
1237
			else
1238
			{
1239
				$_POST['message'] = Util::htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8', true);
1240
1241
				preparsecode($_POST['message']);
1242
				$bbc_parser = \BBC\ParserWrapper::getInstance();
1243
1244
				if (Util::htmltrim(strip_tags($bbc_parser->parseMessage($_POST['message'], false), '<img>')) === '')
1245
				{
1246
					$this->_post_errors->addError('no_message');
1247
					unset($_POST['message']);
1248
				}
1249
			}
1250
		}
1251
1252
		if (isset($_POST['lock']))
1253
		{
1254
			$_POST['lock'] = $this->_checkLocked($_POST['lock'], $row);
1255
		}
1256
1257
		if (isset($_POST['sticky']) && !allowedTo('make_sticky'))
1258
			unset($_POST['sticky']);
1259
1260
		if (!$this->_post_errors->hasErrors())
1261
		{
1262
			if (!empty($modSettings['mentions_enabled']))
1263
			{
1264
				if (!empty($_REQUEST['uid']))
1265
				{
1266
					$query_params = array();
1267
					$query_params['member_ids'] = array_unique(array_map('intval', $_REQUEST['uid']));
1268
					require_once(SUBSDIR . '/Members.subs.php');
1269
					$mentioned_members = membersBy('member_ids', $query_params, true);
1270
					$replacements = 0;
1271
					$actually_mentioned = array();
1272
					foreach ($mentioned_members as $member)
1273
					{
1274
						$_POST['message'] = str_replace('@' . $member['real_name'], '[member=' . $member['id_member'] . ']' . $member['real_name'] . '[/member]', $_POST['message'], $replacements);
1275
						if ($replacements > 0)
1276
							$actually_mentioned[] = $member['id_member'];
1277
					}
1278
				}
1279
1280
				if (!empty($actually_mentioned))
1281
				{
1282
					require_once(CONTROLLERDIR . '/Mentions.controller.php');
1283
					$mentions = new Mentions_Controller();
0 ignored issues
show
Bug introduced by
The call to Mentions_Controller::__construct() misses a required argument $eventManager.

This check looks for function calls that miss required arguments.

Loading history...
1284
					$mentions->setData(array(
0 ignored issues
show
Deprecated Code introduced by
The method Mentions_Controller::setData() has been deprecated with message: since 1.1

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1285
						'id_member' => $actually_mentioned,
1286
						'type' => 'men',
1287
						'id_msg' => $row['id_msg'],
1288
						'status' => $row['approved'] ? 'new' : 'unapproved',
1289
					));
1290
					$mentions->action_add();
0 ignored issues
show
Deprecated Code introduced by
The method Mentions_Controller::action_add() has been deprecated with message: since 1.1 - Use Notifications::create instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1291
				}
1292
			}
1293
1294
			$msgOptions = array(
1295
				'id' => $row['id_msg'],
1296
				'subject' => isset($_POST['subject']) ? $_POST['subject'] : null,
1297
				'body' => isset($_POST['message']) ? $_POST['message'] : null,
1298
				'icon' => isset($_REQUEST['icon']) ? preg_replace('~[\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : null,
1299
			);
1300
1301
			$topicOptions = array(
1302
				'id' => $topic,
1303
				'board' => $board,
1304
				'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null,
1305
				'sticky_mode' => isset($_POST['sticky']) ? (int) $_POST['sticky'] : null,
1306
				'mark_as_read' => false,
1307
			);
1308
1309
			$posterOptions = array();
1310
1311
			// Only consider marking as editing if they have edited the subject, message or icon.
1312
			if ((isset($_POST['subject']) && $_POST['subject'] != $row['subject']) || (isset($_POST['message']) && $_POST['message'] != $row['body']) || (isset($_REQUEST['icon']) && $_REQUEST['icon'] != $row['icon']))
1313
			{
1314
				// And even then only if the time has passed...
1315
				if (time() - $row['poster_time'] > $modSettings['edit_wait_time'] || $user_info['id'] != $row['id_member'])
1316
				{
1317
					$msgOptions['modify_time'] = time();
1318
					$msgOptions['modify_name'] = $user_info['name'];
1319
				}
1320
			}
1321
			// If nothing was changed there's no need to add an entry to the moderation log.
1322
			else
1323
				$moderationAction = false;
1324
1325
			modifyPost($msgOptions, $topicOptions, $posterOptions);
1326
1327
			// If we didn't change anything this time but had before put back the old info.
1328
			if (!isset($msgOptions['modify_time']) && !empty($row['modified_time']))
1329
			{
1330
				$msgOptions['modify_time'] = $row['modified_time'];
1331
				$msgOptions['modify_name'] = $row['modified_name'];
1332
			}
1333
1334
			// Changing the first subject updates other subjects to 'Re: new_subject'.
1335
			if (isset($_POST['subject']) && isset($_REQUEST['change_all_subjects']) && $row['id_first_msg'] == $row['id_msg'] && !empty($row['num_replies']) && (allowedTo('modify_any') || ($row['id_member_started'] == $user_info['id'] && allowedTo('modify_replies'))))
1336
			{
1337
				// Get the proper (default language) response prefix first.
1338
				$context['response_prefix'] = response_prefix();
1339
1340
				topicSubject(array('id_topic' => $topic, 'id_first_msg' => $row['id_first_msg']), $_POST['subject'], $context['response_prefix'], true);
1341
			}
1342
1343
			if (!empty($moderationAction))
1344
				logAction('modify', array('topic' => $topic, 'message' => $row['id_msg'], 'member' => $row['id_member'], 'board' => $board));
1345
		}
1346
1347
		if (isset($_REQUEST['xml']))
1348
		{
1349
			$context['sub_template'] = 'modifydone';
1350
			if (!$this->_post_errors->hasErrors() && isset($msgOptions['subject']) && isset($msgOptions['body']))
1351
			{
1352
				$context['message'] = array(
1353
					'id' => $row['id_msg'],
1354
					'modified' => array(
1355
						'time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '',
1356
						'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '',
1357
						'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0,
1358
						'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '',
1359
					),
1360
					'subject' => $msgOptions['subject'],
1361
					'first_in_topic' => $row['id_msg'] == $row['id_first_msg'],
1362
					'body' => strtr($msgOptions['body'], array(']]>' => ']]]]><![CDATA[>')),
1363
				);
1364
1365
				censorText($context['message']['subject']);
1366
				censorText($context['message']['body']);
1367
1368
				$context['message']['body'] = $bbc_parser->parseMessage($context['message']['body'], $row['smileys_enabled']);
0 ignored issues
show
Bug introduced by
The variable $bbc_parser does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1369
			}
1370
			// Topic?
1371
			elseif (!$this->_post_errors->hasErrors())
1372
			{
1373
				$context['sub_template'] = 'modifytopicdone';
1374
				$context['message'] = array(
1375
					'id' => $row['id_msg'],
1376
					'modified' => array(
1377
						'time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '',
1378
						'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '',
1379
						'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0,
1380
						'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : '',
1381
					),
1382
					'subject' => isset($msgOptions['subject']) ? $msgOptions['subject'] : '',
1383
				);
1384
1385
				censorText($context['message']['subject']);
1386
			}
1387
			else
1388
			{
1389
				$context['message'] = array(
1390
					'id' => $row['id_msg'],
1391
					'errors' => array(),
1392
					'error_in_subject' => $this->_post_errors->hasError('no_subject'),
1393
					'error_in_body' => $this->_post_errors->hasError('no_message') || $this->_post_errors->hasError('long_message'),
1394
				);
1395
				$context['message']['errors'] = $this->_post_errors->prepareErrors();
1396
			}
1397
		}
1398
		else
1399
			obExit(false);
1400
	}
1401
1402
	/**
1403
	 * Spell checks the post for typos ;).
1404
	 * It uses the pspell library, which MUST be installed.
1405
	 * It has problems with internationalization.
1406
	 * It is accessed via ?action=spellcheck.
1407
	 * @deprecated since 1.1
1408
	 */
1409
	public function action_spellcheck()
1410
	{
1411
		// Initialize this controller with its own event manager
1412
		$controller = new Spellcheck_Controller(new Event_Manager());
1413
1414
		// Fetch controllers generic hook name from the action controller
1415
		$hook = $controller->getHook();
1416
1417
		// Call the controllers pre dispatch method
1418
		$controller->pre_dispatch();
1419
1420
		// Call integrate_action_XYZ_before -> XYZ_controller -> integrate_action_XYZ_after
1421
		call_integration_hook('integrate_action_' . $hook . '_before', array('action_index'));
1422
1423
		$result = $controller->action_index();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $controller->action_index() (which targets Spellcheck_Controller::action_index()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
1424
1425
		call_integration_hook('integrate_action_' . $hook . '_after', array('action_index'));
1426
1427
		return $result;
1428
	}
1429
1430
	protected function _checkLocked($lock, $topic_info = null)
1431
	{
1432
		global $user_info;
1433
1434
		// A new topic
1435
		if ($topic_info === null)
1436
		{
1437
			// New topics are by default not locked.
1438
			if (empty($lock))
1439
				return null;
1440
			// Besides, you need permission.
1441
			elseif (!allowedTo(array('lock_any', 'lock_own')))
1442
				return null;
1443
			// A moderator-lock (1) can override a user-lock (2).
1444
			else
1445
				return allowedTo('lock_any') ? 1 : 2;
1446
		}
1447
1448
		// Nothing changes to the lock status.
1449
		if ((empty($lock) && empty($topic_info['locked'])) || (!empty($lock) && !empty($topic_info['locked'])))
1450
			return null;
1451
		// You're simply not allowed to (un)lock this.
1452
		elseif (!allowedTo(array('lock_any', 'lock_own')) || (!allowedTo('lock_any') && $user_info['id'] != $topic_info['id_member_started']))
1453
			return null;
1454
		// You're only allowed to lock your own topics.
1455
		elseif (!allowedTo('lock_any'))
1456
		{
1457
			// You're not allowed to break a moderator's lock.
1458
			if ($topic_info['locked'] == 1)
1459
				return null;
1460
			// Lock it with a soft lock or unlock it.
1461
			else
1462
				$lock = empty($lock) ? 0 : 2;
1463
		}
1464
		// You must be the moderator.
1465
		else
1466
			$lock = empty($lock) ? 0 : 1;
1467
1468
		return $lock;
1469
	}
1470
}
1471