Completed
Branch development (176841)
by Elk
06:59
created

Topic.subs.php ➔ moveTopics()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 300

Duplication

Lines 23
Ratio 7.67 %

Code Coverage

Tests 0
CRAP Score 992

Importance

Changes 0
Metric Value
cc 31
nc 31109
nop 3
dl 23
loc 300
rs 0
c 0
b 0
f 0
ccs 0
cts 134
cp 0
crap 992

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains functions for dealing with topics. Low-level functions,
5
 * i.e. database operations needed to perform.
6
 * These functions do NOT make permissions checks. (they assume those were
7
 * already made).
8
 *
9
 * @package   ElkArte Forum
10
 * @copyright ElkArte Forum contributors
11
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
12
 *
13
 * This file contains code covered by:
14
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
15
 *
16
 * @version 2.0 dev
17
 *
18
 */
19
20
use ElkArte\User;
21
22
/**
23
 * Removes the passed id_topic's checking for permissions.
24
 *
25
 * @param int[]|int $topics The topics to remove (can be an id or an array of ids).
26
 * @throws \ElkArte\Exceptions\Exception
27
 */
28
function removeTopicsPermissions($topics)
29
{
30
	global $board;
31
32
	// They can only delete their own topics. (we wouldn't be here if they couldn't do that..)
33
	$possible_remove = topicAttribute($topics, array('id_topic', 'id_board', 'id_member_started'));
34
35
	$removeCache = array();
36
	$removeCacheBoards = array();
37
	$test_owner = !empty($board) && !allowedTo('remove_any');
38
	foreach ($possible_remove as $row)
39
	{
40
		// Skip if we have to test the owner *and* the user is not the owner
41
		if ($test_owner && $row['id_member_started'] != User::$info->id)
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
42
			continue;
43
44
		$removeCache[] = $row['id_topic'];
45
		$removeCacheBoards[$row['id_topic']] = $row['id_board'];
46
	}
47
48
	// Maybe *none* were their own topics.
49
	if (!empty($removeCache))
50
		removeTopics($removeCache, true, false, true, $removeCacheBoards);
51
}
52
53
/**
54
 * Removes the passed id_topic's.
55
 *
56
 * - Permissions are NOT checked here because the function is used in a scheduled task
57
 *
58
 * @param int[]|int $topics The topics to remove (can be an id or an array of ids).
59
 * @param bool $decreasePostCount if true users' post count will be reduced
60
 * @param bool $ignoreRecycling if true topics are not moved to the recycle board (if it exists).
61
 * @param bool $log if true logs the action.
62
 * @param int[] $removeCacheBoards an array matching topics and boards.
63
 * @throws \ElkArte\Exceptions\Exception
64
 */
65 12
function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false, $log = false, $removeCacheBoards = array())
66
{
67
	global $modSettings;
68 12
69
	// Nothing to do?
70
	if (empty($topics))
71 12
		return;
72 12
73
	$db = database();
74
	$cache = \ElkArte\Cache\Cache::instance();
75 12
76 12
	// Only a single topic.
77
	if (!is_array($topics))
78 12
		$topics = array($topics);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topics. This often makes code more readable.
Loading history...
79
80
	if ($log)
81
	{
82
		// Gotta send the notifications *first*!
83
		foreach ($topics as $topic)
84
		{
85
			// Only log the topic ID if it's not in the recycle board.
86
			logAction('remove', array((empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $removeCacheBoards[$topic] ? 'topic' : 'old_topic_id') => $topic, 'board' => $removeCacheBoards[$topic]));
87
			sendNotifications($topic, 'remove');
88
		}
89
	}
90 12
91
	// Decrease the post counts for members.
92 12
	if ($decreasePostCount)
93
	{
94
		$requestMembers = $db->query('', '
95
			SELECT m.id_member, COUNT(*) AS posts
96
			FROM {db_prefix}messages AS m
97
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
98
			WHERE m.id_topic IN ({array_int:topics})
99
				AND m.icon != {string:recycled}
100
				AND b.count_posts = {int:do_count_posts}
101
				AND m.approved = {int:is_approved}
102 12
			GROUP BY m.id_member',
103 12
			array(
104 12
				'do_count_posts' => 0,
105 12
				'recycled' => 'recycled',
106
				'topics' => $topics,
107
				'is_approved' => 1,
108 12
			)
109
		);
110 12
		if ($db->num_rows($requestMembers) > 0)
111 12
		{
112 12
			require_once(SUBSDIR . '/Members.subs.php');
113
			while ($rowMembers = $db->fetch_assoc($requestMembers))
114 12
				updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $rowMembers['posts']));
115
		}
116
		$db->free_result($requestMembers);
117
	}
118 12
119
	// Recycle topics that aren't in the recycle board...
120
	if (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 && !$ignoreRecycling)
121
	{
122
		$possible_recycle = topicAttribute($topics, array('id_topic', 'id_board', 'unapproved_posts', 'approved'));
123
124
		if (!empty($possible_recycle))
125
		{
126
			detectServer()->setTimeLimit(300);
127
128
			// Get topics that will be recycled.
129
			$recycleTopics = array();
130
			foreach ($possible_recycle as $row)
131
			{
132
				// If it's already in the recycle board do nothing
133
				if ($row['id_board'] == $modSettings['recycle_board'])
134
					continue;
135
136
				$recycleTopics[] = $row['id_topic'];
137
138
				// Set the id_previous_board for this topic - and make it not sticky.
139
				setTopicAttribute($row['id_topic'], array(
140
					'id_previous_board' => $row['id_board'],
141
					'is_sticky' => 0,
142
				));
143
			}
144
145
			if (!empty($recycleTopics))
146
			{
147
				// Mark recycled topics as recycled.
148
				$db->query('', '
149
				UPDATE {db_prefix}messages
150
				SET icon = {string:recycled}
151
				WHERE id_topic IN ({array_int:recycle_topics})',
152
					array(
153
						'recycle_topics' => $recycleTopics,
154
						'recycled' => 'recycled',
155
					)
156
				);
157
158
				// Move the topics to the recycle board.
159
				require_once(SUBSDIR . '/Topic.subs.php');
160
				moveTopics($recycleTopics, $modSettings['recycle_board']);
161
162
				// Close reports that are being recycled.
163
				require_once(SUBSDIR . '/Moderation.subs.php');
164
165
				$db->query('', '
166
				UPDATE {db_prefix}log_reported
167
				SET closed = {int:is_closed}
168
				WHERE id_topic IN ({array_int:recycle_topics})',
169
					array(
170
						'recycle_topics' => $recycleTopics,
171
						'is_closed' => 1,
172
					)
173
				);
174
175
				updateSettings(array('last_mod_report_action' => time()));
176
				recountOpenReports();
177
178
				// Topics that were recycled don't need to be deleted, so subtract them.
179
				$topics = array_diff($topics, $recycleTopics);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topics. This often makes code more readable.
Loading history...
180
			}
181
		}
182
	}
183 12
184
	// Still topics left to delete?
185
	if (empty($topics))
186 12
		return;
187
188
	$adjustBoards = array();
189 12
190
	// Find out how many posts we are deleting.
191
	$request = $db->query('', '
192
		SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts,
193
			SUM(num_replies) AS num_replies
194
		FROM {db_prefix}topics
195
		WHERE id_topic IN ({array_int:topics})
196 12
		GROUP BY id_board, approved',
197
		array(
198
			'topics' => $topics,
199 12
		)
200
	);
201 12 View Code Duplication
	while ($row = $db->fetch_assoc($request))
202
	{
203 12
		if (!isset($adjustBoards[$row['id_board']]['num_posts']))
204
		{
205 12
			$cache->remove('board-' . $row['id_board']);
206 12
207 12
			$adjustBoards[$row['id_board']] = array(
208 12
				'num_posts' => 0,
209 12
				'num_topics' => 0,
210 12
				'unapproved_posts' => 0,
211
				'unapproved_topics' => 0,
212
				'id_board' => $row['id_board']
213
			);
214 12
		}
215 12
		// Posts = (num_replies + 1) for each approved topic.
216
		$adjustBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0);
217
		$adjustBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
218 12
219 12
		// Add the topics to the right type.
220
		if ($row['approved'])
221
			$adjustBoards[$row['id_board']]['num_topics'] += $row['num_topics'];
222
		else
223 12
			$adjustBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics'];
224
	}
225
	$db->free_result($request);
226 12
227 12
	// Decrease number of posts and topics for each board.
228
	detectServer()->setTimeLimit(300);
229 12 View Code Duplication
	foreach ($adjustBoards as $stats)
230
	{
231
		$db->query('', '
232
			UPDATE {db_prefix}boards
233
			SET
234
				num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
235
				num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
236
				unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
237
				unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
238 12
			WHERE id_board = {int:id_board}',
239 12
			array(
240 12
				'id_board' => $stats['id_board'],
241 12
				'num_posts' => $stats['num_posts'],
242 12
				'num_topics' => $stats['num_topics'],
243
				'unapproved_posts' => $stats['unapproved_posts'],
244
				'unapproved_topics' => $stats['unapproved_topics'],
245
			)
246
		);
247
	}
248 12
249 12
	// Remove polls for these topics.
250 12
	$possible_polls = topicAttribute($topics, 'id_poll');
251
	$polls = array();
252 12
	foreach ($possible_polls as $row)
253 9
	{
254
		if (!empty($row['id_poll']))
255
			$polls[] = $row['id_poll'];
256 12
	}
257
258 6
	if (!empty($polls))
259
	{
260
		$db->query('', '
261
			DELETE FROM {db_prefix}polls
262 6
			WHERE id_poll IN ({array_int:polls})',
263
			array(
264
				'polls' => $polls,
265 6
			)
266
		);
267
		$db->query('', '
268
			DELETE FROM {db_prefix}poll_choices
269 6
			WHERE id_poll IN ({array_int:polls})',
270
			array(
271
				'polls' => $polls,
272 6
			)
273
		);
274
		$db->query('', '
275
			DELETE FROM {db_prefix}log_polls
276 6
			WHERE id_poll IN ({array_int:polls})',
277
			array(
278
				'polls' => $polls,
279
			)
280
		);
281
	}
282 12
283
	// Get rid of the attachment(s).
284 12
	require_once(SUBSDIR . '/ManageAttachments.subs.php');
285 12
	$attachmentQuery = array(
286
		'attachment_type' => 0,
287 12
		'id_topic' => $topics,
288
	);
289
	removeAttachments($attachmentQuery, 'messages');
290 12
291
	// Delete search index entries.
292
	if (!empty($modSettings['search_custom_index_config']))
293
	{
294
		$customIndexSettings = \ElkArte\Util::unserialize($modSettings['search_custom_index_config']);
295
296
		$request = $db->query('', '
297
			SELECT id_msg, body
298
			FROM {db_prefix}messages
299
			WHERE id_topic IN ({array_int:topics})',
300
			array(
301
				'topics' => $topics,
302
			)
303
		);
304
		$words = array();
305
		$messages = array();
306
		while ($row = $db->fetch_assoc($request))
307
		{
308
			detectServer()->setTimeLimit(300);
309
310
			$words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true));
311
			$messages[] = $row['id_msg'];
312
		}
313
		$db->free_result($request);
314
		$words = array_unique($words);
315
316
		if (!empty($words) && !empty($messages))
317
			$db->query('', '
318
				DELETE FROM {db_prefix}log_search_words
319
				WHERE id_word IN ({array_int:word_list})
320
					AND id_msg IN ({array_int:message_list})',
321
				array(
322
					'word_list' => $words,
323
					'message_list' => $messages,
324
				)
325
			);
326
	}
327 12
328 12
	// Reuse the message array if available
329
	if (empty($messages))
330
		$messages = messagesInTopics($topics);
331 12
332
	// If there are messages left in this topic
333
	if (!empty($messages))
334 12
	{
335 12
		// Decrease / Update the member like counts
336
		require_once(SUBSDIR . '/Likes.subs.php');
337
		decreaseLikeCounts($messages);
338 12
339
		// Remove all likes now that the topic is gone
340
		$db->query('', '
341
			DELETE FROM {db_prefix}message_likes
342 12
			WHERE id_msg IN ({array_int:messages})',
343
			array(
344
				'messages' => $messages,
345
			)
346
		);
347 12
348
		// Remove all mentions now that the topic is gone
349
		$db->query('', '
350
			DELETE FROM {db_prefix}log_mentions
351
			WHERE id_target IN ({array_int:messages})
352 12
				AND mention_type IN ({array_string:mension_types})',
353
			array(
354
				'messages' => $messages,
355
				'mension_types' => array('mentionmem', 'likemsg', 'rlikemsg'),
356
			)
357
		);
358
	}
359 12
360
	// Delete messages in each topic.
361
	$db->query('', '
362
		DELETE FROM {db_prefix}messages
363 12
		WHERE id_topic IN ({array_int:topics})',
364
		array(
365
			'topics' => $topics,
366
		)
367
	);
368
369 12
	// Remove linked calendar events.
370
	// @todo if unlinked events are enabled, wouldn't this be expected to keep them?
371
	$db->query('', '
372
		DELETE FROM {db_prefix}calendar
373 12
		WHERE id_topic IN ({array_int:topics})',
374
		array(
375
			'topics' => $topics,
376
		)
377
	);
378 12
379
	// Delete log_topics data
380
	$db->query('', '
381
		DELETE FROM {db_prefix}log_topics
382 12
		WHERE id_topic IN ({array_int:topics})',
383
		array(
384
			'topics' => $topics,
385
		)
386
	);
387 12
388
	// Delete notifications
389
	$db->query('', '
390
		DELETE FROM {db_prefix}log_notify
391 12
		WHERE id_topic IN ({array_int:topics})',
392
		array(
393
			'topics' => $topics,
394
		)
395
	);
396 12
397
	// Delete the topics themselves
398
	$db->query('', '
399
		DELETE FROM {db_prefix}topics
400 12
		WHERE id_topic IN ({array_int:topics})',
401
		array(
402
			'topics' => $topics,
403
		)
404
	);
405 12
406
	// Remove data from the subjects for search cache
407
	$db->query('', '
408
		DELETE FROM {db_prefix}log_search_subjects
409 12
		WHERE id_topic IN ({array_int:topics})',
410
		array(
411
			'topics' => $topics,
412 12
		)
413 12
	);
414
	require_once(SUBSDIR . '/FollowUps.subs.php');
415 12
	removeFollowUpsByTopic($topics);
416 12
417
	foreach ($topics as $topic_id)
418
		$cache->remove('topic_board-' . $topic_id);
419 12
420
	// Maybe there's an addon that wants to delete topic related data of its own
421
	call_integration_hook('integrate_remove_topics', array($topics));
422 12
423 12
	// Update the totals...
424 12
	require_once(SUBSDIR . '/Messages.subs.php');
425 12
	updateMessageStats();
426 12
	updateTopicStats();
427
	updateSettings(array(
428
		'calendar_updated' => time(),
429 12
	));
430 12
431 12
	require_once(SUBSDIR . '/Post.subs.php');
432 12
	$updates = array();
433 12
	foreach ($adjustBoards as $stats)
434 12
		$updates[] = $stats['id_board'];
435
	updateLastMessages($updates);
436
}
437
438
/**
439
 * Moves lots of topics to a specific board and checks if the user can move them
440
 *
441
 * @param array $moveCache [0] => int[] is the topic, [1] => int[]  is the board to move to.
442
 * @throws \ElkArte\Exceptions\Exception
443
 */
