Completed
Push — master ( 2119b9...5efbef )
by Matt
02:16
created

similar_topics::is_viewable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 string phpBB root path  */
46
	protected $root_path;
47
48
	/** @var string PHP file extension */
49
	protected $php_ext;
50
51
	/**
52
	 * Constructor
53
	 *
54
	 * @access public
55
	 * @param \phpbb\auth\auth                  $auth
56
	 * @param \phpbb\cache\service              $cache
57
	 * @param \phpbb\config\config              $config
58
	 * @param \phpbb\db\driver\driver_interface $db
59
	 * @param \phpbb\event\dispatcher_interface $dispatcher
60
	 * @param \phpbb\pagination                 $pagination
61
	 * @param \phpbb\request\request            $request
62
	 * @param \phpbb\template\template          $template
63
	 * @param \phpbb\user                       $user
64
	 * @param \phpbb\content_visibility         $content_visibility
65
	 * @param string                            $root_path
66
	 * @param string                            $php_ext
67
	 */
68
	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, $root_path, $php_ext)
69
	{
70
		$this->auth = $auth;
71
		$this->cache = $cache;
72
		$this->config = $config;
73
		$this->db = $db;
74
		$this->dispatcher = $dispatcher;
75
		$this->pagination = $pagination;
76
		$this->request = $request;
77
		$this->template = $template;
78
		$this->user = $user;
79
		$this->content_visibility = $content_visibility;
80
		$this->root_path = $root_path;
81
		$this->php_ext = $php_ext;
82
	}
83
84
	/**
85
	 * Is similar topics available?
86
	 *
87
	 * @access public
88
	 * @return bool True if available, false otherwise
89
	 */
90
	public function is_available()
91
	{
92
		return $this->is_enabled() && $this->is_viewable() && $this->is_mysql();
93
	}
94
95
	/**
96
	 * Is similar topics configured?
97
	 *
98
	 * @access public
99
	 * @return bool True if configured, false otherwise
100
	 */
101
	public function is_enabled()
102
	{
103
		return !empty($this->config['similar_topics']) && !empty($this->config['similar_topics_limit']);
104
	}
105
106
	/**
107
	 * Is similar topics viewable bu the user?
108
	 *
109
	 * @access public
110
	 * @return bool True if viewable, false otherwise
111
	 */
112
	public function is_viewable()
113
	{
114
		return !empty($this->user->data['user_similar_topics']) && $this->auth->acl_get('u_similar_topics');
115
	}
116
117
	/**
118
	 * Get similar topics by matching topic titles
119
	 *
120
	 * NOTE: Currently requires MySQL due to the use of FULLTEXT indexes
121
	 * and MATCH and AGAINST and UNIX_TIMESTAMP. MySQL FULLTEXT has built-in
122
	 * English ignore words. We use phpBB's ignore words for non-English
123
	 * languages. We also remove any admin-defined special ignore words.
124
	 *
125
	 * @access public
126
	 * @param array $topic_data Array with topic data
127
	 */
128
	public function display_similar_topics($topic_data)
