History v1.0

AndyB

Well-known member
Purpose:

The purpose of this thread is to provide additional information on the History v1.0 add-on.

To download and install this add-on please visit the following XenForo Resource:

http://xenforo.com/community/resources/history.2653/

Description:

This add-on will allow members to quickly return to previously viewed threads.

Features:
  • Shows a list of last viewed threads
  • Highlights threads with new posts
  • Places History link in navigation bar for easy access
  • Fully phrased
(Example of History link)

pic001.webp

(Example of History page)

pic002.webp
 
The directory structure with the core files highlighted.

library
--Andy
----History
------ControllerPublic
--------Thread.php
----Install.php
----Listener.php
----Model.php
----Uninstall.php
 
library/Andy/History/ControllerPublic/Thread.php

PHP:
<?php

class Andy_History_ControllerPublic_Thread extends XFCP_Andy_History_ControllerPublic_Thread
{
	public function actionIndex()
	{
		// get parent
		$parent = parent::actionIndex();
		
		//########################################
		// update xf_history table
		//########################################

		// get $userId
		$userId = XenForo_Visitor::getUserId();
		
		// continue if member is logged in
		if ($userId > 0)
		{
			// get database
			$db = XenForo_Application::get('db');
			
			// get $threadId
			$threadId = $this->_input->filterSingle('thread_id', XenForo_Input::UINT);	
			
			// define $dateline
			$dateline = time();
			
			// insert row into xf_history
			$db->query("
				INSERT INTO xf_history
					(user_id, thread_id, thread_read_date)
				VALUES
					(?, ?, ?)
				ON DUPLICATE KEY UPDATE
					thread_read_date = VALUES(thread_read_date)
			", array($userId, $threadId, $dateline));
		}

		// return parent
		return $parent;
	}		
			
	public function actionHistory()
	{
		//########################################
		// display History page
		//########################################
				
		// get $userId
		$userId = XenForo_Visitor::getUserId();
		
		// throw error if no $userId
		if (!$userId){
			throw $this->getNoPermissionResponseException();
		}		
		
		// get $threads data from model
		$threads = $this->getModelFromCache('Andy_History_Model')->getHistoryThreads($userId);
		
		// prepare $viewParams for template
		$viewParams = array(
			'threads' => $threads,
		);		
		
		// send to template for display
		return $this->responseView('Andy_History_ViewPublic_History', 'andy_history', $viewParams);
	}
}

?>
 
library/Andy/History/Install.php

PHP:
<?php

class Andy_History_Install
{
    public static function install()
    {
        $db = XenForo_Application::get('db');		
		
		try
		{	
			$db->query("
				CREATE TABLE xf_history (
				user_id INT( 10 ) UNSIGNED NOT NULL , 
				thread_id INT( 10 ) UNSIGNED NOT NULL , 
				thread_read_date INT( 10 ) UNSIGNED NOT NULL
				) ENGINE = InnoDB
			");
		}
		catch (Zend_Db_Exception $e) {}
		
		try
		{	
			$db->query("
				ALTER TABLE xf_history ADD UNIQUE user_id_thread_id ( user_id , thread_id ) 
			");		
		}
		catch (Zend_Db_Exception $e) {}
    }
}

?>
 
library/Andy/History/Listener.php

PHP:
<?php

class Andy_History_Listener
{
	public static function Thread($class, array &$extend)
	{
		$extend[] = 'Andy_History_ControllerPublic_Thread';
	}	
}

?>
 
library/Andy/History/Model.php

PHP:
<?php

class Andy_History_Model extends XenForo_Model
{
	public function getHistoryThreads($userId)
	{		
		return $this->_getDb()->fetchAll('
		SELECT xf_history.thread_id, 
		xf_history.thread_read_date AS history_read_date, 
		xf_thread.title, 
		xf_thread.reply_count, 
		xf_thread.view_count, 
		xf_thread.last_post_username, 
		xf_thread.last_post_date, 
		xf_node.title AS nodeTitle, 
		xf_thread_user_post.post_count AS user_post_count, 
		xf_user.user_id, xf_user.avatar_date
		FROM xf_history
		INNER JOIN xf_thread ON xf_thread.thread_id = xf_history.thread_id
		INNER JOIN xf_node ON xf_node.node_id = xf_thread.node_id
        LEFT JOIN xf_thread_user_post AS xf_thread_user_post
            ON (xf_thread_user_post.thread_id = xf_thread.thread_id
            AND xf_thread_user_post.user_id = ' . $userId . ')			
		INNER JOIN xf_user ON xf_user.user_id = xf_thread.user_id
		WHERE xf_history.user_id = ' . $userId . '
		ORDER BY xf_history.thread_read_date DESC 
		LIMIT 50');
	}		
}

?>
 
library/Andy/History/Uninstall.php

PHP:
<?php

class Andy_History_Uninstall
{
    public static function uninstall()
    {
        $db = XenForo_Application::get('db');
		
		try
		{		
			$db->query("
				DROP TABLE xf_history
			");
		}
		catch (Zend_Db_Exception $e) {}
    }
}

?>
 
andy_history template

Code:
<xen:require css="andy_history.css" />

{xen:phrase history_last_50_threads_you_have_visited}
<br /><br />

<table class="dataTable">

    <tr class="dataRow">
        <th width="42"></th>
        <th>{xen:phrase title}</th>
        <th>{xen:phrase replies}</th>
        <th>{xen:phrase views}</th>
        <th>{xen:phrase last_reply_from}</th>
        <th>{xen:phrase forum}</th>
    </tr>
    
    <xen:foreach loop="$threads" value="$thread">
    <tr class="dataRow">
    
        <td class="history_avatarContainer">
        <xen:avatar user="$thread" size="s" img="true" class="history_avatar"/>
        <xen:if is="{$thread.user_post_count}"><xen:avatar user="$visitor" size="s" img="true" class="history_miniMe" title="{xen:phrase you_have_posted_x_messages_in_this_thread, 'count={xen:number $thread.user_post_count}'}" /></xen:if>
        </td>
        
        <xen:if is="{$thread.last_post_date} <= {$thread.history_read_date} OR {$thread.last_post_username} == {$visitor.username}">
        <td><a href="{xen:link 'threads',  $thread}" />{$thread.title}</a></td>
        </xen:if>
        
        <xen:if is="{$thread.last_post_date} > {$thread.history_read_date} AND {$thread.last_post_username} != {$visitor.username}">
        <td class="history_title_unread"><a href="{xen:link 'threads/unread',  $thread}" />{$thread.title}</a></td>
        </xen:if>  
        
        <td>{$thread.reply_count}</td>     
        <td>{$thread.view_count}</td>
        <td>{$thread.last_post_username}</td>
        <td>{$thread.nodeTitle}</td>
    
    </tr>
    </xen:foreach>
    
</table>
 
andy_history.css

Code:
.dataTable a:link {
    font-size: 11pt;
}

history_avatar_th {
    width:42px;
}

.history_avatarContainer {
    display: block;
    position: relative;
    padding:5px 5px 5px 5px;
    background: url("styles/default/xenforo/gradients/category-23px-light.png") repeat-x scroll center top #F0F7FC;   
}

.history_avatar img {
    display: block;
    height: 36px;
    width: 36px;
    background-color: #ffffff;
    border: 1px solid #a5cae4;
    border-radius: 4px 4px 4px 4px;
    padding: 2px;
}

.history_miniMe img {
    max-width:20px;
    max-height:20px;
    margin: 0px 0px 1px 5px;
    position: absolute;
    border:none;
    border-radius: 0px 0px 0px 0px;
    bottom: 1px;
    left: 29px;
    padding:none;
    z-index: 10;
}

.history_miniMe img {
    border: 1px none #000000;
    border-radius: 2px;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
    height: 20px;
    padding: 1px;
    width: 20px;
}

.history_title_unread a:link {
    font-weight:bold;
    font-size:12pt;
}
 
You should prefix correctly to avoid any issues with potential XenForo Core updates.

Using xf_tablename is poor practice. You should use something like andyb_history or xf_andyb_history.
 
Will it be possible for staff to see the browsing history of specific members? I have such functionality on vbulletin and we use it a lot when encountering problematic members.
 
The second one is the better practice. You should always prefix your prefixed table names with xf_.
Could you explain why?
I always find it much easier to spot the differences between addons and core if the prefix starts with the addon name. This will sort the addon before or after the core tables, instead of mixing it in.
 
Could you explain why?
I always find it much easier to spot the differences between addons and core if the prefix starts with the addon name. This will sort the addon before or after the core tables, instead of mixing it in.

That would be great! @AndyB plz add this feature!
 
Top Bottom