444
function moveTopicsPermissions($moveCache)
445
{
446
	global $board;
447
448
	$db = database();
449
450
	// I know - I just KNOW you're trying to beat the system.  Too bad for you... we CHECK :P.
451
	$request = $db->query('', '
452
		SELECT t.id_topic, t.id_board, b.count_posts
453
		FROM {db_prefix}topics AS t
454
			LEFT JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
455
		WHERE t.id_topic IN ({array_int:move_topic_ids})' . (!empty($board) && !allowedTo('move_any') ? '
456
			AND t.id_member_started = {int:current_member}' : '') . '
457
		LIMIT ' . count($moveCache[0]),
458
		array(
459
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
460
			'move_topic_ids' => $moveCache[0],
461
		)
462
	);
463
	$moveTos = array();
464
	$moveCache2 = array();
465
	$countPosts = array();
466
	while ($row = $db->fetch_assoc($request))
467
	{
468
		$to = $moveCache[1][$row['id_topic']];
469
470
		if (empty($to))
471
			continue;
472
473
		// Does this topic's board count the posts or not?
474
		$countPosts[$row['id_topic']] = empty($row['count_posts']);
475
476
		if (!isset($moveTos[$to]))
477
			$moveTos[$to] = array();
478
479
		$moveTos[$to][] = $row['id_topic'];
480
481
		// For reporting...
482
		$moveCache2[] = array($row['id_topic'], $row['id_board'], $to);
483
	}
484
	$db->free_result($request);
485
486
	// Do the actual moves...
487
	foreach ($moveTos as $to => $topics)
488
		moveTopics($topics, $to, true);
489
490
	// Does the post counts need to be updated?
491
	if (!empty($moveTos))
492
	{
493
		require_once(SUBSDIR . '/Boards.subs.php');
494
		$topicRecounts = array();
495
		$boards_info = fetchBoardsInfo(array('boards' => array_keys($moveTos)), array('selects' => 'posts'));
496
497
		foreach ($boards_info as $row)
498
		{
499
			$cp = empty($row['count_posts']);
500
501
			// Go through all the topics that are being moved to this board.
502
			foreach ($moveTos[$row['id_board']] as $topic)
503
			{
504
				// If both boards have the same value for post counting then no adjustment needs to be made.
505
				if ($countPosts[$topic] != $cp)
506
				{
507
					// If the board being moved to does count the posts then the other one doesn't so add to their post count.
508
					$topicRecounts[$topic] = $cp ? 1 : -1;
509
				}
510
			}
511
		}
512
513
		if (!empty($topicRecounts))
514
		{
515
			require_once(SUBSDIR . '/Members.subs.php');
516
517
			// Get all the members who have posted in the moved topics.
518
			$posters = topicsPosters(array_keys($topicRecounts));
519
			foreach ($posters as $id_member => $topics)
520
			{
521
				$post_adj = 0;
522
				foreach ($topics as $id_topic)
523
					$post_adj += $topicRecounts[$id_topic];
524
525
				// And now update that member's post counts
526
				if (!empty($post_adj))
527
				{
528
					updateMemberData($id_member, array('posts' => 'posts + ' . $post_adj));
529
				}
530
			}
531
		}
532
	}
533
}
534
535
/**
536
 * Moves one or more topics to a specific board.
537
 *
538
 * What it does:
539
 *
540
 * - Determines the source boards for the supplied topics
541
 * - Handles the moving of mark_read data
542
 * - Updates the posts count of the affected boards
543
 * - This function doesn't check permissions.
544
 *
545
 * @param int[]|int $topics
546
 * @param int $toBoard
547
 * @param bool $log if true logs the action.
548
 * @throws \ElkArte\Exceptions\Exception
549
 */
550
function moveTopics($topics, $toBoard, $log = false)
551
{
552
	global $modSettings;
553
554
	// No topics or no board?
555
	if (empty($topics) || empty($toBoard))
556
		return;
557
558
	$db = database();
559
560
	// Only a single topic.
561
	if (!is_array($topics))
562
		$topics = array($topics);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topics. This often makes code more readable.
Loading history...
563
564
	$fromBoards = array();
565
	$fromCacheBoards = array();
566
567
	// Are we moving to the recycle board?
568
	$isRecycleDest = !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $toBoard;
569
570
	// Determine the source boards...
571
	$request = $db->query('', '
572
		SELECT id_topic, id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts,
573
			SUM(num_replies) AS num_replies
574
		FROM {db_prefix}topics
575
		WHERE id_topic IN ({array_int:topics})
576
		GROUP BY id_board, approved',
577
		array(
578
			'topics' => $topics,
579
		)
580
	);
581
	// Num of rows = 0 -> no topics found. Num of rows > 1 -> topics are on multiple boards.
582
	if ($db->num_rows($request) == 0)
583
		return;
584
585 View Code Duplication
	while ($row = $db->fetch_assoc($request))
586
	{
587
		$fromCacheBoards[$row['id_topic']] = $row['id_board'];
588
		if (!isset($fromBoards[$row['id_board']]['num_posts']))
589
		{
590
			$fromBoards[$row['id_board']] = array(
591
				'num_posts' => 0,
592
				'num_topics' => 0,
593
				'unapproved_posts' => 0,
594
				'unapproved_topics' => 0,
595
				'id_board' => $row['id_board']
596
			);
597
		}
598
		// Posts = (num_replies + 1) for each approved topic.
599
		$fromBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0);
600
		$fromBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
601
602
		// Add the topics to the right type.
603
		if ($row['approved'])
604
			$fromBoards[$row['id_board']]['num_topics'] += $row['num_topics'];
605
		else
606
			$fromBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics'];
607
	}
608
	$db->free_result($request);
609
610
	// Move over the mark_read data. (because it may be read and now not by some!)
611
	$SaveAServer = max(0, $modSettings['maxMsgID'] - 50000);
612
	$request = $db->query('', '
613
		SELECT lmr.id_member, lmr.id_msg, t.id_topic, COALESCE(lt.unwatched, 0) as unwatched
614
		FROM {db_prefix}topics AS t
615
			INNER JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board
616
				AND lmr.id_msg > t.id_first_msg AND lmr.id_msg > {int:protect_lmr_msg})
617
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = lmr.id_member)
618
		WHERE t.id_topic IN ({array_int:topics})
619
			AND lmr.id_msg > COALESCE(lt.id_msg, 0)',
620
		array(
621
			'protect_lmr_msg' => $SaveAServer,
622
			'topics' => $topics,
623
		)
624
	);
625
	$log_topics = array();
626
	while ($row = $db->fetch_assoc($request))
627
	{
628
		$log_topics[] = array($row['id_member'], $row['id_topic'], $row['id_msg'], $row['unwatched']);
629
630
		// Prevent queries from getting too big. Taking some steam off.
631
		if (count($log_topics) > 500)
632
		{
633
			markTopicsRead($log_topics, true);
634
			$log_topics = array();
635
		}
636
	}
637
	$db->free_result($request);
638
639
	// Now that we have all the topics that *should* be marked read, and by which members...
640
	if (!empty($log_topics))
641
	{
642
		// Insert that information into the database!
643
		markTopicsRead($log_topics, true);
644
	}
645
646
	// Update the number of posts on each board.
647
	$totalTopics = 0;
648
	$totalPosts = 0;
649
	$totalUnapprovedTopics = 0;
650
	$totalUnapprovedPosts = 0;
651
	foreach ($fromBoards as $stats)
652
	{
653
		$db->query('', '
654
			UPDATE {db_prefix}boards
655
			SET
656
				num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
657
				num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
658
				unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
659
				unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
660
			WHERE id_board = {int:id_board}',
661
			array(
662
				'id_board' => $stats['id_board'],
663
				'num_posts' => $stats['num_posts'],
664
				'num_topics' => $stats['num_topics'],
665
				'unapproved_posts' => $stats['unapproved_posts'],
666
				'unapproved_topics' => $stats['unapproved_topics'],
667
			)
668
		);
669
		$totalTopics += $stats['num_topics'];
670
		$totalPosts += $stats['num_posts'];
671
		$totalUnapprovedTopics += $stats['unapproved_topics'];
672
		$totalUnapprovedPosts += $stats['unapproved_posts'];
673
	}
674
	$db->query('', '
675
		UPDATE {db_prefix}boards
676
		SET
677
			num_topics = num_topics + {int:total_topics},
678
			num_posts = num_posts + {int:total_posts},' . ($isRecycleDest ? '
679
			unapproved_posts = {int:no_unapproved}, unapproved_topics = {int:no_unapproved}' : '
680
			unapproved_posts = unapproved_posts + {int:total_unapproved_posts},
681
			unapproved_topics = unapproved_topics + {int:total_unapproved_topics}') . '
682
		WHERE id_board = {int:id_board}',
683
		array(
684
			'id_board' => $toBoard,
685
			'total_topics' => $totalTopics,
686
			'total_posts' => $totalPosts,
687
			'total_unapproved_topics' => $totalUnapprovedTopics,
688
			'total_unapproved_posts' => $totalUnapprovedPosts,
689
			'no_unapproved' => 0,
690
		)
691
	);
692
693
	if ($isRecycleDest)
694
	{
695
		$attributes = array(
696
			'id_board' => $toBoard,
697
			'approved' => 1,
698
			'unapproved_posts' => 0,
699
		);
700
	}
701
	else
702
	{
703
		$attributes = array('id_board' => $toBoard);
704
	}
705
706
	// Move the topic.  Done.  :P
707
	setTopicAttribute($topics, $attributes);
708
709
	// If this was going to the recycle bin, check what messages are being recycled, and remove them from the queue.
710
	if ($isRecycleDest && ($totalUnapprovedTopics || $totalUnapprovedPosts))
711
	{
712
		$request = $db->query('', '
713
			SELECT id_msg
714
			FROM {db_prefix}messages
715
			WHERE id_topic IN ({array_int:topics})
716
				and approved = {int:not_approved}',
717
			array(
718
				'topics' => $topics,
719
				'not_approved' => 0,
720
			)
721
		);
722
		$approval_msgs = array();
723
		while ($row = $db->fetch_assoc($request))
724
			$approval_msgs[] = $row['id_msg'];
725
		$db->free_result($request);
726
727
		// Empty the approval queue for these, as we're going to approve them next.
728
		if (!empty($approval_msgs))
729
			$db->query('', '
730
				DELETE FROM {db_prefix}approval_queue
731
				WHERE id_msg IN ({array_int:message_list})
732
					AND id_attach = {int:id_attach}',
733
				array(
734
					'message_list' => $approval_msgs,
735
					'id_attach' => 0,
736
				)
737
			);
738
739
		// Get all the current max and mins.
740
		$topicAttribute = topicAttribute($topics, array('id_topic', 'id_first_msg', 'id_last_msg'));
741
		$topicMaxMin = array();
742
		foreach ($topicAttribute as $row)
743
		{
744
			$topicMaxMin[$row['id_topic']] = array(
745
				'min' => $row['id_first_msg'],
746
				'max' => $row['id_last_msg'],
747
			);
748
		}
749
750
		// Check the MAX and MIN are correct.
751
		$request = $db->query('', '
752
			SELECT id_topic, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg
753
			FROM {db_prefix}messages
754
			WHERE id_topic IN ({array_int:topics})
755
			GROUP BY id_topic',
756
			array(
757
				'topics' => $topics,
758
			)
759
		);
760
		while ($row = $db->fetch_assoc($request))
761
		{
762
			// If not, update.
763
			if ($row['first_msg'] != $topicMaxMin[$row['id_topic']]['min'] || $row['last_msg'] != $topicMaxMin[$row['id_topic']]['max'])
764
				setTopicAttribute($row['id_topic'], array(
765
					'id_first_msg' => $row['first_msg'],
766
					'id_last_msg' => $row['last_msg'],
767
				));
768
		}
769
		$db->free_result($request);
770
	}
771
772
	$db->query('', '
773
		UPDATE {db_prefix}messages
774
		SET id_board = {int:id_board}' . ($isRecycleDest ? ',approved = {int:is_approved}' : '') . '
775
		WHERE id_topic IN ({array_int:topics})',
776
		array(
777
			'id_board' => $toBoard,
778
			'topics' => $topics,
779
			'is_approved' => 1,
780
		)
781
	);
782
	$db->query('', '
783
		UPDATE {db_prefix}log_reported
784
		SET id_board = {int:id_board}
785
		WHERE id_topic IN ({array_int:topics})',
786
		array(
787
			'id_board' => $toBoard,
788
			'topics' => $topics,
789
		)
790
	);
791
	$db->query('', '
792
		UPDATE {db_prefix}calendar
793
		SET id_board = {int:id_board}
794
		WHERE id_topic IN ({array_int:topics})',
795
		array(
796
			'id_board' => $toBoard,
797
			'topics' => $topics,
798
		)
799
	);
800
801
	// Mark target board as seen, if it was already marked as seen before.
802
	$request = $db->query('', '
803
		SELECT (COALESCE(lb.id_msg, 0) >= b.id_msg_updated) AS isSeen
804
		FROM {db_prefix}boards AS b
805
			LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
806
		WHERE b.id_board = {int:id_board}',
807
		array(
808
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
809
			'id_board' => $toBoard,
810
		)
811
	);
812
	list ($isSeen) = $db->fetch_row($request);
813
	$db->free_result($request);
814
815
	if (!empty($isSeen) && User::$info->is_guest === false)
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
816
	{
817
		require_once(SUBSDIR . '/Boards.subs.php');
818
		markBoardsRead($toBoard);
819
	}
820
821
	$cache = \ElkArte\Cache\Cache::instance();
822
	// Update the cache?
823
	foreach ($topics as $topic_id)
824
		$cache->remove('topic_board-' . $topic_id);
825
826
	require_once(SUBSDIR . '/Post.subs.php');
827
828
	$updates = array_keys($fromBoards);
829
	$updates[] = $toBoard;
830
831
	updateLastMessages(array_unique($updates));
832
833
	// Update 'em pesky stats.
834
	updateTopicStats();
835
	require_once(SUBSDIR . '/Messages.subs.php');
836
	updateMessageStats();
837
	updateSettings(array(
838
		'calendar_updated' => time(),
839
	));
840
841
	if ($log)
842
	{
843
		foreach ($topics as $topic)
844
		{
845
			logAction('move', array('topic' => $topic, 'board_from' => $fromCacheBoards[$topic], 'board_to' => $toBoard));
846
			sendNotifications($topic, 'move');
847
		}
848
	}
849
}
850
851
/**
852
 * Called after a topic is moved to update $board_link and $topic_link to point
853
 * to new location
854
 *
855
 * @param int $move_from The board the topic belongs to
856
 * @param int $id_board The "current" board
857
 * @param int $id_topic The topic id
858
 *
859
 * @return bool
860
 * @throws \ElkArte\Exceptions\Exception topic_already_moved
861
 */