129
	{
130
		// If the forum should not display similar topics, no need to continue
131
		if ($topic_data['similar_topics_hide'])
132
		{
133
			return;
134
		}
135
136
		$topic_title = $this->clean_topic_title($topic_data['topic_title']);
137
138
		// If the cleaned up topic_title is empty, no need to continue
139
		if (empty($topic_title))
140
		{
141
			return;
142
		}
143
144
		// Similar Topics query
145
		$sql_array = array(
146
			'SELECT'	=> "f.forum_id, f.forum_name, t.*,
147
				MATCH (t.topic_title) AGAINST ('" . $this->db->sql_escape($topic_title) . "') AS score",
148
149
			'FROM'		=> array(
150
				TOPICS_TABLE	=> 't',
151
			),
152
			'LEFT_JOIN'	=> array(
153
				array(
154
					'FROM'	=>	array(FORUMS_TABLE	=> 'f'),
155
					'ON'	=> 'f.forum_id = t.forum_id',
156
				),
157
			),
158
			'WHERE'		=> "MATCH (t.topic_title) AGAINST ('" . $this->db->sql_escape($topic_title) . "') >= 0.5
159
				AND t.topic_status <> " . ITEM_MOVED . '
160
				AND t.topic_visibility = ' . ITEM_APPROVED . '
161
				AND t.topic_time > (UNIX_TIMESTAMP() - ' . $this->config['similar_topics_time'] . ')
162
				AND t.topic_id <> ' . (int) $topic_data['topic_id'],
163
			//'GROUP_BY'	=> 't.topic_id',
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
164
			//'ORDER_BY'	=> 'score DESC', // this is done automatically by MySQL when not using IN BOOLEAN MODE
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
165
		);
166
167
		// Add topic tracking data to the query (only if query caching is off)
168
		if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
169
		{
170
			$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']);
171
			$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']);
172
			$sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as f_mark_time';
173
		}
174
		else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
175
		{
176
			// Cookie based tracking copied from search.php
177
			$tracking_topics = $this->request->variable($this->config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
178
			$tracking_topics = $tracking_topics ? tracking_unserialize($tracking_topics) : array();
179
		}
180
181
		// We need to exclude passworded forums so we do not leak the topic title
182
		$passworded_forums = $this->user->get_passworded_forums();
183
184
		// See if the admin set this forum to only search a specific group of other forums, and include them
185
		if (!empty($topic_data['similar_topic_forums']))
186
		{
187
			// Remove any passworded forums from this group of forums we will be searching
188
			$included_forums = array_diff(json_decode($topic_data['similar_topic_forums'], true), $passworded_forums);
189
			// if there's nothing left to display (user has no access to the forums we want to search)
190
			if (empty($included_forums))
191
			{
192
				return;
193
			}
194
195
			$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $included_forums);
196
		}
197
		// Otherwise exclude any ignored forums
198
		else
199
		{
200
			// Remove any passworded forums
201
			if (sizeof($passworded_forums))
202
			{
203
				$sql_array['WHERE'] .= ' AND ' . $this->db->sql_in_set('f.forum_id', $passworded_forums, true);
204
			}
205
206
			$sql_array['WHERE'] .= ' AND f.similar_topics_ignore = 0';
207
		}
208
209
		/**
210
		 * Event to modify the sql_array for similar topics
211
		 *
212
		 * @event vse.similartopics.get_topic_data
213
		 * @var array sql_array SQL array to get similar topics data
214
		 * @since 1.3.0
215
		 */
216
		$vars = array('sql_array');
217
		extract($this->dispatcher->trigger_event('vse.similartopics.get_topic_data', compact($vars)));
218
219
		$sql = $this->db->sql_build_query('SELECT', $sql_array);
220
		$result = $this->db->sql_query_limit($sql, $this->config['similar_topics_limit'], 0, $this->config['similar_topics_cache']);
221
222
		// Grab icons
223
		$icons = $this->cache->obtain_icons();
224
225
		$rowset = array();
226
227
		while ($row = $this->db->sql_fetchrow($result))
228
		{
229
			$similar_forum_id = (int) $row['forum_id'];
230
			$similar_topic_id = (int) $row['topic_id'];
231
			$rowset[$similar_topic_id] = $row;
232
233
			if ($this->auth->acl_get('f_read', $similar_forum_id))
234
			{
235
				// Get topic tracking info
236
				if ($this->user->data['is_registered'] && $this->config['load_db_lastread'] && !$this->config['similar_topics_cache'])
237
				{
238
					$topic_tracking_info = get_topic_tracking($similar_forum_id, $similar_topic_id, $rowset, array($similar_forum_id => $row['f_mark_time']));
239
				}
240
				else if ($this->config['load_anon_lastread'] || $this->user->data['is_registered'])
241
				{
242
					$topic_tracking_info = get_complete_topic_tracking($similar_forum_id, $similar_topic_id);
243
244
					if (!$this->user->data['is_registered'])
245
					{
246
						$this->user->data['user_lastmark'] = isset($tracking_topics['l']) ? ((int) base_convert($tracking_topics['l'], 36, 10) + (int) $this->config['board_startdate']) : 0;
247
					}
248
				}
249
250
				// Replies
251
				$replies = $this->content_visibility->get_count('topic_posts', $row, $similar_forum_id) - 1;
252
253
				// Get folder img, topic status/type related information
254
				$folder_img = $folder_alt = $topic_type = '';
255
				$unread_topic = isset($topic_tracking_info[$similar_topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$similar_topic_id];
256
				topic_status($row, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type);
257
258
				$topic_unapproved = $row['topic_visibility'] == ITEM_UNAPPROVED && $this->auth->acl_get('m_approve', $similar_forum_id);
259
				$posts_unapproved = $row['topic_visibility'] == ITEM_APPROVED && $row['topic_posts_unapproved'] && $this->auth->acl_get('m_approve', $similar_forum_id);
260
				//$topic_deleted = $row['topic_visibility'] == ITEM_DELETED;
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
261
				$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) : '';
262
				//$u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? append_sid("{$this->root_path}mcp.{$this->php_ext}", "i=queue&amp;mode=deleted_topics&amp;t=$similar_topic_id", true, $this->user->session_id) : $u_mcp_queue;
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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_TOPIC_DELETED'		=> $topic_deleted,
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
292
					'S_HAS_POLL'			=> (bool) $row['poll_start'],
293
294
					'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',
295
					'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'],
296
					'U_VIEW_TOPIC'			=> append_sid("{$this->root_path}viewtopic.{$this->php_ext}", 'f=' . $similar_forum_id . '&amp;t=' . $similar_topic_id),
297
					'U_VIEW_FORUM'			=> append_sid("{$this->root_path}viewforum.{$this->php_ext}", 'f=' . $similar_forum_id),
298
					'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),
299
					'U_MCP_QUEUE'			=> $u_mcp_queue,
300
				);
301
302
				/**
303
				 * Event to modify the similar topics template block
304
				 *
305
				 * @event vse.similartopics.modify_topicrow
306
				 * @var array row       Array with similar topic data
307
				 * @var array topic_row Template block array
308
				 * @since 1.3.0
309
				 */
310
				$vars = array('row', 'topic_row');
311
				extract($this->dispatcher->trigger_event('vse.similartopics.modify_topicrow', compact($vars)));
312
313
				$this->template->assign_block_vars('similar', $topic_row);
314
315
				$this->pagination->generate_template_pagination($base_url, 'similar.pagination', 'start', $replies + 1, $this->config['posts_per_page'], 1, true, true);
316
			}
