dbMostLikesGivenUser()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 57
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 23
dl 0
loc 57
rs 9.552
c 0
b 0
f 0
nc 2
nop 1
ccs 0
cts 17
cp 0
crap 12

How to fix   Long Method   

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 the database work for likes.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
use BBC\ParserWrapper;
15
use ElkArte\Helper\Util;
16
use ElkArte\MembersList;
17
use ElkArte\User;
18
19
/**
20
 * Updates the like value for a post/member combo if there are no problems with
21
 * the request, such as being a narcissist
22
 *
23
 * @param int $id_liker - user_id of the liker/disliker
24
 * @param mixed $liked_message - message array that is being worked on
25
 * @param string $direction - + for like - for unlike a previous liked one
26
 *
27
 * @return bool
28
 * @package Likes
29
 *
30
 */
31
function likePost($id_liker, $liked_message, $direction)
32
{
33
	global $txt, $modSettings;
34 6
35
	// If we have a message, then we have passed all checks ...
36
	if (!empty($liked_message))
37 6
	{
38
		// You can't like your own stuff, no matter how brilliant you think you are
39
		if ($liked_message['id_member'] == $id_liker && empty($modSettings['likeAllowSelf']))
40 4
		{
41
			return $txt['cant_like_yourself'];
42
		}
43
44
		updateLike($id_liker, $liked_message, $direction);
45
46 4
		return true;
47
	}
48 4
}
49
50
/**
51 2
 * Loads the likes for a group of messages
52
 * Returns an array of message_id to members who liked that post
53
 * If prepare is true, will also prep the array for template use
54
 *
55
 * @param int[]|int $messages
56
 * @param bool $prepare
57
 *
58
 * @return array|int[]
59
 * @package Likes
60
 *
61
 */
62
function loadLikes($messages, $prepare = true)
63
{
64
	$db = database();
65
	$likes = [];
66
67
	if (empty($messages))
68 8
	{
69 8
		return $likes;
70
	}
71 8
72
	if (!is_array($messages))
73
	{
74
		$messages = [(int) $messages];
75
	}
76 8
77
	// Load up them likes from the db
78 4
	$db->fetchQuery('
79
		SELECT
80
			l.id_member, l.id_msg,
81
			m.real_name
82 8
		FROM {db_prefix}message_likes AS l
83
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = l.id_member)
84
		WHERE id_msg IN ({array_int:id_messages})',
85
		[
86
			'id_messages' => $messages,
87
		]
88
	)->fetch_callback(
89
		function ($row) use (&$likes) {
90 8
			$likes[$row['id_msg']]['member'][$row['id_member']] = $row['real_name'];
91
		}
92 8
	);
93
94 6
	// Total likes for this group
95 8
	foreach ($likes as $msg_id => $like)
96
	{
97
		$likes[$msg_id]['count'] = count($like['member']);
98
	}
99 8
100
	if ($prepare)
101 6
	{
102
		$likes = prepareLikes($likes);
103
	}
104 8
105
	return $likes;
106 6
}
107
108
/**
109 8
 * Prepares the like array for use in the template
110
 *
111
 * What it does:
112
 *
113
 * - Replaces the current member id with 'You' if they like a post and makes it first
114
 * - Truncates the like list at a given number and adds in +x others
115
 *
116
 * @param array $likes array of like ids to process
117
 *
118
 * @return int[]
119
 * @package Likes
120
 */
121
function prepareLikes($likes)
122
{
123
	global $modSettings, $txt;
124
125
	// Prepare this like page context for the user
126
	foreach ($likes as $msg_id => $like)
127 6
	{
128
		// Did they like this message ?
129
		$you_liked = isset($like['member'][User::$info->id]);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
130 6
		if ($you_liked)
131
		{
132
			unset($likes[$msg_id]['member'][User::$info->id]);
133 4
		}
134 4
135
		// Any limits on how many to display
136 4
		$limit = $modSettings['likeDisplayLimit'] ?? 0;
137
138
		// If there are a lot of likes for this message, we cull the herd
139
		if ($limit > 0 && $like['count'] > $limit)
140 4
		{
141
			// Mix up the line, so we don't show the same ones every time
142
			shuffle($likes[$msg_id]['member']);
143 4
			$likes[$msg_id]['member'] = array_slice($likes[$msg_id]['member'], 0, $you_liked ? $limit - 1 : $limit);
144
145
			// Trick, member id's below $limit will cause a wrong +x others due to the slice above
146
			if (User::$info->id <= $limit)
147
			{
148
				$like['count'] += 1;
149
			}
150
151
			// How many others liked this
152
			$likes[$msg_id]['member'][] = sprintf('%+d %s', ($like['count'] - $limit), $txt['liked_more']);
153
		}
154
155
		// Top billing just for you, the big lights, the grand stage, plus we need that key returned
156
		if ($you_liked)
157
		{
158
			$likes[$msg_id]['member'] = [User::$info->id => $txt['liked_you']] + $likes[$msg_id]['member'];
159
		}
160 4
	}
161
162 4
	return $likes;
163
}
164
165
/**
166 6
 * Clear the likes log of older actions ... used to prevent a like love fest
167
 *
168
 * @param int $likeWaitTime
169
 * @package Likes
170
 */
