Completed
Pull Request — master (#25)
by Matt
02:01
created

similar_topics::is_mysql()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
nop 0
1
<?php
2
/**
3
 *
4
 * Precise Similar Topics
5
 *
6
 * @copyright (c) 2013 Matt Friedman
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 */
10
11
namespace vse\similartopics\core;
12
13
class similar_topics
14
{
15
	/** @var \phpbb\auth\auth */
16
	protected $auth;
17
18
	/** @var \phpbb\cache\service */
19
	protected $cache;
20
21
	/** @var \phpbb\config\config */
22
	protected $config;
23
24
	/** @var \phpbb\db\driver\driver_interface */
25
	protected $db;
26
27
	/** @var \phpbb\event\dispatcher_interface */
28
	protected $dispatcher;
29
30
	/** @var \phpbb\pagination */
31
	protected $pagination;
32
33
	/** @var \phpbb\request\request */
34
	protected $request;
35
36
	/** @var \phpbb\template\template */
37
	protected $template;
38
39
	/** @var \phpbb\user */
40
	protected $user;
41
42
	/** @var \phpbb\content_visibility */
43
	protected $content_visibility;
44
45
	/** @var \vse\similartopics\driver\driver_interface */
46
	protected $similartopics;
47
48
	/** @var string phpBB root path  */
49
	protected $root_path;
50
51
	/** @var string PHP file extension */
52
	protected $php_ext;
53
54
	/**
55
	 * Constructor
56
	 *
57
	 * @access public
58
	 * @param \phpbb\auth\auth                  $auth
59
	 * @param \phpbb\cache\service              $cache
60
	 * @param \phpbb\config\config              $config
61
	 * @param \phpbb\db\driver\driver_interface $db
62
	 * @param \phpbb\event\dispatcher_interface $dispatcher
63
	 * @param \phpbb\pagination                 $pagination
64
	 * @param \phpbb\request\request            $request
65
	 * @param \phpbb\template\template          $template
66
	 * @param \phpbb\user                       $user
67
	 * @param \phpbb\content_visibility         $content_visibility
68
	 * @param \vse\similartopics\driver\manager $similartopics_manager
69
	 * @param string                            $root_path
70
	 * @param string                            $php_ext
71
	 */
72
	public function __construct(\phpbb\auth\auth $auth, \phpbb\cache\service $cache, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\pagination $pagination, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, \phpbb\content_visibility $content_visibility, \vse\similartopics\driver\manager $similartopics_manager, $root_path, $php_ext)
73
	{
74
		$this->auth = $auth;
75
		$this->cache = $cache;
76
		$this->config = $config;
77
		$this->db = $db;
78
		$this->dispatcher = $dispatcher;
79
		$this->pagination = $pagination;
80
		$this->request = $request;
81
		$this->template = $template;
82
		$this->user = $user;
83
		$this->content_visibility = $content_visibility;
84
		$this->root_path = $root_path;
85
		$this->php_ext = $php_ext;
86
87
		$this->similartopics = $similartopics_manager->get_driver($db->get_sql_layer());
88
	}
89
90
	/**
91
	 * Is similar topics available?
92
	 *
93
	 * @access public
94
	 * @return bool True if available, false otherwise
95
	 */
96
	public function is_available()
97
	{
98
		return $this->is_enabled() && $this->is_viewable() && $this->similartopics !== null;
99
	}
100
101
	/**
102
	 * Is similar topics configured?
103
	 *
104
	 * @access public
105
	 * @return bool True if configured, false otherwise
106
	 */
107
	public function is_enabled()
108
	{
109
		return !empty($this->config['similar_topics']) && !empty($this->config['similar_topics_limit']);
110
	}
111
112
	/**
113
	 * Is similar topics viewable by the user?
114
	 *
115
	 * @access public
116
	 * @return bool True if viewable, false otherwise
117
	 */
118
	public function is_viewable()
119
	{
120
		return !empty($this->user->data['user_similar_topics']) && $this->auth->acl_get('u_similar_topics');
121
	}
122
123
	/**
124
	 * Get similar topics by matching topic titles
125
	 *
126
	 * NOTE: FULLTEXT has built-in English ignore words. We use phpBB's
127
	 * ignore words for non-English languages. We also remove any
128
	 * admin-defined special ignore words.
129
	 *
130
	 * @access public
131
	 * @param array $topic_data Array with topic data
132
	 */
133
	public function display_similar_topics($topic_data)
134
	{
135
		// If the forum should not display similar topics, no need to continue
136
		if ($topic_data['similar_topics_hide'])
137
		{
138
			return;
139
		}
140
141
		$topic_title = $this->clean_topic_title($topic_data['topic_title']);
142
143
		// If the cleaned up topic_title is empty, no need to continue
144
		if (empty($topic_title))
145
		{
146
			return;
147
		}
148
149
		// Get stored sensitivity value and divide by 10. In query it should be a number between 0.0 to 1.0.
150
		$sensitivity = $this->config->offsetExists('similar_topics_sense') ? $this->config['similar_topics_sense'] / 10 : '0.5';
151
152
		// Similar Topics SQL query is generated in similar topics driver
153
		$sql_array = $this->similartopics->get_query($topic_data['topic_id'], $topic_title, $this->config['similar_topics_time'], $sensitivity);
154
155
		// Add topic tracking data to the query (only if query caching is off)
156
		if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
157
		{
158
			$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']);
159
			$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']);
160
			$sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as f_mark_time';
161
		}
162
		else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
163
		{
164
			// Cookie based tracking copied from search.php
165
			$tracking_topics = $this->request->variable($this->config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
166
			$tracking_topics = $tracking_topics ? tracking_unserialize($tracking_topics) : array();
167
		}
168
169
		// We need to exclude passworded forums so we do not leak the topic title
170
		$passworded_forums = $this->user->get_passworded_forums();
171
172
		// See if the admin set this forum to only search a specific group of other forums, and include them
173
		if (!empty($topic_data['similar_topic_forums']))
174
		{
175
			// Remove any passworded forums from this group of forums we will be searching
176
			$included_forums = array_diff(json_decode($topic_data['similar_topic_forums'], true), $passworded_forums);
177
			// if there's nothing left to display (user has no access to the forums we want to search)
178
			if (empty($included_forums))
179
			{
180
				return;
181
			}
182
183
			$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $included_forums);
184
		}
185
		// Otherwise exclude any ignored forums
186
		else
187
		{
188
			// Remove any passworded forums
189
			if (count($passworded_forums))
190
			{
191
				$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $passworded_forums, true);
192
			}
193
194
			$sql_array['WHERE'] .= ' AND f.similar_topics_ignore = 0';
195
		}
196
197
		/**
198
		 * Event to modify the sql_array for similar topics
199
		 *
200
		 * @event vse.similartopics.get_topic_data
201
		 * @var array sql_array SQL array to get similar topics data
202
		 * @since 1.3.0
203
		 */
204
		$vars = array('sql_array');
205
		extract($this->dispatcher->trigger_event('vse.similartopics.get_topic_data', compact($vars)));
206
207
		$rowset = array();
208
209
		$sql = $this->db->sql_build_query('SELECT', $sql_array);
210
		$result = $this->db->sql_query_limit($sql, $this->config['similar_topics_limit'], 0, $this->config['similar_topics_cache']);
211
		while ($row = $this->db->sql_fetchrow($result))
212
		{
213
			$rowset[(int) $row['topic_id']] = $row;
214
		}
215
		$this->db->sql_freeresult($result);
216
217
		// Grab icons
218
		$icons = $this->cache->obtain_icons();
219
220
		/**
221
		 * Modify the rowset data for similar topics
222
		 *
223
		 * @event vse.similartopics.modify_rowset
224
		 * @var	array rowset Array with the search results data
225
		 * @since 1.4.2
226
		 */
227
		$vars = array('rowset');
228
		extract($this->dispatcher->trigger_event('vse.similartopics.modify_rowset', compact($vars)));
229
230
		foreach ($rowset as $row)
231
		{
232
			$similar_forum_id = (int) $row['forum_id'];
233
			$similar_topic_id = (int) $row['topic_id'];
234
235
			if ($this->auth->acl_get('f_read', $similar_forum_id))
236
			{
237
				// Get topic tracking info
238
				if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
239
				{
240
					$topic_tracking_info = get_topic_tracking($similar_forum_id, $similar_topic_id, $rowset, array($similar_forum_id => $row['f_mark_time']));
241
				}
242
				else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
243
				{
244
					$topic_tracking_info = get_complete_topic_tracking($similar_forum_id, $similar_topic_id);
245
246
					if (!$this->user->data['is_registered'])
247
					{
248
						$this->user->data['user_lastmark'] = isset($tracking_topics['l']) ? ((int) base_convert($tracking_topics['l'], 36, 10) + (int) $this->config['board_startdate']) : 0;
249
					}
250
				}
251
252
				// Replies
253
				$replies = $this->content_visibility->get_count('topic_posts', $row, $similar_forum_id) - 1;
254
255
				// Get folder img, topic status/type related information
256
				$folder_img = $folder_alt = $topic_type = '';
257
				$unread_topic = isset($topic_tracking_info[$similar_topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$similar_topic_id];
258
				topic_status($row, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type);
259
260
				$topic_unapproved = $row['topic_visibility'] == ITEM_UNAPPROVED && $this->auth->acl_get('m_approve', $similar_forum_id);
261
				$posts_unapproved = $row['topic_visibility'] == ITEM_APPROVED && $row['topic_posts_unapproved'] && $this->auth->acl_get('m_approve', $similar_forum_id);
262
				$u_mcp_queue = ($topic_unapproved || $posts_unapproved) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=queue&amp;mode=' . ($topic_unapproved ? 'approve_details' : 'unapproved_posts') . "&amp;t=$similar_topic_id", true, $this->user->session_id) : '';
263
264
				$base_url = append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id);
265
266
				$topic_row = array(
267
					'TOPIC_AUTHOR_FULL'		=> get_username_string('full', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']),
268
					'FIRST_POST_TIME'		=> $this->user->format_date($row['topic_time']),
269
					'LAST_POST_TIME'		=> $this->user->format_date($row['topic_last_post_time']),
270
					'LAST_POST_AUTHOR_FULL'	=> get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']),
271
272
					'TOPIC_REPLIES'			=> $replies,
273
					'TOPIC_VIEWS'			=> $row['topic_views'],
274
					'TOPIC_TITLE'			=> censor_text($row['topic_title']),
275
					'FORUM_TITLE'			=> $row['forum_name'],
276
277
					'TOPIC_IMG_STYLE'		=> $folder_img,
278
					'TOPIC_FOLDER_IMG'		=> $this->user->img($folder_img, $folder_alt),
279
					'TOPIC_FOLDER_IMG_ALT'	=> $this->user->lang($folder_alt),
280
281
					'TOPIC_ICON_IMG'		=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '',
282
					'TOPIC_ICON_IMG_WIDTH'	=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '',
283
					'TOPIC_ICON_IMG_HEIGHT'	=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['height'] : '',
284
					'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')) : '',
285
					'UNAPPROVED_IMG'		=> ($topic_unapproved || $posts_unapproved) ? $this->user->img('icon_topic_unapproved', $topic_unapproved ? 'TOPIC_UNAPPROVED' : 'POSTS_UNAPPROVED') : '',
286
287
					'S_UNREAD_TOPIC'		=> $unread_topic,
288
					'S_TOPIC_REPORTED'		=> !empty($row['topic_reported']) && $this->auth->acl_get('m_report', $similar_forum_id),
289
					'S_TOPIC_UNAPPROVED'	=> $topic_unapproved,
290
					'S_POSTS_UNAPPROVED'	=> $posts_unapproved,
291
					'S_HAS_POLL'			=> (bool) $row['poll_start'],
292
293
					'U_NEWEST_POST'			=> append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id . '&amp;view=unread') . '#unread',
294
					'U_LAST_POST'			=> append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id . '&amp;p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'],
295
					'U_VIEW_TOPIC'			=> append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id),
296
					'U_VIEW_FORUM'			=> append_sid("{$this->root_path}viewforum.{$this->php_ext}", 'f=' . $similar_forum_id),
297
					'U_MCP_REPORT'			=> append_sid("{$this->root_path}mcp.{$this->php_ext}", 'i=reports&amp;mode=reports&amp;f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id, true, $this->user->session_id),
298
					'U_MCP_QUEUE'			=> $u_mcp_queue,
299
				);
300
301
				/**
302
				 * Event to modify the similar topics template block
303
				 *
304
				 * @event vse.similartopics.modify_topicrow
305
				 * @var array row       Array with similar topic data
306
				 * @var array topic_row Template block array
307
				 * @since 1.3.0
308
				 */
309
				$vars = array('row', 'topic_row');
310
				extract($this->dispatcher->trigger_event('vse.similartopics.modify_topicrow', compact($vars)));
311
312
				$this->template->assign_block_vars('similar', $topic_row);
313
314
				$this->pagination->generate_template_pagination($base_url, 'similar.pagination', 'start', $replies + 1, $this->config['posts_per_page'], 1, true, true);
315
			}
