Browse Source

! I like this. It feels ugly, though, but it works (non AJAXively for now)

Signed-off-by: Peter Spicer <[email protected]>
Peter Spicer 10 years ago
parent
commit
807a6f6ec6

+ 43 - 2
Sources/Display.php

@@ -1016,7 +1016,7 @@ function Display()
 		$messages_request = $smcFunc['db_query']('', '
 			SELECT
 				id_msg, icon, subject, poster_time, poster_ip, id_member, modified_time, modified_name, body,
-				smileys_enabled, poster_name, poster_email, approved,
+				smileys_enabled, poster_name, poster_email, approved, likes,
 				id_msg_modified < {int:new_from} AS is_read
 				' . (!empty($msg_selects) ? implode(',', $msg_selects) : '') . '
 			FROM {db_prefix}messages
@@ -1026,6 +1026,9 @@ function Display()
 			$msg_parameters
 		);
 
+		// And the likes
+		$context['my_likes'] = $context['user']['is_guest'] ? array() : prepareLikesContext();
+
 		// Go to the last message if the given time is beyond the time of the last message.
 		if (isset($context['start_from']) && $context['start_from'] >= $topicinfo['num_replies'])
 			$context['start_from'] = $topicinfo['num_replies'];
@@ -1042,6 +1045,8 @@ function Display()
 		$messages_request = false;
 		$context['first_message'] = 0;
 		$context['first_new_message'] = false;
+
+		$context['likes'] = array();
 	}
 
 	$context['jump_to'] = array(
@@ -1307,6 +1312,11 @@ function prepareDisplayContext($reset = false)
 			'timestamp' => forum_time(true, $message['modified_time']),
 			'name' => $message['modified_name']
 		),
+		'likes' => array(
+			'count' => $message['likes'],
+			'you' => in_array($message['id_msg'], $context['my_likes']),
+			'can_like' => !$context['user']['is_guest'], // @todo!
+		),
 		'body' => $message['body'],
 		'new' => empty($message['is_read']),
 		'approved' => $message['approved'],
@@ -1844,4 +1854,35 @@ function QuickInTopicModeration()
 	redirectexit(!empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start']);
 }
 
-?>
+function prepareLikesContext()
+{
+	global $context, $smcFunc, $topic;
+
+	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
+	$cache_key = 'likes_topic_' . $topic . '_' . $context['user']['id'];
+	$ttl = 180;
+
+	if ($temp = cache_get_data($cache_key, $ttl) === null)
+	{
+		$temp = array();
+		$request = $smcFunc['db_query']('', '
+			SELECT content_id
+			FROM {db_prefix}user_likes AS l
+				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
+			WHERE l.id_member = {int:current_user}
+				AND l.content_type = {literal:msg}
+				AND m.id_topic = {int:topic}',
+			array(
+				'current_user' => $context['user']['id'],
+				'topic' => $topic,
+			)
+		);
+		while ($row = $smcFunc['db_fetch_assoc']($request))
+			$temp[] = (int) $row['content_id'];
+
+		cache_put_data($cache_key, $temp, $ttl);
+	}
+	return $temp;
+}
+
+?>

+ 0 - 63
Sources/Karma.php

@@ -151,67 +151,4 @@ function ModifyKarma()
 	}
 }
 
