Passed
Pull Request — patch_1-1-7 (#3404)
by
unknown
15:47
created

Unread   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 171
dl 0
loc 443
rs 3.52
c 1
b 0
f 0
wmc 61

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getUnreads() 0 6 2
A setEarliestMsg() 0 3 1
A setSorting() 0 4 1
B _countRecentTopics() 0 23 8
A _countUnreadReplies() 0 37 4
A setAction() 0 4 2
A bodyPreview() 0 6 2
D _getUnreadReplies() 0 105 19
A setBoards() 0 6 2
C _getUnreadTopics() 0 79 16
A isSortAsc() 0 3 1
A __construct() 0 8 1
A numUnreads() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Unread often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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

1
<?php
2
3
/**
4
 * Find and retrieve information about recently posted topics, messages, and the like.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.7
15
 *
16
 */
17
18
/**
19
 * Unread posts and replies Controller
20
 */
21
class Unread
22
{
23
	const UNREAD = 0;
24
	const UNREADREPLIES = 1;
25
26
	/** @var bool */
27
	private $_ascending = false;
28
	/** @var string */
29
	private $_sort_query = '';
30
	/** @var int */
31
	private $_num_topics = 0;
32
	/** @var int */
33
	private $_min_message = 0;
34
	/** @var int */
35
	private $_action = self::UNREAD;
36
	/** @var int */
37
	private $_earliest_msg = 0;
38
	/** @var bool */
39
	private $_showing_all_topics = false;
40
	/** @var int */
41
	private $_user_id = 0;
42
	/** @var bool */
43
	private $_post_mod = false;
44
	/** @var bool */
45
	private $_unwatch = false;
46
	/** @var Database|null */
47
	private $_db = null;
48
	/** @var int|string */
49
	private $_preview_bodies = 0;
50
51
	/**
52
	 * Parameters for the main query.
53
	 */
54
	private $_query_parameters = array();
55
56
	/**
57
	 * Constructor
58
	 *
59
	 * @param int $user - ID of the user
60
	 * @param bool|int $post_mod - if post moderation is active or not
61
	 * @param bool|int $unwatch - if unwatch topics is active or not
62
	 * @param bool|int $showing_all_topics - Is the user looking at all the unread
63
	 *             replies, or the recent topics?
64
	 */
65
	public function __construct($user, $post_mod, $unwatch, $showing_all_topics = false)
66
	{
67
		$this->_user_id = (int) $user;
68
		$this->_post_mod = (bool) $post_mod;
69
		$this->_unwatch = (bool) $unwatch;
70
		$this->_showing_all_topics = (bool) $showing_all_topics;
71
72
		$this->_db = database();
73
	}
74
75
	/**
76
	 * Sets the boards the member is looking at
77
	 *
78
	 * @param int|int[] $boards - the id of the boards
79
	 */
80
	public function setBoards($boards)
81
	{
82
		if (is_array($boards))
83
			$this->_query_parameters['boards'] = $boards;
84
		else
85
			$this->_query_parameters['boards'] = array($boards);
86
	}
87
88
	/**
89
	 * The action the user is performing
90
	 *
91
	 * @param int $action - Unread::UNREAD, Unread::UNREADREPLIES
92
	 */
93
	public function setAction($action)
94
	{
95
		if (in_array($action, array(self::UNREAD, self::UNREADREPLIES)))
96
			$this->_action = $action;
97
	}
98
99
	/**
100
	 * Sets the lower message id to be taken in consideration
101
	 *
102
	 * @param int $msg_id - id of the earliest message to consider
103
	 */
104
	public function setEarliestMsg($msg_id)
105
	{
106
		$this->_earliest_msg = (int) $msg_id;
107
	}
108
109
	/**
110
	 * Sets the sorting query and the direction
111
	 *
112
	 * @param string $query - The query to be used in the ORDER clause
113
	 * @param bool|int $asc - If the sorting is ascending or not
114
	 */
115
	public function setSorting($query, $asc)
116
	{
117
		$this->_sort_query = $query;
118
		$this->_ascending = $asc;
0 ignored issues
show
Documentation Bug introduced by
It seems like $asc can also be of type integer. However, the property $_ascending is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
119
	}
120
121
	/**
122
	 * Return the sorting direction
123
	 *
124
	 * @return boolean
125
	 */
126
	public function isSortAsc()
127
	{
128
		return $this->_ascending;
129
	}
130
131
	/**
132
	 * Sets if the data returned by the class will include a shorted version
133
	 * of the body of the last message.
134
	 *
135
	 * @param bool|int $chars - The number of chars to retrieve.
136
	 *                 If true it will return the entire body,
137
	 *                 if 0 no preview will be generated.
138
	 */
139
	public function bodyPreview($chars)
140
	{
141
		if ($chars === true)
142
			$this->_preview_bodies = 'all';
143
		else
144
			$this->_preview_bodies = (int) $chars;
145
	}
146
147
	/**
148
	 * Counts the number of unread topics or messages
149
	 *
150
	 * @param bool $first_login - If this is the first login of the user
151
	 * @param int $id_msg_last_visit - highest id_msg found during the last visit
152
	 */
153
	public function numUnreads($first_login = false, $id_msg_last_visit = 0)
154
	{
155
		if ($this->_action === self::UNREAD)
156
			$this->_countRecentTopics($first_login, $id_msg_last_visit);
157
		else
158
			$this->_countUnreadReplies();
159
160
		return $this->_num_topics;
161
	}
162
163
	/**
164
	 * Retrieves unread topics or messages
165
	 *
166
	 * @param string $join - kind of "JOIN" to execute. If 'topic' JOINs boards on
167
	 *                       the topics table, otherwise ('message') the JOIN is on
168
	 *                       the messages table
169
	 * @param int $start - position to start the query
170
	 * @param int $limit - number of entries to grab
171
	 * @param bool $include_avatars - if avatars should be retrieved as well
172
	 * @return mixed[] - see Topic_Util::prepareContext
173
	 */
174
	public function getUnreads($join, $start, $limit, $include_avatars)
175
	{
176
		if ($this->_action === self::UNREAD)
177
			return $this->_getUnreadTopics($join, $start, $limit, $include_avatars);
178
		else
179
			return $this->_getUnreadReplies($start, $limit, $include_avatars);
180
	}
181
182
	/**
183
	 * Retrieves unread topics, used in *all* unread replies with temp table and
184
	 * new posts since last visit
185
	 *
186
	 * @param string $join - kind of "JOIN" to execute. If 'topic' JOINs boards on
187
	 *                       the topics table, otherwise ('message') the JOIN is on
188
	 *                       the messages table
189
	 * @param int $start - position to start the query
190
	 * @param int $limit - number of entries to grab
191
	 * @param bool|int $include_avatars - if avatars should be retrieved as well
192
	 * @return mixed[] - see Topic_Util::prepareContext
193
	 */
194
	private function _getUnreadTopics($join, $start, $limit, $include_avatars = false)
195
	{
196
		if ($this->_preview_bodies == 'all')
197
			$body_query = 'ml.body AS last_body, ms.body AS first_body,';
198
		else
199
		{
200
			// If empty, no preview at all
201
			if (empty($this->_preview_bodies))
202
				$body_query = '';
203
			// Default: a SUBSTRING
204
			else
205
				$body_query = 'SUBSTRING(ml.body, 1, ' . ($this->_preview_bodies + 256) . ') AS last_body, SUBSTRING(ms.body, 1, ' . ($this->_preview_bodies + 256) . ') AS first_body,';
206
		}
207
208
		if (!empty($include_avatars))
209
		{
210
			// Double equal comparison for 1 because it is backward compatible with 1.0 where the value was true/false
211
			if ($include_avatars == 1 || $include_avatars === 3)
212
			{
213
				$custom_selects = array('meml.avatar', 'COALESCE(a.id_attach, 0) AS id_attach', 'a.filename', 'a.attachment_type', 'meml.email_address');
214
				$custom_joins = array('LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = ml.id_member AND a.id_member != 0)');
215
			}
216
			else
217
			{
218
				$custom_selects = array();
219
				$custom_joins = array();
220
			}
221
222
			if ($include_avatars === 2 || $include_avatars === 3)
223
			{
224
				$custom_selects = array_merge($custom_selects, array('memf.avatar AS avatar_first', 'COALESCE(af.id_attach, 0) AS id_attach_first', 'af.filename AS filename_first', 'af.attachment_type AS attachment_type_first', 'memf.email_address AS email_address_first'));
225
				$custom_joins = array_merge($custom_joins, array('LEFT JOIN {db_prefix}attachments AS af ON (af.id_member = mf.id_member AND af.id_member != 0)'));
226
			}
227
		}
228
229
		$request = $this->_db->query('substring', '
0 ignored issues
show
Bug introduced by
The method query() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

229
		/** @scrutinizer ignore-call */ 
230
  $request = $this->_db->query('substring', '

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
230
			SELECT
231
				ms.subject AS first_subject, ms.poster_time AS first_poster_time, ms.poster_name AS first_member_name,
232
				ms.id_topic, t.id_board, b.name AS bname, t.num_replies, t.num_views, t.num_likes, t.approved,
233
				ms.id_member AS first_id_member, ml.id_member AS last_id_member, ml.poster_name AS last_member_name,
234
				ml.poster_time AS last_poster_time, COALESCE(mems.real_name, ms.poster_name) AS first_display_name,
235
				COALESCE(meml.real_name, ml.poster_name) AS last_display_name, ml.subject AS last_subject,
236
				ml.icon AS last_icon, ms.icon AS first_icon, t.id_poll, t.is_sticky, t.locked, ml.modified_time AS last_modified_time,
237
				COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from,
238
				' . $body_query . '
239
				' . (!empty($custom_selects) ? implode(',', $custom_selects) . ', ' : '') . '
240
				ml.smileys_enabled AS last_smileys, ms.smileys_enabled AS first_smileys, t.id_first_msg, t.id_last_msg
241
			FROM {db_prefix}messages AS ms
242
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ms.id_topic AND t.id_first_msg = ms.id_msg)
243
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)' . ($join == 'topics' ? '
244
				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)' : '
245
				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = ms.id_board)') . '
246
				LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member)