171
function clearLikes($likeWaitTime)
172
{
173
	$db = database();
174
175
	// Delete all older items from the log
176
	$db->query('', '
177
		DELETE FROM {db_prefix}log_likes
178 2
		WHERE {int:current_time} - log_time > {int:wait_time}',
179
		[
180
			'wait_time' => $likeWaitTime * 60,
181 2
			'current_time' => time(),
182
		]
183
	);
184
}
185 2
186 2
/**
187
 * Checks if the member has exceeded the number of like actions they are
188
 * allowed in a given time period.
189 2
 *
190
 * - The log is maintained to the time period by the clearLikes function so
191
 * the count is always current.
192
 * - returns true if they can like again, or false if they have to wait a bit
193
 *
194
 * @param int $id_liker
195
 *
196
 * @return bool
197
 * @package Likes
198
 *
199
 */
200
function lastLikeOn($id_liker)
201
{
202
	global $modSettings;
203
204
	if (empty($modSettings['likeWaitCount']))
205
	{
206
		return true;
207
	}
208
209
	// Find out if, and how many, this user has done recently...
210
	$db = database();
211
	$request = $db->query('', '
212
		SELECT 
213
			action
214
		FROM {db_prefix}log_likes
215
		WHERE id_member = {int:current_member}',
216
		[
217
			'current_member' => $id_liker,
218
		]
219
	);
220
	$actions = $request->num_rows();
221
	$request->free_result();
222
223
	return $actions < $modSettings['likeWaitCount'];
224
}
225
226
/**
227
 * Perform a like action, either + or -
228
 *
229
 * @param int $id_liker
230
 * @param int[] $liked_message
231
 * @param string $direction - options: - or +
232
 * @package Likes
233
 */
234
function updateLike($id_liker, $liked_message, $direction)
235
{
236
	$db = database();
237
238
	// See if they already likeyed this message
239
	$request = $db->query('', '
240
		SELECT
241
			id_member
242
		FROM {db_prefix}message_likes
243 4
		WHERE id_member = {int:id_member}
244
			AND id_msg = {int:id_msg}
245
		LIMIT 1',
246 4
		[
247
			'id_member' => $id_liker,
248
			'id_msg' => $liked_message['id_msg'],
249
		]
250
	);
251
	$count = $request->num_rows();
252
	$request->free_result();
253
254 4
	// Not previously liked, and you want to
255 4
	if ($count === 0 && $direction === '+')
256
	{
257
		$db->insert('',
258 4
			'{db_prefix}message_likes',
259 4
			['id_member' => 'int', 'id_msg' => 'int', 'id_poster' => 'int', 'like_timestamp' => 'int',],
260
			[$id_liker, $liked_message['id_msg'], $liked_message['id_member'], time()],
261
			['id_msg', 'id_member', 'id_poster']
262 4
		);
263
264 4
		// If we are liking the first message in a topic, we are de facto liking the topic
265 4
		if ($liked_message['id_msg'] === $liked_message['id_first_msg'])
266 4
		{
267 4
			increaseTopicLikes($liked_message['id_topic'], $direction);
268 4
		}
269
270
		// And update the stats
271
		require_once(SUBSDIR . '/Members.subs.php');
272 4
		updateMemberData($id_liker, ['likes_given' => '+']);
273
		updateMemberData($liked_message['id_member'], ['likes_received' => '+']);
274 4
	}
275
	// Or you are just being fickle?
276
	elseif ($count !== 0 && $direction === '-')
277
	{
278 4
		$db->query('', '
279 4
			DELETE FROM {db_prefix}message_likes
280 4
			WHERE id_member = {int:id_member}
281
				AND id_msg = {int:id_msg}',
282
			[
283
				'id_member' => $id_liker,
284
				'id_msg' => $liked_message['id_msg'],
285
			]
286
		);
287
288
		// If we are unliking the first message in a topic, we are de facto unliking the topic
289
		if ($liked_message['id_msg'] === $liked_message['id_first_msg'])
290
		{
291
			increaseTopicLikes($liked_message['id_topic'], $direction);
292
		}
293
294
		// And update the stats
295
		require_once(SUBSDIR . '/Members.subs.php');
296
		updateMemberData($id_liker, ['likes_given' => '-']);
297
		updateMemberData($liked_message['id_member'], ['likes_received' => '-']);
298
	}
299
300
	// Put it in the log so we can prevent flooding the system with likes
301
	$db->replace(
302
		'{db_prefix}log_likes',
303
		['action' => 'string', 'id_target' => 'int', 'id_member' => 'int', 'log_time' => 'int'],
304
		[$direction, $liked_message['id_msg'], $id_liker, time()],
305
		['id_target', 'id_member']
306
	);
307
}
308 4
309 4
/**
310 4
 * Increase the number of likes for this topic.
311 4
 *
312 4
 * @param int $id_topic - the topic
313
 * @param string $direction +/- liking or unliking
314 4
 * @package Likes
315
 */
316
function increaseTopicLikes($id_topic, $direction)
317
{
318
	$db = database();
319
320
	$db->query('', '
321
		UPDATE {db_prefix}topics
322
		SET 
323
			num_likes = num_likes ' . ($direction === '+' ? '+ 1' : '- 1') . '
324
		WHERE id_topic = {int:current_topic}',
325
		[
326 4
			'current_topic' => $id_topic,
327
		]
328 4
	);
329
}
330
331 4
/**
332
 * Return how many likes a user has given or the count of their posts that
333
 * have received a like (not the total likes received)
334 4
 *
335
 * @param int $memberID
336
 * @param bool $given
337 4
 *
338
 * @return int
339
 * @package Likes
340
 *
341
 */