316
		}
317
318
		$this->user->add_lang_ext('vse/similartopics', 'similar_topics');
319
320
		$this->template->assign_vars(array(
321
			'L_SIMILAR_TOPICS'	=> $this->user->lang('SIMILAR_TOPICS'),
322
			'NEWEST_POST_IMG'	=> $this->user->img('icon_topic_newest', 'VIEW_NEWEST_POST'),
323
			'LAST_POST_IMG'		=> $this->user->img('icon_topic_latest', 'VIEW_LATEST_POST'),
324
			'REPORTED_IMG'		=> $this->user->img('icon_topic_reported', 'TOPIC_REPORTED'),
325
			'POLL_IMG'			=> $this->user->img('icon_topic_poll', 'TOPIC_POLL'),
326
			'S_PST_BRANCH'		=> phpbb_version_compare(max($this->config['phpbb_version'], PHPBB_VERSION), '3.2.0-dev', '<') ? '31x' : '32x',
327
		));
328
	}
329
330
	/**
331
	 * Clean topic title (and if needed, ignore-words)
332
	 *
333
	 * @access public
334
	 * @param string $text The topic title
335
	 * @return string The topic title
336
	 */
337
	public function clean_topic_title($text)
338
	{
339
		// Strip quotes, ampersands
340
		$text = str_replace(array('&quot;', '&amp;'), '', $text);
341
342
		if (!$this->english_lang() || $this->has_ignore_words())
343
		{
344
			$text = $this->strip_stop_words($text);
345
		}
346
347
		return $text;
348
	}