862
function moveTopicConcurrence($move_from, $id_board, $id_topic)
863
{
864
	$db = database();
865
866
	if (empty($move_from) || empty($id_board) || empty($id_topic))
867
	{
868
		return true;
869
	}
870
871
	if ($move_from == $id_board)
872
	{
873
		return true;
874
	}
875
	else
876
	{
877
		$request = $db->query('', '
878
			SELECT m.subject, b.name
879
			FROM {db_prefix}topics AS t
880
				LEFT JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
881
				LEFT JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
882
			WHERE t.id_topic = {int:topic_id}
883
			LIMIT 1',
884
			array(
885
				'topic_id' => $id_topic,
886
			)
887
		);
888
		list ($topic_subject, $board_name) = $db->fetch_row($request);
889
		$db->free_result($request);
890
891
		$board_link = '<a href="' . getUrl('board', ['board' => $id_board, 'start' => '0', 'name' => $board_name]) . '">' . $board_name . '</a>';
892
		$topic_link = '<a href="' . getUrl('topic', ['topic' => $id_topic, 'start' => '0', 'subject' => $topic_subject]) . '">' . $topic_subject . '</a>';
893
		throw new \ElkArte\Exceptions\Exception('topic_already_moved', false, array($topic_link, $board_link));
894
	}
895
}
896
897
/**
898
 * Determine if the topic has already been deleted by another user.
899
 *
900
 * What it does:
901
 *  - If the topic has been removed and resides in the recycle bin, present confirm dialog
902
 *  - If recycling is not enabled, or user confirms or topic is not in recycle simply returns
903
 */
904
function removeDeleteConcurrence()
905
{
906
	global $modSettings, $board, $context;
907
908
	$recycled_enabled = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']);
909
910
	if ($recycled_enabled && !empty($board))
911
	{
912
		// Trying to removed from the recycle bin
913
		if (!isset($_GET['confirm_delete']) && $modSettings['recycle_board'] == $board)
914
		{
915
			if (isset($_REQUEST['msg']))
916
			{
917
				$confirm_url = getUrl('action', ['action' => 'deletemsg', 'confirm_delete', 'topic' => $context['current_topic'] . '.0', 'msg' => $_REQUEST['msg'], '{session_data}']);
918
			}
919
			else
920
			{
921
				$confirm_url = getUrl('action', ['action' => 'removetopic2', 'confirm_delete', 'topic' => $context['current_topic'] . '.0', '{session_data}']);
922
			}
923
924
			// Give them a prompt before we remove the message
925
			throw new \ElkArte\Exceptions\Exception('post_already_deleted', false, array($confirm_url));
926
		}
927
	}
928
}
929
930
/**
931
 * Increase the number of views of this topic.
932
 *
933
 * @param int $id_topic the topic being viewed or whatnot.
934
 */
935
function increaseViewCounter($id_topic)
936
{
937
	$db = database();
938
939
	$db->query('', '
940
		UPDATE {db_prefix}topics
941
		SET num_views = num_views + 1
942
		WHERE id_topic = {int:current_topic}',
943
		array(
944
			'current_topic' => $id_topic,
945
		)
946
	);
947
}
948
949
/**
950
 * Mark topic(s) as read by the given member, at the specified message.
951
 *
952
 * @param mixed[] $mark_topics array($id_member, $id_topic, $id_msg)
953
 * @param bool $was_set = false - whether the topic has been previously read by the user
954
 */
955 2
function markTopicsRead($mark_topics, $was_set = false)
956
{
957 2
	$db = database();
958
959
	if (!is_array($mark_topics))
960 2
		return;
961 2
962
	$db->insert($was_set ? 'replace' : 'ignore',
963 2
		'{db_prefix}log_topics',
964
		array(
965 1
			'id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int',
966 2
		),
967
		$mark_topics,
968 2
		array('id_member', 'id_topic')
969
	);
970
}
971
972
/**
973
 * Update user notifications for a topic... or the board it's in.
974
 * @todo look at board notification...
975
 *
976
 * @param int $id_topic
977
 * @param int $id_board
978
 */
979
function updateReadNotificationsFor($id_topic, $id_board)
980
{
981
	global $context;
982
983
	$db = database();
984
985
	// Check for notifications on this topic OR board.
986
	$request = $db->query('', '
987
		SELECT sent, id_topic
988
		FROM {db_prefix}log_notify
989
		WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
990
			AND id_member = {int:current_member}
991
		LIMIT 2',
992
		array(
993
			'current_board' => $id_board,
994
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
995
			'current_topic' => $id_topic,
996
		)
997
	);
998
999
	while ($row = $db->fetch_assoc($request))
1000
	{
1001
		// Find if this topic is marked for notification...
1002
		if (!empty($row['id_topic']))
1003
			$context['is_marked_notify'] = true;
1004
1005
		// Only do this once, but mark the notifications as "not sent yet" for next time.
1006
		if (!empty($row['sent']))
1007
		{
1008
			$db->query('', '
1009
				UPDATE {db_prefix}log_notify
1010
				SET sent = {int:is_not_sent}
1011
				WHERE (id_topic = {int:current_topic} OR id_board = {int:current_board})
1012
					AND id_member = {int:current_member}',
1013
				array(
1014
					'current_board' => $id_board,
1015
					'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1016
					'current_topic' => $id_topic,
1017
					'is_not_sent' => 0,
1018
				)
1019
			);
1020
			break;
1021
		}
1022
	}
1023
	$db->free_result($request);
1024
}
1025
1026
/**
1027
 * How many topics are still unread since (last visit)
1028
 *
1029
 * @param int $id_board
1030
 * @param int $id_msg_last_visit
1031
 * @return int
1032
 */
1033 View Code Duplication
function getUnreadCountSince($id_board, $id_msg_last_visit)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1034
{
1035
	$db = database();
1036
1037
	$request = $db->query('', '
1038
		SELECT COUNT(*)
1039
		FROM {db_prefix}topics AS t
1040
			LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = {int:current_board} AND lb.id_member = {int:current_member})
1041
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
1042
		WHERE t.id_board = {int:current_board}
1043
			AND t.id_last_msg > COALESCE(lb.id_msg, 0)
1044
			AND t.id_last_msg > COALESCE(lt.id_msg, 0)' .
1045
				(empty($id_msg_last_visit) ? '' : '
1046
			AND t.id_last_msg > {int:id_msg_last_visit}'),
1047
		array(
1048
			'current_board' => $id_board,
1049
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1050
			'id_msg_last_visit' => (int) $id_msg_last_visit,
1051
		)
1052
	);
1053
	list ($unread) = $db->fetch_row($request);
1054
	$db->free_result($request);
1055
1056
	return $unread;
1057
}
1058
1059
/**
1060
 * Returns whether this member has notification turned on for the specified topic.
1061
 *
1062
 * @param int $id_member
1063
 * @param int $id_topic
1064
 * @return bool
1065
 */
1066
function hasTopicNotification($id_member, $id_topic)
1067
{
1068
	$db = database();
1069
1070
	// Find out if they have notification set for this topic already.
1071
	$request = $db->query('', '
1072
		SELECT id_member
1073
		FROM {db_prefix}log_notify
1074
		WHERE id_member = {int:current_member}
1075
			AND id_topic = {int:current_topic}
1076
		LIMIT 1',
1077
		array(
1078
			'current_member' => $id_member,
1079
			'current_topic' => $id_topic,
1080
		)
1081
	);
1082
	$hasNotification = $db->num_rows($request) != 0;
1083
	$db->free_result($request);
1084
1085
	return $hasNotification;
1086
}
1087
1088
/**
1089
 * Set topic notification on or off for the given member.
1090
 *
1091
 * @param int $id_member
1092
 * @param int $id_topic
1093
 * @param bool $on
1094
 */
1095 View Code Duplication
function setTopicNotification($id_member, $id_topic, $on = false)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1096
{
1097
	$db = database();
1098
1099
	if ($on)
1100
	{
1101
		// Attempt to turn notifications on.
1102
		$db->insert('ignore',
1103
			'{db_prefix}log_notify',
1104
			array('id_member' => 'int', 'id_topic' => 'int'),
1105
			array($id_member, $id_topic),
1106
			array('id_member', 'id_topic')
1107
		);
1108
	}
1109
	else
1110
	{
1111
		// Just turn notifications off.
1112
		$db->query('', '
1113
			DELETE FROM {db_prefix}log_notify
1114
			WHERE id_member = {int:current_member}
1115
				AND id_topic = {int:current_topic}',
1116
			array(
1117
				'current_member' => $id_member,
1118
				'current_topic' => $id_topic,
1119
			)
1120
		);
1121
	}
1122
}
1123
1124
/**
1125
 * Get the previous topic from where we are.
1126
 *
1127
 * @param int $id_topic origin topic id
1128
 * @param int $id_board board id
1129
 * @param int $id_member = 0 member id
1130
 * @param bool $includeUnapproved = false whether to include unapproved topics
1131
 * @param bool $includeStickies = true whether to include sticky topics
1132
 * @return int topic number
1133
 */
1134
function previousTopic($id_topic, $id_board, $id_member = 0, $includeUnapproved = false, $includeStickies = true)
1135
{
1136
	return topicPointer($id_topic, $id_board, false, $id_member, $includeUnapproved, $includeStickies);
1137
}
1138
1139
/**
1140
 * Get the next topic from where we are.
1141
 *
1142
 * @param int $id_topic origin topic id
1143
 * @param int $id_board board id
1144
 * @param int $id_member = 0 member id
1145
 * @param bool $includeUnapproved = false whether to include unapproved topics
1146
 * @param bool $includeStickies = true whether to include sticky topics
1147
 * @return int topic number
1148
 */
1149
function nextTopic($id_topic, $id_board, $id_member = 0, $includeUnapproved = false, $includeStickies = true)
1150
{
1151
	return topicPointer($id_topic, $id_board, true, $id_member, $includeUnapproved, $includeStickies);
1152
}
1153
1154
/**
1155
 * Advance topic pointer.
1156
 * (in either direction)
1157
 * This function is used by previousTopic() and nextTopic()
1158
 * The boolean parameter $next determines direction.
1159
 *
1160
 * @param int $id_topic origin topic id
1161
 * @param int $id_board board id
1162
 * @param bool $next = true whether to increase or decrease the pointer
1163
 * @param int $id_member = 0 member id
1164
 * @param bool $includeUnapproved = false whether to include unapproved topics
1165
 * @param bool $includeStickies = true whether to include sticky topics
1166
 * @return int the topic number
1167
 */
1168
function topicPointer($id_topic, $id_board, $next = true, $id_member = 0, $includeUnapproved = false, $includeStickies = true)
1169
{
1170
	$db = database();
1171
1172
	$request = $db->query('', '
1173
		SELECT t2.id_topic
1174
		FROM {db_prefix}topics AS t
1175
		INNER JOIN {db_prefix}topics AS t2 ON (' .
1176
			(empty($includeStickies) ? '
1177
				t2.id_last_msg {raw:strictly} t.id_last_msg' : '
1178
				(t2.id_last_msg {raw:strictly} t.id_last_msg AND t2.is_sticky {raw:strictly_equal} t.is_sticky) OR t2.is_sticky {raw:strictly} t.is_sticky')
1179
			. ')
1180
		WHERE t.id_topic = {int:current_topic}
1181
			AND t2.id_board = {int:current_board}' .
1182
			($includeUnapproved ? '' : '
1183
				AND (t2.approved = {int:is_approved} OR (t2.id_member_started != {int:id_member_started} AND t2.id_member_started = {int:current_member}))'
1184
				) . '
1185
		ORDER BY' . (
1186
			$includeStickies ? '
1187
				t2.is_sticky {raw:sorting},' :
1188
				'') .
1189
			' t2.id_last_msg {raw:sorting}
1190
		LIMIT 1',
1191
		array(
1192
			'strictly' => $next ? '<' : '>',
1193
			'strictly_equal' => $next ? '<=' : '>=',
1194
			'sorting' => $next ? 'DESC' : '',
1195
			'current_board' => $id_board,
1196
			'current_member' => $id_member,
1197
			'current_topic' => $id_topic,
1198
			'is_approved' => 1,
1199
			'id_member_started' => 0,
1200
		)
1201
	);
1202
1203
	// Was there any?
1204
	if ($db->num_rows($request) == 0)
1205
	{
1206
		$db->free_result($request);
1207
1208
		// Roll over - if we're going prev, get the last - otherwise the first.
1209
		$request = $db->query('', '
1210
			SELECT id_topic
1211
			FROM {db_prefix}topics
1212
			WHERE id_board = {int:current_board}' .
1213
			($includeUnapproved ? '' : '
1214
				AND (approved = {int:is_approved} OR (id_member_started != {int:id_member_started} AND id_member_started = {int:current_member}))') . '
1215
			ORDER BY' . (
1216
				$includeStickies ? ' is_sticky {raw:sorting},' : '') .
1217
				' id_last_msg {raw:sorting}
1218
			LIMIT 1',
1219
			array(
1220
				'sorting' => $next ? 'DESC' : '',
1221
				'current_board' => $id_board,
1222
				'current_member' => $id_member,
1223
				'is_approved' => 1,
1224
				'id_member_started' => 0,
1225
			)
1226
		);
1227
	}
1228
	// Now you can be sure $topic is the id_topic to view.
1229
	list ($topic) = $db->fetch_row($request);
1230
	$db->free_result($request);
1231
1232
	return $topic;
1233
}
1234
1235
/**
1236
 * Set off/on unread reply subscription for a topic
1237
 *
1238
 * @param int $id_member
1239
 * @param int $topic
1240
 * @param bool $on = false
1241
 */
1242
function setTopicWatch($id_member, $topic, $on = false)
1243
{
1244
	$db = database();
1245
1246
	// find the current entry if it exists that is
1247
	$was_set = getLoggedTopics(User::$info->id, array($topic));
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1248
1249
	// Set topic unwatched on/off for this topic.
1250
	$db->insert(empty($was_set[$topic]) ? 'ignore' : 'replace',
1251
		'{db_prefix}log_topics',
1252
		array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
1253
		array($id_member, $topic, !empty($was_set[$topic]['id_msg']) ? $was_set[$topic]['id_msg'] : 0, $on ? 1 : 0),
1254
		array('id_member', 'id_topic')
1255
	);
1256
}
1257
1258
/**
1259
 * Get all the details for a given topic
1260
 * - returns the basic topic information when $full is false
1261
 * - returns topic details, subject, last message read, etc when full is true
1262
 * - uses any integration information (value selects, tables and parameters) if passed and full is true
1263
 *
1264
 * @param mixed[]|int $topic_parameters can also accept a int value for a topic
1265
 * @param string $full defines the values returned by the function:
1266
 *    - if empty returns only the data from {db_prefix}topics
1267
 *    - if 'message' returns also information about the message (subject, body, etc.)
1268
 *    - if 'starter' returns also information about the topic starter (id_member and poster_name)
1269
 *    - if 'all' returns additional infos about the read/unwatched status
1270
 * @param string[] $selects (optional from integration)
1271
 * @param string[] $tables (optional from integration)
1272
 * @return mixed[]|bool to topic attributes
1273
 */
1274
function getTopicInfo($topic_parameters, $full = '', $selects = array(), $tables = array())
1275
{
1276
	global $modSettings, $board;
1277
1278
	$db = database();
1279
1280
	// Nothing to do
1281
	if (empty($topic_parameters))
1282
		return false;
1283
1284
	// Build what we can with what we were given
1285
	if (!is_array($topic_parameters))
1286
		$topic_parameters = array(
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topic_parameters. This often makes code more readable.
Loading history...
1287
			'topic' => $topic_parameters,
1288
			'member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1289
			'board' => (int) $board,
1290
		);
1291
1292
	$messages_table = $full === 'message' || $full === 'all' || $full === 'starter';
1293
	$members_table = $full === 'starter';
1294
	$logs_table = $full === 'all';
1295
1296
	// Create the query, taking full and integration in to account
1297
	$request = $db->query('', '
1298
		SELECT
1299
			t.id_topic, t.is_sticky, t.id_board, t.id_first_msg, t.id_last_msg,
1300
			t.id_member_started, t.id_member_updated, t.id_poll,
1301
			t.num_replies, t.num_views, t.num_likes, t.locked, t.redirect_expires,
1302
			t.id_redirect_topic, t.unapproved_posts, t.approved' . ($messages_table ? ',
1303
			ms.subject, ms.body, ms.id_member, ms.poster_time, ms.approved as msg_approved' : '') . ($members_table ? ',
1304
			COALESCE(mem.real_name, ms.poster_name) AS poster_name' : '') . ($logs_table ? ',
1305
			' . (User::$info->is_guest ? 't.id_last_msg + 1' : 'COALESCE(lt.id_msg, lmr.id_msg, -1) + 1') . ' AS new_from
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1306
			' . (!empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board ? ', t.id_previous_board, t.id_previous_topic' : '') . '
1307
			' . (User::$info->is_guest === false ? ', COALESCE(lt.unwatched, 0) as unwatched' : '') : '') .
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1308
			(!empty($selects) ? ', ' . implode(', ', $selects) : '') . '
1309
		FROM {db_prefix}topics AS t' . ($messages_table ? '
1310
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)' : '') . ($members_table ? '
1311
			LEFT JOIN {db_prefix}members as mem ON (mem.id_member = ms.id_member)' : '') . ($logs_table && User::$info->is_guest === false ? '
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1312
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = {int:topic} AND lt.id_member = {int:member})
1313
			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = {int:board} AND lmr.id_member = {int:member})' : '') . (!empty($tables) ? '