342
function likesCount($memberID, $given = true)
343
{
344
	$db = database();
345
346
	// Give is a given, received takes a query so its only the unique messages
347
	if ($given)
348
	{
349
		$likes = MembersList::get($memberID)->likes_given;
0 ignored issues
show
Bug Best Practice introduced by
The property likes_given does not exist on anonymous//sources/ElkArte/MembersList.php$0. Since you implemented __get, consider adding a @property annotation.
Loading history...
350
	}
351
	else
352
	{
353 2
		$request = $db->query('', '
354
			SELECT 
355
				COUNT(*)
356 2
			FROM {db_prefix}message_likes
357
			WHERE id_poster = {int:id_member}',
358 2
			[
359
				'id_member' => $memberID,
360
			]
361
		);
362
		$likes = $request->num_rows();
363
		$request->free_result();
364
	}
365
366
	return $likes;
367
}
368
369
/**
370
 * Return an array of details based on posts a user has liked
371
 *
372
 * Used for action=profile;area=showlikes;sa=given
373
 *
374
 * @param int $start The item to start with (for pagination purposes)
375
 * @param int $items_per_page The number of items to show per page
376 2
 * @param string $sort A string indicating how to sort the results
377
 * @param int $memberID
378
 *
379
 * @return array
380
 * @package Likes
381
 *
382
 */
383
function likesPostsGiven($start, $items_per_page, $sort, $memberID)
384
{
385
	global $scripturl, $context, $modSettings;
386
387
	$db = database();
388
389
	// Load up what the user likes from the db
390
	return $db->fetchQuery('
391
		SELECT
392
			l.id_member, l.id_msg,
393
			m.subject, m.poster_name, m.id_board, m.id_topic,
394
			b.name
395
		FROM {db_prefix}message_likes AS l
396 2
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = l.id_msg)
397
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
398 2
		WHERE l.id_member = {int:id_member}' . (!empty($modSettings['recycle_enable']) ? ('
399
			AND b.id_board != ' . $modSettings['recycle_board']) : '') . '
400
		ORDER BY {raw:sort}
401 2
		LIMIT {int:per_page} OFFSET {int:start}',
402
		[
403
			'id_member' => $memberID,
404
			'sort' => $sort,
405
			'start' => $start,
406
			'per_page' => $items_per_page,
407
		]
408
	)->fetch_callback(
409 2
		function ($row) use ($scripturl, $context) {
410 2
			return [
411
				'subject' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>',
412
				'poster_name' => $row['poster_name'],
413
				'name' => $row['name'],
414 2
				'delete' => $scripturl . '?action=likes;sa=unlikepost;profile;msg=' . $row['id_msg'] . ';' . $context['session_var'] . '=' . $context['session_id'],
415 2
			];
416 2
		}
417 2
	);
418
}
419 2
420
/**
421
 * Returns an array of details based on posts that others have liked of this user
422
 * Creates links to show the users who liked a post
423
 *
424
 * Used by action=profile;area=showlikes;sa=received
425
 *
426
 * @param int $start The item to start with (for pagination purposes)
427 2
 * @param int $items_per_page The number of items to show per page
428
 * @param string $sort A string indicating how to sort the results
429
 * @param int $memberID
430
 *
431
 * @return array
432
 * @package Likes
433
 *
434
 */
435
function likesPostsReceived($start, $items_per_page, $sort, $memberID)
436
{
437
	global $scripturl, $modSettings;
438
439
	$db = database();
440
441
	// Load up what the user likes from the db
442
	return $db->fetchQuery('
443
		SELECT
444
			m.subject, m.id_topic,
445
			b.name, m.id_msg, COUNT(l.id_msg) AS likes
446
		FROM {db_prefix}message_likes AS l
447
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg = l.id_msg)
448
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
449
		WHERE l.id_poster = {int:id_member}' . (!empty($modSettings['recycle_enable']) ? ('
450
			AND b.id_board != ' . $modSettings['recycle_board']) : '') . '
451
		GROUP BY m.subject, m.id_topic, b.name, m.id_msg
452
		ORDER BY {raw:sort}
453
		LIMIT {int:per_page} OFFSET {int:start}',
454
		[
455
			'id_member' => $memberID,
456
			'sort' => $sort,
457
			'start' => $start,
458
			'per_page' => $items_per_page,
459
		]
460
	)->fetch_callback(
461
		function ($row) use ($scripturl) {
462
			return [
463
				'subject' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>',
464
				'name' => $row['name'],
465
				'who' => $scripturl . '?action=likes;sa=showWhoLiked;msg=' . $row['id_msg'],
466
				'likes' => $row['likes']
467
			];
468
		}
469
	);
470
}
471
472
/**
473
 * Function to load all likers of a message
474
 *
475
 * @param int $start The item to start with (for pagination purposes)
476
 * @param int $items_per_page The number of items to show per page
477
 * @param string $sort A string indicating how to sort the results
478
 * @param int $messageID
479
 * @param bool $simple
480
 *
481
 * @return array
482
 * @package Likes
483
 *
484
 */