349
350
	/**
351
	 * Remove any non-english and/or custom defined ignore-words
352
	 *
353
	 * @access protected
354
	 * @param string $text The topic title
355
	 * @return string The topic title
356
	 */
357
	protected function strip_stop_words($text)
358
	{
359
		$words = array();
360
361
		// If non-English, look for a list of stop-words to be ignored
362
		// in either the core or the extension (deprecated from core)
363
		if (!$this->english_lang())
364
		{
365
			if (file_exists($search_ignore_words = "{$this->user->lang_path}{$this->user->lang_name}/search_ignore_words.{$this->php_ext}") ||
366
				file_exists($search_ignore_words = "{$this->root_path}ext/vse/similartopics/language/{$this->user->lang_name}/search_ignore_words.{$this->php_ext}"))
367
			{
368
				include($search_ignore_words);
369
			}
370
		}
371
372
		if ($this->has_ignore_words())
373
		{
374
			// Merge any custom defined ignore words from the ACP to the stop-words array
375
			$words = array_merge($this->make_word_array($this->config['similar_topics_words']), $words);
376
		}
377
378
		// Remove stop-words from the topic title text
379
		$words = array_diff($this->make_word_array($text), $words);
380
381
		// Convert our words array back to a string
382
		return implode(' ', $words);
383
	}