247
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)' . ($this->_have_temp_table ? '
0 ignored issues
show
Bug Best Practice introduced by
The property _have_temp_table does not exist on Unread. Did you maybe forget to declare it?
Loading history...
248
				LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic)' : '
249
				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})') . (!empty($custom_joins) ? implode("\n\t\t\t\t", $custom_joins) : '') . '
250
				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})
251
			WHERE t.id_board IN ({array_int:boards})
252
				AND t.id_last_msg >= {int:min_message}
253
				AND COALESCE(lt.id_msg, lmr.id_msg, 0) < ml.id_msg' .
254
				($this->_post_mod ? ' AND ms.approved = {int:is_approved}' : '') .
255
				($this->_unwatch ? ' AND COALESCE(lt.unwatched, 0) != 1' : '') . '
256
			ORDER BY {raw:order}
257
			LIMIT {int:offset}, {int:limit}',
258
			array_merge($this->_query_parameters, array(
259
				'current_member' => $this->_user_id,
260
				'min_message' => $this->_min_message,
261
				'is_approved' => 1,
262
				'order' => $this->_sort_query . ($this->_ascending ? '' : ' DESC'),
263
				'offset' => $start,
264
				'limit' => $limit,
265
			))
266
		);
267
		$topics = array();
268
		while ($row = $this->_db->fetch_assoc($request))
