1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Handles the moving of topics from board to board |
||
5 | * |
||
6 | * @package ElkArte Forum |
||
7 | * @copyright ElkArte Forum contributors |
||
8 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||
9 | * |
||
10 | * This file contains code covered by: |
||
11 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||
12 | * |
||
13 | * @version 2.0 dev |
||
14 | * |
||
15 | */ |
||
16 | |||
17 | namespace ElkArte\Controller; |
||
18 | |||
19 | use ElkArte\AbstractController; |
||
20 | use ElkArte\Exceptions\Exception; |
||
21 | use ElkArte\Helper\Util; |
||
22 | use ElkArte\Languages\Loader; |
||
23 | |||
24 | /** |
||
25 | * Move Topic Controller |
||
26 | */ |
||
27 | class MoveTopic extends AbstractController |
||
28 | { |
||
29 | /** @var int The id of the topic being manipulated */ |
||
30 | private $_topic; |
||
31 | |||
32 | /** @var array Information about the topic being moved */ |
||
33 | private $_topic_info; |
||
34 | |||
35 | /** @var array Information about the board where the topic resides */ |
||
36 | private $_board_info; |
||
37 | |||
38 | /** @var int Board that will receive the topic */ |
||
39 | private $_toboard; |
||
40 | |||
41 | /** |
||
42 | * Pre Dispatch, called before other methods. |
||
43 | */ |
||
44 | public function pre_dispatch() |
||
45 | { |
||
46 | global $topic; |
||
47 | |||
48 | // Set the topic from the global, yes yes |
||
49 | $this->_topic = $topic; |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * Forwards to the action method to handle the action. |
||
54 | * |
||
55 | * @see AbstractController::action_index |
||
56 | */ |
||
57 | public function action_index() |
||
58 | { |
||
59 | // move a topic, what else?! |
||
60 | // $this->action_movetopic(); |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * This function allows to move a topic |
||
65 | * |
||
66 | * What it does: |
||
67 | * |
||
68 | * - It must be called with a topic specified. (that is, global $topic must |
||
69 | * be set... @todo fix this thing.) |
||
70 | * - Validates access |
||
71 | * - Accessed via ?action=movetopic. |
||
72 | * |
||
73 | * @uses template_move_topic() sub-template in MoveTopic.template.php |
||
74 | */ |
||
75 | public function action_movetopic() |
||
76 | { |
||
77 | global $context; |
||
78 | |||
79 | // Lets make sure they can access the topic being moved and have permissions to move it |
||
80 | $this->_check_access(); |
||
81 | |||
82 | // Get a list of boards this moderator can move to. |
||
83 | require_once(SUBSDIR . '/Boards.subs.php'); |
||
84 | $context += getBoardList(array('not_redirection' => true)); |
||
85 | |||
86 | // No boards? |
||
87 | if (empty($context['categories']) || $context['num_boards'] == 1) |
||
88 | { |
||
89 | throw new Exception('moveto_noboards', false); |
||
90 | } |
||
91 | |||
92 | // Already used the function, let's set the selected board back to the last |
||
93 | $last_moved_to = isset($_SESSION['move_to_topic']['move_to']) && $_SESSION['move_to_topic']['move_to'] != $context['current_board'] ? (int) $_SESSION['move_to_topic']['move_to'] : 0; |
||
94 | if (!empty($last_moved_to)) |
||
95 | { |
||
96 | foreach ($context['categories'] as $id => $values) |
||
97 | { |
||
98 | if (isset($values['boards'][$last_moved_to])) |
||
99 | { |
||
100 | $context['categories'][$id]['boards'][$last_moved_to]['selected'] = true; |
||
101 | break; |
||
102 | } |
||
103 | } |
||
104 | } |
||
105 | |||
106 | // Set up for the template |
||
107 | theme()->getTemplates()->load('MoveTopic'); |
||
108 | $this->_prep_template(); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Validates that the member can access the topic |
||
113 | * |
||
114 | * What it does: |
||
115 | * |
||
116 | * - Checks that a topic is supplied |
||
117 | * - Validates the topic information can be loaded |
||
118 | * - If the topic is not approved yet, must have approve permissions to move it |
||
119 | * - If the member is the topic starter requires the move_own permission, otherwise the move_any permission. |
||
120 | */ |
||
121 | private function _check_access() |
||
122 | { |
||
123 | global $modSettings; |
||
124 | |||
125 | if (empty($this->_topic)) |
||
126 | { |
||
127 | throw new Exception('no_access', false); |
||
128 | } |
||
129 | |||
130 | // Retrieve the basic topic information for whats being moved |
||
131 | require_once(SUBSDIR . '/Topic.subs.php'); |
||
132 | $this->_topic_info = getTopicInfo($this->_topic, 'message'); |
||
0 ignored issues
–
show
|
|||
133 | |||
134 | if (empty($this->_topic_info)) |
||
135 | { |
||
136 | throw new Exception('topic_gone', false); |
||
137 | } |
||
138 | |||
139 | // Can they see it - if not approved? |
||
140 | if ($modSettings['postmod_active'] && !$this->_topic_info['approved']) |
||
141 | { |
||
142 | isAllowedTo('approve_posts'); |
||
143 | } |
||
144 | |||
145 | // Are they allowed to actually move any topics or even their own? |
||
146 | if (allowedTo('move_any')) |
||
147 | { |
||
148 | return; |
||
149 | } |
||
150 | |||
151 | if (!($this->_topic_info['id_member_started'] == $this->user->id && !allowedTo('move_own'))) |
||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
152 | { |
||
153 | return; |
||
154 | } |
||
155 | |||
156 | throw new Exception('cannot_move_any', false); |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * Prepares the content for use in the move topic template |
||
161 | */ |
||
162 | private function _prep_template() |
||
163 | { |
||
164 | global $context, $txt, $language, $board; |
||
165 | |||
166 | $context['is_approved'] = $this->_topic_info['approved']; |
||
167 | $context['subject'] = $this->_topic_info['subject']; |
||
168 | $context['redirect_topic'] = isset($_SESSION['move_to_topic']['redirect_topic']) ? (int) $_SESSION['move_to_topic']['redirect_topic'] : 0; |
||
169 | $context['redirect_expires'] = isset($_SESSION['move_to_topic']['redirect_expires']) ? (int) $_SESSION['move_to_topic']['redirect_expires'] : 0; |
||
170 | $context['page_title'] = $txt['move_topic']; |
||
171 | $context['sub_template'] = 'move_topic'; |
||
172 | |||
173 | // Breadcrumbs |
||
174 | $context['breadcrumbs'][] = [ |
||
175 | 'url' => getUrl('topic', ['topic' => $this->_topic, 'start' => 0, 'subject' => $context['subject']]), |
||
176 | 'name' => $context['subject'], |
||
177 | ]; |
||
178 | $context['breadcrumbs'][] = [ |
||
179 | 'url' => '#', |
||
180 | 'name' => $txt['move_topic'], |
||
181 | ]; |
||
182 | |||
183 | $context['back_to_topic'] = isset($this->_req->post->goback); |
||
184 | |||
185 | // Ugly ! |
||
186 | if ($this->user->language !== $language) |
||
0 ignored issues
–
show
The property
language does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
187 | { |
||
188 | $mtxt = []; |
||
189 | $lang = new Loader($language, $mtxt, database()); |
||
190 | $lang->load('index'); |
||
191 | $txt['movetopic_default'] = $mtxt['movetopic_default']; |
||
192 | } |
||
193 | |||
194 | // We will need this |
||
195 | if (isset($this->_req->query->current_board)) |
||
196 | { |
||
197 | moveTopicConcurrence((int) $this->_req->query->current_board, $board, $this->_topic); |
||
198 | } |
||
199 | |||
200 | // Register this form and get a sequence number in $context. |
||
201 | checkSubmitOnce('register'); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Executes the actual move of a topic. |
||
206 | * |
||
207 | * What it does: |
||
208 | * |
||
209 | * - It is called on the submit of action_movetopic. |
||
210 | * - This function logs that topics have been moved in the moderation log. |
||
211 | * - Upon successful completion redirects to message index. |
||
212 | * - Accessed via ?action=movetopic2. |
||
213 | * |
||
214 | * @uses subs/Post.subs.php. |
||
215 | */ |
||
216 | public function action_movetopic2() |
||
217 | { |
||
218 | global $board; |
||
219 | |||
220 | $this->_check_access_2(); |
||
221 | |||
222 | checkSession(); |
||
223 | require_once(SUBSDIR . '/Post.subs.php'); |
||
224 | require_once(SUBSDIR . '/Boards.subs.php'); |
||
225 | |||
226 | // The destination board must be numeric. |
||
227 | $this->_toboard = (int) $this->_req->post->toboard; |
||
228 | |||
229 | // Make sure they can see the board they are trying to move to (and get whether posts count in the target board). |
||
230 | $this->_board_info = boardInfo($this->_toboard, $this->_topic); |
||
231 | if (empty($this->_board_info)) |
||
232 | { |
||
233 | throw new Exception('no_board'); |
||
234 | } |
||
235 | |||
236 | // Remember this for later. |
||
237 | $_SESSION['move_to_topic'] = array( |
||
238 | 'move_to' => $this->_toboard |
||
239 | ); |
||
240 | |||
241 | // Rename the topic if needed |
||
242 | $this->_rename_topic(); |
||
243 | |||
244 | // Create a link to this in the old board. |
||
245 | $this->_post_redirect(); |
||
246 | |||
247 | // Account for boards that count posts and those that don't |
||
248 | $this->_count_update(); |
||
249 | |||
250 | // Do the move (includes statistics update needed for the redirect topic). |
||
251 | moveTopics($this->_topic, $this->_toboard); |
||
252 | |||
253 | // Log that they moved this topic. |
||
254 | if (!allowedTo('move_own') || $this->_topic_info['id_member_started'] != $this->user->id) |
||
255 | { |
||
256 | logAction('move', array('topic' => $this->_topic, 'board_from' => $board, 'board_to' => $this->_toboard)); |
||
257 | } |
||
258 | |||
259 | // Notify people that this topic has been moved? |
||
260 | require_once(SUBSDIR . '/Notification.subs.php'); |
||
261 | sendNotifications($this->_topic, 'move'); |
||
262 | |||
263 | // Why not go back to the original board in case they want to keep moving? |
||
264 | if (!isset($this->_req->post->goback)) |
||
265 | { |
||
266 | redirectexit('board=' . $board . '.0'); |
||
267 | } |
||
268 | else |
||
269 | { |
||
270 | redirectexit('topic=' . $this->_topic . '.0'); |
||
271 | } |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Checks access and input validation before committing the move |
||
276 | * |
||
277 | * What it does: |
||
278 | * |
||
279 | * - Checks that a topic is supplied |
||
280 | * - Validates the move location |
||
281 | * - Checks redirection details if its a redirection is to be posted |
||
282 | * - If the member is the topic starter requires the move_own permission, otherwise the move_any permission. |
||
283 | * |
||
284 | * @return bool |
||
285 | * @throws \ElkArte\Exceptions\Exception no_access |
||
286 | */ |
||
287 | private function _check_access_2() |
||
288 | { |
||
289 | global $board; |
||
290 | |||
291 | if (empty($this->_topic)) |
||
292 | { |
||
293 | throw new Exception('no_access', false); |
||
294 | } |
||
295 | |||
296 | // You can't choose to have a redirection topic and not provide a reason. |
||
297 | if (isset($this->_req->post->postRedirect) && $this->_req->getPost('reason', 'trim', '') === '') |
||
298 | { |
||
299 | throw new Exception('movetopic_no_reason', false); |
||
300 | } |
||
301 | |||
302 | // You have to tell us were you are moving to |
||
303 | if (!isset($this->_req->post->toboard)) |
||
304 | { |
||
305 | throw new Exception('movetopic_no_board', false); |
||
306 | } |
||
307 | |||
308 | // We will need this |
||
309 | require_once(SUBSDIR . '/Topic.subs.php'); |
||
310 | if (isset($this->_req->query->current_board)) |
||
311 | { |
||
312 | moveTopicConcurrence((int) $this->_req->query->current_board, $board, $this->_topic); |
||
313 | } |
||
314 | |||
315 | // Make sure this form hasn't been submitted before. |
||
316 | checkSubmitOnce('check'); |
||
317 | |||
318 | // Get the basic details on this topic (again) |
||
319 | $this->_topic_info = getTopicInfo($this->_topic); |
||
0 ignored issues
–
show
It seems like
getTopicInfo($this->_topic) can also be of type boolean . However, the property $_topic_info is declared as type array . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
320 | |||
321 | // Not approved then you need approval permissions to move it as well |
||
322 | if (!$this->_topic_info['approved']) |
||
323 | { |
||
324 | isAllowedTo('approve_posts'); |
||
325 | } |
||
326 | |||
327 | // Can they move topics on this board? |
||
328 | if (!allowedTo('move_any')) |
||
329 | { |
||
330 | if ($this->_topic_info['id_member_started'] == $this->user->id) |
||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
331 | { |
||
332 | isAllowedTo('move_own'); |
||
333 | } |
||
334 | else |
||
335 | { |
||
336 | isAllowedTo('move_any'); |
||
337 | } |
||
338 | } |
||
339 | |||
340 | return true; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * Renames the topic during the move if requested |
||
345 | * |
||
346 | * What it does: |
||
347 | * |
||
348 | * - Renames the moved topic with a new topic subject |
||
349 | * - If enforce_subject is set, renames all posts withing the moved topic posts with a new subject |
||
350 | */ |
||
351 | private function _rename_topic() |
||
352 | { |
||
353 | global $context; |
||
354 | |||
355 | // Rename the topic... |
||
356 | if (isset($this->_req->post->reset_subject, $this->_req->post->custom_subject) && $this->_req->post->custom_subject != '') |
||
357 | { |
||
358 | $custom_subject = strtr(Util::htmltrim(Util::htmlspecialchars($this->_req->post->custom_subject)), array("\r" => '', "\n" => '', "\t" => '')); |
||
359 | |||
360 | // Keep checking the length. |
||
361 | if (Util::strlen($custom_subject) > 100) |
||
362 | { |
||
363 | $custom_subject = Util::substr($custom_subject, 0, 100); |
||
364 | } |
||
365 | |||
366 | // If it's still valid move onwards and upwards. |
||
367 | if ($custom_subject !== '') |
||
368 | { |
||
369 | $this->_board_info['subject_new'] = $custom_subject; |
||
370 | $all_messages = isset($this->_req->post->enforce_subject); |
||
371 | if ($all_messages) |
||
372 | { |
||
373 | // Get a response prefix, but in the forum's default language. |
||
374 | $context['response_prefix'] = response_prefix(); |
||
375 | |||
376 | topicSubject($this->_topic_info, $custom_subject, $context['response_prefix'], $all_messages); |
||
377 | } |
||
378 | else |
||
379 | { |
||
380 | topicSubject($this->_topic_info, $custom_subject); |
||
381 | } |
||
382 | |||
383 | // Fix the subject cache. |
||
384 | require_once(SUBSDIR . '/Messages.subs.php'); |
||
385 | updateSubjectStats($this->_topic, $custom_subject); |
||
386 | } |
||
387 | } |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Posts a redirection topic in the original location of the moved topic |
||
392 | * |
||
393 | * What it does: |
||
394 | * |
||
395 | * - If leaving a moved "where did it go" topic, validates the needed inputs |
||
396 | * - Posts a new topic in the originating board of the topic to be moved. |
||
397 | */ |
||
398 | private function _post_redirect() |
||
399 | { |
||
400 | global $board, $language; |
||
401 | |||
402 | // @todo Does this make sense if the topic was unapproved before? I'd just about say so. |
||
403 | if (isset($this->_req->post->postRedirect)) |
||
404 | { |
||
405 | // Should be in the boardwide language. |
||
406 | if ($this->user->language !== $language) |
||
0 ignored issues
–
show
The property
language does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
407 | { |
||
408 | $mtxt = []; |
||
409 | $lang = new Loader($language, $mtxt, database()); |
||
410 | $lang->load('index'); |
||
411 | } |
||
412 | |||
413 | $reason = Util::htmlspecialchars($this->_req->post->reason, ENT_QUOTES); |
||
414 | preparsecode($reason); |
||
415 | |||
416 | // Add a URL onto the message. |
||
417 | $reason = strtr($reason, array( |
||
418 | $mtxt['movetopic_auto_board'] => '[url=' . getUrl('board', ['board' => $this->_toboard, 'start' => 0, 'name' => $this->_board_info['name']]) . ']' . $this->_board_info['name'] . '[/url]', |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
419 | $mtxt['movetopic_auto_topic'] => '[iurl=' . getUrl('topic', ['topic' => $this->_topic, 'start' => 0, 'subject' => $this->_board_info['subject_new'] ?? $this->_board_info['subject']]) . ']' . ($this->_board_info['subject_new'] ?? $this->_board_info['subject']) . '[/iurl]' |
||
420 | )); |
||
421 | |||
422 | // Auto remove this MOVED redirection topic in the future? |
||
423 | $redirect_expires = empty($this->_req->post->redirect_expires) ? 0 : (int) $this->_req->post->redirect_expires; |
||
424 | |||
425 | // Redirect to the MOVED topic from topic list? |
||
426 | $redirect_topic = isset($this->_req->post->redirect_topic) ? $this->_topic : 0; |
||
427 | |||
428 | // And remember the last expiry period too. |
||
429 | $_SESSION['move_to_topic']['redirect_topic'] = $redirect_topic; |
||
430 | $_SESSION['move_to_topic']['redirect_expires'] = $redirect_expires; |
||
431 | |||
432 | $msgOptions = array( |
||
433 | 'subject' => $mtxt['moved'] . ': ' . $this->_board_info['subject'], |
||
434 | 'body' => $reason, |
||
435 | 'icon' => 'moved', |
||
436 | 'smileys_enabled' => 1, |
||
437 | ); |
||
438 | |||
439 | $topicOptions = array( |
||
440 | 'board' => $board, |
||
441 | 'lock_mode' => 1, |
||
442 | 'mark_as_read' => true, |
||
443 | 'redirect_expires' => empty($redirect_expires) ? 0 : ($redirect_expires * 60) + time(), |
||
444 | 'redirect_topic' => $redirect_topic, |
||
445 | ); |
||
446 | |||
447 | $posterOptions = array( |
||
448 | 'id' => $this->user->id, |
||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
449 | 'update_post_count' => empty($this->_board_info['count_posts']), |
||
450 | ); |
||
451 | createPost($msgOptions, $topicOptions, $posterOptions); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * Accounts for board / user post counts when a topic is moved. |
||
457 | * |
||
458 | * What it does: |
||
459 | * |
||
460 | * - Checks if a topic is being moved to/from a board that does/does'nt count posts. |
||
461 | */ |
||
462 | private function _count_update() |
||
463 | { |
||
464 | global $board; |
||
465 | |||
466 | $board_from = boardInfo($board); |
||
467 | if ($board_from['count_posts'] != $this->_board_info['count_posts']) |
||
468 | { |
||
469 | require_once(SUBSDIR . '/Members.subs.php'); |
||
470 | $posters = postersCount($this->_topic); |
||
471 | |||
472 | foreach ($posters as $id_member => $posts) |
||
473 | { |
||
474 | // The board we're moving from counted posts, but not to. |
||
475 | if (empty($board_from['count_posts'])) |
||
476 | { |
||
477 | updateMemberData($id_member, array('posts' => 'posts - ' . $posts)); |
||
478 | } |
||
479 | // The reverse: from didn't, to did. |
||
480 | else |
||
481 | { |
||
482 | updateMemberData($id_member, array('posts' => 'posts + ' . $posts)); |
||
483 | } |
||
484 | } |
||
485 | } |
||
486 | } |
||
487 | } |
||
488 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.