1314
			' . implode("\n\t\t\t", $tables) : '') . '
1315
		WHERE t.id_topic = {int:topic}
1316
		LIMIT 1',
1317
			$topic_parameters
1318
	);
1319
	$topic_info = array();
1320
	if ($request !== false)
1321
		$topic_info = $db->fetch_assoc($request);
1322
	$db->free_result($request);
1323
1324
	return $topic_info;
1325
}
1326
1327
/**
1328
 * Get all the details for a given topic and message.
1329
 * Respects permissions and post moderation
1330
 *
1331
 * @param int $topic id of a topic
1332
 * @param int|null $msg the id of a message, if empty, t.id_first_msg is used
1333
 * @return mixed[]|boolean to topic attributes
1334
 */
1335
function getTopicInfoByMsg($topic, $msg = null)
1336
{
1337
	global $modSettings;
1338
1339
	// Nothing to do
1340
	if (empty($topic))
1341
		return false;
1342
1343
	$db = database();
1344
1345
	$request = $db->query('', '
1346
		SELECT
1347
			t.locked, t.num_replies, t.id_member_started, t.id_first_msg,
1348
			m.id_msg, m.id_member, m.poster_time, m.subject, m.smileys_enabled, m.body, m.icon,
1349
			m.modified_time, m.modified_name, m.approved
1350
		FROM {db_prefix}messages AS m
1351
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
1352
		WHERE m.id_msg = {raw:id_msg}
1353
			AND m.id_topic = {int:current_topic}' . (allowedTo('modify_any') || allowedTo('approve_posts') ? '' : (!$modSettings['postmod_active'] ? '
1354
			AND (m.id_member != {int:guest_id} AND m.id_member = {int:current_member})' : '
1355
			AND (m.approved = {int:is_approved} OR (m.id_member != {int:guest_id} AND m.id_member = {int:current_member}))')),
1356
		array(
1357
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1358
			'current_topic' => $topic,
1359
			'id_msg' => empty($msg) ? 't.id_first_msg' : $msg,
1360
			'is_approved' => 1,
1361
			'guest_id' => 0,
1362
		)
1363
	);
1364
	$topic_info = array();
1365
	if ($request !== false)
1366
	{
1367
		$topic_info = $db->fetch_assoc($request);
1368
	}
1369
	$db->free_result($request);
1370
1371
	return $topic_info;
1372
}
1373
1374
/**
1375
 * So long as you are sure... all old posts will be gone.
1376
 * Used in Maintenance.controller.php to prune old topics.
1377
 *
1378
 * @param int[] $boards
1379
 * @param string $delete_type
1380
 * @param boolean $exclude_stickies
1381
 * @param int $older_than
1382
 * @throws \ElkArte\Exceptions\Exception
1383
 */
1384
function removeOldTopics(array $boards, $delete_type, $exclude_stickies, $older_than)
1385
{
1386
	$db = database();
1387
1388
	// Custom conditions.
1389
	$condition = '';
1390
	$condition_params = array(
1391
		'boards' => $boards,
1392
		'poster_time' => $older_than,
1393
	);
1394
1395
	// Just moved notice topics?
1396
	if ($delete_type == 'moved')
1397
	{
1398
		$condition .= '
1399
			AND m.icon = {string:icon}
1400
			AND t.locked = {int:locked}';
1401
		$condition_params['icon'] = 'moved';
1402
		$condition_params['locked'] = 1;
1403
	}
1404
	// Otherwise, maybe locked topics only?
1405
	elseif ($delete_type == 'locked')
1406
	{
1407
		$condition .= '
1408
			AND t.locked = {int:locked}';
1409
		$condition_params['locked'] = 1;
1410
	}
1411
1412
	// Exclude stickies?
1413
	if ($exclude_stickies)
1414
	{
1415
		$condition .= '
1416
			AND t.is_sticky = {int:is_sticky}';
1417
		$condition_params['is_sticky'] = 0;
1418
	}
1419
1420
	// All we're gonna do here is grab the id_topic's and send them to removeTopics().
1421
	$request = $db->query('', '
1422
		SELECT t.id_topic
1423
		FROM {db_prefix}topics AS t
1424
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
1425
		WHERE
1426
			m.poster_time < {int:poster_time}' . $condition . '
1427
			AND t.id_board IN ({array_int:boards})',
1428
		$condition_params
1429
	);
1430
	$topics = array();
1431
	while ($row = $db->fetch_assoc($request))
1432
		$topics[] = $row['id_topic'];
1433
	$db->free_result($request);
1434
1435
	removeTopics($topics, false, true);
1436
}
1437
1438
/**
1439
 * Retrieve all topics started by the given member.
1440
 *
1441
 * @param int $memberID
1442
 *
1443
 * @return array
1444
 */
1445
function topicsStartedBy($memberID)
1446
{
1447
	$db = database();
1448
1449
	// Fetch all topics started by this user.
1450
	$request = $db->query('', '
1451
		SELECT t.id_topic
1452
		FROM {db_prefix}topics AS t
1453
		WHERE t.id_member_started = {int:selected_member}',
1454
			array(
1455
				'selected_member' => $memberID,
1456
			)
1457
		);
1458
	$topicIDs = array();
1459
	while ($row = $db->fetch_assoc($request))
1460
		$topicIDs[] = $row['id_topic'];
1461
	$db->free_result($request);
1462
1463
	return $topicIDs;
1464
}
1465
1466
/**
1467
 * Retrieve the messages of the given topic, that are at or after
1468
 * a message.
1469
 * Used by split topics actions.
1470
 *
1471
 * @param int $id_topic
1472
 * @param int $id_msg
1473
 * @param bool $include_current = false
1474
 * @param bool $only_approved = false
1475
 *
1476
 * @return array message ids
1477
 */
1478
function messagesSince($id_topic, $id_msg, $include_current = false, $only_approved = false)
1479
{
1480
	$db = database();
1481
1482
	// Fetch the message IDs of the topic that are at or after the message.
1483
	$request = $db->query('', '
1484
		SELECT id_msg
1485
		FROM {db_prefix}messages
1486
		WHERE id_topic = {int:current_topic}
1487
			AND id_msg ' . ($include_current ? '>=' : '>') . ' {int:last_msg}' . ($only_approved ? '
1488
			AND approved = {int:approved}' : ''),
1489
		array(
1490
			'current_topic' => $id_topic,
1491
			'last_msg' => $id_msg,
1492
			'approved' => 1,
1493
		)
1494
	);
1495
	$messages = array();
1496
	while ($row = $db->fetch_assoc($request))
1497
		$messages[] = $row['id_msg'];
1498
	$db->free_result($request);
1499
1500
	return $messages;
1501
}
1502
1503
/**
1504
 * This function returns the number of messages in a topic,
1505
 * posted after $id_msg.
1506
 *
1507
 * @param int $id_topic
1508
 * @param int $id_msg
1509
 * @param bool $include_current = false
1510
 * @param bool $only_approved = false
1511
 *
1512
 * @return int
1513
 */
1514
function countMessagesSince($id_topic, $id_msg, $include_current = false, $only_approved = false)
1515
{
1516
	$db = database();
1517
1518
	// Give us something to work with
1519
	if (empty($id_topic) || empty($id_msg))
1520
		return false;
1521
1522
	$request = $db->query('', '
1523
		SELECT COUNT(*)
1524
		FROM {db_prefix}messages
1525
		WHERE id_topic = {int:current_topic}
1526
			AND id_msg ' . ($include_current ? '>=' : '>') . ' {int:last_msg}' . ($only_approved ? '
1527
			AND approved = {int:approved}' : '') . '
1528
		LIMIT 1',
1529
		array(
1530
			'current_topic' => $id_topic,
1531
			'last_msg' => $id_msg,
1532
			'approved' => 1,
1533
		)
1534
	);
1535
	list ($count) = $db->fetch_row($request);
1536
	$db->free_result($request);
1537
1538
	return $count;
1539
}
1540
1541
/**
1542
 * Returns how many messages are in a topic before the specified message id.
1543
 * Used in display to compute the start value for a specific message.
1544
 *
1545
 * @param int $id_topic
1546
 * @param int $id_msg
1547
 * @param bool $include_current = false
1548
 * @param bool $only_approved = false
1549
 * @param bool $include_own = false
1550
 * @return int
1551
 */
1552
function countMessagesBefore($id_topic, $id_msg, $include_current = false, $only_approved = false, $include_own = false)
1553
{
1554
	$db = database();
1555
1556
	$request = $db->query('', '
1557
		SELECT COUNT(*)
1558
		FROM {db_prefix}messages
1559
		WHERE id_msg ' . ($include_current ? '<=' : '<') . ' {int:id_msg}
1560
			AND id_topic = {int:current_topic}' . ($only_approved ? '
1561
			AND (approved = {int:is_approved}' . ($include_own ? '
1562
			OR id_member = {int:current_member}' : '') . ')' : ''),
1563
		array(
1564
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1565
			'current_topic' => $id_topic,
1566
			'id_msg' => $id_msg,
1567
			'is_approved' => 1,
1568
		)
1569
	);
1570
	list ($count) = $db->fetch_row($request);
1571
	$db->free_result($request);
1572
1573
	return $count;
1574
}
1575
1576
/**
1577
 * Select a part of the messages in a topic.
1578
 *
1579
 * @param int $topic
1580
 * @param int $start The item to start with (for pagination purposes)
1581
 * @param int $items_per_page The number of items to show per page
1582
 * @param mixed[] $messages
1583
 * @param bool $only_approved
1584
 *
1585
 * @return array|mixed[]
1586
 */
1587
function selectMessages($topic, $start, $items_per_page, $messages = array(), $only_approved = false)
1588
{
1589
	$db = database();
1590
1591
	// Get the messages and stick them into an array.
1592
	$request = $db->query('', '
1593
		SELECT m.subject, COALESCE(mem.real_name, m.poster_name) AS real_name, m.poster_time, m.body, m.id_msg, m.smileys_enabled, m.id_member
1594
		FROM {db_prefix}messages AS m
1595
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1596
		WHERE m.id_topic = {int:current_topic}' . (empty($messages['before']) ? '' : '
1597
			AND m.id_msg < {int:msg_before}') . (empty($messages['after']) ? '' : '
1598
			AND m.id_msg > {int:msg_after}') . (empty($messages['excluded']) ? '' : '
1599
			AND m.id_msg NOT IN ({array_int:no_split_msgs})') . (empty($messages['included']) ? '' : '
1600
			AND m.id_msg IN ({array_int:split_msgs})') . (!$only_approved ? '' : '
1601
			AND approved = {int:is_approved}') . '
1602
		ORDER BY m.id_msg DESC
1603
		LIMIT {int:start}, {int:messages_per_page}',
1604
		array(
1605
			'current_topic' => $topic,
1606
			'no_split_msgs' => !empty($messages['excluded']) ? $messages['excluded'] : array(),
1607
			'split_msgs' => !empty($messages['included']) ? $messages['included'] : array(),
1608
			'is_approved' => 1,
1609
			'start' => $start,
1610
			'messages_per_page' => $items_per_page,
1611
			'msg_before' => !empty($messages['before']) ? (int) $messages['before'] : 0,
1612
			'msg_after' => !empty($messages['after']) ? (int) $messages['after'] : 0,
1613
		)
1614
	);
1615
1616
	$messages = array();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $messages. This often makes code more readable.
Loading history...
1617
	$parser = \BBC\ParserWrapper::instance();
1618
1619
	for ($counter = 0; $row = $db->fetch_assoc($request); $counter++)
1620
	{
1621
		$row['subject'] = censor($row['subject']);
1622
		$row['body'] = censor($row['body']);
1623
1624
		$row['body'] = $parser->parseMessage($row['body'], (bool) $row['smileys_enabled']);
1625
1626
		$messages[$row['id_msg']] = array(
1627
			'id' => $row['id_msg'],
1628
			'alternate' => $counter % 2,
1629
			'subject' => $row['subject'],
1630
			'time' => standardTime($row['poster_time']),
1631
			'html_time' => htmlTime($row['poster_time']),
1632
			'timestamp' => forum_time(true, $row['poster_time']),
1633
			'body' => $row['body'],
1634
			'poster' => $row['real_name'],
1635
			'id_poster' => $row['id_member'],
1636
		);
1637
	}
1638
	$db->free_result($request);
1639
1640
	return $messages;
1641
}
1642
1643
/**
1644
 * Loads all the messages of a topic
1645
 * Used when printing or other functions that require a topic listing
1646
 *
1647
 * @param int $topic
1648
 * @param string $render defaults to print style rendering for parse_bbc
1649
 *
1650
 * @return array
1651
 */
1652
function topicMessages($topic, $render = 'print')
1653
{
1654
	global $modSettings;
1655
1656
	$db = database();
1657
1658
	$request = $db->query('', '
1659
		SELECT subject, poster_time, body, COALESCE(mem.real_name, poster_name) AS poster_name, id_msg
1660
		FROM {db_prefix}messages AS m
1661
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
1662
		WHERE m.id_topic = {int:current_topic}' . ($modSettings['postmod_active'] && !allowedTo('approve_posts') ? '
1663
			AND (m.approved = {int:is_approved}' . (User::$info->is_guest ? '' : ' OR m.id_member = {int:current_member}') . ')' : '') . '
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1664
		ORDER BY m.id_msg',
1665
		array(
1666
			'current_topic' => $topic,
1667
			'is_approved' => 1,
1668
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1669
		)
1670
	);
1671
1672
	$posts = array();
1673
	$parser = \BBC\ParserWrapper::instance();
1674
1675
	if ($render === 'print')
1676
	{
1677
		$parser->getCodes()->setForPrinting();
1678
	}
1679
1680
	while ($row = $db->fetch_assoc($request))
1681
	{
1682
		// Censor the subject and message.
1683
		$row['subject'] = censor($row['subject']);
1684
		$row['body'] = censor($row['body']);
1685
1686
		$posts[$row['id_msg']] = array(
1687
			'subject' => $row['subject'],
1688
			'member' => $row['poster_name'],
1689
			'time' => standardTime($row['poster_time'], false),
1690
			'html_time' => htmlTime($row['poster_time']),
1691
			'timestamp' => forum_time(true, $row['poster_time']),
1692
			'body' => $parser->parseMessage($row['body'], $render !== 'print'),
1693
			'id_msg' => $row['id_msg'],
1694
		);
1695
	}
1696
	$db->free_result($request);
1697
1698
	return $posts;
1699
}
1700
1701
/**
1702
 * Load message image attachments for use in the print page function
1703
 * Returns array of file attachment name along with width/height properties
1704
 * Will only return approved attachments
1705
 *
1706
 * @param int[] $id_messages
1707
 *
1708
 * @return array
1709
 */