269
			$topics[] = $row;
270
		$this->_db->free_result($request);
271
272
		return Topic_Util::prepareContext($topics, true, ((int) $this->_preview_bodies) + 128);
273
	}
274
275
	/**
276
	 * Counts unread replies
277
	 */
278
	private function _countUnreadReplies()
279
	{
280
		if (!empty($this->_have_temp_table))
281
		{
282
			$request = $this->_db->query('', '
283
				SELECT COUNT(*)
284
				FROM {db_prefix}topics_posted_in AS pi
285
					LEFT JOIN {db_prefix}log_topics_posted_in AS lt ON (lt.id_topic = pi.id_topic)
286
				WHERE pi.id_board IN ({array_int:boards})
287
					AND COALESCE(lt.id_msg, pi.id_msg) < pi.id_last_msg',
288
				array_merge($this->_query_parameters, array(
289
				))
290
			);
291
			list ($this->_num_topics) = $this->_db->fetch_row($request);
292
			$this->_db->free_result($request);
293
			$this->_min_message = 0;
294
		}
295
		else
296
		{
297
			$request = $this->_db->query('unread_fetch_topic_count', '
298
				SELECT COUNT(DISTINCT t.id_topic), MIN(t.id_last_msg)
299
				FROM {db_prefix}topics AS t
300
					INNER JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
301
					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
302
					LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})
303
				WHERE t.id_board IN ({array_int:boards})
304
					AND m.id_member = {int:current_member}
305
					AND COALESCE(lt.id_msg, lmr.id_msg, 0) < t.id_last_msg' . ($this->_post_mod ? '
306
					AND t.approved = {int:is_approved}' : '') . ($this->_unwatch ? '
307
					AND COALESCE(lt.unwatched, 0) != 1' : ''),
308
				array_merge($this->_query_parameters, array(
309
					'current_member' => $this->_user_id,
310
					'is_approved' => 1,
311
				))
312
			);