485
function postLikers($start, $items_per_page, $sort, $messageID, $simple = true)
486
{
487
	global $scripturl;
488
489
	$db = database();
490
	$likes = [];
491
492
	if (empty($messageID))
493
	{
494
		return $likes;
495
	}
496
497
	// Load up the likes for this message
498
	return $db->fetchQuery('
499
		SELECT
500
			l.id_member, l.id_msg,
501
			m.real_name' . ($simple ? '' : ',
502
			COALESCE(a.id_attach, 0) AS id_attach,
503
			a.filename, a.attachment_type, m.avatar, m.email_address') . '
504
		FROM {db_prefix}message_likes AS l
505
			LEFT JOIN {db_prefix}members AS m ON (m.id_member = l.id_member)' . ($simple ? '' : '
506
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)') . '
507
		WHERE l.id_msg = {int:id_message}
508
		ORDER BY {raw:sort}
509
		LIMIT {int:per_page} OFFSET {int:start}',
510
		[
511
			'id_message' => $messageID,
512
			'sort' => $sort,
513
			'start' => $start,
514
			'per_page' => $items_per_page,
515
		]
516
	)->fetch_callback(
517
		function ($row) use ($scripturl, $simple) {
518
			$like = [
519
				'real_name' => $row['real_name'],
520
				'id_member' => $row['id_member'],
521
				'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
522
			];
523
			if (!$simple)
524
			{
525
				$avatar = determineAvatar($row);
526
				$like['href'] = !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '';
527
				$like['avatar'] = $avatar['href'];
528
			}
529
530
			return $like;
531
		}
532
	);
533
}
534
535
/**
536
 * Function to get the number of likes for a message
537
 *
538
 * @param int $message
539
 *
540
 * @return int
541
 * @package Likes
542
 *
543
 */
544
function messageLikeCount($message)
545
{
546
	$db = database();
547
	$total = 0;
548
549
	if (empty($message))
550
	{
551
		return $total;
552
	}
553
554
	// Count up the likes for this message
555
	$request = $db->query('', '
556
		SELECT COUNT(*)
557
		FROM {db_prefix}message_likes
558
		WHERE id_msg = {int:id_message}',
559
		[
560 4
			'id_message' => $message,
561 4
		]
562
	);
563 4
	list ($total) = $request->fetch_row();
564
	$request->free_result();
565
566
	return (int) $total;
567
}
568
569 4
/**
570
 * Function to get most liked messages
571
 *
572
 * @param int $limit the number of top liked messages to fetch
573
 *
574 4
 * @return array
575
 * @package Likes
576
 */
577 4
function dbMostLikedMessage($limit = 10)
578 4
{
579
	global $txt;
580 4
581
	$db = database();
582
583
	// Most liked Message
584
	$mostLikedMessages = [];
585
	$bbc_parser = ParserWrapper::instance();
586
	$db->fetchQuery('
587
		SELECT
588
			COALESCE(mem.real_name, m.poster_name) AS member_received_name,
589
			lp.id_msg, lp.like_count AS like_count,
590
			m.id_topic, m.id_board, m.id_member, m.subject, m.body, m.poster_time, m.smileys_enabled,
591
			COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
592
			mem.avatar, mem.posts, mem.email_address
593
		FROM (
594
				SELECT
595
					COUNT(lp.id_msg) AS like_count, lp.id_msg
596
				FROM {db_prefix}message_likes AS lp
597
				GROUP BY lp.id_msg
598
				ORDER BY like_count DESC
599
				LIMIT {int:limit}
600
			) AS lp
601
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
602
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
603
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
604
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)
605
		WHERE {query_wanna_see_board}
606
		ORDER BY like_count DESC
607
		LIMIT {int:limit}',
608
		[
609
			'limit' => $limit,
610
		]
611
	)->fetch_callback(
612
		function ($row) use (&$mostLikedMessages, $bbc_parser) {
613
			global $scripturl;
614
615
			// Censor it!
616
			$row['subject'] = censor($row['subject']);
617
			$row['body'] = censor($row['body']);
618
619
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
620
621
			// Something short and sweet
622
			$msgString = Util::shorten_html($row['body'], 255);
623
			$preview = Util::htmlspecialchars(strtr($msgString, ['<br />' => "\n", '&nbsp;' => ' ']));
624
625
			// Love those avatars
626
			$avatar = determineAvatar($row);
627
628
			// Build it out
629
			$mostLikedMessages[] = [
630
				'id_msg' => $row['id_msg'],
631
				'id_topic' => $row['id_topic'],
632
				'id_board' => $row['id_board'],
633
				'like_count' => $row['like_count'],
634
				'subject' => $row['subject'],
635
				'preview' => $preview,
636
				'body' => $msgString,
637
				'time' => standardTime($row['poster_time']),
638
				'html_time' => htmlTime($row['poster_time']),
639
				'timestamp' => forum_time(true, $row['poster_time']),
640
				'member_received' => [
641
					'id_member' => $row['id_member'],
642
					'name' => $row['member_received_name'],
643
					'total_posts' => $row['posts'],
644
					'href' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
645
					'avatar' => $avatar['href'],
646
				],
647
				'member_liked_data' => postLikers(0, min($row['like_count'], 50), 'l.id_member DESC', $row['id_msg'], false),
648
			];
649
		}
650
	);
651
652
	// No likes in the system?
653
	if (empty($mostLikedMessages))
654
	{
655
		return [
656
			'noDataMessage' => $txt['like_post_error_no_data']
657
		];
658
	}
659
660
	return $mostLikedMessages;
