Passed
Pull Request — master (#132)
by Matt
01:19
created

ideas::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 9
c 4
b 0
f 0
dl 0
loc 13
rs 9.9666
cc 1
nc 1
nop 9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 *
4
 * Ideas extension for the phpBB Forum Software package.
5
 *
6
 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 */
10
11
namespace phpbb\ideas\factory;
12
13
use phpbb\exception\runtime_exception;
14
use phpbb\ideas\ext;
15
16
/**
17
 * Class for handling multiple ideas
18
 */
19
class ideas extends base
20
{
21
	/** @var int */
22
	protected $idea_count;
23
24
	/** @var array */
25
	protected $sql;
26
27
	/**
28
	 * Returns an array of ideas. Defaults to ten ideas ordered by date
29
	 * excluding implemented, duplicate or invalid ideas.
30
	 *
31
	 * @param int       $number    The number of ideas to return
32
	 * @param string    $sort      A sorting option/collection
33
	 * @param string    $direction Should either be ASC or DESC
34
	 * @param array|int $status    The id of the status(es) to load
35
	 * @param int       $start     Start value for pagination
36
	 *
37
	 * @return array Array of row data
38
	 */
39
	public function get_ideas($number = 10, $sort = 'date', $direction = 'DESC', $status = [], $start = 0)
40
	{
41
		// Initialize a query to request ideas
42
		$sql = $this->query_ideas()
43
			->query_sort($sort, $direction)
44
			->query_status($status);
45
46
		// For pagination, get a count of the total ideas being requested
47
		if ($number >= $this->config['posts_per_page'])
48
		{
49
			$this->idea_count = $sql->query_count();
50
		}
51
52
		$ideas = $sql->query_get($number, $start);
53
54
		if (count($ideas))
55
		{
56
			$topic_ids = array_column($ideas, 'topic_id');
57
			$idea_ids = array_column($ideas, 'idea_id');
58
59
			$topic_tracking_info = get_complete_topic_tracking((int) $this->config['ideas_forum_id'], $topic_ids);
60
			$user_voting_info = $this->get_users_votes($this->user->data['user_id'], $idea_ids);
61
62
			foreach ($ideas as &$idea)
63
			{
64
				$idea['read'] = !(isset($topic_tracking_info[$idea['topic_id']]) && $idea['topic_last_post_time'] > $topic_tracking_info[$idea['topic_id']]);
65
				$idea['u_voted'] = isset($user_voting_info[$idea['idea_id']]) ? (int) $user_voting_info[$idea['idea_id']] : '';
66
			}
67
			unset ($idea);
68
		}
69
70
		return $ideas;
71
	}
72
73
	/**
74
	 * Initialize the $sql property with necessary SQL statements.
75
	 *
76
	 * @return \phpbb\ideas\factory\ideas $this For chaining calls
77
	 */
78
	protected function query_ideas()
79
	{
80
		$this->sql = [];
81
82
		$this->sql['SELECT'][] = 't.topic_last_post_time, t.topic_status, t.topic_visibility, i.*';
83
		$this->sql['FROM'] = "{$this->table_ideas} i";
84
		$this->sql['JOIN'] = "{$this->table_topics} t ON i.topic_id = t.topic_id";
85
		$this->sql['WHERE'][] = 't.forum_id = ' . (int) $this->config['ideas_forum_id'];
86
87
		// Only get approved topics for regular users, Moderators will see unapproved topics
88
		if (!$this->auth->acl_get('m_', $this->config['ideas_forum_id']))
89
		{
90
			$this->sql['WHERE'][] = 't.topic_visibility = ' . ITEM_APPROVED;
91
		}
92
93
		return $this;
94
	}
95
96
	/**
97
	 * Update the $sql property with ORDER BY statements to obtain
98
	 * the requested collection of Ideas. Some instances may add
99
	 * additional WHERE or SELECT statements to refine the collection.
100
	 *
101
	 * @param string $sort      A sorting option/collection
102
	 * @param string $direction Will either be ASC or DESC
103
	 *
104
	 * @return \phpbb\ideas\factory\ideas $this For chaining calls
105
	 */
106
	protected function query_sort($sort, $direction)
107
	{
108
		$sort = strtolower($sort);
109
		$direction = $direction === 'DESC' ? 'DESC' : 'ASC';
110
111
		// Most sorting relies on simple ORDER BY statements, but some may use a WHERE statement
112
		$statements = [
113
			ext::SORT_DATE    => ['ORDER_BY' => 'i.idea_date'],
114
			ext::SORT_TITLE   => ['ORDER_BY' => 'i.idea_title'],
115
			ext::SORT_AUTHOR  => ['ORDER_BY' => 'i.idea_author'],
116
			ext::SORT_SCORE   => ['ORDER_BY' => 'CAST(i.idea_votes_up AS decimal) - CAST(i.idea_votes_down AS decimal)'],
117
			ext::SORT_VOTES   => ['ORDER_BY' => 'i.idea_votes_up + i.idea_votes_down'],
118
			ext::SORT_TOP     => ['WHERE' => 'i.idea_votes_up > i.idea_votes_down'],
119
			ext::SORT_MYIDEAS => ['ORDER_BY' => 'i.idea_date', 'WHERE' => 'i.idea_author = ' . (int) $this->user->data['user_id']],
120
		];
121
122
		// Append a new WHERE statement if the sort has one
123
		if (isset($statements[$sort]['WHERE']))
124
		{
125
			$this->sql['WHERE'][] = $statements[$sort]['WHERE'];
126
		}
127
128
		// If we have an ORDER BY we use that. The absence of an ORDER BY
129
		// means we will default to sorting ideas by their calculated score.
130
		if (isset($statements[$sort]['ORDER_BY']))
131
		{
132
			$this->sql['ORDER_BY'] = "{$statements[$sort]['ORDER_BY']} $direction";
133
		}
134
		else
135
		{
136
			// https://www.evanmiller.org/how-not-to-sort-by-average-rating.html
137
			$this->sql['SELECT'][] = '((i.idea_votes_up + 1.9208) / (i.idea_votes_up + i.idea_votes_down) -
138
				1.96 * SQRT((i.idea_votes_up * i.idea_votes_down) / (i.idea_votes_up + i.idea_votes_down) + 0.9604) /
139
				(i.idea_votes_up + i.idea_votes_down)) / (1 + 3.8416 / (i.idea_votes_up + i.idea_votes_down))
140
				AS ci_lower_bound';
141
142
			$this->sql['ORDER_BY'] = "ci_lower_bound $direction";
143
		}
144
145
		return $this;
146
	}
147
148
	/**
149
	 * Update $sql property with additional SQL statements to filter ideas
150
	 * by status. If $status is given we'll get those ideas. If no $status
151
	 * is given, the default is to get all ideas excluding Duplicates, Invalid
152
	 * and Implemented statuses (because they are considered done & dusted,
153
	 * if they were gases they'd be inert).
154
	 *
155
	 * @param array|int $status The id(s) of the status(es) to load
156
	 *
157
	 * @return \phpbb\ideas\factory\ideas $this For chaining calls
158
	 */
159
	protected function query_status($status = [])
160
	{
161
		$this->sql['WHERE'][] = !empty($status) ? $this->db->sql_in_set('i.idea_status', $status) : $this->db->sql_in_set(
162
			'i.idea_status', [ext::$statuses['IMPLEMENTED'], ext::$statuses['DUPLICATE'], ext::$statuses['INVALID'],
163
		], true);
164
165
		return $this;
166
	}
167
168
	/**
169
	 * Run a query using the $sql property to get a collection of ideas.
170
	 *
171
	 * @param int $number The number of ideas to return
172
	 * @param int $start  Start value for pagination
173
	 *
174
	 * @return mixed      Nested array if the query had rows, false otherwise
175
	 * @throws \phpbb\exception\runtime_exception
176
	 */
177
	protected function query_get($number, $start)
178
	{
179
		if (empty($this->sql))
180
		{
181
			throw new runtime_exception('INVALID_IDEA_QUERY');
182
		}
183
184
		$sql = 'SELECT ' . implode(', ', $this->sql['SELECT']) . '
185
			FROM ' . $this->sql['FROM'] . '
186
			INNER JOIN ' . $this->sql['JOIN'] . '
187
			WHERE ' . implode(' AND ', $this->sql['WHERE']) . '
188
			ORDER BY ' . $this->sql['ORDER_BY'];
189
190
		$result = $this->db->sql_query_limit($sql, $number, $start);
191
		$rows = $this->db->sql_fetchrowset($result);
192
		$this->db->sql_freeresult($result);
193
194
		return $rows;
195
	}
196
197
	/**
198
	 * Run a query using the $sql property to get a count of ideas.
199
	 *
200
	 * @return int The number of ideas
201
	 * @throws \phpbb\exception\runtime_exception
202
	 */
203
	protected function query_count()
204
	{
205
		if (empty($this->sql))
206
		{
207
			throw new runtime_exception('INVALID_IDEA_QUERY');
208
		}
209
210
		$sql = 'SELECT COUNT(i.idea_id) as count
211
			FROM ' . $this->sql['FROM'] . '
212
       		INNER JOIN ' . $this->sql['JOIN'] . '
213
			WHERE ' . implode(' AND ', $this->sql['WHERE']);
214
215
		$result = $this->db->sql_query($sql);
216
		$count = (int) $this->db->sql_fetchfield('count');
217
		$this->db->sql_freeresult($result);
218
219
		return $count;
220
	}
221
222
	/**
223
	 * Do a live search on idea titles. Return any matches based on a given search query.
224
	 *
225
	 * @param string $search The string of characters to search using LIKE
226
	 * @param int    $limit  The number of results to return
227
	 *
228
	 * @return array An array of matching idea id/key and title/values
229
	 */
230
	public function livesearch($search, $limit = 10)
231
	{
232
		$results = [];
233
		$sql = 'SELECT idea_title, idea_id
234
			FROM ' . $this->table_ideas . '
235
			WHERE idea_title ' . $this->db->sql_like_expression($this->db->get_any_char() . $search . $this->db->get_any_char());
236
		$result = $this->db->sql_query_limit($sql, $limit);
237
		while ($row = $this->db->sql_fetchrow($result))
238
		{
239
			$results[] = [
240
				'idea_id'     => $row['idea_id'],
241
				'result'      => $row['idea_id'],
242
				'clean_title' => $row['idea_title'],
243
				'display'     => "<span>{$row['idea_title']}</span>", // spans are expected in phpBB's live search JS
244
			];
245
		}
246
		$this->db->sql_freeresult($result);
247
248
		return $results;
249
	}
250
251
	/**
252
	 * Get a user's votes from a group of ideas
253
	 *
254
	 * @param int $user_id The user's id
255
	 * @param array $ids An array of idea ids
256
	 *
257
	 * @return array An array of ideas the user voted on and their vote result, or empty otherwise.
258
	 *               example: [idea_id => vote_result]
259
	 *                         1 => 1, idea 1, voted up by the user
260
	 *                         2 => 0, idea 2, voted down by the user
261
	 */
262
	public function get_users_votes($user_id, array $ids)
263
	{
264
		$results = [];
265
		$sql = 'SELECT idea_id, vote_value
266
			FROM ' . $this->table_votes . '
267
			WHERE user_id = ' . (int) $user_id . '
268
			AND ' . $this->db->sql_in_set('idea_id', $ids, false, true);
269
		$result = $this->db->sql_query($sql);
270
		while ($row = $this->db->sql_fetchrow($result))
271
		{
272
			$results[$row['idea_id']] = $row['vote_value'];
273
		}
274
		$this->db->sql_freeresult($result);
275
276
		return $results;
277
	}
278
279
	/**
280
	 * Delete orphaned ideas. Orphaned ideas may exist after a
281
	 * topic has been deleted or moved to another forum.
282
	 *
283
	 * @return int Number of rows affected
284
	 */
285
	public function delete_orphans()
286
	{
287
		// Find any orphans
288
		$sql = 'SELECT idea_id FROM ' . $this->table_ideas . '
289
 			WHERE topic_id NOT IN (SELECT t.topic_id
290
 			FROM ' . $this->table_topics . ' t
291
 				WHERE t.forum_id = ' . (int) $this->config['ideas_forum_id'] . ')';
292
		$result = $this->db->sql_query($sql);
293
		$rows = $this->db->sql_fetchrowset($result);
294
		$this->db->sql_freeresult($result);
295
296
		if (empty($rows))
297
		{
298
			return 0;
299
		}
300
301
		$this->db->sql_transaction('begin');
302
303
		foreach ($rows as $row)
304
		{
305
			// Delete idea
306
			$this->delete_idea_data($row['idea_id'], $this->table_ideas);
307
308
			// Delete votes
309
			$this->delete_idea_data($row['idea_id'], $this->table_votes);
310
		}
311
312
		$this->db->sql_transaction('commit');
313
314
		return count($rows);
315
	}
316
317
	/**
318
	 * Get the stored idea count
319
	 * Note: this should only be called after get_ideas()
320
	 *
321
	 * @return int Count of ideas
322
	 */
323
	public function get_idea_count()
324
	{
325
		return isset($this->idea_count) ? $this->idea_count : 0;
326
	}
327
}
328