317
		}
318
319
		$this->db->sql_freeresult($result);
320
321
		$this->user->add_lang_ext('vse/similartopics', 'similar_topics');
322
323
		$this->template->assign_vars(array(
324
			'L_SIMILAR_TOPICS'	=> $this->user->lang('SIMILAR_TOPICS'),
325
			'NEWEST_POST_IMG'	=> $this->user->img('icon_topic_newest', 'VIEW_NEWEST_POST'),
326
			'LAST_POST_IMG'		=> $this->user->img('icon_topic_latest', 'VIEW_LATEST_POST'),
327
			'REPORTED_IMG'		=> $this->user->img('icon_topic_reported', 'TOPIC_REPORTED'),
328
			//'DELETED_IMG'		=> $this->user->img('icon_topic_deleted', 'TOPIC_DELETED'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
329
			'POLL_IMG'			=> $this->user->img('icon_topic_poll', 'TOPIC_POLL'),
330
			'S_PST_BRANCH'		=> phpbb_version_compare(max($this->config['phpbb_version'], PHPBB_VERSION), '3.2.0-dev', '<') ? '31x' : '32x',
331
		));
332
	}
333
334
	/**
335
	 * Clean topic title (and if needed, ignore-words)
336
	 *
337
	 * @access public
338
	 * @param string $text The topic title
339
	 * @return string The topic title
340
	 */
341
	public function clean_topic_title($text)
342
	{
343
		// Strip quotes, ampersands
344
		$text = str_replace(array('&quot;', '&amp;'), '', $text);
345
346
		if (!$this->english_lang() || $this->has_ignore_words())
347
		{
348
			$text = $this->strip_stop_words($text);
349
		}
350
351
		return $text;
352
	}
353
354
	/**
355
	 * Remove any non-english and/or custom defined ignore-words
356
	 *
357
	 * @access protected
358
	 * @param string $text The topic title
359
	 * @return string The topic title
360
	 */
361
	protected function strip_stop_words($text)
362
	{
363
		$words = array();
364
365
		// Retrieve a language dependent list of words to be ignored (method copied from search.php)
366
		$search_ignore_words = "{$this->user->lang_path}{$this->user->lang_name}/search_ignore_words.{$this->php_ext}";
367
		if (!$this->english_lang() && file_exists($search_ignore_words))
368
		{
369
			include($search_ignore_words);
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
	/**
433
	 * Check if the database layer is MySQL4 or later
434
	 *
435
	 * @access protected
436
	 * @return bool True is MySQL4 or later, false otherwise
437
	 */
438
	protected function is_mysql()
439
	{
440
		return ($this->db->get_sql_layer() === 'mysql4' || $this->db->get_sql_layer() === 'mysqli');
441
	}
442
}
443