661
}
662
663
/**
664
 * Function to get most liked messages in a topic
665
 *
666
 * What it does:
667
 *
668
 * - For a supplied topic gets the, default 5, posts that have been liked
669
 * - Returns the messages in descending order of likes
670
 *
671
 * @param int $topic the topic_id we are going to look for liked posts within
672
 * @param int $limit the maximum number of liked posts to return
673
 *
674
 * @return array
675
 * @package Likes
676
 */
677
function dbMostLikedMessagesByTopic($topic, $limit = 5)
678
{
679
	global $scripturl;
680
681
	$db = database();
682
	$bbc_parser = ParserWrapper::instance();
683
684
	// Most liked messages in a given topic
685
	return $db->fetchQuery('
686
		SELECT
687
			COALESCE(mem.real_name, m.poster_name) AS member_received_name, lp.id_msg,
688
			m.id_topic, m.id_board, m.id_member, m.subject, m.body, m.poster_time,
689
			lp.like_count AS like_count,
690
			COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
691
			mem.posts, m.smileys_enabled, mem.email_address, mem.avatar
692
		FROM (
693
				SELECT
694
					COUNT(lp.id_msg) AS like_count, lp.id_msg
695
				FROM {db_prefix}message_likes AS lp
696
				GROUP BY lp.id_msg
697
				ORDER BY like_count DESC
698
			) AS lp
699
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
700
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
701
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
702
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member AND a.attachment_type = {int:type_avatar})
703
		WHERE t.id_topic = {int:id_topic}
704
		ORDER BY lp.like_count DESC
705
		LIMIT {int:limit}',
706
		[
707
			'id_topic' => $topic,
708
			'limit' => $limit,
709
			'type_avatar' => 1,
710
		]
711
	)->fetch_callback(
712
		function ($row) use ($scripturl, $bbc_parser) {
713
			// Censor those naughty words
714
			$row['body'] = censor($row['body']);
715
			$row['subject'] = censor($row['subject']);
716
717
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
718
719
			// Something short to show is all that's needed
720
			$msgString = Util::shorten_html($row['body'], 255);
721
			$preview = Util::htmlspecialchars(strtr($msgString, ['<br />' => "\n", '&nbsp;' => ' ']));
722
723
			$avatar = determineAvatar($row);
724
725
			return [
726
				'id_msg' => $row['id_msg'],
727
				'id_topic' => $row['id_topic'],
728
				'id_board' => $row['id_board'],
729
				'like_count' => $row['like_count'],
730
				'subject' => $row['subject'],
731
				'body' => $msgString,
732
				'preview' => $preview,
733
				'time' => standardTime($row['poster_time']),
734
				'html_time' => htmlTime($row['poster_time']),
735
				'timestamp' => forum_time(true, $row['poster_time']),
736
				'member' => [
737
					'id_member' => $row['id_member'],
738
					'name' => $row['member_received_name'],
739
					'total_posts' => $row['posts'],
740
					'href' => !empty($row['id_member']) ? $scripturl . '?action=profile;u=' . $row['id_member'] : '',
741
					'avatar' => $avatar['href'],
742
				],
743
			];
744
		}
745
	);
746
}
747
748
/**
749
 * Function to get most liked topics.
750
 *
751
 *  - Rewards threads that generate distinct likers in fewer posts.  So if a thread generated 20 unique
752
 * likes in 3 posts vs 20 in 20 posts it would get more weight.
753
 * - The more unique members that like a thread the more popular it will be.
754
 * - Adds weight to threads which have posts with many likes vs threads with many posts with many single likes
755
 * - Can still be gamed but what can you do
756
 *
757
 * @param null|int $board - An optional board id to find most liked topics in.
758
 *  If omitted, {query_wanna_see_board} is used to return the most liked topics in the boards
759
 * they can see
760
 * @param int $limit - Optional, number of topics to return (default 10).
761
 *
762
 * @return array
763
 * @package Likes
764
 *
765
 */