-/**
- * What's this?  I dunno, what are you talking about?  Never seen this before, nope.  No sir.
- */
-function BookOfUnknown()
-{
-	global $context, $scripturl;
-
-	if (strpos($_GET['action'], 'mozilla') !== false && !isBrowser('gecko'))
-		redirectexit('http://www.getfirefox.com/');
-	elseif (strpos($_GET['action'], 'mozilla') !== false)
-		redirectexit('about:mozilla', true);
-
-	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"', $context['right_to_left'] ? ' dir="rtl"' : '', '>
-	<head>
-		<title>The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', '</title>
-		<style type="text/css">
-			em
-			{
-				font-size: 1.3em;
-				line-height: 0;
-			}
-		</style>
-	</head>
-	<body style="background-color: #444455; color: white; font-style: italic; font-family: serif;">
-		<div style="margin-top: 12%; font-size: 1.1em; line-height: 1.4; text-align: center;">';
-
-	if (!isset($_GET['verse']) || ($_GET['verse'] != '2:18' && $_GET['verse'] != '22:1-2'))
-		$_GET['verse'] = '4:16';
-
-	if ($_GET['verse'] == '2:18')
-		echo '
-			Woe, it was that his name wasn\'t <em>known</em>, that he came in mystery, and was recognized by none.&nbsp;And it became to be in those days <em>something</em>.&nbsp; Something not yet <em id="unknown" name="[Unknown]">unknown</em> to mankind.&nbsp; And thus what was to be known the <em>secret project</em> began into its existence.&nbsp; Henceforth the opposition was only <em>weary</em> and <em>fearful</em>, for now their match was at arms against them.';
-	elseif ($_GET['verse'] == '4:16')
-		echo '
-			And it came to pass that the <em>unbelievers</em> dwindled in number and saw rise of many <em>proselytizers</em>, and the opposition found fear in the face of the <em>x</em> and the <em>j</em> while those who stood with the <em>something</em> grew stronger and came together.&nbsp; Still, this was only the <em>beginning</em>, and what lay in the future was <em id="unknown" name="[Unknown]">unknown</em> to all, even those on the right side.';
-	elseif ($_GET['verse'] == '22:1-2')
-		echo '
-			<p>Now <em>behold</em>, that which was once the secret project was <em id="unknown" name="[Unknown]">unknown</em> no longer.&nbsp; Alas, it needed more than <em>only one</em>, but yet even thought otherwise.&nbsp; It became that the opposition <em>rumored</em> and lied, but still to no avail.&nbsp; Their match, though not <em>perfect</em>, had them outdone.</p>
-			<p style="margin: 2ex 1ex 0 1ex; font-size: 1.05em; line-height: 1.5; text-align: center;">Let it continue.&nbsp; <em>The end</em>.</p>';
-
-	echo '
-		</div>
-		<div style="margin-top: 2ex; font-size: 2em; text-align: right;">';
-
-	if ($_GET['verse'] == '2:18')
-		echo '
-			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=4:16" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 2:18</span>';
-	elseif ($_GET['verse'] == '4:16')
-		echo '
-			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=22:1-2" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 4:16</span>';
-	elseif ($_GET['verse'] == '22:1-2')
-		echo '
-			from <span style="font-family: Georgia, serif;"><strong>The Book of Unknown</strong>, 22:1-2</span>';
-
-	echo '
-		</div>
-	</body>
-</html>';
-
-	obExit(false);
-}
-
 ?>

+ 328 - 0
Sources/Likes.php

