Файл: forsoc.ru/ext/vse/similartopics/core/similar_topics.php
Строк: 387
<?php
/**
*
* Precise Similar Topics
*
* @copyright (c) 2013 Matt Friedman
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/
namespace vsesimilartopicscore;
class similar_topics
{
/** @var phpbbauthauth */
protected $auth;
/** @var phpbbcacheservice */
protected $cache;
/** @var phpbbconfigconfig */
protected $config;
/** @var phpbbdbdriverdriver_interface */
protected $db;
/** @var phpbbeventdispatcher_interface */
protected $dispatcher;
/** @var phpbbpagination */
protected $pagination;
/** @var phpbbrequestrequest */
protected $request;
/** @var phpbbtemplatetemplate */
protected $template;
/** @var phpbbuser */
protected $user;
/** @var phpbbcontent_visibility */
protected $content_visibility;
/** @var string phpBB root path */
protected $root_path;
/** @var string PHP file extension */
protected $php_ext;
/**
* Constructor
*
* @param phpbbauthauth $auth
* @param phpbbcacheservice $cache
* @param phpbbconfigconfig $config
* @param phpbbdbdriverdriver_interface $db
* @param phpbbeventdispatcher_interface $dispatcher
* @param phpbbpagination $pagination
* @param phpbbrequestrequest $request
* @param phpbbtemplatetemplate $template
* @param phpbbuser $user
* @param phpbbcontent_visibility $content_visibility
* @param string $root_path
* @param string $php_ext
* @access public
*/
public function __construct(phpbbauthauth $auth, phpbbcacheservice $cache, phpbbconfigconfig $config, phpbbdbdriverdriver_interface $db, phpbbeventdispatcher_interface $dispatcher, phpbbpagination $pagination, phpbbrequestrequest $request, phpbbtemplatetemplate $template, phpbbuser $user, phpbbcontent_visibility $content_visibility, $root_path, $php_ext)
{
$this->auth = $auth;
$this->cache = $cache;
$this->config = $config;
$this->db = $db;
$this->dispatcher = $dispatcher;
$this->pagination = $pagination;
$this->request = $request;
$this->template = $template;
$this->user = $user;
$this->content_visibility = $content_visibility;
$this->root_path = $root_path;
$this->php_ext = $php_ext;
}
/**
* Is similar topics available?
*
* @return bool True if available, false otherwise
* @access public
*/
public function is_available()
{
return !empty($this->config['similar_topics']) && // is enabled
!empty($this->config['similar_topics_limit']) && // num of topics to display > 0
!empty($this->user->data['user_similar_topics']) && // user view similar topics enabled
$this->auth->acl_get('u_similar_topics') && // user authorized to view similar topics
$this->is_mysql() // database is MySQL
;
}
/**
* Is the forum available for displaying similar topics
*
* @param int $forum_id A forum identifier
* @return bool True if available, false otherwise
* @access public
*/
public function forum_available($forum_id)
{
return !in_array($forum_id, explode(',', $this->config['similar_topics_hide']));
}
/**
* Get similar topics by matching topic titles
*
* NOTE: Currently requires MySQL due to the use of FULLTEXT indexes
* and MATCH and AGAINST and UNIX_TIMESTAMP. MySQL FULLTEXT has built-in
* English ignore words. We use phpBB's ignore words for non-English
* languages. We also remove any admin-defined special ignore words.
*
* @param array $topic_data Array with topic data
* @return null
* @access public
*/
public function display_similar_topics($topic_data)
{
$topic_title = $this->clean_topic_title($topic_data['topic_title']);
// If the cleaned up topic_title is empty, no need to continue
if (empty($topic_title))
{
return;
}
// Similar Topics query
$sql_array = array(
'SELECT' => "f.forum_id, f.forum_name, t.*,
MATCH (t.topic_title) AGAINST ('" . $this->db->sql_escape($topic_title) . "') AS score",
'FROM' => array(
TOPICS_TABLE => 't',
),
'LEFT_JOIN' => array(
array(
'FROM' => array(FORUMS_TABLE => 'f'),
'ON' => 'f.forum_id = t.forum_id',
),
),
'WHERE' => "MATCH (t.topic_title) AGAINST ('" . $this->db->sql_escape($topic_title) . "') >= 0.5
AND t.topic_status <> " . ITEM_MOVED . '
AND t.topic_visibility = ' . ITEM_APPROVED . '
AND t.topic_time > (UNIX_TIMESTAMP() - ' . $this->config['similar_topics_time'] . ')
AND t.topic_id <> ' . (int) $topic_data['topic_id'],
//'GROUP_BY' => 't.topic_id',
//'ORDER_BY' => 'score DESC', // this is done automatically by MySQL when not using IN BOOLEAN MODE
);
// Add topic tracking data to the query (only if query caching is off)
if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
{
$sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_TRACK_TABLE => 'tt'), 'ON' => 'tt.topic_id = t.topic_id AND tt.user_id = ' . $this->user->data['user_id']);
$sql_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TRACK_TABLE => 'ft'), 'ON' => 'ft.forum_id = f.forum_id AND ft.user_id = ' . $this->user->data['user_id']);
$sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as f_mark_time';
}
else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
{
// Cookie based tracking copied from search.php
$tracking_topics = $this->request->variable($this->config['cookie_name'] . '_track', '', true, phpbbrequestrequest_interface::COOKIE);
$tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
}
// We need to exclude passworded forums so we do not leak the topic title
$passworded_forums = $this->user->get_passworded_forums();
// See if the admin set this forum to only search a specific group of other forums, and include them
if (!empty($topic_data['similar_topic_forums']))
{
// Remove any passworded forums from this group of forums we will be searching
$included_forums = array_diff(explode(',', $topic_data['similar_topic_forums']), $passworded_forums);
// if there's nothing left to display (user has no access to the forums we want to search)
if (empty($included_forums))
{
return;
}
$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $included_forums);
}
// Otherwise, see what forums are not allowed to be searched, and exclude them
else if (!empty($this->config['similar_topics_ignore']))
{
// Add passworded forums to the exlude array
$excluded_forums = array_unique(array_merge(explode(',', $this->config['similar_topics_ignore']), $passworded_forums));
$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $excluded_forums, true);
}
// In all other cases, exclude any passworded forums the user is not allowed to view
else if (!empty($passworded_forums))
{
$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $passworded_forums, true);
}
/**
* Event to modify the sql_array for similar topics
*
* @event vse.similartopics.get_topic_data
* @var array sql_array SQL array to get similar topics data
* @since 1.3.0
*/
$vars = array('sql_array');
extract($this->dispatcher->trigger_event('vse.similartopics.get_topic_data', compact($vars)));
$sql = $this->db->sql_build_query('SELECT', $sql_array);
$result = $this->db->sql_query_limit($sql, $this->config['similar_topics_limit'], 0, $this->config['similar_topics_cache']);
// Grab icons
$icons = $this->cache->obtain_icons();
$rowset = array();
while ($row = $this->db->sql_fetchrow($result))
{
$similar_forum_id = (int) $row['forum_id'];
$similar_topic_id = (int) $row['topic_id'];
$rowset[$similar_topic_id] = $row;
if ($this->auth->acl_get('f_read', $similar_forum_id))
{
// Get topic tracking info
if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
{
$topic_tracking_info = get_topic_tracking($similar_forum_id, $similar_topic_id, $rowset, array($similar_forum_id => $row['f_mark_time']));
}
else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
{
$topic_tracking_info = get_complete_topic_tracking($similar_forum_id, $similar_topic_id);
if (!$this->user->data['is_registered'])
{
$this->user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $this->config['board_startdate']) : 0;
}
}
// Replies
$replies = $this->content_visibility->get_count('topic_posts', $row, $similar_forum_id) - 1;
// Get folder img, topic status/type related information
$folder_img = $folder_alt = $topic_type = '';
$unread_topic = (isset($topic_tracking_info[$similar_topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$similar_topic_id]) ? true : false;
topic_status($row, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type);
$topic_unapproved = ($row['topic_visibility'] == ITEM_UNAPPROVED && $this->auth->acl_get('m_approve', $similar_forum_id)) ? true : false;
$posts_unapproved = ($row['topic_visibility'] == ITEM_APPROVED && $row['topic_posts_unapproved'] && $this->auth->acl_get('m_approve', $similar_forum_id)) ? true : false;
//$topic_deleted = $row['topic_visibility'] == ITEM_DELETED;
$u_mcp_queue = ($topic_unapproved || $posts_unapproved) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=queue&mode=' . (($topic_unapproved) ? 'approve_details' : 'unapproved_posts') . "&t=$similar_topic_id", true, $this->user->session_id) : '';
//$u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", "i=queue&mode=deleted_topics&t=$similar_topic_id", true, $this->user->session_id) : $u_mcp_queue;
$base_url = append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&t=' . $similar_topic_id);
$topic_row = array(
'TOPIC_AUTHOR_FULL' => get_username_string('full', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']),
'FIRST_POST_TIME' => $this->user->format_date($row['topic_time']),
'LAST_POST_TIME' => $this->user->format_date($row['topic_last_post_time']),
'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']),
'TOPIC_REPLIES' => $replies,
'TOPIC_VIEWS' => $row['topic_views'],
'TOPIC_TITLE' => $row['topic_title'],
'FORUM_TITLE' => $row['forum_name'],
'TOPIC_IMG_STYLE' => $folder_img,
'TOPIC_FOLDER_IMG' => $this->user->img($folder_img, $folder_alt),
'TOPIC_FOLDER_IMG_ALT' => $this->user->lang($folder_alt),
'TOPIC_ICON_IMG' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '',
'TOPIC_ICON_IMG_WIDTH' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '',
'TOPIC_ICON_IMG_HEIGHT' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['height'] : '',
'ATTACH_ICON_IMG' => ($this->auth->acl_get('u_download') && $this->auth->acl_get('f_download', $similar_forum_id) && $row['topic_attachment']) ? $this->user->img('icon_topic_attach', $this->user->lang('TOTAL_ATTACHMENTS')) : '',
'UNAPPROVED_IMG' => ($topic_unapproved || $posts_unapproved) ? $this->user->img('icon_topic_unapproved', ($topic_unapproved) ? 'TOPIC_UNAPPROVED' : 'POSTS_UNAPPROVED') : '',
'S_UNREAD_TOPIC' => $unread_topic,
'S_TOPIC_REPORTED' => (!empty($row['topic_reported']) && $this->auth->acl_get('m_report', $similar_forum_id)) ? true : false,
'S_TOPIC_UNAPPROVED' => $topic_unapproved,
'S_POSTS_UNAPPROVED' => $posts_unapproved,
//'S_TOPIC_DELETED' => $topic_deleted,
'S_HAS_POLL' => ($row['poll_start']) ? true : false,
'U_NEWEST_POST' => append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&t=' . $similar_topic_id . '&view=unread') . '#unread',
'U_LAST_POST' => append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&t=' . $similar_topic_id . '&p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'],
'U_VIEW_TOPIC' => append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&t=' . $similar_topic_id),
'U_VIEW_FORUM' => append_sid("{$this->root_path}viewforum.{$this->php_ext}", 'f=' . $similar_forum_id),
'U_MCP_REPORT' => append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=reports&mode=reports&f=' . $similar_forum_id . '&t=' . $similar_topic_id, true, $this->user->session_id),
'U_MCP_QUEUE' => $u_mcp_queue,
);
/**
* Event to modify the similar topics template block
*
* @event vse.similartopics.modify_topicrow
* @var array row Array with similar topic data
* @var array topic_row Template block array
* @since 1.3.0
*/
$vars = array('row', 'topic_row');
extract($this->dispatcher->trigger_event('vse.similartopics.modify_topicrow', compact($vars)));
$this->template->assign_block_vars('similar', $topic_row);
$this->pagination->generate_template_pagination($base_url, 'similar.pagination', 'start', $replies + 1, $this->config['posts_per_page'], 1, true, true);
}
}
$this->db->sql_freeresult($result);
$this->user->add_lang_ext('vse/similartopics', 'similar_topics');
$this->template->assign_vars(array(
'L_SIMILAR_TOPICS' => $this->user->lang('SIMILAR_TOPICS'),
'NEWEST_POST_IMG' => $this->user->img('icon_topic_newest', 'VIEW_NEWEST_POST'),
'LAST_POST_IMG' => $this->user->img('icon_topic_latest', 'VIEW_LATEST_POST'),
'REPORTED_IMG' => $this->user->img('icon_topic_reported', 'TOPIC_REPORTED'),
//'DELETED_IMG' => $this->user->img('icon_topic_deleted', 'TOPIC_DELETED'),
'POLL_IMG' => $this->user->img('icon_topic_poll', 'TOPIC_POLL'),
));
}
/**
* Clean topic title (and if needed, ignore-words)
*
* @param string $text The topic title
* @return string The topic title
* @access public
*/
public function clean_topic_title($text)
{
// Strip quotes, ampersands
$text = str_replace(array('"', '&'), '', $text);
if (!$this->english_lang() || $this->has_ignore_words())
{
$text = $this->strip_stop_words($text);
}
return $text;
}
/**
* Remove any non-english and/or custom defined ignore-words
*
* @param string $text The topic title
* @return string The topic title
* @access protected
*/
protected function strip_stop_words($text)
{
$words = array();
// Retrieve a language dependent list of words to be ignored (method copied from search.php)
$search_ignore_words = "{$this->user->lang_path}{$this->user->lang_name}/search_ignore_words.{$this->php_ext}";
if (!$this->english_lang() && file_exists($search_ignore_words))
{
include($search_ignore_words);
}
if ($this->has_ignore_words())
{
// Merge any custom defined ignore words from the ACP to the stop-words array
$words = array_merge($this->make_word_array($this->config['similar_topics_words']), $words);
}
// Remove stop-words from the topic title text
$words = array_diff($this->make_word_array($text), $words);
// Convert our words array back to a string
$text = (!empty($words)) ? implode(' ', $words) : '';
return $text;
}
/**
* Helper function to split string into an array of words
*
* @param string $text String of plain text words
* @return array Array of plaintext words
* @access protected
*/
protected function make_word_array($text)
{
// Strip out any non-alpha-numeric characters using PCRE regex syntax
$text = trim(preg_replace('#[^p{L}p{N}]+#u', ' ', $text));
$words = explode(' ', utf8_strtolower($text));
foreach ($words as $key => $word)
{
// Strip words of 2 characters or less
if (utf8_strlen(trim($word)) < 3)
{
unset($words[$key]);
}
}
return $words;
}
/**
* Check if English is the current user's language
*
* @return bool True if lang is 'en' or 'en_us', false otherwise
* @access protected
*/
protected function english_lang()
{
return ($this->user->lang_name == 'en' || $this->user->lang_name == 'en_us');
}
/**
* Check if custom ignore words have been defined for similar topics
*
* @return bool True or false
* @access protected
*/
protected function has_ignore_words()
{
return !empty($this->config['similar_topics_words']);
}
/**
* Check if the database layer is MySQL4 or later
*
* @return bool True is MySQL4 or later, false otherwise
* @access protected
*/
protected function is_mysql()
{
return ($this->db->get_sql_layer() == 'mysql4' || $this->db->get_sql_layer() == 'mysqli');
}
}