766
function dbMostLikedTopic($board = null, $limit = 10)
767
{
768
	global $txt;
769
770
	$db = database();
771
772
	// The most liked topics by sum of likes and distinct likers
773
	$mostLikedTopics = [];
774
	$db->fetchQuery('
775
		SELECT
776
			t.id_topic, t.num_replies, t.id_board,
777
			COUNT(lp.id_msg) AS like_count,
778
			COUNT(DISTINCT lp.id_member) AS distinct_likers,
779
			COUNT(DISTINCT m.id_msg) AS num_messages_liked
780
		FROM {db_prefix}message_likes AS lp
781
			INNER JOIN {db_prefix}messages AS m ON (lp.id_msg = m.id_msg)
782
			INNER JOIN {db_prefix}topics AS t ON (m.id_topic = t.id_topic)
783
			INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
784
		WHERE ' . ($board === null ? '{query_wanna_see_board}' : 'b.id_board = {int:id_board}') . '
785
		GROUP BY t.id_topic, t.num_replies, t.id_board
786
		ORDER BY distinct_likers DESC
787
		LIMIT {int:limit}',
788
		[
789
			'id_board' => $board,
790
			'limit' => $limit * 10,
791
		]
792
	)->fetch_callback(
793
		function ($row) use (&$mostLikedTopics) {
794
			$row['num_replies'] = (int) $row['num_replies'];
795
			$row['like_count']  = (int) $row['like_count'];
796
			$row['distinct_likers'] = (int) $row['distinct_likers'];
797
			$row['num_messages_liked'] = (int) $row['num_messages_liked'];
798
			$mostLikedTopics[$row['id_topic']] = $row;
799
800
			$log = log($row['like_count'] / ($row['num_replies'] + ($row['num_replies'] === 0 || $row['like_count'] === $row['num_replies'] ? 1 : 0)));
801
			$distinct_likers = max(1,
802
				min($row['distinct_likers'],
803
					1 / ($log === 0 ? 1 : $log)));
804
805
			$mostLikedTopics[$row['id_topic']]['relevance'] = $row['distinct_likers'] +
806
				$row['distinct_likers'] / $row['num_messages_liked'] +
807
				$distinct_likers;
808
		}
809
	);
810
811
	// Sort the results from the net we cast, then cut it down to the top X limit
812
	uasort($mostLikedTopics, 'sort_by_relevance');
813
	$mostLikedTopics = array_slice($mostLikedTopics, 0, $limit);
814
815
	// Fetch some sample posts for each of the top X topics
816
	foreach ($mostLikedTopics as $key => $topic)
817
	{
818
		$mostLikedTopics[$key]['msg_data'] = dbMostLikedMessagesByTopic($topic['id_topic']);
819
	}
820
821
	// Looks like there is nothing liked
822
	if (empty($mostLikedTopics))
823
	{
824
		return [
825
			'noDataMessage' => $txt['like_post_error_no_data']
826
		];
827
	}
828
829
	return $mostLikedTopics;
830
}
831
832
/**
833
 * Helper function to sort by topic like relevance
834
 *
835
 * @param float $a
836
 * @param float $b
837
 *
838
 * @return mixed
839
 */
840
function sort_by_relevance($a, $b)
841
{
842
	return $b['relevance'] - $a['relevance'];
843
}
844
845
/**
846
 * Function to get most liked board
847
 *
848
 * @package Likes
849
 */
850
function dbMostLikedBoard()
851
{
852
	global $txt;
853
854
	$db = database();
855
856
	// Most liked board
857
	$request = $db->query('', '
858
		SELECT
859
		 	b.id_board, b.name, b.num_topics, b.num_posts,
860
			tc.topics_liked, tc.msgs_liked, tc.like_count
861
		FROM {db_prefix}boards AS b
862
			INNER JOIN (
863
				SELECT
864
					m.id_board,
865
					COUNT(DISTINCT(m.id_topic)) AS topics_liked,
866
					COUNT(DISTINCT(lp.id_msg)) AS msgs_liked,
867
					COUNT(m.id_board) AS like_count
868
				FROM {db_prefix}message_likes AS lp
869
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
870
					INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
871
				WHERE {query_wanna_see_board}
872
				GROUP BY m.id_board
873
				ORDER BY like_count DESC
874
				LIMIT {int:limit}
875
			) AS tc ON (tc.id_board = b.id_board)
876
		LIMIT {int:limit}',
877
		[
878
			'limit' => 1
879
		]
880
	);
881
	$mostLikedBoard = $request->fetch_assoc();
882
	$request->free_result();
883
884
	if (empty($mostLikedBoard['id_board']))
885
	{
886
		return [
887
			'noDataMessage' => $txt['like_post_error_no_data']
888
		];
889
	}
890
891
	$mostLikedTopic = dbMostLikedTopic($mostLikedBoard['id_board']);
892
	$mostLikedBoard['topic_data'] = $mostLikedTopic[0]['msg_data'];
893
894
	return $mostLikedBoard;
895
}
896
897
/**
898
 * Function to get most liked members
899
 *
900
 * @param int $limit the number of most liked members to return
901
 *
902
 * @return array
903
 * @package Likes
904
 *
905
 */
906
function dbMostLikesReceivedUser($limit = 10)
907
{
908
	global  $txt;
909
910
	$db = database();
911
912
	$mostLikedMembers = [];
913
	$db->fetchQuery('
914
		SELECT
915
			lp.id_poster, lp.like_count,
916
			COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
917
			COALESCE(mem.real_name, m.poster_name) AS real_name,
918
			mem.avatar, mem.date_registered, mem.posts, mem.email_address
919
		FROM (
920
			SELECT
921
				id_poster,
922
				COUNT(id_msg) AS like_count,
923
				MAX(id_msg) AS id_msg
924
			FROM {db_prefix}message_likes
925
			WHERE id_poster != 0
926
			GROUP BY id_poster
927
			ORDER BY like_count DESC
928
			LIMIT {int:limit}
929
		) AS lp
930
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
931
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
932
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = m.id_member)
933
		ORDER BY like_count DESC	
934
		LIMIT {int:limit}',
935
		[
936
			'limit' => $limit
937
		]
938
	)->fetch_callback(
939
		function ($row) use (&$mostLikedMembers) {
940
			global $scripturl;
941
942
			$avatar = determineAvatar($row);
943
			$mostLikedMembers[] = [
944
				'member_received' => [
945
					'id_member' => $row['id_poster'],
946
					'name' => $row['real_name'],
947
					'total_posts' => $row['posts'],
948
					'date_registered' => $row['date_registered'],
949
					'href' => !empty($row['id_poster']) ? $scripturl . '?action=profile;u=' . $row['id_poster'] : '',
950
					'avatar' => $avatar['href'],
951
				],
952
				'like_count' => $row['like_count'],
953
				'post_data' => dbMostLikedPostsByUser($row['id_poster']),
954
			];
955
		}
956
	);