384
385
	/**
386
	 * Helper function to split string into an array of words
387
	 *
388
	 * @access protected
389
	 * @param string $text String of plain text words
390
	 * @return array Array of plaintext words
391
	 */
392
	protected function make_word_array($text)
393
	{
394
		// Strip out any non-alpha-numeric characters using PCRE regex syntax
395
		$text = trim(preg_replace('#[^\p{L}\p{N}]+#u', ' ', $text));
396
397
		$words = explode(' ', utf8_strtolower($text));
398
		foreach ($words as $key => $word)
399
		{
400
			// Strip words of 2 characters or less
401
			if (utf8_strlen(trim($word)) < 3)
402
			{
403
				unset($words[$key]);
404
			}
405
		}
406
407
		return $words;
408
	}
409
410
	/**
411
	 * Check if English is the current user's language
412
	 *
413
	 * @access protected
414
	 * @return bool True if lang is 'en' or 'en_us', false otherwise
415
	 */
416
	protected function english_lang()
417
	{
418
		return ($this->user->lang_name === 'en' || $this->user->lang_name === 'en_us');
419
	}
420
421
	/**
422
	 * Check if custom ignore words have been defined for similar topics
423
	 *
424
	 * @access protected
425
	 * @return bool True or false
426
	 */
427
	protected function has_ignore_words()
428
	{
429
		return !empty($this->config['similar_topics_words']);
430
	}
431
}
432