@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * This file contains liking posts and displaying the list of who liked a post.
+ *
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines http://www.simplemachines.org
+ * @copyright 2013 Simple Machines and individual contributors
+ * @license http://www.simplemachines.org/about/smf/license.php BSD
+ *
+ * @version 2.1 Alpha 1
+ */
+
+if (!defined('SMF'))
+	die('No direct access...');
+
+/**
+ * The main handler. Verifies permissions (whether the user can see the content in question)
+ * before either liking/unliking or spitting out the list of likers.
+ * Accessed from index.php?action=likes
+ */
+function Likes()
+{
+	global $context, $smcFunc;
+
+	// Zerothly, they did indicate some kind of content to like, right?
+	$like_type = isset($_GET['ltype']) ? $_GET['ltype'] : '';
+	preg_match('~^([a-z0-9\-\_]{1,6})~i', $like_type, $matches);
+	$like_type = isset($matches[1]) ? $matches[1] : '';
+	$like_content = isset($_GET['like']) ? (int) $_GET['like'] : 0;
+
+	if ($like_type == '' || $like_content <= 0)
+		fatal_lang_error(isset($_GET['view']) ? 'cannot_view_likes' : 'cannot_like_content', false);
+
+	// First we need to verify if the user can see the type of content or not. This is set up to be extensible,
+	// so we'll check for the one type we do know about, and if it's not that, we'll defer to any hooks.
+	if ($like_type == 'msg')
+	{
+		// So we're doing something off a like. We need to verify that it exists, and that the current user can see it.
+		// Fortunately for messages, this is quite easy to do - and we'll get the topic id while we're at it, because
+		// we need this later for other things.
+		$request = $smcFunc['db_query']('', '
+			SELECT m.id_topic
+			FROM {db_prefix}messages AS m
+				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
+			WHERE {query_see_board}
+				AND m.id_msg = {int:msg}',
+			array(
+				'msg' => $like_content,
+			)
+		);
+		if ($smcFunc['db_num_rows']($request) == 1)
+			list ($id_topic) = $smcFunc['db_fetch_row']($request);
+
+		$smcFunc['db_free_result']($request);
+		if (empty($id_topic))
+			fatal_lang_error(isset($_GET['view']) ? 'cannot_view_likes' : 'cannot_like_content', false);
+
+		// So we know what topic it's in and more importantly we know the user can see it.
+		// If we're not viewing, we need some info set up.
+		if (!isset($_GET['view']))
+		{
+			$context['flush_cache'] = 'likes_topic_' . $id_topic . '_' . $context['user']['id'];
+			$context['redirect_from_like'] = 'topic=' . $id_topic . '.msg' . $like_content . '#msg' . $like_content;
+			add_integration_function('integrate_issue_like', 'msg_issue_like', '', false);
+		}
+	}
+	else
+	{
+		// Modders: This will give you whatever the user offers up in terms of liking, e.g. $like_type=msg, $like_content=1
+		// When you hook this, check $like_type first. If it is not something your mod worries about, return false.
+		// Otherwise, determine (however you need to) that the user can see the relevant liked content (and it exists).
+		// If the user cannot see it, return false. If the user can see it and can like it, you MUST return your $like_type back.
+		// See also issueLike() for further notes.
+		$can_like = call_integration_hook('integrate_valid_likes', array($like_type, $like_content));
+
+		$found = false;
+		if (!empty($can_like))
+		{
+			$can_like = (array) $can_like;
+			foreach ($can_like as $result)
+			{
+				if ($result !== false)
+				{
+					$like_type = $result;
+					$found = true;
+					break;
+				}
+			}
+		}
+
+		if (!$found)
+			fatal_lang_error(isset($_GET['view']) ? 'cannot_view_likes' : 'cannot_like_content', false);
+	}
+
+	// So at this point, whatever type of like the user supplied and the item of content in question,
+	// we know it exists, now we need to figure out what we're doing with that.
+
+	if (isset($_GET['view']))
+		viewLikes($like_type, $like_content);
+	else
+	{
+		// Only registered users may actually like content.
+		is_not_guest();
+		issueLike($like_type, $like_content);
+	}
+}
+
+function issueLike($like_type, $like_content)
+{
+	global $context, $smcFunc;
+
+	// Do we already like this?
+	$request = $smcFunc['db_query']('', '
+		SELECT content_id, content_type, id_member
+		FROM {db_prefix}user_likes
+		WHERE content_id = {int:like_content}
+			AND content_type = {string:like_type}
+			AND id_member = {int:id_member}',
+		array(
+			'like_content' => $like_content,
+			'like_type' => $like_type,
+			'id_member' => $context['user']['id'],
+		)
+	);
+	$already_liked = $smcFunc['db_num_rows']($request) != 0;
+	$smcFunc['db_free_result']($request);
+
+	if ($already_liked)
+	{
+		$smcFunc['db_query']('', '
+			DELETE FROM {db_prefix}user_likes
+			WHERE content_id = {int:like_content}
+				AND content_type = {string:like_type}
+				AND id_member = {int:id_member}',
+			array(
+				'like_content' => $like_content,
+				'like_type' => $like_type,
+				'id_member' => $context['user']['id'],
+			)
+		);
+	}
+	else
+	{
+		$smcFunc['db_insert']('insert',
+			'{db_prefix}user_likes',
+			array('content_id' => 'int', 'content_type' => 'string-6', 'id_member' => 'int', 'like_time' => 'int'),
+			array($like_content, $like_type, $context['user']['id'], time()),
+			array('content_id', 'content_type', 'id_member')
+		);
+	}
+
+	// Now, how many people like this content now? We *could* just +1 / -1 the relevant container but that has proven to become unstable.
+	$request = $smcFunc['db_query']('', '
+		SELECT COUNT(id_member)
+		FROM {db_prefix}user_likes
+		WHERE content_id = {int:like_content}
+			AND content_type = {string:like_type}',
+		array(
+			'like_content' => $like_content,
+			'like_type' => $like_type,
+		)
+	);
+	list ($num_likes) = $smcFunc['db_fetch_row']($request);
+	$smcFunc['db_free_result']($request);
+
+	// Sometimes there might be other things that need updating after we do this like.
+	call_integration_hook('integrate_issue_like', array($like_type, $like_content, $num_likes));
+
+	// Now some clean up. This is provided here for any like handlers that want to do any cache flushing.
+	// This way a like handler doesn't need to explicitly declare anything in integrate_issue_like, but do so
+	// in integrate_valid_likes where it absolutely has to exist.
+	if (!empty($context['flush_cache']))
+		cache_put_data($context['flush_cache'], null);
+
+	if (!empty($context['redirect_from_like']))
+		redirectexit($context['redirect_from_like']);
+	else
+		redirectexit(); // Because we have to go *somewhere*.
+}
+
+/**
+ * Callback attached to integrate_issue_like.
+ * Partly it indicates how it's supposed to work and partly it deals with updating the count of likes
+ * attached to this message now.
+ */
+function msg_issue_like($like_type, $like_content, $num_likes)
+{
+	global $smcFunc;
+
+	if ($like_type !== 'msg')
+		return;
+
+	$smcFunc['db_query']('', '
+		UPDATE {db_prefix}messages
+		SET likes = {int:num_likes}
+		WHERE id_msg = {int:id_msg}',
+		array(
+			'id_msg' => $like_content,
+			'num_likes' => $num_likes,
+		)
+	);
+
+	// Note that we could just as easily have cleared the cache here, or set up the redirection address
+	// but if your liked content doesn't need to do anything other than have the record in smf_user_likes,
+	// there's no point in creating another function unnecessarily.
+}
+
+/**
+ * This is for viewing the people who liked a thing.
+ * Accessed from index.php?action=likes;view and should generally load in a popup.
+ * We use a template for this in case themers want to style it.
+ */
+function viewLikes($like_type, $like_content)
+{
+	global $smcFunc, $txt, $context, $memberContext;
+
+	// Firstly, load what we need. We already know we can see this, so that's something.
+	$context['likers'] = array();
+	$request = $smcFunc['db_query']('', '
+		SELECT id_member, like_time
+		FROM {db_prefix}user_likes
+		WHERE content_id = {int:like_content}
+			AND content_type = {string:like_type}
+		ORDER BY like_time DESC',
+		array(
+			'like_content' => $like_content,
+			'like_type' => $like_type,
+		)
+	);
+	while ($row = $smcFunc['db_fetch_assoc']($request))
+		$context['likers'][$row['id_member']] = array('timestamp' => $row['like_time']);
+
+	// Now to get member data, including avatars and so on.
+	$members = array_keys($context['likers']);
+	$loaded = loadMemberData($members);
+	if (count($loaded) != count($members))
+	{
+		$members = array_diff($members, $loaded);
+		foreach ($members as $not_loaded)
+			unset ($context['likers'][$not_loaded]);
+	}
+
+	foreach ($context['likers'] as $liker => $dummy)
+	{
+		$loaded = loadMemberContext($liker);
+		if (!$loaded)
+		{
+			unset ($context['likers'][$liker]);
+			continue;
+		}
+
+		$context['likers'][$liker]['profile'] = &$memberContext[$liker];
+		$context['likers'][$liker]['time'] = timeformat($dummy['timestamp']);
+	}
+
+	$count = count($context['likers']);
+	$title_base = isset($txt['likes_' . $count]) ? 'likes_' . $count : 'likes_n';
+	$context['page_title'] = strip_tags(sprintf($txt[$title_base], '', comma_format($count)));
+
+	// Lastly, setting up for display
+	loadTemplate('Likes');
+	loadLanguage('Help'); // for the close window button
+	$context['template_layers'] = array();
+	$context['sub_template'] = 'popup';
+}
+
+/**
+ * What's this?  I dunno, what are you talking about?  Never seen this before, nope.  No sir.
+ */
+function BookOfUnknown()
+{
+	global $context, $scripturl;
+
+	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"', $context['right_to_left'] ? ' dir="rtl"' : '', '>
+	<head>
+		<title>The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', '</title>
+		<style type="text/css">
+			em
+			{
+				font-size: 1.3em;
+				line-height: 0;
+			}
+		</style>
+	</head>
+	<body style="background-color: #444455; color: white; font-style: italic; font-family: serif;">
+		<div style="margin-top: 12%; font-size: 1.1em; line-height: 1.4; text-align: center;">';
+
+	if (!isset($_GET['verse']) || ($_GET['verse'] != '2:18' && $_GET['verse'] != '22:1-2'))
+		$_GET['verse'] = '4:16';
+
+	if ($_GET['verse'] == '2:18')
+		echo '
+			Woe, it was that his name wasn\'t <em>known</em>, that he came in mystery, and was recognized by none.&nbsp;And it became to be in those days <em>something</em>.&nbsp; Something not yet <em id="unknown" name="[Unknown]">unknown</em> to mankind.&nbsp; And thus what was to be known the <em>secret project</em> began into its existence.&nbsp; Henceforth the opposition was only <em>weary</em> and <em>fearful</em>, for now their match was at arms against them.';
+	elseif ($_GET['verse'] == '4:16')
+		echo '
+			And it came to pass that the <em>unbelievers</em> dwindled in number and saw rise of many <em>proselytizers</em>, and the opposition found fear in the face of the <em>x</em> and the <em>j</em> while those who stood with the <em>something</em> grew stronger and came together.&nbsp; Still, this was only the <em>beginning</em>, and what lay in the future was <em id="unknown" name="[Unknown]">unknown</em> to all, even those on the right side.';
+	elseif ($_GET['verse'] == '22:1-2')
+		echo '
+			<p>Now <em>behold</em>, that which was once the secret project was <em id="unknown" name="[Unknown]">unknown</em> no longer.&nbsp; Alas, it needed more than <em>only one</em>, but yet even thought otherwise.&nbsp; It became that the opposition <em>rumored</em> and lied, but still to no avail.&nbsp; Their match, though not <em>perfect</em>, had them outdone.</p>
+			<p style="margin: 2ex 1ex 0 1ex; font-size: 1.05em; line-height: 1.5; text-align: center;">Let it continue.&nbsp; <em>The end</em>.</p>';
+
+	echo '
+		</div>
+		<div style="margin-top: 2ex; font-size: 2em; text-align: right;">';
+
+	if ($_GET['verse'] == '2:18')
+		echo '
+			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=4:16" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 2:18</span>';
+	elseif ($_GET['verse'] == '4:16')
+		echo '
+			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=22:1-2" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 4:16</span>';
+	elseif ($_GET['verse'] == '22:1-2')
+		echo '
+			from <span style="font-family: Georgia, serif;"><strong>The Book of Unknown</strong>, 22:1-2</span>';
+
+	echo '
+		</div>
+	</body>
+</html>';
+
+	obExit(false);
+}
+
+?>