957
958
	if (empty($mostLikedMembers))
959
	{
960
		return [
961
			'noDataMessage' => $txt['like_post_error_no_data']
962
		];
963
	}
964
965
	return $mostLikedMembers;
966
}
967
968
/**
969
 * Returns the most liked posts of a given user
970
 *
971
 * @param int $id_member find top posts for this member id
972
 * @param int $limit then number of top posts to return
973
 *
974
 * @return array
975
 */
976
function dbMostLikedPostsByUser($id_member, $limit = 10)
977
{
978
	$db = database();
979
	$bbc_parser = ParserWrapper::instance();
980
981
	// Lets fetch highest liked posts by this user
982
	return $db->fetchQuery('
983
		SELECT
984
			lp.id_msg, COUNT(lp.id_msg) AS like_count,
985
			m.body, m.poster_time, m.smileys_enabled, m.id_topic, m.subject
986
		FROM {db_prefix}message_likes AS lp
987
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
988
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
989
		WHERE {query_wanna_see_board}
990
			AND lp.id_poster = {int:id_member}
991
		GROUP BY lp.id_msg, m.id_topic, m.subject, m.body, m.poster_time, m.smileys_enabled
992
		ORDER BY like_count DESC
993
		LIMIT {int:limit}',
994
		[
995
			'id_member' => $id_member,
996
			'limit' => $limit
997
		]
998
	)->fetch_callback(
999
		function ($row) use ($bbc_parser) {
1000
			// Censor those naughty words
1001
			$row['body'] = censor($row['body']);
1002
			$row['subject'] = censor($row['subject']);
1003
1004
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
1005
1006
			// Something short to show is all that's needed
1007
			$msgString = Util::shorten_html($row['body'], 255);
1008
			$preview = Util::htmlspecialchars(strtr($msgString, ['<br />' => "\n", '&nbsp;' => ' ']));
1009
1010
			return [
1011
				'id_topic' => $row['id_topic'],
1012
				'id_msg' => $row['id_msg'],
1013
				'like_count' => $row['like_count'],
1014
				'subject' => $row['subject'],
1015
				'body' => $msgString,
1016
				'preview' => $preview,
1017
				'time' => standardTime($row['poster_time']),
1018
				'html_time' => htmlTime($row['poster_time']),
1019
				'timestamp' => forum_time(true, $row['poster_time']),
1020
			];
1021
		}
1022
	);
1023
}
1024
1025
/**
1026
 * Function to get most likes giving user
1027
 *
1028
 * @param int $limit the number of members to return
1029
 *
1030
 * @return array
1031
 * @package Likes
1032
 *
1033
 */
1034
function dbMostLikesGivenUser($limit = 10)
1035
{
1036
	global $txt;
1037
1038
	$db = database();
1039
1040
	$mostLikeGivingMembers = [];
1041
	$db->fetchQuery('
1042
		SELECT
1043
			lp.id_member, lp.like_count,
1044
			COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1045
			COALESCE(mem.real_name, m.poster_name) AS real_name,
1046
			mem.avatar, mem.date_registered, mem.posts, mem.email_address
1047
		FROM (
1048
			SELECT
1049
				COUNT(id_msg) AS like_count, id_member, MAX(id_msg) AS id_msg
1050
			FROM {db_prefix}message_likes
1051
			GROUP BY id_member
1052
			ORDER BY like_count DESC
1053
			LIMIT {int:limit}
1054
		) AS lp
1055
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = lp.id_msg)
1056
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lp.id_member)
1057
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = lp.id_member)
1058
		ORDER BY like_count DESC',
1059
		[
1060
			'limit' => $limit
1061
		]
1062
	)->fetch_callback(
1063
		function ($row) use (&$mostLikeGivingMembers) {
1064
			global $scripturl;
1065
1066
			$avatar = determineAvatar($row);
1067
1068
			$mostLikeGivingMembers[] = [
1069
				'member_given' => [
1070
					'id_member' => $row['id_member'],
1071
					'name' => $row['real_name'],
1072
					'total_posts' => $row['posts'],
1073
					'date_registered' => $row['date_registered'],
1074
					'href' => !empty($row['id_member_gave']) ? $scripturl . '?action=profile;u=' . $row['id_member_gave'] : '',
1075
					'avatar' => $avatar['href'],
1076
				],
1077
				'like_count' => $row['like_count'],
1078
				'post_data' => dbRecentlyLikedPostsGivenUser($row['id_member'])
1079
			];
1080
		}
1081
	);
1082
1083
	if (empty($mostLikeGivingMembers))
1084
	{
1085
		return [
1086
			'noDataMessage' => $txt['like_post_error_no_data']
1087
		];
1088
	}
1089
1090
	return $mostLikeGivingMembers;
1091
}
1092
1093
/**
1094
 * Returns posts that were recently liked by a given user
1095
 *
1096
 * @param int $id_liker the userid to find recently liked posts
1097
 * @param int $limit number of recently liked posts to fetch
1098
 * @return array
1099
 */