1710
function messagesAttachments($id_messages)
1711
{
1712
	global $modSettings;
1713
1714
	require_once(SUBSDIR . '/Attachments.subs.php');
1715
1716
	$db = database();
1717
1718
	$request = $db->query('', '
1719
		SELECT
1720
			a.id_attach, a.id_msg, a.approved, a.width, a.height, a.file_hash, a.filename, a.id_folder, a.mime_type
1721
		FROM {db_prefix}attachments AS a
1722
		WHERE a.id_msg IN ({array_int:message_list})
1723
			AND a.attachment_type = {int:attachment_type}',
1724
		array(
1725
			'message_list' => $id_messages,
1726
			'attachment_type' => 0,
1727
			'is_approved' => 1,
1728
		)
1729
	);
1730
	$temp = array();
1731
	$printattach = array();
1732
	while ($row = $db->fetch_assoc($request))
1733
	{
1734
		$temp[$row['id_attach']] = $row;
1735
		if (!isset($printattach[$row['id_msg']]))
1736
			$printattach[$row['id_msg']] = array();
1737
	}
1738
	$db->free_result($request);
1739
	ksort($temp);
1740
1741
	// Load them into $context so the template can use them
1742
	foreach ($temp as $row)
1743
	{
1744
		if (!empty($row['width']) && !empty($row['height']))
1745
		{
1746
			if (!empty($modSettings['max_image_width']) && (empty($modSettings['max_image_height']) || $row['height'] * ($modSettings['max_image_width'] / $row['width']) <= $modSettings['max_image_height']))
1747
			{
1748 View Code Duplication
				if ($row['width'] > $modSettings['max_image_width'])
1749
				{
1750
					$row['height'] = floor($row['height'] * ($modSettings['max_image_width'] / $row['width']));
1751
					$row['width'] = $modSettings['max_image_width'];
1752
				}
1753
			}
1754 View Code Duplication
			elseif (!empty($modSettings['max_image_width']))
1755
			{
1756
				if ($row['height'] > $modSettings['max_image_height'])
1757
				{
1758
					$row['width'] = floor($row['width'] * $modSettings['max_image_height'] / $row['height']);
1759
					$row['height'] = $modSettings['max_image_height'];
1760
				}
1761
			}
1762
1763
			$row['filename'] = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
1764
1765
			// save for the template
1766
			$printattach[$row['id_msg']][] = $row;
1767
		}
1768
	}
1769
1770
	return $printattach;
1771
}
1772
1773
/**
1774
 * Retrieve unapproved posts of the member
1775
 * in a specific topic
1776
 *
1777
 * @param int $id_topic topic id
1778
 * @param int $id_member member id
1779
 * @return array|int empty array if no member supplied, otherwise number of posts
1780
 */
1781
function unapprovedPosts($id_topic, $id_member)
1782
{
1783
	$db = database();
1784
1785
	// not all guests are the same!
1786
	if (empty($id_member))
1787
		return array();
1788
1789
	$request = $db->query('', '
1790
			SELECT COUNT(id_member) AS my_unapproved_posts
1791
			FROM {db_prefix}messages
1792
			WHERE id_topic = {int:current_topic}
1793
				AND id_member = {int:current_member}
1794
				AND approved = 0',
1795
			array(
1796
				'current_topic' => $id_topic,
1797
				'current_member' => $id_member,
1798
			)
1799
		);
1800
	list ($myUnapprovedPosts) = $db->fetch_row($request);
1801
	$db->free_result($request);
1802
1803
	return $myUnapprovedPosts;
1804
}
1805
1806
/**
1807
 * Update topic info after a successful split of a topic.
1808
 *
1809
 * @param mixed[] $options
1810
 * @param int $id_board
1811
 */
1812
function updateSplitTopics($options, $id_board)
1813
{
1814
	$db = database();
1815
1816
	// Any associated reported posts better follow...
1817
	$db->query('', '
1818
		UPDATE {db_prefix}log_reported
1819
		SET id_topic = {int:id_topic}
1820
		WHERE id_msg IN ({array_int:split_msgs})
1821
			AND type = {string:a_message}',
1822
		array(
1823
			'split_msgs' => $options['splitMessages'],
1824
			'id_topic' => $options['split2_ID_TOPIC'],
1825
			'a_message' => 'msg',
1826
		)
1827
	);
1828
1829
	// Mess with the old topic's first, last, and number of messages.
1830
	setTopicAttribute($options['split1_ID_TOPIC'], array(
1831
		'num_replies' => $options['split1_replies'],
1832
		'id_first_msg' => $options['split1_first_msg'],
1833
		'id_last_msg' => $options['split1_last_msg'],
1834
		'id_member_started' => $options['split1_firstMem'],
1835
		'id_member_updated' => $options['split1_lastMem'],
1836
		'unapproved_posts' => $options['split1_unapprovedposts'],
1837
	));
1838
1839
	// Now, put the first/last message back to what they should be.
1840
	setTopicAttribute($options['split2_ID_TOPIC'], array(
1841
		'id_first_msg' => $options['split2_first_msg'],
1842
		'id_last_msg' => $options['split2_last_msg'],
1843
	));
1844
1845
	// If the new topic isn't approved ensure the first message flags
1846
	// this just in case.
1847
	if (!$options['split2_approved'])
1848
		$db->query('', '
1849
			UPDATE {db_prefix}messages
1850
			SET approved = {int:approved}
1851
			WHERE id_msg = {int:id_msg}
1852
				AND id_topic = {int:id_topic}',
1853
			array(
1854
				'approved' => 0,
1855
				'id_msg' => $options['split2_first_msg'],
1856
				'id_topic' => $options['split2_ID_TOPIC'],
1857
			)
1858
		);
1859
1860
	// The board has more topics now (Or more unapproved ones!).
1861
	$db->query('', '
1862
		UPDATE {db_prefix}boards
1863
		SET ' . ($options['split2_approved'] ? '
1864
			num_topics = num_topics + 1' : '
1865
			unapproved_topics = unapproved_topics + 1') . '
1866
		WHERE id_board = {int:id_board}',
1867
		array(
1868
			'id_board' => $id_board,
1869
		)
1870
	);
1871
}
1872
1873
/**
1874
 * Find out who started a topic, and the lock status
1875
 *
1876
 * @param int $topic
1877
 * @return array with id_member_started and locked
1878
 */
1879
function topicStatus($topic)
1880
{
1881
	// Find out who started the topic, and the lock status.
1882
	$starter = topicAttribute($topic, array('id_member_started', 'locked'));
1883
1884
	return array($starter['id_member_started'], $starter['locked']);
1885
}
1886
1887
/**
1888
 * Set attributes for a topic, i.e. locked, sticky.
1889
 * Parameter $attributes is an array where the key is the column name of the
1890
 * attribute to change, and the value is... the new value of the attribute.
1891
 * It sets the new value for the attribute as passed to it.
1892
 * <b>It is currently limited to integer values only</b>
1893
 *
1894
 * @param int|int[] $topic
1895
 * @param mixed[] $attributes
1896
 * @todo limited to integer attributes
1897
 * @return int number of row affected
1898
 */
1899
function setTopicAttribute($topic, $attributes)
1900
{
1901
	$db = database();
1902
1903
	$update = array();
1904
	foreach ($attributes as $key => $attr)
1905 8
	{
1906
		$attributes[$key] = (int) $attr;
1907 8
		$update[] = '
1908 8
				' . $key . ' = {int:' . $key . '}';
1909
	}
1910 8
1911 8
	if (empty($update))
1912 8
		return false;
1913
1914
	$attributes['current_topic'] = (array) $topic;
1915 8
1916
	$db->query('', '
1917
		UPDATE {db_prefix}topics
1918 8
		SET ' . implode(',', $update) . '
1919
		WHERE id_topic IN ({array_int:current_topic})',
1920 8
		$attributes
1921
	);
1922 8
1923
	return $db->affected_rows();
1924 4
}
1925
1926
/**
1927 8
 * Retrieve the locked or sticky status of a topic.
1928
 *
1929
 * @param int|int[] $id_topic topic to get the status for
1930
 * @param string|string[] $attributes Basically the column names
1931
 * @return array named array based on attributes requested
1932
 */
1933
function topicAttribute($id_topic, $attributes)
1934
{
1935
	$db = database();
1936
1937
	// @todo maybe add a filer for known attributes... or not
1938
// 	$attributes = array(
1939 12
// 		'locked' => 'locked',
1940
// 		'sticky' => 'is_sticky',
1941
// 	);
1942
1943
	// check the lock status
1944
	$request = $db->query('', '
1945
		SELECT {raw:attribute}
1946
		FROM {db_prefix}topics
1947
		WHERE id_topic IN ({array_int:current_topic})',
1948 12
		array(
1949
			'current_topic' => (array) $id_topic,
1950
			'attribute' => implode(',', (array) $attributes),
1951
		)
1952
	);
1953 12
1954 12
	if (is_array($id_topic))
1955
	{
1956
		$status = array();
1957
		while ($row = $db->fetch_assoc($request))
1958 12
			$status[] = $row;
1959
	}
1960 12
	else
1961 12
	{
1962 12
		$status = $db->fetch_assoc($request);
1963
	}
1964
	$db->free_result($request);
1965
1966 6
	return $status;
1967
}
1968 12
1969
/**
1970 12
 * Retrieve some topic attributes based on the user:
1971
 *   - locked
1972
 *   - notify
1973
 *   - is_sticky
1974
 *   - id_poll
1975
 *   - id_last_msg
1976
 *   - id_member of the first message in the topic
1977
 *   - id_first_msg
1978
 *   - subject of the first message in the topic
1979
 *   - last_post_time that is poster_time if poster_time > modified_time, or
1980
 *       modified_time otherwise
1981
 *
1982
 * @param int $id_topic topic to get the status for
1983
 * @param int $user a user id
1984
 * @return mixed[]
1985
 */
1986
function topicUserAttributes($id_topic, $user)
1987
{
1988
	$db = database();
1989
1990
	$request = $db->query('', '
1991
		SELECT
1992
			t.locked, COALESCE(ln.id_topic, 0) AS notify, t.is_sticky, t.id_poll,
1993
			t.id_last_msg, mf.id_member, t.id_first_msg, mf.subject,
1994
			CASE WHEN ml.poster_time > ml.modified_time THEN ml.poster_time ELSE ml.modified_time END AS last_post_time
1995
		FROM {db_prefix}topics AS t
1996
			LEFT JOIN {db_prefix}log_notify AS ln ON (ln.id_topic = t.id_topic AND ln.id_member = {int:current_member})
1997
			LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
1998
			LEFT JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
1999
		WHERE t.id_topic = {int:current_topic}
2000
		LIMIT 1',
2001
		array(
2002
			'current_member' => $user,
2003
			'current_topic' => $id_topic,
2004
		)
2005
	);
2006
	$return = $db->fetch_assoc($request);
2007
	$db->free_result($request);
2008
2009
	return $return;
2010
}
2011
2012
/**
2013
 * Retrieve some details about the topic
2014
 *
2015
 * @param int[] $topics an array of topic id
2016
 *
2017
 * @return array
2018
 */
2019
function topicsDetails($topics)
2020
{
2021
	$returns = topicAttribute($topics, array('id_topic', 'id_member_started', 'id_board', 'locked', 'approved', 'unapproved_posts'));
2022
2023
	return $returns;
2024
}
2025
2026
/**
2027
 * Toggle sticky status for the passed topics and logs the action.
2028
 *
2029
 * @param int[] $topics
2030
 * @param bool $log If true the action is logged
2031
 * @return int Number of topics toggled
2032
 * @throws \ElkArte\Exceptions\Exception
2033
 */
2034
function toggleTopicSticky($topics, $log = false)
2035
{
2036
	$db = database();
2037
2038
	$topics = is_array($topics) ? $topics : array($topics);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topics. This often makes code more readable.
Loading history...
2039
2040
	$db->query('', '
2041
		UPDATE {db_prefix}topics
2042
		SET is_sticky = CASE WHEN is_sticky = 1 THEN 0 ELSE 1 END
2043
		WHERE id_topic IN ({array_int:sticky_topic_ids})',
2044
		array(
2045
			'sticky_topic_ids' => $topics,
2046
		)
2047
	);
2048
2049
	$toggled = $db->affected_rows();
2050
2051
	if ($log)
2052
	{
2053
		// Get the board IDs and Sticky status
2054
		$topicAttributes = topicAttribute($topics, array('id_topic', 'id_board', 'is_sticky'));
2055
		$stickyCacheBoards = array();
2056
		$stickyCacheStatus = array();
2057
		foreach ($topicAttributes as $row)
2058
		{
2059
			$stickyCacheBoards[$row['id_topic']] = $row['id_board'];
2060
			$stickyCacheStatus[$row['id_topic']] = empty($row['is_sticky']);
2061
		}
2062
2063
		foreach ($topics as $topic)
2064
		{
2065
			logAction($stickyCacheStatus[$topic] ? 'unsticky' : 'sticky', array('topic' => $topic, 'board' => $stickyCacheBoards[$topic]));
2066
			sendNotifications($topic, 'sticky');
2067
		}
2068
	}
2069
2070
	return $toggled;
2071
}
2072
2073
/**
2074
 * Get topics from the log_topics table belonging to a certain user
2075
 *
2076
 * @param int $member a member id
2077
 * @param int[] $topics an array of topics
2078
 * @return array an array of topics in the table (key) and its unwatched status (value)
2079
 *
2080
 * @todo find a better name
2081
 */
2082 View Code Duplication
function getLoggedTopics($member, $topics)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2083
{
2084
	$db = database();
2085
2086
	$request = $db->query('', '
2087
		SELECT id_topic, id_msg, unwatched
2088
		FROM {db_prefix}log_topics
2089
		WHERE id_topic IN ({array_int:selected_topics})
2090
			AND id_member = {int:current_user}',
2091
		array(
2092
			'selected_topics' => $topics,
2093
			'current_user' => $member,
2094
		)
2095
	);
2096
	$logged_topics = array();
2097
	while ($row = $db->fetch_assoc($request))
2098
		$logged_topics[$row['id_topic']] = $row;
2099
	$db->free_result($request);
2100
2101
	return $logged_topics;
2102
}
2103
2104
/**
2105
 * Returns a list of topics ids and their subjects
2106
 *
2107
 * @param int[] $topic_ids
2108
 *
2109
 * @return array
2110
 */