+ 1 - 1
Sources/Load.php

@@ -191,7 +191,7 @@ function reloadSettings()
 	{
 		$integration_settings = unserialize(SMF_INTEGRATION_SETTINGS);
 		foreach ($integration_settings as $hook => $function)
-			add_integration_function($hook, $function, false);
+			add_integration_function($hook, $function, '', false);
 	}
 
 	// Any files to pre include?

+ 58 - 21
Themes/default/Display.template.php

@@ -535,92 +535,125 @@ function template_main()
 							</div>';
 		}
 
+		// And stuff below the attachments
 		echo '
-						</div>';
+							<div class="under_message">';
+
 		// Maybe they want to report this post to the moderator(s)?
 		if ($context['can_report_moderator'])
 			echo '
-						<ul class="floatright smalltext">
-							<li class="report_link"><a href="', $scripturl, '?action=reporttm;topic=', $context['current_topic'], '.', $message['counter'], ';msg=', $message['id'], '">', $txt['report_to_mod'], '</a></li>
-						</ul>';
+								<ul class="floatright smalltext">
+									<li class="report_link"><a href="', $scripturl, '?action=reporttm;topic=', $context['current_topic'], '.', $message['counter'], ';msg=', $message['id'], '">', $txt['report_to_mod'], '</a></li>
+								</ul>';
+
+		// What about likes?
+		echo '
+								<ul class="floatleft">';
+		if (!empty($message['likes']['can_like']))
+		{
+			echo '
+									<li class="like_button"><a href="', $scripturl, '?action=likes;ltype=msg;like=', $message['id'], '"><span class="', $message['likes']['you'] ? 'unlike' : 'like', '"></span>', $message['likes']['you'] ? $txt['unlike'] : $txt['like'], '</a></li>';
+		}
+
+		if (!empty($message['likes']['count']))
+		{
+			$context['some_likes'] = true;
+			$count = $message['likes']['count'];
+			$base = 'likes_';
+			if ($message['likes']['you'])
+			{
+				$base = 'you_' . $base;
+				$count--;
+			}
+			$base .= (isset($txt[$base . $count])) ? $count : 'n';
+
+			echo '
+									<li class="like_count smalltext">', sprintf($txt[$base], $scripturl . '?action=likes;view;ltype=msg;like=' . $message['id'], comma_format($count)), '</li>';
+		}
+
+		echo '
+								</ul>';
 
 		// Show the quickbuttons, for various operations on posts.
 		if ($message['can_approve'] || $message['can_unapprove'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg'] || $context['can_quote'])
 		{
 			echo '
-					<ul class="quickbuttons">';
+								<ul class="quickbuttons">';
 
 			// Can they reply? Have they turned on quick reply?
 			if ($context['can_quote'] && !empty($options['display_quick_reply']))
 				echo '
-							<li><a href="', $scripturl, '?action=post;quote=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], ';last_msg=', $context['topic_last_message'], '" onclick="return oQuickReply.quote(', $message['id'], ');" class="quote_button">', $txt['quote_action'], '</a></li>';
+									<li><a href="', $scripturl, '?action=post;quote=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], ';last_msg=', $context['topic_last_message'], '" onclick="return oQuickReply.quote(', $message['id'], ');" class="quote_button">', $txt['quote_action'], '</a></li>';
 
 			// So... quick reply is off, but they *can* reply?
 			elseif ($context['can_quote'])
 				echo '
-							<li><a href="', $scripturl, '?action=post;quote=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], ';last_msg=', $context['topic_last_message'], '" class="quote_button">', $txt['quote_action'], '</a></li>';
+									<li><a href="', $scripturl, '?action=post;quote=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], ';last_msg=', $context['topic_last_message'], '" class="quote_button">', $txt['quote_action'], '</a></li>';
 
 			// Can the user modify the contents of this post?  Show the modify inline image.
 			if ($message['can_modify'])
 				echo '
-							<li class="quick_edit"><img src="', $settings['images_url'], '/icons/modify_inline.png" alt="', $txt['modify_msg'], '" title="', $txt['modify_msg'], '" class="modifybutton" id="modify_button_', $message['id'], '" style="cursor: pointer; margin: 0;" onclick="oQuickModify.modifyMsg(\'', $message['id'], '\')" />', $txt['quick_edit'], '</li>';
+									<li class="quick_edit"><img src="', $settings['images_url'], '/icons/modify_inline.png" alt="', $txt['modify_msg'], '" title="', $txt['modify_msg'], '" class="modifybutton" id="modify_button_', $message['id'], '" style="cursor: pointer; margin: 0;" onclick="oQuickModify.modifyMsg(\'', $message['id'], '\')" />', $txt['quick_edit'], '</li>';
 
 			if ($message['can_approve'] || $message['can_unapprove'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg'])
 				echo '
-							<li class="post_options">', $txt['post_options'];
+									<li class="post_options">', $txt['post_options'];
 
 			echo '
-								<ul>';
+										<ul>';
 
 					// Can the user modify the contents of this post?
 					if ($message['can_modify'])
 						echo '
-									<li><a href="', $scripturl, '?action=post;msg=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], '" class="modify_button">', $txt['modify'], '</a></li>';
+											<li><a href="', $scripturl, '?action=post;msg=', $message['id'], ';topic=', $context['current_topic'], '.', $context['start'], '" class="modify_button">', $txt['modify'], '</a></li>';
 
 					// How about... even... remove it entirely?!
 					if ($message['can_remove'])
 						echo '
-									<li><a href="', $scripturl, '?action=deletemsg;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '" onclick="return confirm(\'', $txt['remove_message'], '?\');" class="remove_button">', $txt['remove'], '</a></li>';
+											<li><a href="', $scripturl, '?action=deletemsg;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '" onclick="return confirm(\'', $txt['remove_message'], '?\');" class="remove_button">', $txt['remove'], '</a></li>';
 
 					// What about splitting it off the rest of the topic?
 					if ($context['can_split'] && !empty($context['real_num_replies']))
 						echo '
-									<li><a href="', $scripturl, '?action=splittopics;topic=', $context['current_topic'], '.0;at=', $message['id'], '" class="split_button">', $txt['split'], '</a></li>';
+											<li><a href="', $scripturl, '?action=splittopics;topic=', $context['current_topic'], '.0;at=', $message['id'], '" class="split_button">', $txt['split'], '</a></li>';
 
 					// Can we issue a warning because of this post?  Remember, we can't give guests warnings.
 					if ($context['can_issue_warning'] && !$message['is_message_author'] && !$message['member']['is_guest'])
 						echo '
-									<li><a href="', $scripturl, '?action=profile;area=issuewarning;u=', $message['member']['id'], ';msg=', $message['id'], '" class="warn_button">', $txt['issue_warning'], '</a></li>';
+											<li><a href="', $scripturl, '?action=profile;area=issuewarning;u=', $message['member']['id'], ';msg=', $message['id'], '" class="warn_button">', $txt['issue_warning'], '</a></li>';
 
 					// Can we restore topics?
 					if ($context['can_restore_msg'])
 						echo '
-									<li><a href="', $scripturl, '?action=restoretopic;msgs=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '" class="restore_button">', $txt['restore_message'], '</a></li>';
+											<li><a href="', $scripturl, '?action=restoretopic;msgs=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '" class="restore_button">', $txt['restore_message'], '</a></li>';
 
 					// Maybe we can approve it, maybe we should?
 					if ($message['can_approve'])
 						echo '
-									<li><a href="', $scripturl, '?action=moderate;area=postmod;sa=approve;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '"  class="approve_button">', $txt['approve'], '</a></li>';
+											<li><a href="', $scripturl, '?action=moderate;area=postmod;sa=approve;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '"  class="approve_button">', $txt['approve'], '</a></li>';
 
 					// Maybe we can unapprove it?
 					if ($message['can_unapprove'])
 						echo '
-									<li><a href="', $scripturl, '?action=moderate;area=postmod;sa=approve;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '"  class="unapprove_button">', $txt['unapprove'], '</a></li>';
+											<li><a href="', $scripturl, '?action=moderate;area=postmod;sa=approve;topic=', $context['current_topic'], '.', $context['start'], ';msg=', $message['id'], ';', $context['session_var'], '=', $context['session_id'], '"  class="unapprove_button">', $txt['unapprove'], '</a></li>';
 
 				echo '
-								</ul>
-							</li>';
+										</ul>
+									</li>';
 
 			// Show a checkbox for quick moderation?
 			if (!empty($options['display_quick_mod']) && $options['display_quick_mod'] == 1 && $message['can_remove'])
 				echo '
-							<li class="inline_mod_check" style="display: none;" id="in_topic_mod_check_', $message['id'], '"></li>';
+									<li class="inline_mod_check" style="display: none;" id="in_topic_mod_check_', $message['id'], '"></li>';
 
 			if ($message['can_approve'] || $context['can_reply'] || $message['can_modify'] || $message['can_remove'] || $context['can_split'] || $context['can_restore_msg'])
 				echo '
-						</ul>';
+								</ul>';
 		}
 
+		echo '
+							</div>
+						</div>';
 		echo '
 						<div class="moderatorbar">';
 
@@ -824,6 +857,10 @@ function template_main()
 				<script type="text/javascript" src="', $settings['default_theme_url'], '/scripts/topic.js"></script>
 				<script type="text/javascript"><!-- // --><![CDATA[';
 
+	if (!empty($context['some_likes']))
+		echo '
+					add_like_popup();';
+
 	if (!empty($options['display_quick_reply']))
 		echo '
 					var oQuickReply = new QuickReply({

+ 53 - 0
Themes/default/Likes.template.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines
+ * @copyright 2013 Simple Machines and individual contributors
+ * @license http://www.simplemachines.org/about/smf/license.php BSD
+ *
+ * @version 2.1 Alpha 1
+ */
+
+function template_popup()
+{
+	global $context, $settings, $options, $txt;
+
+	// Since this is a popup of its own we need to start the html, etc.
+	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"', $context['right_to_left'] ? ' dir="rtl"' : '', '>
+	<head>
+		<meta http-equiv="Content-Type" content="text/html; charset=', $context['character_set'], '" />
+		<meta name="robots" content="noindex" />
+		<title>', $context['page_title'], '</title>
+		<link rel="stylesheet" type="text/css" href="', $settings['theme_url'], '/css/index', $context['theme_variant'], '.css?alp21" />
+		<script type="text/javascript" src="', $settings['default_theme_url'], '/scripts/script.js"></script>
+	</head>
+	<body id="likes_popup">
+		<div class="windowbg">
+			<ul id="likes">';
+
+	foreach ($context['likers'] as $liker => $like_details)
+	{
+		echo '
+				<li>
+					<span class="floatleft avatar">', $like_details['profile']['avatar']['image'], '</span>
+					<span class="floatright">', $like_details['time'], '</span>
+					<span class="floatleft">
+						', $like_details['profile']['link_color'], '<br />
+						', $like_details['profile']['group'], '
+					</span>
+				</li>';
+	}
+
+	echo '
+			</ul>
+			<br class="clear" />
+			<a href="javascript:self.close();">', $txt['close_window'], '</a>
+		</div>
+	</body>
+</html>';
+}
+
+?>

+ 27 - 1
Themes/default/css/index.css

@@ -46,6 +46,19 @@ body#help_popup {
 	padding: 12px;
 }
 
+ul#likes li {
+	clear: both;
+}
+
+ul#likes span.avatar {
+	width: 45px;
+	height: 45px;
+	padding: 2px;
+}
+ul#likes span.avatar img {
+	max-height: 40px;
+}
+
 /* Tables should show empty cells. */
 table {
 	empty-cells: show;
@@ -650,7 +663,7 @@ div.pagesection div.floatright input, div.pagesection div.floatright select {
 }
 
 /* All the signatures used in the forum.  If your forum users use Mozilla, Opera, or Safari, you might add max-height here ;). */
-.signature, .attachments, .custom_fields_above_signature {
+.signature, .attachments, .under_message, .custom_fields_above_signature {
 	width: 100%;
 	overflow: auto;
 	clear: right;
@@ -662,6 +675,19 @@ div.pagesection div.floatright input, div.pagesection div.floatright select {
 	-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
 }
 
+/* While we're doing stuff under the message, time to do some likes stuff. */
+.like_button .like, .like_button .unlike {
+	width: 16px;
+	height: 16px;
+	display: inline-block;
+	background: url(../images/likes.png) no-repeat 0 0;
+	padding-right: 2px;
+	margin-bottom: -1px;
+}
+.like_button .unlike {
+	background-position: 0 -16px;
+}
+
 /* Sometimes there will be an error when you post */
 .error {
 	color: red;

+ 2 - 0
Themes/default/languages/Errors.english.php

@@ -56,6 +56,8 @@ $txt['verify_url_fail'] = 'Unable to verify referring url.  Please go back and t
 $txt['token_verify_fail'] = 'Token verification failed.  Please go back and try again.';
 $txt['guest_vote_disabled'] = 'Guests cannot vote in this poll.';
 
+$txt['cannot_like_content'] = 'You are not able to like that content.';
+$txt['cannot_view_likes'] = 'You are not able to view who liked that content.';
 $txt['cannot_access_mod_center'] = 'You do not have permission to access the moderation center.';
 $txt['cannot_admin_forum'] = 'You are not allowed to administrate this forum.';
 $txt['cannot_announce_topic'] = 'You are not allowed to announce topics on this board.';

+ 9 - 0
Themes/default/scripts/topic.js

@@ -754,3 +754,12 @@ function ignore_toggles(msgids, text)
 		});
 	}
 }
+
+function add_like_popup()
+{
+	$(".like_count a").click(function(e) {
+		e.preventDefault();
+		var title = $(this).parent().text();
+		return reqOverlayDiv($(this).prop("href"), title);
+	});
+}

+ 1 - 1
index.php

@@ -295,6 +295,7 @@ function smf_main()
 		'helpadmin' => array('Help.php', 'ShowAdminHelp'),
 		'jsmodify' => array('Post.php', 'JavaScriptModify'),
 		'jsoption' => array('Themes.php', 'SetJavaScript'),
+		'likes' => array('Likes.php', 'Likes'),
 		'loadeditorlocale' => array('Subs-Editor.php', 'loadLocale'),
 		'lock' => array('Topic.php', 'LockTopic'),
 		'lockvoting' => array('Poll.php', 'LockVoting'),
@@ -340,7 +341,6 @@ function smf_main()
 		'sticky' => array('Topic.php', 'Sticky'),
 		'theme' => array('Themes.php', 'ThemesMain'),
 		'trackip' => array('Profile-View.php', 'trackIP'),
-		'about:mozilla' => array('Karma.php', 'BookOfUnknown'),
 		'about:unknown' => array('Karma.php', 'BookOfUnknown'),
 		'unread' => array('Recent.php', 'UnreadTopics'),
 		'unreadreplies' => array('Recent.php', 'UnreadTopics'),