1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Similar Threads v1.4

Discussion in 'XenForo Development Discussions' started by AndyB, Oct 30, 2013.

  1. AndyB

    AndyB Well-Known Member

    Purpose:

    The purpose of this thread is to provide additional information on the Similar Threads v1.4 add-on. To download and install this add-on please visit the following XenForo Resource:

    http://xenforo.com/community/resources/similar-threads.2441/

    Description:

    The Similar Threads add-on will do the following:
    • Show a list of similar threads when a user is creating a new thread
    • Show a list of similar threads when a user is viewing a thread
    Key Features:
    • Seven admin options that allow you to customize this add-on
    • Node view permissions honored
    • Results immediately displayed
    • Automatically disabled in responsive mode
    • Fully phrased
    Results Example:

    pic001.jpg

    Admin Options:

    pic002.jpg
     
    Mirovinger and imno007 like this.
  2. AndyB

    AndyB Well-Known Member

    The directory structure with the core files highlighted.

    js
    --xenforo
    ----similarthreads.js
    library
    --Andy
    ----SimilarThreads
    ------ControllerPublic
    --------Abstract.php
    --------Thread.php
    ------Model.php
    ------Route
    --------Prefix
    ----------SimilarThreads.php
     
  3. AndyB

    AndyB Well-Known Member

    js/xenforo/similarthreads.js

    Code:
    !function($, window, document, _undefined)
    {
    	XenForo.similarthreadsId = function($form)
    	{		
    		var typewatch = (function()
    		{
    			var timer = 0;
    			return function(callback, ms)
    			{
    				clearTimeout (timer);
    				timer = setTimeout(callback, ms);
    			}  
    		})(); 
    		$title = $form.find('input[name="title"]');
    		$title.keyup(function() 
    		{
    			typewatch(function () 
    			{
    				XenForo.ajax(
    				$('base').attr('href') + 'similarthreads/',
    				$form.serializeArray(),
    				function(ajaxData, textStatus)
    				{
    					if (ajaxData.templateHtml)
    					{
    						new XenForo.ExtLoader(ajaxData, function()
    						{
    							$('#similarthreadsId-result').html('<div>' + ajaxData.templateHtml + '</div>');
    						});
    					}
    				});
    			}, 500);
    		});		
    	}	
    	XenForo.register('#similarthreadsId', 'XenForo.similarthreadsId');	
    }
    (jQuery, this, document);
    
     
  4. AndyB

    AndyB Well-Known Member

    library/Andy/SimilarThreads/ControllerPublic/Abstract.php

    PHP:
    <?php

    class Andy_SimilarThreads_ControllerPublic_Abstract extends XenForo_ControllerPublic_Abstract
    {
        public function 
    actionIndex()
        {        
            
    // get options from Admin CP -> Options -> Similar Threads -> Show on new thread
            
    $showCreateThread XenForo_Application::get('options')->showCreateThread;
        
            
    // show similar threads if true
            
    if ($showCreateThread)
            {
                
    // declare variables
                
    $currentNodeId '';
                
    $currentThreadId '';
                
    $similarThreads = array();
                
    $searchWords = array();
                
    $searchWord1 '';
                
    $searchWord2 '';            
                
    $safeSearchWord1 '';
                
    $safeSearchWord2 '';    
                
                
    // get newTitle
                
    $newTitle $this->_request->getParam('title');
        
                
    // put into array
                
    $newTitle explode(' '$newTitle);
                
                
    // get options from Admin CP -> Options -> Similar Threads -> Common Words    
                
    $commonWords XenForo_Application::get('options')->commonWords;
                
                
    // convert to lowercase
                
    $commonWords strtolower($commonWords);    
                
                
    // put $commonWordsLower into an array
                
    $commonWords explode(' '$commonWords); 
                
                
    // remove any common words from array
                
    foreach($newTitle as $var)
                {
                    if (!
    in_array(strtolower($var), $commonWords))
                    {
                        
    // get options from Admin CP -> Options -> Similar Threads -> Miniumum Common Word Length    
                        
    $minimumCommonWordLength XenForo_Application::get('options')->minimumCommonWordLength;                    
                        
                        if (
    strlen($var) >= $minimumCommonWordLength)
                        {
                            
    $searchWords[] = $var;
                        }
                    }
                }
                
                
    $count count($searchWords);
                
                
    // only continue if we have a search word
                
    if ($count 0)
                {                
                    
    // get first search word
                    
    $searchWord1 $searchWords[0];
                    
                    
    // make safe for query
                    
    $safeSearchWord1 addslashes($searchWords[0]);
                    
                    if (
    $count 1)
                    {    
                        
    // get second search word
                        
    $searchWord2 $searchWords[1];    
                                
                        
    // make safe for query
                        
    $safeSearchWord2 addslashes($searchWords[1]);    
                    }            
                }
                
                
    // run query only if we have a search
                
    if ($safeSearchWord1 != '')
                {
                    
    // run query in model    
                    
    $similarThreads $this->getModelFromCache('Andy_SimilarThreads_Model')->getThreads($safeSearchWord1,$safeSearchWord2,$currentNodeId,$currentThreadId);    
                } 
                
                
    // prepare $viewParams for template
                
    $viewParams = array(
                    
    'similarThreads' => $similarThreads,
                    
    'searchWord1' => $searchWord1,
                    
    'searchWord2' => $searchWord2,
                );
                
                
    // send to template
                
    return $this->responseView('Andy_SimilarThreads_ViewPublic_SimilarThreads''andy_similarthreads_create_thread'$viewParams);
            }
        }
    }

    ?>
     
  5. AndyB

    AndyB Well-Known Member

    library/Andy/SimilarThreads/ControllerPublic/Thread.php

    PHP:
    <?php

    class Andy_SimilarThreads_ControllerPublic_Thread extends XFCP_Andy_SimilarThreads_ControllerPublic_Thread
    {    
        public function 
    actionIndex()
        {
            
    //########################################
            // When you call a controller function (an action), it doesn't display on execution,
            // it displays when the script is finished. By default it just returns the view object. 
            // $parent = parent::actionIndex() stores the object into the parent field
            // which is how you can access ->params, ->viewName, templateName, etc...
            // If you did return parent::actionIndex(); then you'd be displaying the parent object.
            //########################################
            
            
    $parent parent::actionIndex();
        
            
    //########################################
            // Return parent action if this is a redirect or other non View response.
            // See /library/XenForo/ControllerResponse for different types of responses.
            //########################################
                 
            
    if (!$parent instanceof XenForo_ControllerResponse_View)
            {
                return 
    parent::actionIndex();
            }
            
            
    // get options from Admin CP -> Options -> Similar Threads -> Show Below First Post    
            
    $showBelowFirstPost XenForo_Application::get('options')->showBelowFirstPost;    
                
            
    // get options from Admin CP -> Options -> Similar Threads -> Show Below Quick Reply    
            
    $showBelowQuickReply XenForo_Application::get('options')->showBelowQuickReply;            
        
            
    // show similar threads if true
            
    if ($showBelowFirstPost OR $showBelowQuickReply)
            {             
                
    // declare variables
                
    $viewParams = array();
                
    $searchWords = array();
                
    $similarThreads = array();
                
    $searchWord1 '';
                
    $searchWord2 '';     
                
    $safeSearchWord1 '';
                
    $safeSearchWord2 '';    
                
                
    // get $currentNodeId
                
    $currentNodeId $parent->params['thread']['node_id'];                    

                
    // get $currentThreadId
                
    $currentThreadId $parent->params['thread']['thread_id'];    
                
                
    // get $threadTitle
                
    $threadTitle $parent->params['thread']['title']; 
                
                
    // put into array
                
    $threadTitle explode(' '$threadTitle);
                
                
    // get options from Admin CP -> Options -> Similar Threads -> Common Words    
                
    $commonWords XenForo_Application::get('options')->commonWords;
                
                
    // convert to lowercase
                
    $commonWords strtolower($commonWords);    
                
                
    // put $commonWordsLower into an array
                
    $commonWords explode(' '$commonWords); 
                
                
    // remove any common words from array
                
    foreach($threadTitle as $var)
                {
                    if (!
    in_array(strtolower($var), $commonWords))
                    {
                        
    // get options from Admin CP -> Options -> Similar Threads -> Miniumum Common Word Length    
                        
    $minimumCommonWordLength XenForo_Application::get('options')->minimumCommonWordLength;                    
                        
                        if (
    strlen($var) >= $minimumCommonWordLength)
                        {
                            
    $searchWords[] = $var;
                        }
                    }
                }
                
                
    $count count($searchWords);
                
                
    // only continue if we have a search word
                
    if ($count 0)
                {                
                    
    // get first search word
                    
    $searchWord1 $searchWords[0];
                    
                    
    // make safe for query
                    
    $safeSearchWord1 addslashes($searchWords[0]);
                    
                    if (
    $count 1)
                    {    
                        
    // get second search word
                        
    $searchWord2 $searchWords[1];    
                                
                        
    // make safe for query
                        
    $safeSearchWord2 addslashes($searchWords[1]);    
                    }            
                }
                
                
    // run query only if we have a search
                
    if ($safeSearchWord1 != '')
                {
                    
    // run query in model    
                    
    $similarThreads $this->getModelFromCache('Andy_SimilarThreads_Model')->getThreads($safeSearchWord1,$safeSearchWord2,$currentNodeId,$currentThreadId);    
                } 
                
                
    // prepare $viewParams for template
                
    $viewParams = array(
                    
    'similarThreads' => $similarThreads,
                    
    'showBelowFirstPost' => $showBelowFirstPost,
                    
    'showBelowQuickReply' => $showBelowQuickReply,
                    
    'searchWord1' => $searchWord1,
                    
    'searchWord2' => $searchWord2,
                );
                
                return 
    $this->responseView($parent->viewName$parent->templateNamearray_merge($parent->params$viewParams));    
            }
            else
            {
                
    // neither option switch is set so return parent action
                
    return parent::actionIndex();    
            }
        }
    }

    ?>
     
  6. AndyB

    AndyB Well-Known Member

    library/Andy/SimilarThreads/Listener.php

    PHP:
    <?php

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

    ?>
     
  7. AndyB

    AndyB Well-Known Member

    library/Andy/SimilarThreads/Model.php

    PHP:
    <?php

    class Andy_SimilarThreads_Model extends XenForo_Model
    {    
        public function 
    getThreads($safeSearchWord1,$safeSearchWord2,$currentNodeId,$currentThreadId)
        { 
            
    // declare variables
            
    $whereclause1 '';
            
    $whereclause2 '';
            
    $whereclause3 '';
            
    $results1 = array();
            
    $results2 = array();
            
    $results3 = array();
            
    $excludeResults1 '';
            
    $excludeResults2 '';
            
    $resultsCount1 = array();
            
    $resultsCount2 = array();
        
            
    //########################################
            // $whereclause1
            // permissions
            //########################################
                        
            // declare variable    
            
    $visitor XenForo_Visitor::getInstance();            
            
    $permissionCombinationId $visitor['permission_combination_id'];                    
            
            
    // get permissions
            
    $permissions $this->_getDb()->fetchAll("
                SELECT xf_permission_cache_content.cache_value, xf_permission_cache_content.content_id
                FROM xf_permission_cache_content
                INNER JOIN xf_forum ON xf_forum.node_id = xf_permission_cache_content.content_id
                WHERE xf_permission_cache_content.permission_combination_id = '
    $permissionCombinationId'
                ORDER BY xf_permission_cache_content.content_id ASC
            "
    );    
            
            foreach (
    $permissions AS $k => $v)
            {
                
    $cache_value[] = unserialize($v['cache_value']);
                
    $content_id[] = $v['content_id'];                
            }
            
            
    $i 0;
            
            foreach (
    $cache_value AS $v)
            {
                if (isset(
    $v['view']))
                {
                    
    $view[] = $v['view'];
                }
                
                if (isset(
    $v['view']) AND $v['view'] != 1)
                {
                    
    $forum_id_no_permission[] = $content_id[$i];
                }
                
    $i++;
            } 
            
            
    // exclude forums the user cannot view
            
    if (isset($forum_id_no_permission))
            {
                
    $whereclause1 'AND xf_thread.node_id <> ' implode(' AND xf_thread.node_id <> '$forum_id_no_permission);
            }
            else
            {
                
    $whereclause1 '';
            }
            
            
    //########################################
            // $whereclause2
            // exclude thread that is being viewed
            //########################################
            
            // if coming from Thread.php don't include the thread we are viewing
            
    if (isset($currentThreadId))
            {
                
    $whereclause2 "AND xf_thread.thread_id <> '$currentThreadId'";
            }
            
            
    //########################################
            // $whereclause3
            // show results from same forum
            //########################################
            
            // get options from Admin CP -> Options -> Similar Threads -> Show Results From Same Forum    
            
    $sameForum XenForo_Application::get('options')->sameForum;
            
            
    // check if coming from Thread.php 
            
    $visitor XenForo_Visitor::getInstance();
            
    $userId $visitor['user_id'];    
             
            
    $params $this->_getDb()->fetchOne("
            SELECT params
            FROM xf_session_activity
            WHERE user_id = '
    $userId'
            AND controller_action = 'CreateThread'
            "
    );
            
            if (
    $params != ''
            {
                
    $pos1 strpos($params,'node_id=');
                
                if (
    is_numeric($pos1))
                {
                    
    $currentNodeId substr($params,8);
                }
            }
            
            
    // create $whereclause3                
            
    if ($sameForum == AND $currentNodeId != '')
            {
                
    $whereclause3 "AND xf_thread.node_id = '$currentNodeId'";
            }            
            
            
    //########################################
            // search 1
            // $safeSearchWord1 AND $safeSearchWord2
            //########################################
            
            // get option from Admin CP -> Options -> Similar Threads -> Maximum Results    
            
    $maxResults XenForo_Application::get('options')->maxResults;         
            
            if (
    $safeSearchWord1 != '' AND $safeSearchWord2 != '')
            {
                
    // get threads
                
    $results1 $this->_getDb()->fetchAll("
                    SELECT xf_thread.thread_id, xf_thread.title, xf_thread.node_id, xf_node.title AS nodetitle, xf_thread.post_date
                    FROM xf_thread
                    INNER JOIN xf_node ON xf_node.node_id = xf_thread.node_id
                    WHERE xf_thread.title LIKE '%
    $safeSearchWord1%'
                    AND xf_thread.title LIKE '%
    $safeSearchWord2%'
                    AND xf_thread.discussion_type <> 'redirect'
                    
    $whereclause1
                    
    $whereclause2
                    
    $whereclause3
                    ORDER BY xf_thread.thread_id DESC
                    LIMIT 
    $maxResults
                "
    );    
                
                
    // prepare $results for return
                
    $results $results1;
            }
            
            
    //########################################
            // search 2
            // $safeSearchWord1
            //########################################

            
    if ($safeSearchWord1 != '')
            {
            
                foreach (
    $results1 AS $k => $v)
                {
                    
    $resultsCount1[] = $v['thread_id'];
                    
                    
    // exclude previously found thread_id's
                    
    $excludeResults1 'AND xf_thread.thread_id <> ' implode(' AND xf_thread.thread_id <> '$resultsCount1);
                }
                
                
    $count count($resultsCount1);
                
                if (
    $count $maxResults AND is_numeric($count))
                {
                    
    $maxResults2 $maxResults $count;
                    
                    
    // get threads
                    
    $results2 $this->_getDb()->fetchAll("
                        SELECT xf_thread.thread_id, xf_thread.title, xf_thread.node_id, xf_node.title AS nodetitle, xf_thread.post_date
                        FROM xf_thread
                        INNER JOIN xf_node ON xf_node.node_id = xf_thread.node_id
                        WHERE xf_thread.title LIKE '%
    $safeSearchWord1%'
                        AND xf_thread.discussion_type <> 'redirect'
                        
    $whereclause1
                        
    $whereclause2
                        
    $whereclause3
                        
    $excludeResults1
                        ORDER BY xf_thread.thread_id DESC
                        LIMIT 
    $maxResults2
                    "
    );    
                    
                    
    // prepare $results for return
                    
    $results array_merge($results1$results2);
                }
            }
            
            
    //########################################
            // search 3
            // $safeSearchWord2
            //########################################
            
            
    if ($safeSearchWord2 != '')
            {            
                foreach (
    $results2 AS $k => $v)
                {
                    
    $resultsCount2[] = $v['thread_id'];
                    
                    
    // exclude previously found thread_id's
                    
    $excludeResults2 'AND xf_thread.thread_id <> ' implode(' AND xf_thread.thread_id <> '$resultsCount2);
                }
                
                
    $count count($resultsCount1) + count($resultsCount2);
                
                if (
    $count $maxResults AND is_numeric($count))
                {
                    
    $maxResults3 $maxResults $count;
                    
                    
    // get threads
                    
    $results3 $this->_getDb()->fetchAll("
                        SELECT xf_thread.thread_id, xf_thread.title, xf_thread.node_id, xf_node.title AS nodetitle, xf_thread.post_date
                        FROM xf_thread
                        INNER JOIN xf_node ON xf_node.node_id = xf_thread.node_id
                        WHERE xf_thread.title LIKE '%
    $safeSearchWord2%'
                        AND xf_thread.discussion_type <> 'redirect'
                        
    $whereclause1
                        
    $whereclause2
                        
    $whereclause3
                        
    $excludeResults1
                        
    $excludeResults2
                        ORDER BY xf_thread.thread_id DESC
                        LIMIT 
    $maxResults3
                    "
    );    
                    
                    
    // prepare $results for return
                    
    $results array_merge($results1$results2$results3);
                } 
            }
        
            
    //########################################
            // return results
            //########################################    

            
    return $results;    
        }
    }

    ?>
     
  8. AndyB

    AndyB Well-Known Member

    library/Andy/SimilarThreads/Model/Route/Prefix/SimilarThreads.php

    PHP:
    <?php

    class Andy_SimilarThreads_Route_Prefix_SimilarThreads implements XenForo_Route_Interface
    {
        public function 
    match($routePathZend_Controller_Request_Http $requestXenForo_Router $router)
        {
            return 
    $router->getRouteMatch('Andy_SimilarThreads_ControllerPublic_Abstract''Index'$routePath);
        }
    }

    ?>
     
  9. AndyB

    AndyB Well-Known Member

    This add-on uses six template modifications:

    pic003.jpg
     
  10. AndyB

    AndyB Well-Known Member

    There are three templates all very similar to this one:

    Code:
    <xen:if is="{$similarThreads}">
    
        <div class="sectionMain similarThreads">
        
            <div class="primaryContent">
            	{xen:phrase similar_threads} {$searchWord1} {$searchWord2}
            </div>
            
            <table class="dataTable">
            
            <tr class="dataRow">
            <th>{xen:phrase forum}</th>
            <th>{xen:phrase title}</th>
            <th>{xen:phrase date}</th>
            </tr>
            
            <xen:foreach loop="$similarThreads" key="$index" value="$similarThread">
            
                <tr class="dataRow">
                <td><a href="{xen:link forums, $similarThread}" title="{$similarThread.nodetitle}" target="_blank">{$similarThread.nodetitle}</a></td>
                <td><a href="{xen:link threads, $similarThread}" title="{$similarThread.title}" target="_blank">{$similarThread.title}</a></td>
                <td>{xen:datetime $similarThread.post_date}</td>
                </tr>
            
            </xen:foreach>
            
            </table>
    
        </div>
    
    </xen:if>
    
    <xen:if is="!{$similarThreads}">
    </xen:if>
    
     
  11. AndyB

    AndyB Well-Known Member

    The search engine will follow these steps:
    1. Parse thread title and remove common words
    2. Select the first two search words
    3. Search for threads that contain both search words
    4. Search for threads that contain the first search word
    5. Search for threads that contain the second search word
    Starting with step #3, if the maximum results set in the options is met, the remainder of queries are not run.
     
  12. AndyB

    AndyB Well-Known Member

    Other information about this add-on:
    • Five template modifications
    • Three custom templates
    • Five additional queries
    • One code event listener
    • One javascript file
    • No database changes
     

Share This Page