2111
function topicsList($topic_ids)
2112
{
2113
	global $modSettings;
2114
2115
	// you have to want *something* from this function
2116
	if (empty($topic_ids))
2117
		return array();
2118
2119
	$db = database();
2120
2121
	$topics = array();
2122
2123
	$result = $db->query('', '
2124
		SELECT t.id_topic, m.subject
2125
		FROM {db_prefix}topics AS t
2126
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
2127
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
2128
		WHERE {query_see_board}
2129
			AND t.id_topic IN ({array_int:topic_list})' . ($modSettings['postmod_active'] ? '
2130
			AND t.approved = {int:is_approved}' : '') . '
2131
		LIMIT {int:limit}',
2132
		array(
2133
			'topic_list' => $topic_ids,
2134
			'is_approved' => 1,
2135
			'limit' => count($topic_ids),
2136
		)
2137
	);
2138
	while ($row = $db->fetch_assoc($result))
2139
	{
2140
		$topics[$row['id_topic']] = array(
2141
			'id_topic' => $row['id_topic'],
2142
			'subject' => censor($row['subject']),
2143
		);
2144
	}
2145
	$db->free_result($result);
2146
2147
	return $topics;
2148
}
2149
2150
/**
2151
 * Get each post and poster in this topic and take care of user settings such as
2152
 * limit or sort direction.
2153
 *
2154
 * @param int $topic
2155
 * @param mixed[] $limit
2156
 * @param boolean $sort set to false for a desc sort
2157
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2158
 */
2159
function getTopicsPostsAndPoster($topic, $limit, $sort)
2160
{
2161
	global $modSettings;
2162
2163
	$db = database();
2164
2165
	$topic_details = array(
2166
		'messages' => array(),
2167
		'all_posters' => array(),
2168
	);
2169
2170
	$request = $db->query('', '
2171
		SELECT id_msg, id_member, approved
2172
		FROM {db_prefix}messages
2173
		WHERE id_topic = {int:current_topic}' . (!$modSettings['postmod_active'] || allowedTo('approve_posts') ? '' : '
2174
			AND (approved = {int:is_approved}' . (User::$info->is_guest ? '' : ' OR id_member = {int:current_member}') . ')') . '
0 ignored issues
show
Documentation introduced by
The property is_guest does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2175
		ORDER BY id_msg ' . ($sort ? '' : 'DESC') . ($limit['messages_per_page'] == -1 ? '' : '
2176
		LIMIT ' . $limit['start'] . ', ' . $limit['offset']),
2177
		array(
2178
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2179
			'current_topic' => $topic,
2180
			'is_approved' => 1,
2181
			'blank_id_member' => 0,
2182
		)
2183
	);
2184
	while ($row = $db->fetch_assoc($request))
2185
	{
2186
		if (!empty($row['id_member']))
2187
			$topic_details['all_posters'][$row['id_msg']] = $row['id_member'];
2188
			$topic_details['messages'][] = $row['id_msg'];
2189
	}
2190
	$db->free_result($request);
2191
2192
	return $topic_details;
2193
}
2194
2195
/**
2196
 * Remove a batch of messages (or topics)
2197
 *
2198
 * @param int[] $messages
2199
 * @param mixed[] $messageDetails
2200
 * @param string $type = replies
2201
 * @throws \ElkArte\Exceptions\Exception
2202
 */
2203
function removeMessages($messages, $messageDetails, $type = 'replies')
2204
{
2205
	global $modSettings;
2206
2207
	// @todo something's not right, removeMessage() does check permissions,
2208
	// removeTopics() doesn't
2209
	if ($type == 'topics')
2210
	{
2211
		removeTopics($messages);
2212
2213
		// and tell the world about it
2214
		foreach ($messages as $topic)
2215
		{
2216
			// Note, only log topic ID in native form if it's not gone forever.
2217
			logAction('remove', array(
2218
				(empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $messageDetails[$topic]['board'] ? 'topic' : 'old_topic_id') => $topic, 'subject' => $messageDetails[$topic]['subject'], 'member' => $messageDetails[$topic]['member'], 'board' => $messageDetails[$topic]['board']));
2219
		}
2220
	}
2221
	else
2222
	{
2223
		$remover = new \ElkArte\MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
0 ignored issues
show
Bug introduced by
The call to MessagesDelete::__construct() misses a required argument $user.

This check looks for function calls that miss required arguments.

Loading history...
2224
		foreach ($messages as $post)
2225
		{
2226
			$remover->removeMessage($post);
2227
		}
2228
	}
2229
}
2230
2231
/**
2232
 * Approve a batch of posts (or topics in their own right)
2233
 *
2234
 * @param int[] $messages
2235
 * @param mixed[] $messageDetails
2236
 * @param string $type = replies
2237
 * @throws \ElkArte\Exceptions\Exception
2238
 */
2239
function approveMessages($messages, $messageDetails, $type = 'replies')
2240
{
2241
	if ($type == 'topics')
2242
	{
2243
		approveTopics($messages, true, true);
2244
	}
2245
	else
2246
	{
2247
		require_once(SUBSDIR . '/Post.subs.php');
2248
		approvePosts($messages);
2249
2250
		// and tell the world about it again
2251
		foreach ($messages as $post)
2252
			logAction('approve', array('topic' => $messageDetails[$post]['topic'], 'subject' => $messageDetails[$post]['subject'], 'member' => $messageDetails[$post]['member'], 'board' => $messageDetails[$post]['board']));
2253
	}
2254
}
2255
2256
/**
2257
 * Approve topics, all we got.
2258
 *
2259
 * @param int[] $topics array of topics ids
2260
 * @param bool $approve = true
2261
 * @param bool $log if true logs the action.
2262
 *
2263
 * @return bool|void
2264
 * @throws \ElkArte\Exceptions\Exception
2265
 */
2266
function approveTopics($topics, $approve = true, $log = false)
2267
{
2268
	global $board;
2269
2270
	if (!is_array($topics))
2271
		$topics = array($topics);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $topics. This often makes code more readable.
Loading history...
2272
2273
	if (empty($topics))
2274
		return false;
2275
2276
	$db = database();
2277
2278
	$approve_type = $approve ? 0 : 1;
2279
2280
	if ($log)
2281
	{
2282
		$log_action = $approve ? 'approve_topic' : 'unapprove_topic';
2283
2284
		// We need unapproved topic ids, their authors and the subjects!
2285
		$request = $db->query('', '
2286
			SELECT t.id_topic, t.id_member_started, m.subject
2287
			FROM {db_prefix}topics as t
2288
				LEFT JOIN {db_prefix}messages AS m ON (t.id_first_msg = m.id_msg)
2289
			WHERE t.id_topic IN ({array_int:approve_topic_ids})
2290
				AND t.approved = {int:approve_type}
2291
			LIMIT ' . count($topics),
2292
			array(
2293
				'approve_topic_ids' => $topics,
2294
				'approve_type' => $approve_type,
2295
			)
2296
		);
2297
		while ($row = $db->fetch_assoc($request))
2298
		{
2299
			logAction($log_action, array('topic' => $row['id_topic'], 'subject' => $row['subject'], 'member' => $row['id_member_started'], 'board' => $board));
2300
		}
2301
		$db->free_result($request);
2302
	}
2303
2304
	// Just get the messages to be approved and pass through...
2305
	$request = $db->query('', '
2306
		SELECT id_msg
2307
		FROM {db_prefix}messages
2308
		WHERE id_topic IN ({array_int:topic_list})
2309
			AND approved = {int:approve_type}',
2310
		array(
2311
			'topic_list' => $topics,
2312
			'approve_type' => $approve_type,
2313
		)
2314
	);
2315
	$msgs = array();
2316
	while ($row = $db->fetch_assoc($request))
2317
		$msgs[] = $row['id_msg'];
2318
	$db->free_result($request);
2319
2320
	require_once(SUBSDIR . '/Post.subs.php');
2321
	return approvePosts($msgs, $approve);
2322
}
2323
2324
/**
2325
 * Post a message at the end of the original topic
2326
 *
2327
 * @param string $reason the text that will become the message body
2328
 * @param string $subject the text that will become the message subject
2329
 * @param mixed[] $board_info some board information (at least id, name, if posts are counted)
2330
 * @param string $new_topic used to build the url for moving to a new topic
2331
 * @throws \ElkArte\Exceptions\Exception
2332
 */
2333
function postSplitRedirect($reason, $subject, $board_info, $new_topic)
2334
{
2335
	global $language, $txt, $topic, $board;
2336
2337
	// Should be in the boardwide language.
2338
	if (User::$info->language != $language)
0 ignored issues
show
Documentation introduced by
The property language does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2339
	{
2340
		theme()->getTemplates()->loadLanguageFile('index', $language);
2341
	}
2342
2343
	preparsecode($reason);
2344
2345
	// Add a URL onto the message.
2346
	$reason = strtr($reason, array(
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $reason. This often makes code more readable.
Loading history...
2347
		$txt['movetopic_auto_board'] => '[url=' . getUrl('board', ['board' => $board_info['id'], 'start' => '0', 'name' => $board_info['name']]) . ']' . $board_info['name'] . '[/url]',
2348
		$txt['movetopic_auto_topic'] => '[iurl]' . getUrl('topic', ['topic' => $new_topic, 'start' => '0', 'subject' => $subject]) . '[/iurl]'
2349
	));
2350
2351
	$msgOptions = array(
2352
		'subject' => $txt['split'] . ': ' . strtr(\ElkArte\Util::htmltrim(\ElkArte\Util::htmlspecialchars($subject)), array("\r" => '', "\n" => '', "\t" => '')),
2353
		'body' => $reason,
2354
		'icon' => 'moved',
2355
		'smileys_enabled' => 1,
2356
	);
2357
2358
	$topicOptions = array(
2359
		'id' => $topic,
2360
		'board' => $board,
2361
		'mark_as_read' => true,
2362
	);
2363
2364
	$posterOptions = array(
2365
		'id' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2366
		'update_post_count' => empty($board_info['count_posts']),
2367
	);
2368
2369
	createPost($msgOptions, $topicOptions, $posterOptions);
2370
}
2371
2372
/**
2373
 * General function to split off a topic.
2374
 * creates a new topic and moves the messages with the IDs in
2375
 * array messagesToBeSplit to the new topic.
2376
 * the subject of the newly created topic is set to 'newSubject'.
2377
 * marks the newly created message as read for the user splitting it.
2378
 * updates the statistics to reflect a newly created topic.
2379
 * logs the action in the moderation log.
2380
 * a notification is sent to all users monitoring this topic.
2381
 *
2382
 * @param int    $split1_ID_TOPIC
2383
 * @param int[]  $splitMessages
2384
 * @param string $new_subject
2385
 *
2386
 * @return int the topic ID of the new split topic.
2387
 * @throws \ElkArte\Exceptions\Exception no_posts_selected
2388
 */
2389
function splitTopic($split1_ID_TOPIC, $splitMessages, $new_subject)
2390
{
2391
	global $txt, $modSettings;
2392
2393
	$db = database();
2394
2395
	// Nothing to split?
2396
	if (empty($splitMessages))
2397
		throw new \ElkArte\Exceptions\Exception('no_posts_selected', false);
2398
2399
	// Get some board info.
2400
	$topicAttribute = topicAttribute($split1_ID_TOPIC, array('id_board', 'approved'));
2401
	$id_board = $topicAttribute['id_board'];
2402
	$split1_approved = $topicAttribute['approved'];
2403
2404
	// Find the new first and last not in the list. (old topic)
2405
	$request = $db->query('', '
2406
		SELECT
2407
			MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg, COUNT(*) AS message_count, m.approved
2408
		FROM {db_prefix}messages AS m
2409
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:id_topic})
2410
		WHERE m.id_msg NOT IN ({array_int:no_msg_list})
2411
			AND m.id_topic = {int:id_topic}
2412
		GROUP BY m.approved
2413
		ORDER BY m.approved DESC
2414
		LIMIT 2',
2415
		array(
2416
			'id_topic' => $split1_ID_TOPIC,
2417
			'no_msg_list' => $splitMessages,
2418
		)
2419
	);
2420
	// You can't select ALL the messages!
2421
	if ($db->num_rows($request) == 0)
2422
		throw new \ElkArte\Exceptions\Exception('selected_all_posts', false);
2423
2424
	$split1_first_msg = null;
2425
	$split1_last_msg = null;
2426
2427
	while ($row = $db->fetch_assoc($request))
2428
	{
2429
		// Get the right first and last message dependant on approved state...
2430
		if (empty($split1_first_msg) || $row['myid_first_msg'] < $split1_first_msg)
2431
			$split1_first_msg = $row['myid_first_msg'];
2432
2433
		if (empty($split1_last_msg) || $row['approved'])
2434
			$split1_last_msg = $row['myid_last_msg'];
2435
2436
		// Get the counts correct...
2437
		if ($row['approved'])
2438
		{
2439
			$split1_replies = $row['message_count'] - 1;
2440
			$split1_unapprovedposts = 0;
2441
		}
2442
		else
2443
		{
2444
			if (!isset($split1_replies))
2445
				$split1_replies = 0;
2446
			// If the topic isn't approved then num replies must go up by one... as first post wouldn't be counted.
2447
			elseif (!$split1_approved)
2448
				$split1_replies++;
2449
2450
			$split1_unapprovedposts = $row['message_count'];
2451
		}
2452
	}
2453
	$db->free_result($request);
2454
	$split1_firstMem = getMsgMemberID($split1_first_msg);
2455
	$split1_lastMem = getMsgMemberID($split1_last_msg);
2456
2457
	// Find the first and last in the list. (new topic)
2458
	$request = $db->query('', '
2459
		SELECT MIN(id_msg) AS myid_first_msg, MAX(id_msg) AS myid_last_msg, COUNT(*) AS message_count, approved
2460
		FROM {db_prefix}messages
2461
		WHERE id_msg IN ({array_int:msg_list})
2462
			AND id_topic = {int:id_topic}
2463
		GROUP BY id_topic, approved
2464
		ORDER BY approved DESC
2465
		LIMIT 2',
2466
		array(
2467
			'msg_list' => $splitMessages,
2468
			'id_topic' => $split1_ID_TOPIC,
2469
		)
2470
	);
2471
	while ($row = $db->fetch_assoc($request))
2472
	{
2473
		// As before get the right first and last message dependant on approved state...
2474
		if (empty($split2_first_msg) || $row['myid_first_msg'] < $split2_first_msg)
2475
			$split2_first_msg = $row['myid_first_msg'];
2476
2477
		if (empty($split2_last_msg) || $row['approved'])
2478
			$split2_last_msg = $row['myid_last_msg'];
2479
2480
		// Then do the counts again...
2481
		if ($row['approved'])
2482
		{
2483
			$split2_approved = true;
2484
			$split2_replies = $row['message_count'] - 1;
2485
			$split2_unapprovedposts = 0;
2486
		}
2487
		else
2488
		{
2489
			// Should this one be approved??
2490
			if ($split2_first_msg == $row['myid_first_msg'])
2491
				$split2_approved = false;
2492
2493
			if (!isset($split2_replies))
2494
				$split2_replies = 0;
2495
			// As before, fix number of replies.
2496
			elseif (!$split2_approved)
0 ignored issues
show
Bug introduced by
The variable $split2_approved 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...
2497
				$split2_replies++;
2498
2499
			$split2_unapprovedposts = $row['message_count'];
2500
		}
2501
	}
2502
	$db->free_result($request);
2503
	$split2_firstMem = getMsgMemberID($split2_first_msg);
0 ignored issues
show
Bug introduced by
The variable $split2_first_msg 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...
2504
	$split2_lastMem = getMsgMemberID($split2_last_msg);
0 ignored issues
show
Bug introduced by
The variable $split2_last_msg 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...
2505
2506
	// No database changes yet, so let's double check to see if everything makes at least a little sense.