1100
function dbRecentlyLikedPostsGivenUser($id_liker, $limit = 5)
1101
{
1102
	$db = database();
1103
	$bbc_parser = ParserWrapper::instance();
1104
1105
	// Lets fetch the latest liked posts by this user
1106
	return $db->fetchQuery('
1107
		SELECT
1108
			m.id_msg, m.id_topic, m.subject, m.body, m.poster_time, m.smileys_enabled
1109
		FROM {db_prefix}message_likes AS ml
1110
			INNER JOIN {db_prefix}messages AS m ON (ml.id_msg = m.id_msg)
1111
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1112
		WHERE {query_wanna_see_board}
1113
			AND ml.id_member = {int:id_member}
1114
		ORDER BY m.id_msg DESC
1115
		LIMIT {int:limit}',
1116
		[
1117
			'id_member' => $id_liker,
1118
			'limit' => $limit
1119
		]
1120
	)->fetch_callback(
1121
		function ($row) use ($bbc_parser) {
1122
			// Censor those $%#^&% words
1123
			$row['body'] = censor($row['body']);
1124
			$row['subject'] = censor($row['subject']);
1125
1126
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
1127
1128
			// Something short to show is all that's required
1129
			$msgString = Util::shorten_html($row['body'], 255);
1130
			$preview = Util::htmlspecialchars(strtr($msgString, ['<br />' => "\n", '&nbsp;' => ' ']));
1131
1132
			return [
1133
				'id_msg' => $row['id_msg'],
1134
				'id_topic' => $row['id_topic'],
1135
				'subject' => $row['subject'],
1136
				'body' => $msgString,
1137
				'preview' => $preview,
1138
				'time' => standardTime($row['poster_time']),
1139
				'html_time' => htmlTime($row['poster_time']),
1140
				'timestamp' => forum_time(true, $row['poster_time']),
1141
			];
1142
		}
1143
	);
1144
}
1145
1146
/**
1147
 * Utility function to decrease member like counts when a message is removed
1148
 *
1149
 * When a message is removed, we need update the like counts for those who liked the message
1150
 * as well as those who posted the message
1151
 *  - Members who liked the message have likes given decreased
1152
 *  - The member who posted has the likes received decreased by the number of likers
1153
 * for that message.
1154
 *
1155
 * @param int[]|int $messages
1156
 */
1157
function decreaseLikeCounts($messages)
1158
{
1159
	$db = database();
1160
1161
	// Start off with no changes
1162
	$update_given = [];
1163
	$update_received = [];
1164
1165
	// Only a single message
1166
	if (is_numeric($messages))
1167
	{
1168
		$messages = [$messages];
1169
	}
1170
1171
	// Load the members who liked and who posted for this group of messages
1172
	$posters = [];
1173
	$likers = [];
1174 12
	$db->fetchQuery('
1175
		SELECT
1176
			id_member, id_poster
1177 12
		FROM {db_prefix}message_likes
1178 12
		WHERE id_msg IN ({array_int:messages})',
1179
		[
1180
			'messages' => $messages,
1181 12
		]
1182
	)->fetch_callback(
1183
		function ($row) use (&$posters, &$likers) {
1184
			// Track how many likes each member gave and how many were received
1185
			$posters[$row['id_poster']] = isset($posters[$row['id_poster']]) ? $posters[$row['id_poster']]++ : 1;
1186
			$likers[$row['id_member']] = isset($likers[$row['id_member']]) ? $likers[$row['id_member']]++ : 1;
1187 12
		}
1188 12
	);
1189 12
1190
	// No one?
1191
	if (empty($posters) && empty($likers))
1192
	{
1193
		return;
1194
	}
1195 12
1196
	// Re-count the "likes given" totals for the likers
1197 12
	if (!empty($likers))
1198
	{
1199
		$db->fetchQuery('
1200
			SELECT
1201
				COUNT(id_msg) AS likes, id_member
1202 12
			FROM {db_prefix}message_likes
1203
			WHERE id_member IN ({array_int:members})
1204
			GROUP BY id_member',
1205
			[
1206 12
				'members' => array_keys($likers),
1207
			]
1208 12
		)->fetch_callback(
1209
			function ($row) use (&$update_given, $likers) {
1210
				// All who liked these messages have their "likes given" reduced
1211
				$update_given[$row['id_member']] = $row['likes'] - $likers[$row['id_member']];
1212
			}
1213
		);
1214
	}
1215
1216
	// Count the "likes received" totals for the message posters
1217
	if (!empty($posters))
1218
	{
1219
		$db->fetchQuery('
1220
			SELECT
1221
				COUNT(id_msg) AS likes, id_poster
1222
			FROM {db_prefix}message_likes
1223
			WHERE id_poster IN ({array_int:members})
1224
			GROUP BY id_poster',
1225
			[
1226
				'members' => array_keys($posters),
1227
			]
1228
		)->fetch_callback(
1229
			function ($row) use (&$update_received, $posters) {
1230
				// The message posters have their "likes received" reduced
1231
				$update_received[$row['id_poster']] = $row['likes'] - $posters[$row['id_poster']];
1232
			}
1233
		);
1234
	}
1235
1236
	// Update the totals for these members
1237
	require_once(SUBSDIR . '/Members.subs.php');
1238
1239
	foreach ($update_given as $id_member => $total)
1240
	{
1241
		updateMemberData($id_member, ['likes_given' => (int) $total]);
1242
	}
1243
1244
	foreach ($update_received as $id_member => $total)
1245
	{
1246
		updateMemberData($id_member, ['likes_received' => (int) $total]);
1247
	}
1248
}
1249