313
			list ($this->_num_topics, $this->_min_message) = $this->_db->fetch_row($request);
314
			$this->_db->free_result($request);
315
		}
316
	}
317
318
	/**
319
	 * Counts unread topics, used in *all* unread replies with temp table and
320
	 * new posts since last visit
321
	 *
322
	 * @param bool $is_first_login - if the member has already logged in at least
323
	 *             once, then there is an $id_msg_last_visit
324
	 * @param int $id_msg_last_visit - highest id_msg found during the last visit
325
	 */
326
	private function _countRecentTopics($is_first_login, $id_msg_last_visit = 0)
327
	{
328
		$request = $this->_db->query('', '
329
			SELECT COUNT(*), MIN(t.id_last_msg)
330
			FROM {db_prefix}topics AS t' . (!empty($this->_have_temp_table) ? '
331
				LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic)' : '
332
				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})') . '
333
				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})
334
			WHERE t.id_board IN ({array_int:boards})' . ($this->_showing_all_topics && !empty($this->_earliest_msg) ? '
335
				AND t.id_last_msg > {int:earliest_msg}' : (!$this->_showing_all_topics && $is_first_login ? '
336
				AND t.id_last_msg > {int:id_msg_last_visit}' : '')) . '
337
				AND COALESCE(lt.id_msg, lmr.id_msg, 0) < t.id_last_msg' .
338
				($this->_post_mod ? ' AND t.approved = {int:is_approved}' : '') .
339
				($this->_unwatch ? ' AND COALESCE(lt.unwatched, 0) != 1' : ''),
340
			array_merge($this->_query_parameters, array(
341
				'current_member' => $this->_user_id,
342
				'earliest_msg' => $this->_earliest_msg,
343
				'id_msg_last_visit' => $id_msg_last_visit,
344
				'is_approved' => 1,
345
			))
346
		);
347
		list ($this->_num_topics, $this->_min_message) = $this->_db->fetch_row($request);
348
		$this->_db->free_result($request);
349
	}
350
351
	/**
352
	 * Retrieves unread replies since last visit
353
	 *
354
	 * @param int $start - position to start the query
355
	 * @param int $limit - number of entries to grab
356
	 * @param bool|int $include_avatars - if avatars should be retrieved as well
357
	 * @return mixed[] - see Topic_Util::prepareContext
358
	 */
359
	private function _getUnreadReplies($start, $limit, $include_avatars = false)
360
	{
361
        $request = $this->_db->query('unread_replies', '
362
				SELECT DISTINCT t.id_topic
363
				FROM {db_prefix}topics AS t
364
					INNER JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic AND m.id_member = {int:current_member})' . (strpos($this->_sort_query, 'ms.') === false ? '' : '
365
					INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)') . (strpos($this->_sort_query, 'mems.') === false ? '' : '
366
					LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member)') . '
367
					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
368
					LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})
369
				WHERE t.id_board IN ({array_int:boards})
370
					AND t.id_last_msg >= {int:min_message}
371
					AND COALESCE(lt.id_msg, lmr.id_msg, 0) < t.id_last_msg' .
372
            ($this->_post_mod ? ' AND t.approved = {int:is_approved}' : '') .
373
            ($this->_unwatch ? ' AND COALESCE(lt.unwatched, 0) != 1' : '') . '
374
				ORDER BY {raw:order}
375
				LIMIT {int:offset}, {int:limit}',
376
            array_merge($this->_query_parameters, array(
377
                'current_member' => $this->_user_id,
378
                'min_message' => $this->_min_message,
379
                'is_approved' => 1,
380
                'order' => $this->_sort_query . ($this->_ascending ? '' : ' DESC'),
381
                'offset' => $start,
382
                'limit' => $limit,
383
            ))