2507
	if ($split1_first_msg <= 0 || $split1_last_msg <= 0 || $split2_first_msg <= 0 || $split2_last_msg <= 0 || $split1_replies < 0 || $split2_replies < 0 || $split1_unapprovedposts < 0 || $split2_unapprovedposts < 0 || !isset($split1_approved) || !isset($split2_approved))
0 ignored issues
show
Bug introduced by
The variable $split1_replies 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...
Bug introduced by
The variable $split2_replies 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...
Bug introduced by
The variable $split1_unapprovedposts 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...
Bug introduced by
The variable $split2_unapprovedposts 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...
2508
		throw new \ElkArte\Exceptions\Exception('cant_find_messages');
2509
2510
	// You cannot split off the first message of a topic.
2511
	if ($split1_first_msg > $split2_first_msg)
2512
		throw new \ElkArte\Exceptions\Exception('split_first_post', false);
2513
2514
	// The message that is starting the new topic may have likes, these become topic likes
2515
	require_once(SUBSDIR . '/Likes.subs.php');
2516
	$split2_first_msg_likes = messageLikeCount($split2_first_msg);
2517
2518
	// We're off to insert the new topic!  Use 0 for now to avoid UNIQUE errors.
2519
	$db->insert('',
2520
		'{db_prefix}topics',
2521
		array(
2522
			'id_board' => 'int',
2523
			'id_member_started' => 'int',
2524
			'id_member_updated' => 'int',
2525
			'id_first_msg' => 'int',
2526
			'id_last_msg' => 'int',
2527
			'num_replies' => 'int',
2528
			'unapproved_posts' => 'int',
2529
			'approved' => 'int',
2530
			'is_sticky' => 'int',
2531
			'num_likes' => 'int',
2532
		),
2533
		array(
2534
			(int) $id_board, $split2_firstMem, $split2_lastMem, 0,
2535
			0, $split2_replies, $split2_unapprovedposts, (int) $split2_approved, 0, $split2_first_msg_likes,
2536
		),
2537
		array('id_topic')
2538
	);
2539
	$split2_ID_TOPIC = $db->insert_id('{db_prefix}topics', 'id_topic');
0 ignored issues
show
Unused Code introduced by
The call to QueryInterface::insert_id() has too many arguments starting with 'id_topic'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2540
	if ($split2_ID_TOPIC <= 0)
2541
		throw new \ElkArte\Exceptions\Exception('cant_insert_topic');
2542
2543
	// Move the messages over to the other topic.
2544
	$new_subject = strtr(\ElkArte\Util::htmltrim(\ElkArte\Util::htmlspecialchars($new_subject)), array("\r" => '', "\n" => '', "\t" => ''));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $new_subject. This often makes code more readable.
Loading history...
2545
2546
	// Check the subject length.
2547
	if (\ElkArte\Util::strlen($new_subject) > 100)
2548
		$new_subject = \ElkArte\Util::substr($new_subject, 0, 100);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $new_subject. This often makes code more readable.
Loading history...
2549
2550
	// Valid subject?
2551
	if ($new_subject != '')
2552
	{
2553
		$db->query('', '
2554
			UPDATE {db_prefix}messages
2555
			SET
2556
				id_topic = {int:id_topic},
2557
				subject = CASE WHEN id_msg = {int:split_first_msg} THEN {string:new_subject} ELSE {string:new_subject_replies} END
2558
			WHERE id_msg IN ({array_int:split_msgs})',
2559
			array(
2560
				'split_msgs' => $splitMessages,
2561
				'id_topic' => $split2_ID_TOPIC,
2562
				'new_subject' => $new_subject,
2563
				'split_first_msg' => $split2_first_msg,
2564
				'new_subject_replies' => $txt['response_prefix'] . $new_subject,
2565
			)
2566
		);
2567
2568
		// Cache the new topics subject... we can do it now as all the subjects are the same!
2569
		require_once(SUBSDIR . '/Messages.subs.php');
2570
		updateSubjectStats($split2_ID_TOPIC, $new_subject);
2571
	}
2572
2573
	// Any associated reported posts better follow...
2574
	require_once(SUBSDIR . '/Topic.subs.php');
2575
	updateSplitTopics(array(
2576
		'splitMessages' => $splitMessages,
2577
		'split1_replies' => $split1_replies,
2578
		'split1_first_msg' => $split1_first_msg,
2579
		'split1_last_msg' => $split1_last_msg,
2580
		'split1_firstMem' => $split1_firstMem,
2581
		'split1_lastMem' => $split1_lastMem,
2582
		'split1_unapprovedposts' => $split1_unapprovedposts,
2583
		'split1_ID_TOPIC' => $split1_ID_TOPIC,
2584
		'split2_first_msg' => $split2_first_msg,
2585
		'split2_last_msg' => $split2_last_msg,
2586
		'split2_ID_TOPIC' => $split2_ID_TOPIC,
2587
		'split2_approved' => $split2_approved,
2588
	), $id_board);
2589
2590
	require_once(SUBSDIR . '/FollowUps.subs.php');
2591
2592
	// Let's see if we can create a stronger bridge between the two topics
2593
	// @todo not sure what message from the oldest topic I should link to the new one, so I'll go with the first
2594
	linkMessages($split1_first_msg, $split2_ID_TOPIC);
2595
2596
	// Copy log topic entries.
2597
	// @todo This should really be chunked.
2598
	$request = $db->query('', '
2599
		SELECT id_member, id_msg, unwatched
2600
		FROM {db_prefix}log_topics
2601
		WHERE id_topic = {int:id_topic}',
2602
		array(
2603
			'id_topic' => (int) $split1_ID_TOPIC,
2604
		)
2605
	);
2606
	if ($db->num_rows($request) > 0)
2607
	{
2608
		$replaceEntries = array();
2609 View Code Duplication
		while ($row = $db->fetch_assoc($request))
2610
			$replaceEntries[] = array($row['id_member'], $split2_ID_TOPIC, $row['id_msg'], $row['unwatched']);
2611
2612
		require_once(SUBSDIR . '/Topic.subs.php');
2613
		markTopicsRead($replaceEntries, false);
2614
		unset($replaceEntries);
2615
	}
2616
	$db->free_result($request);
2617
2618
	// Housekeeping.
2619
	updateTopicStats();
2620
	updateLastMessages($id_board);
2621
2622
	logAction('split', array('topic' => $split1_ID_TOPIC, 'new_topic' => $split2_ID_TOPIC, 'board' => $id_board));
2623
2624
	// Notify people that this topic has been split?
2625
	require_once(SUBSDIR . '/Notification.subs.php');
2626
	sendNotifications($split1_ID_TOPIC, 'split');
2627
2628
	// If there's a search index that needs updating, update it...
2629
	$searchAPI = new \ElkArte\Search\SearchApiWrapper(!empty($modSettings['search_index']) ? $modSettings['search_index'] : '');
2630
	$searchAPI->topicSplit($split2_ID_TOPIC, $splitMessages);
2631
2632
	// Return the ID of the newly created topic.
2633
	return $split2_ID_TOPIC;
2634
}
2635
2636
/**
2637
 * If we are also moving the topic somewhere else, let's try do to it
2638
 * Includes checks for permissions move_own/any, etc.
2639
 *
2640
 * @param mixed[] $boards an array containing basic info of the origin and destination boards (from splitDestinationBoard)
2641
 * @param int $totopic id of the destination topic
2642
 * @throws \ElkArte\Exceptions\Exception
2643
 */
2644
function splitAttemptMove($boards, $totopic)
2645
{
2646
	global $board;
2647
2648
	$db = database();
2649
2650
	// If the starting and final boards are different we have to check some permissions and stuff
2651
	if ($boards['destination']['id'] != $board)
2652
	{
2653
		$doMove = false;
2654
		if (allowedTo('move_any'))
2655
			$doMove = true;
2656
		else
2657
		{
2658
			$new_topic = getTopicInfo($totopic);
2659
			if ($new_topic['id_member_started'] == User::$info->id && allowedTo('move_own'))
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2660
				$doMove = true;
2661
		}
2662
2663
		if ($doMove)
2664
		{
2665
			// Update member statistics if needed
2666
			// @todo this should probably go into a function...
2667
			if ($boards['destination']['count_posts'] != $boards['current']['count_posts'])
2668
			{
2669
				$request = $db->query('', '
2670
					SELECT id_member
2671
					FROM {db_prefix}messages
2672
					WHERE id_topic = {int:current_topic}
2673
						AND approved = {int:is_approved}',
2674
					array(
2675
						'current_topic' => $totopic,
2676
						'is_approved' => 1,
2677
					)
2678
				);
2679
				$posters = array();
2680
				while ($row = $db->fetch_assoc($request))
2681
				{
2682
					if (!isset($posters[$row['id_member']]))
2683
						$posters[$row['id_member']] = 0;
2684
2685
					$posters[$row['id_member']]++;
2686
				}
2687
				$db->free_result($request);
2688
2689
				require_once(SUBSDIR . '/Members.subs.php');
2690 View Code Duplication
				foreach ($posters as $id_member => $posts)
2691
				{
2692
					// The board we're moving from counted posts, but not to.
2693
					if (empty($boards['current']['count_posts']))
2694
						updateMemberData($id_member, array('posts' => 'posts - ' . $posts));
2695
					// The reverse: from didn't, to did.
2696
					else
2697
						updateMemberData($id_member, array('posts' => 'posts + ' . $posts));
2698
				}
2699
			}
2700
2701
			// And finally move it!
2702
			moveTopics($totopic, $boards['destination']['id']);
2703
		}
2704
		else
2705
			$boards['destination'] = $boards['current'];
2706
	}
2707
}
2708
2709
/**
2710
 * Retrieves information of the current and destination board of a split topic
2711
 *
2712
 * @param int $toboard
2713
 *
2714
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2715
 * @throws \ElkArte\Exceptions\Exception no_board
2716
 */
2717
function splitDestinationBoard($toboard = 0)
2718
{
2719
	global $board, $topic;
2720
2721
	$current_board = boardInfo($board, $topic);
2722
	if (empty($current_board))
2723
		throw new \ElkArte\Exceptions\Exception('no_board');
2724
2725
	if (!empty($toboard) && $board !== $toboard)
2726
	{
2727
		$destination_board = boardInfo($toboard);
2728
		if (empty($destination_board))
2729
			throw new \ElkArte\Exceptions\Exception('no_board');
2730
	}
2731
2732
	if (!isset($destination_board))
2733
		$destination_board = array_merge($current_board, array('id' => $board));
2734
	else
2735
		$destination_board['id'] = $toboard;
2736
2737
	return array('current' => $current_board, 'destination' => $destination_board);
2738
}
2739
2740
/**
2741
 * Retrieve topic notifications count.
2742
 * (used by createList() callbacks, amongst others.)
2743
 *
2744
 * @param int $memID id_member
2745
 * @return integer
2746
 */
2747
function topicNotificationCount($memID)
2748
{
2749
	global $modSettings;
2750
2751
	$db = database();
2752
2753
	$request = $db->query('', '
2754
		SELECT COUNT(*)
2755
		FROM {db_prefix}log_notify AS ln' . (!$modSettings['postmod_active'] && User::$info->query_see_board === '1=1' ? '' : '
0 ignored issues
show
Documentation introduced by
The property query_see_board does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2756
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)') . (User::$info->query_see_board === '1=1' ? '' : '
0 ignored issues
show
Documentation introduced by
The property query_see_board does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2757
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)') . '
2758
		WHERE ln.id_member = {int:selected_member}' . (User::$info->query_see_board === '1=1' ? '' : '
0 ignored issues
show
Documentation introduced by
The property query_see_board does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2759
			AND {query_see_board}') . ($modSettings['postmod_active'] ? '
2760
			AND t.approved = {int:is_approved}' : ''),
2761
		array(
2762
			'selected_member' => $memID,
2763
			'is_approved' => 1,
2764
		)
2765
	);
2766
	list ($totalNotifications) = $db->fetch_row($request);
2767
	$db->free_result($request);
2768
2769
	return (int) $totalNotifications;
2770
}
2771
2772
/**
2773
 * Retrieve all topic notifications for the given user.
2774
 * (used by createList() callbacks)
2775
 *
2776
 * @param int $start The item to start with (for pagination purposes)
2777
 * @param int $items_per_page  The number of items to show per page
2778
 * @param string $sort A string indicating how to sort the results
2779
 * @param int $memID id_member
2780
 * @return array
2781
 */
2782
function topicNotifications($start, $items_per_page, $sort, $memID)
2783
{
2784
	global $modSettings;
2785
2786
	$db = database();
2787
2788
	// All the topics with notification on...
2789
	$request = $db->query('', '
2790
		SELECT
2791
			COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from, b.id_board, b.name,
2792
			t.id_topic, ms.subject, ms.id_member, COALESCE(mem.real_name, ms.poster_name) AS real_name_col,
2793
			ml.id_msg_modified, ml.poster_time, ml.id_member AS id_member_updated,
2794
			COALESCE(mem2.real_name, ml.poster_name) AS last_real_name
2795
		FROM {db_prefix}log_notify AS ln
2796
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ')
2797
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board})
2798
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
2799
			INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
2800
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ms.id_member)
2801
			LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = ml.id_member)
2802
			LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
2803
			LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member})
2804
		WHERE ln.id_member = {int:selected_member}
2805
		ORDER BY {raw:sort}
2806
		LIMIT {int:offset}, {int:items_per_page}',
2807
		array(
2808
			'current_member' => User::$info->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2809
			'is_approved' => 1,
2810
			'selected_member' => $memID,
2811
			'sort' => $sort,
2812
			'offset' => $start,
2813
			'items_per_page' => $items_per_page,
2814
		)
2815
	);
2816
	$notification_topics = array();
2817
	while ($row = $db->fetch_assoc($request))
2818
	{
2819
		$row['subject'] = censor($row['subject']);
2820
		$topic_href = getUrl('topic', ['topic' => $row['id_topic'], 'start' => '0', 'subject' => $row['subject']]);
2821
		$topic_new_href = getUrl('topic', ['topic' => $row['id_topic'], 'start' => 'msg' . $row['new_from'], 'subject' => $row['subject']]);
2822
2823
		$notification_topics[] = array(
2824
			'id' => $row['id_topic'],
2825
			'poster_link' => empty($row['id_member']) ? $row['real_name_col'] : '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name_col']]) . '">' . $row['real_name_col'] . '</a>',
2826
			'poster_updated_link' => empty($row['id_member_updated']) ? $row['last_real_name'] : '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member_updated'], 'name' => $row['last_real_name']]) . '</a>',
2827
			'subject' => $row['subject'],
2828
			'href' => $topic_href,
2829
			'link' => '<a href="' . $topic_href . '">' . $row['subject'] . '</a>',
2830
			'new' => $row['new_from'] <= $row['id_msg_modified'],
2831
			'new_from' => $row['new_from'],
2832
			'updated' => standardTime($row['poster_time']),
2833
			'new_href' => $topic_new_href . '#new',
2834
			'new_link' => '<a href="' . $topic_new_href . '#new">' . $row['subject'] . '</a>',
2835
			'board_link' => '<a href="' . getUrl('board', ['board' => $row['id_board'], 'start' => '0', 'name' => $row['name']]) . '">' . $row['name'] . '</a>',
2836
		);
2837
	}
2838
	$db->free_result($request);
2839
2840
	return $notification_topics;
2841
}
2842
2843
/**
2844
 * Get a list of posters in this topic, and their posts counts in the topic.
2845
 * Used to update users posts counts when topics are moved or are deleted.
2846
 *
2847
 * @param int $id_topic topic id to work with
2848
 *
2849
 * @return array
2850
 */
2851 View Code Duplication
function postersCount($id_topic)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2852
{
2853
	$db = database();
2854
2855
	// We only care about approved topics, the rest don't count.
2856
	$request = $db->query('', '
2857
		SELECT id_member
2858
		FROM {db_prefix}messages
2859
		WHERE id_topic = {int:current_topic}
2860
			AND approved = {int:is_approved}',
2861
		array(
2862
			'current_topic' => $id_topic,
2863
			'is_approved' => 1,
2864
		)
2865
	);
2866
	$posters = array();
2867
	while ($row = $db->fetch_assoc($request))
2868
	{
2869
		if (!isset($posters[$row['id_member']]))
2870
			$posters[$row['id_member']] = 0;
2871
2872
		$posters[$row['id_member']]++;
2873
	}
2874
	$db->free_result($request);
2875
2876
	return $posters;
2877
}
2878
2879
/**
2880
 * Counts topics from the given id_board.
2881
 *
2882
 * @param int $board
2883
 * @param bool $approved
2884
 * @return int
2885
 */
2886 View Code Duplication
function countTopicsByBoard($board, $approved = false)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2887
{
2888
	$db = database();
2889
2890
	// How many topics are on this board?  (used for paging.)
2891
	$request = $db->query('', '
2892
		SELECT COUNT(*)
2893
		FROM {db_prefix}topics AS t
2894
		WHERE t.id_board = {int:id_board}' . (empty($approved) ? '
2895
			AND t.approved = {int:is_approved}' : ''),
2896
		array(
2897
			'id_board' => $board,
2898
			'is_approved' => 1,
2899
		)
2900
	);
2901
	list ($topics) = $db->fetch_row($request);
2902
	$db->free_result($request);
2903
2904
	return $topics;
2905
}
2906
2907
/**
2908
 * Determines topics which can be merged from a specific board.
2909
 *
2910
 * @param int $id_board
2911
 * @param int $id_topic
2912
 * @param bool $approved
2913
 * @param int $offset
2914
 * @return array
2915
 */
2916
function mergeableTopics($id_board, $id_topic, $approved, $offset)
2917
{
2918
	global $modSettings;
2919
2920
	$db = database();
2921
2922
	// Get some topics to merge it with.
2923
	$request = $db->query('', '
2924
		SELECT t.id_topic, m.subject, m.id_member, COALESCE(mem.real_name, m.poster_name) AS poster_name
2925
		FROM {db_prefix}topics AS t
2926
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
2927
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
2928
		WHERE t.id_board = {int:id_board}
2929
			AND t.id_topic != {int:id_topic}' . (empty($approved) ? '
2930
			AND t.approved = {int:is_approved}' : '') . '
2931
		ORDER BY t.is_sticky DESC, t.id_last_msg DESC
2932
		LIMIT {int:offset}, {int:limit}',
2933
		array(
2934
			'id_board' => $id_board,
2935
			'id_topic' => $id_topic,
2936
			'offset' => $offset,
2937
			'limit' => $modSettings['defaultMaxTopics'],
2938
			'is_approved' => 1,
2939
		)
2940
	);
2941
	$topics = array();
2942
	while ($row = $db->fetch_assoc($request))
2943
	{
2944
		$row['subject'] = censor($row['subject']);
2945
2946
		$href = getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['poster_name']]);
2947
		$topics[] = array(
2948
			'id' => $row['id_topic'],
2949
			'poster' => array(
2950
				'id' => $row['id_member'],
2951
				'name' => $row['poster_name'],
2952
				'href' => empty($row['id_member']) ? '' : $href,
2953
				'link' => empty($row['id_member']) ? $row['poster_name'] : '<a href="' . $href . '" target="_blank" class="new_win">' . $row['poster_name'] . '</a>'
2954
			),
2955
			'subject' => $row['subject'],
2956
			'js_subject' => addcslashes(addslashes($row['subject']), '/')
2957
		);
2958
	}
2959
	$db->free_result($request);
2960
2961
	return $topics;
2962
}
2963
2964
/**
2965
 * Determines all messages from a given array of topics.
2966
 *
2967
 * @param int[] $topics integer array of topics to work with
2968
 * @return array
2969
 */
2970
function messagesInTopics($topics)
2971
{
2972
	$db = database();
2973
2974 12
	// Obtain all the message ids we are going to affect.
2975
	$request = $db->query('', '
2976
		SELECT id_msg
2977 12
		FROM {db_prefix}messages
2978
		WHERE id_topic IN ({array_int:topic_list})',
2979
		array(
2980
			'topic_list' => $topics,
2981
	));
2982 12
	$messages = array();
2983
	while ($row = $db->fetch_assoc($request))
2984 12
		$messages[] = $row['id_msg'];
2985 12
	$db->free_result($request);
2986 12
2987 12
	return $messages;
2988
}
2989 12
2990
/**
2991
 * Retrieves the members that posted in a group of topics.
2992
 *
2993
 * @param int[] $topics integer array of topics to work with
2994
 * @return array of topics each member posted in (grouped by members)
2995
 */
2996
function topicsPosters($topics)
2997
{
2998
	$db = database();
2999
3000
	// Obtain all the member ids
3001
	$members = array();
3002
	$request = $db->query('', '
3003
		SELECT id_member, id_topic
3004
		FROM {db_prefix}messages
3005
		WHERE id_topic IN ({array_int:topic_list})',
3006
		array(
3007
			'topic_list' => $topics,
3008
	));
3009
	while ($row = $db->fetch_assoc($request))
3010
		$members[$row['id_member']][] = $row['id_topic'];
3011
	$db->free_result($request);
3012
3013
	return $members;
3014
}
3015
3016
/**
3017
 * Updates all the tables involved when two or more topics are merged
3018
 *
3019
 * @param int $first_msg the first message of the new topic
3020
 * @param int[] $topics ids of all the topics merged
3021
 * @param int $id_topic id of the merged topic
3022
 * @param int $target_board id of the target board where the topic will resides
3023
 * @param string $target_subject subject of the new topic
3024
 * @param string $enforce_subject if not empty all the messages will be set to the same subject
3025
 * @param int[] $notifications array of topics with active notifications
3026
 */
3027
function fixMergedTopics($first_msg, $topics, $id_topic, $target_board, $target_subject, $enforce_subject, $notifications)
3028
{
3029
	$db = database();
3030
3031
	// Delete the remaining topics.
3032
	$deleted_topics = array_diff($topics, array($id_topic));
3033
	$db->query('', '
3034
		DELETE FROM {db_prefix}topics
3035
		WHERE id_topic IN ({array_int:deleted_topics})',
3036
		array(
3037
			'deleted_topics' => $deleted_topics,
3038
		)
3039
	);
3040
3041
	$db->query('', '
3042
		DELETE FROM {db_prefix}log_search_subjects
3043
		WHERE id_topic IN ({array_int:deleted_topics})',
3044
		array(
3045
			'deleted_topics' => $deleted_topics,
3046
		)
3047
	);
3048
3049
	// Change the topic IDs of all messages that will be merged.  Also adjust subjects if 'enforce subject' was checked.
3050
	$db->query('', '
3051
		UPDATE {db_prefix}messages
3052
		SET
3053
			id_topic = {int:id_topic},
3054
			id_board = {int:target_board}' . (empty($enforce_subject) ? '' : ',
3055
			subject = {string:subject}') . '
3056
		WHERE id_topic IN ({array_int:topic_list})',
3057
		array(
3058
			'topic_list' => $topics,
3059
			'id_topic' => $id_topic,
3060
			'target_board' => $target_board,
3061
			'subject' => response_prefix() . $target_subject,
3062
		)
3063
	);
3064
3065
	// Any reported posts should reflect the new board.
3066
	$db->query('', '
3067
		UPDATE {db_prefix}log_reported
3068
		SET
3069
			id_topic = {int:id_topic},
3070
			id_board = {int:target_board}
3071
		WHERE id_topic IN ({array_int:topics_list})',
3072
		array(
3073
			'topics_list' => $topics,
3074
			'id_topic' => $id_topic,
3075
			'target_board' => $target_board,
3076
		)
3077
	);
3078
3079
	// Change the subject of the first message...
3080
	$db->query('', '
3081
		UPDATE {db_prefix}messages
3082
		SET subject = {string:target_subject}
3083
		WHERE id_msg = {int:first_msg}',
3084
		array(
3085
			'first_msg' => $first_msg,
3086
			'target_subject' => $target_subject,
3087
		)
3088
	);
3089
3090
	// Adjust all calendar events to point to the new topic.
3091
	$db->query('', '
3092
		UPDATE {db_prefix}calendar
3093
		SET
3094
			id_topic = {int:id_topic},
3095
			id_board = {int:target_board}
3096
		WHERE id_topic IN ({array_int:deleted_topics})',
3097
		array(
3098
			'deleted_topics' => $deleted_topics,
3099
			'id_topic' => $id_topic,
3100
			'target_board' => $target_board,
3101
		)
3102
	);
3103
3104
	// Merge log topic entries.
3105
	// The unwatched setting comes from the oldest topic
3106
	$request = $db->query('', '
3107
		SELECT id_member, MIN(id_msg) AS new_id_msg, unwatched
3108
		FROM {db_prefix}log_topics
3109
		WHERE id_topic IN ({array_int:topics})
3110
		GROUP BY id_member',
3111
		array(
3112
			'topics' => $topics,
3113
		)
3114
	);
3115
3116
	if ($db->num_rows($request) > 0)
3117
	{
3118
		$replaceEntries = array();
3119 View Code Duplication
		while ($row = $db->fetch_assoc($request))
3120
			$replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg'], $row['unwatched']);
3121
3122
		markTopicsRead($replaceEntries, true);
3123
		unset($replaceEntries);
3124
3125
		// Get rid of the old log entries.
3126
		$db->query('', '
3127
			DELETE FROM {db_prefix}log_topics
3128
			WHERE id_topic IN ({array_int:deleted_topics})',
3129
			array(
3130
				'deleted_topics' => $deleted_topics,
3131
			)
3132
		);
3133
	}
3134
	$db->free_result($request);
3135
3136
	if (!empty($notifications))
3137
	{
3138
		$request = $db->query('', '
3139
			SELECT id_member, MAX(sent) AS sent
3140
			FROM {db_prefix}log_notify
3141
			WHERE id_topic IN ({array_int:topics_list})
3142
			GROUP BY id_member',
3143
			array(
3144
				'topics_list' => $notifications,
3145
			)
3146
		);
3147
		if ($db->num_rows($request) > 0)
3148
		{
3149
			$replaceEntries = array();
3150 View Code Duplication
			while ($row = $db->fetch_assoc($request))
3151
				$replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']);
3152
3153
			$db->insert('replace',
3154
					'{db_prefix}log_notify',
3155
					array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'sent' => 'int'),
3156
					$replaceEntries,
3157
					array('id_member', 'id_topic', 'id_board')
3158
				);
3159
			unset($replaceEntries);
3160
3161
			$db->query('', '
3162
				DELETE FROM {db_prefix}log_topics
3163
				WHERE id_topic IN ({array_int:deleted_topics})',
3164
				array(
3165
					'deleted_topics' => $deleted_topics,
3166
				)
3167
			);
3168
		}
3169
		$db->free_result($request);
3170
	}
3171
}
3172
3173
/**
3174
 * Load the subject from a given topic id.
3175
 *
3176
 * @param int $id_topic
3177
 *
3178
 * @return string
3179
 * @throws \ElkArte\Exceptions\Exception topic_gone
3180
 */
3181
function getSubject($id_topic)
3182
{
3183
	global $modSettings;
3184
3185
	$db = database();
3186
3187
	$request = $db->query('', '
3188
		SELECT ms.subject
3189
		FROM {db_prefix}topics AS t
3190
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
3191
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
3192
		WHERE t.id_topic = {int:search_topic_id}
3193
			AND {query_see_board}' . ($modSettings['postmod_active'] ? '
3194
			AND t.approved = {int:is_approved_true}' : '') . '
3195
		LIMIT 1',
3196
		array(
3197
			'is_approved_true' => 1,
3198
			'search_topic_id' => $id_topic,
3199
		)
3200
	);
3201
3202
	if ($db->num_rows($request) == 0)
3203
		throw new \ElkArte\Exceptions\Exception('topic_gone', false);
3204
3205
	list ($subject) = $db->fetch_row($request);
3206
	$db->free_result($request);
3207
3208
	return $subject;
3209
}
3210
3211
/**
3212
 * This function updates the total number of topics,
3213
 * or if parameter $increment is true it simply increments them.
3214
 *
3215
 * @param bool|null $increment = null if true, increment + 1 the total topics, otherwise recount all topics
3216
 */
3217
function updateTopicStats($increment = null)
3218
{
3219
	global $modSettings;
3220
3221 22
	$db = database();
3222
3223 22
	if ($increment === true)
3224
		updateSettings(array('totalTopics' => true), true);
3225 22
	else
3226 22
	{
3227
		// Get the number of topics - a SUM is better for InnoDB tables.
3228
		// We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
3229
		$request = $db->query('', '
3230
			SELECT SUM(num_topics + unapproved_topics) AS total_topics
3231 12
			FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
3232
			WHERE id_board != {int:recycle_board}' : ''),
3233 12
			array(
3234 12
				'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
3235
			)
3236 12
		);
3237
		$row = $db->fetch_assoc($request);
3238
		$db->free_result($request);
3239 12
3240 12
		updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
3241
	}
3242 12
}
3243
3244 22
/**
3245
 * Toggles the locked status of the passed id_topic's checking for permissions.
3246
 *
3247
 * @param int[] $topics The topics to lock (can be an id or an array of ids).
3248
 * @param bool $log if true logs the action.
3249
 * @throws \ElkArte\Exceptions\Exception
3250
 */
3251
function toggleTopicsLock($topics, $log = false)
3252
{
3253
	global $board;
3254
3255
	$db = database();
3256
3257
	$needs_check = !empty($board) && !allowedTo('lock_any');
3258
	$lockCache = array();
3259
3260
	$topicAttribute = topicAttribute($topics, array('id_topic', 'locked', 'id_board', 'id_member_started'));
3261
3262
	foreach ($topicAttribute as $row)
3263
	{
3264
		// Skip the entry if it needs to be checked and the user is not the owen and
3265
		// the topic was not locked or locked by someone with more permissions
3266
		if ($needs_check && (User::$info->id != $row['id_member_started'] || !in_array($row['locked'], array(0, 2))))
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
3267
			continue;
3268
3269
		$lockCache[] = $row['id_topic'];
3270
3271
		if ($log)
3272
		{
3273
			$lockStatus = empty($row['locked']) ? 'lock' : 'unlock';
3274
3275
			logAction($lockStatus, array('topic' => $row['id_topic'], 'board' => $row['id_board']));
3276
			sendNotifications($row['id_topic'], $lockStatus);
3277
		}
3278
	}
3279
3280
	// It could just be that *none* were their own topics...
3281
	if (!empty($lockCache))
3282
	{
3283
		// Alternate the locked value.
3284
		$db->query('', '
3285
			UPDATE {db_prefix}topics
3286
			SET locked = CASE WHEN locked = {int:is_locked} THEN ' . (allowedTo('lock_any') ? '1' : '2') . ' ELSE 0 END
3287
			WHERE id_topic IN ({array_int:locked_topic_ids})',
3288
			array(
3289
				'locked_topic_ids' => $lockCache,
3290
				'is_locked' => 0,
3291
			)
3292
		);
3293
	}
3294
}
3295