384
        );
385
386
		$topics = array();
387
		while ($row = $this->_db->fetch_assoc($request))
388
			$topics[] = $row['id_topic'];
389
		$this->_db->free_result($request);
390
391
		// Sanity... where have you gone?
392
		if (empty($topics))
393
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array<mixed,mixed>.
Loading history...
394
395
		if ($this->_preview_bodies == 'all')
396
			$body_query = 'ml.body AS last_body, ms.body AS first_body,';
397
		else
398
		{
399
			// If empty, no preview at all
400
			if (empty($this->_preview_bodies))
401
				$body_query = '';
402
			// Default: a SUBSTRING
403
			else
404
				$body_query = 'SUBSTRING(ml.body, 1, ' . ($this->_preview_bodies + 256) . ') AS last_body, SUBSTRING(ms.body, 1, ' . ($this->_preview_bodies + 256) . ') AS first_body,';
405
		}
406
407
		if (!empty($include_avatars))
408
		{
409
			// Double equal comparison for 1 because it is backward compatible with 1.0 where the value was true/false
410
			if ($include_avatars == 1 || $include_avatars === 3)
411
			{
412
				$custom_selects = array('meml.avatar', 'COALESCE(a.id_attach, 0) AS id_attach', 'a.filename', 'a.attachment_type', 'meml.email_address');
413
				$custom_joins = array('LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = ml.id_member AND a.id_member != 0)');
414
			}
415
			else
416
			{
417
				$custom_selects = array();
418
				$custom_joins = array();
419
			}
420
421
			if ($include_avatars === 2 || $include_avatars === 3)
422
			{
423
				$custom_selects = array_merge($custom_selects, array('memf.avatar AS avatar_first', 'COALESCE(af.id_attach, 0) AS id_attach_first', 'af.filename AS filename_first', 'af.attachment_type AS attachment_type_first', 'memf.email_address AS email_address_first'));
424
				$custom_joins = array_merge($custom_joins, array('LEFT JOIN {db_prefix}attachments AS af ON (af.id_member = ms.id_member AND af.id_member != 0)'));
425
			}
426
		}
427
428
		$request = $this->_db->query('substring', '
429
			SELECT
430
				ms.subject AS first_subject, ms.poster_time AS first_poster_time, ms.id_topic, t.id_board, b.name AS bname,
431
				ms.poster_name AS first_member_name, ml.poster_name AS last_member_name, t.approved,
432
				t.num_replies, t.num_views, t.num_likes, ms.id_member AS first_id_member, ml.id_member AS last_id_member,
433
				ml.poster_time AS last_poster_time, COALESCE(mems.real_name, ms.poster_name) AS first_display_name,
434
				COALESCE(meml.real_name, ml.poster_name) AS last_display_name, ml.subject AS last_subject,
435
				ml.icon AS last_icon, ms.icon AS first_icon, t.id_poll, t.is_sticky, t.locked, ml.modified_time AS last_modified_time,
436
				COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from,
437
				' . $body_query . '
438
				' . (!empty($custom_selects) ? implode(',', $custom_selects) . ', ' : '') . '
439
				ml.smileys_enabled AS last_smileys, ms.smileys_enabled AS first_smileys, t.id_first_msg, t.id_last_msg
440
			FROM {db_prefix}topics AS t
441
				INNER JOIN {db_prefix}messages AS ms ON (ms.id_topic = t.id_topic AND ms.id_msg = t.id_first_msg)
442
				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
443
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
444
				LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member)
445
				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)
446
				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
447
				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member})' . (!empty($custom_joins) ? implode("\n\t\t\t\t", $custom_joins) : '') . '
448
			WHERE t.id_topic IN ({array_int:topic_list})
449
			ORDER BY {raw:order}
450
			LIMIT {int:limit}',
451
			array(
452
				'current_member' => $this->_user_id,
453
				'order' => $this->_sort_query . ($this->_ascending ? '' : ' DESC'),
454
				'topic_list' => $topics,
455
				'limit' => count($topics),
456
			)
457
		);
458
		$return = array();
459
		while ($row = $this->_db->fetch_assoc($request))
460
			$return[] = $row;
461
		$this->_db->free_result($request);
462
463
		return Topic_Util::prepareContext($return, true, ((int) $this->_preview_bodies) + 128);
464
	}
465
}
466