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\auth\auth; |
14
|
|
|
use phpbb\config\config; |
15
|
|
|
use phpbb\db\driver\driver_interface; |
16
|
|
|
use phpbb\exception\runtime_exception; |
17
|
|
|
use phpbb\language\language; |
18
|
|
|
use phpbb\user; |
19
|
|
|
|
20
|
|
|
class ideas |
21
|
|
|
{ |
22
|
|
|
const SORT_AUTHOR = 'author'; |
23
|
|
|
const SORT_DATE = 'date'; |
24
|
|
|
const SORT_NEW = 'new'; |
25
|
|
|
const SORT_SCORE = 'score'; |
26
|
|
|
const SORT_TITLE = 'title'; |
27
|
|
|
const SORT_TOP = 'top'; |
28
|
|
|
const SORT_VOTES = 'votes'; |
29
|
|
|
const SORT_MYIDEAS = 'egosearch'; |
30
|
|
|
const SUBJECT_LENGTH = 120; |
31
|
|
|
|
32
|
|
|
/** @var array Idea status names and IDs */ |
33
|
|
|
public static $statuses = array( |
34
|
|
|
'NEW' => 1, |
35
|
|
|
'IN_PROGRESS' => 2, |
36
|
|
|
'IMPLEMENTED' => 3, |
37
|
|
|
'DUPLICATE' => 4, |
38
|
|
|
'INVALID' => 5, |
39
|
|
|
); |
40
|
|
|
|
41
|
|
|
/** @var auth */ |
42
|
|
|
protected $auth; |
43
|
|
|
|
44
|
|
|
/* @var config */ |
45
|
|
|
protected $config; |
46
|
|
|
|
47
|
|
|
/* @var driver_interface */ |
48
|
|
|
protected $db; |
49
|
|
|
|
50
|
|
|
/** @var language */ |
51
|
|
|
protected $language; |
52
|
|
|
|
53
|
|
|
/* @var user */ |
54
|
|
|
protected $user; |
55
|
|
|
|
56
|
|
|
/** @var string */ |
57
|
|
|
protected $table_ideas; |
58
|
|
|
|
59
|
|
|
/** @var string */ |
60
|
|
|
protected $table_votes; |
61
|
|
|
|
62
|
|
|
/** @var string */ |
63
|
|
|
protected $table_topics; |
64
|
|
|
|
65
|
|
|
/** @var int */ |
66
|
|
|
protected $idea_count; |
67
|
|
|
|
68
|
|
|
/** @var string */ |
69
|
|
|
protected $php_ext; |
70
|
|
|
|
71
|
|
|
/** @var string */ |
72
|
|
|
protected $profile_url; |
73
|
|
|
|
74
|
|
|
/** @var array */ |
75
|
|
|
protected $sql; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @param auth $auth |
79
|
|
|
* @param config $config |
80
|
|
|
* @param driver_interface $db |
81
|
|
|
* @param language $language |
82
|
|
|
* @param user $user |
83
|
|
|
* @param string $table_ideas |
84
|
|
|
* @param string $table_votes |
85
|
|
|
* @param string $table_topics |
86
|
|
|
* @param string $phpEx |
87
|
|
|
*/ |
88
|
|
View Code Duplication |
public function __construct(auth $auth, config $config, driver_interface $db, language $language, user $user, $table_ideas, $table_votes, $table_topics, $phpEx) |
|
|
|
|
89
|
|
|
{ |
90
|
|
|
$this->auth = $auth; |
91
|
|
|
$this->config = $config; |
92
|
|
|
$this->db = $db; |
93
|
|
|
$this->language = $language; |
94
|
|
|
$this->user = $user; |
95
|
|
|
|
96
|
|
|
$this->php_ext = $phpEx; |
97
|
|
|
|
98
|
|
|
$this->table_ideas = $table_ideas; |
99
|
|
|
$this->table_votes = $table_votes; |
100
|
|
|
$this->table_topics = $table_topics; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns an array of ideas. Defaults to ten ideas ordered by date |
105
|
|
|
* excluding implemented, duplicate or invalid ideas. |
106
|
|
|
* |
107
|
|
|
* @param int $number The number of ideas to return |
108
|
|
|
* @param string $sort A sorting option/collection |
109
|
|
|
* @param string $direction Should either be ASC or DESC |
110
|
|
|
* @param array|int $status The id of the status(es) to load |
111
|
|
|
* @param int $start Start value for pagination |
112
|
|
|
* |
113
|
|
|
* @return array Array of row data |
114
|
|
|
*/ |
115
|
|
|
public function get_ideas($number = 10, $sort = 'date', $direction = 'DESC', $status = [], $start = 0) |
116
|
|
|
{ |
117
|
|
|
// Initialize a query to request ideas |
118
|
|
|
$sql = $this->query_ideas() |
119
|
|
|
->query_sort($sort, $direction) |
120
|
|
|
->query_status($status); |
121
|
|
|
|
122
|
|
|
// For pagination, get a count of the total ideas being requested |
123
|
|
|
if ($number >= $this->config['posts_per_page']) |
124
|
|
|
{ |
125
|
|
|
$this->idea_count = $sql->query_count(); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
$ideas = $sql->query_get($number, $start); |
129
|
|
|
|
130
|
|
|
if (count($ideas)) |
131
|
|
|
{ |
132
|
|
|
$topic_ids = array_column($ideas, 'topic_id'); |
133
|
|
|
$idea_ids = array_column($ideas, 'idea_id'); |
134
|
|
|
|
135
|
|
|
$topic_tracking_info = get_complete_topic_tracking((int) $this->config['ideas_forum_id'], $topic_ids); |
136
|
|
|
$user_voting_info = $this->get_users_votes($this->user->data['user_id'], $idea_ids); |
137
|
|
|
|
138
|
|
|
foreach ($ideas as &$idea) |
139
|
|
|
{ |
140
|
|
|
$idea['read'] = !(isset($topic_tracking_info[$idea['topic_id']]) && $idea['topic_last_post_time'] > $topic_tracking_info[$idea['topic_id']]); |
141
|
|
|
$idea['u_voted'] = isset($user_voting_info[$idea['idea_id']]) ? (int) $user_voting_info[$idea['idea_id']] : ''; |
142
|
|
|
} |
143
|
|
|
unset ($idea); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return $ideas; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Initialize the $sql property with necessary SQL statements. |
151
|
|
|
* |
152
|
|
|
* @return \phpbb\ideas\factory\ideas $this For chaining calls |
153
|
|
|
*/ |
154
|
|
|
protected function query_ideas() |
155
|
|
|
{ |
156
|
|
|
$this->sql = []; |
157
|
|
|
|
158
|
|
|
$this->sql['SELECT'][] = 't.topic_last_post_time, t.topic_status, t.topic_visibility, i.*'; |
159
|
|
|
$this->sql['FROM'] = "{$this->table_ideas} i"; |
160
|
|
|
$this->sql['JOIN'] = "{$this->table_topics} t ON i.topic_id = t.topic_id"; |
161
|
|
|
$this->sql['WHERE'][] = 't.forum_id = ' . (int) $this->config['ideas_forum_id']; |
162
|
|
|
|
163
|
|
|
// Only get approved topics for regular users, Moderators will see unapproved topics |
164
|
|
|
if (!$this->auth->acl_get('m_', $this->config['ideas_forum_id'])) |
165
|
|
|
{ |
166
|
|
|
$this->sql['WHERE'][] = 't.topic_visibility = ' . ITEM_APPROVED; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Update the $sql property with ORDER BY statements to obtain |
174
|
|
|
* the requested collection of Ideas. Some instances may add |
175
|
|
|
* additional WHERE or SELECT statements to refine the collection. |
176
|
|
|
* |
177
|
|
|
* @param string $sort A sorting option/collection |
178
|
|
|
* @param string $direction Will either be ASC or DESC |
179
|
|
|
* |
180
|
|
|
* @return \phpbb\ideas\factory\ideas $this For chaining calls |
181
|
|
|
*/ |
182
|
|
|
protected function query_sort($sort, $direction) |
183
|
|
|
{ |
184
|
|
|
$sort = strtolower($sort); |
185
|
|
|
$direction = $direction === 'DESC' ? 'DESC' : 'ASC'; |
186
|
|
|
|
187
|
|
|
// Most sorting relies on simple ORDER BY statements, but some may use a WHERE statement |
188
|
|
|
$statements = [ |
189
|
|
|
self::SORT_DATE => ['ORDER_BY' => 'i.idea_date'], |
190
|
|
|
self::SORT_TITLE => ['ORDER_BY' => 'i.idea_title'], |
191
|
|
|
self::SORT_AUTHOR => ['ORDER_BY' => 'i.idea_author'], |
192
|
|
|
self::SORT_SCORE => ['ORDER_BY' => 'CAST(i.idea_votes_up AS decimal) - CAST(i.idea_votes_down AS decimal)'], |
193
|
|
|
self::SORT_VOTES => ['ORDER_BY' => 'i.idea_votes_up + i.idea_votes_down'], |
194
|
|
|
self::SORT_TOP => ['WHERE' => 'i.idea_votes_up > i.idea_votes_down'], |
195
|
|
|
self::SORT_MYIDEAS => ['ORDER_BY' => 'i.idea_date', 'WHERE' => 'i.idea_author = ' . (int) $this->user->data['user_id']], |
196
|
|
|
]; |
197
|
|
|
|
198
|
|
|
// Append a new WHERE statement if the sort has one |
199
|
|
|
if (isset($statements[$sort]['WHERE'])) |
200
|
|
|
{ |
201
|
|
|
$this->sql['WHERE'][] = $statements[$sort]['WHERE']; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
// If we have an ORDER BY we use that. The absence of an ORDER BY |
205
|
|
|
// means we will default to sorting ideas by their calculated score. |
206
|
|
|
if (isset($statements[$sort]['ORDER_BY'])) |
207
|
|
|
{ |
208
|
|
|
$this->sql['ORDER_BY'] = "{$statements[$sort]['ORDER_BY']} $direction"; |
209
|
|
|
} |
210
|
|
|
else |
211
|
|
|
{ |
212
|
|
|
// https://www.evanmiller.org/how-not-to-sort-by-average-rating.html |
213
|
|
|
$this->sql['SELECT'][] = '((i.idea_votes_up + 1.9208) / (i.idea_votes_up + i.idea_votes_down) - |
214
|
|
|
1.96 * SQRT((i.idea_votes_up * i.idea_votes_down) / (i.idea_votes_up + i.idea_votes_down) + 0.9604) / |
215
|
|
|
(i.idea_votes_up + i.idea_votes_down)) / (1 + 3.8416 / (i.idea_votes_up + i.idea_votes_down)) |
216
|
|
|
AS ci_lower_bound'; |
217
|
|
|
|
218
|
|
|
$this->sql['ORDER_BY'] = "ci_lower_bound $direction"; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
return $this; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Update $sql property with additional SQL statements to filter ideas |
226
|
|
|
* by status. If $status is given we'll get those ideas. If no $status |
227
|
|
|
* is given, the default is to get all ideas excluding Duplicates, Invalid |
228
|
|
|
* and Implemented statuses (because they are considered done & dusted, |
229
|
|
|
* if they were gases they'd be inert). |
230
|
|
|
* |
231
|
|
|
* @param array|int $status The id(s) of the status(es) to load |
232
|
|
|
* |
233
|
|
|
* @return \phpbb\ideas\factory\ideas $this For chaining calls |
234
|
|
|
*/ |
235
|
|
|
protected function query_status($status = []) |
236
|
|
|
{ |
237
|
|
|
$this->sql['WHERE'][] = !empty($status) ? $this->db->sql_in_set('i.idea_status', $status) : $this->db->sql_in_set( |
238
|
|
|
'i.idea_status', [self::$statuses['IMPLEMENTED'], self::$statuses['DUPLICATE'], self::$statuses['INVALID'], |
239
|
|
|
], true); |
240
|
|
|
|
241
|
|
|
return $this; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Run a query using the $sql property to get a collection of ideas. |
246
|
|
|
* |
247
|
|
|
* @param int $number The number of ideas to return |
248
|
|
|
* @param int $start Start value for pagination |
249
|
|
|
* |
250
|
|
|
* @return mixed Nested array if the query had rows, false otherwise |
251
|
|
|
* @throws \phpbb\exception\runtime_exception |
252
|
|
|
*/ |
253
|
|
|
protected function query_get($number, $start) |
254
|
|
|
{ |
255
|
|
|
if (empty($this->sql)) |
256
|
|
|
{ |
257
|
|
|
throw new runtime_exception('INVALID_IDEA_QUERY'); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$sql = 'SELECT ' . implode(', ', $this->sql['SELECT']) . ' |
261
|
|
|
FROM ' . $this->sql['FROM'] . ' |
262
|
|
|
INNER JOIN ' . $this->sql['JOIN'] . ' |
263
|
|
|
WHERE ' . implode(' AND ', $this->sql['WHERE']) . ' |
264
|
|
|
ORDER BY ' . $this->sql['ORDER_BY']; |
265
|
|
|
|
266
|
|
|
$result = $this->db->sql_query_limit($sql, $number, $start); |
267
|
|
|
$rows = $this->db->sql_fetchrowset($result); |
268
|
|
|
$this->db->sql_freeresult($result); |
269
|
|
|
|
270
|
|
|
return $rows; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Run a query using the $sql property to get a count of ideas. |
275
|
|
|
* |
276
|
|
|
* @return int The number of ideas |
277
|
|
|
* @throws \phpbb\exception\runtime_exception |
278
|
|
|
*/ |
279
|
|
|
protected function query_count() |
280
|
|
|
{ |
281
|
|
|
if (empty($this->sql)) |
282
|
|
|
{ |
283
|
|
|
throw new runtime_exception('INVALID_IDEA_QUERY'); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
$sql = 'SELECT COUNT(i.idea_id) as count |
287
|
|
|
FROM ' . $this->sql['FROM'] . ' |
288
|
|
|
INNER JOIN ' . $this->sql['JOIN'] . ' |
289
|
|
|
WHERE ' . implode(' AND ', $this->sql['WHERE']); |
290
|
|
|
|
291
|
|
|
$result = $this->db->sql_query($sql); |
292
|
|
|
$count = (int) $this->db->sql_fetchfield('count'); |
293
|
|
|
$this->db->sql_freeresult($result); |
294
|
|
|
|
295
|
|
|
return $count; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Returns the specified idea. |
300
|
|
|
* |
301
|
|
|
* @param int $id The ID of the idea to return. |
302
|
|
|
* |
303
|
|
|
* @return array|false The idea row set, or false if not found. |
304
|
|
|
*/ |
305
|
|
View Code Duplication |
public function get_idea($id) |
|
|
|
|
306
|
|
|
{ |
307
|
|
|
$sql = 'SELECT * |
308
|
|
|
FROM ' . $this->table_ideas . ' |
309
|
|
|
WHERE idea_id = ' . (int) $id; |
310
|
|
|
$result = $this->db->sql_query_limit($sql, 1); |
311
|
|
|
$row = $this->db->sql_fetchrow($result); |
312
|
|
|
$this->db->sql_freeresult($result); |
313
|
|
|
|
314
|
|
|
return $row; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Returns an idea specified by its topic ID. |
319
|
|
|
* |
320
|
|
|
* @param int $id The ID of the idea to return. |
321
|
|
|
* |
322
|
|
|
* @return array|false The idea row set, or false if not found. |
323
|
|
|
*/ |
324
|
|
View Code Duplication |
public function get_idea_by_topic_id($id) |
|
|
|
|
325
|
|
|
{ |
326
|
|
|
$sql = 'SELECT idea_id |
327
|
|
|
FROM ' . $this->table_ideas . ' |
328
|
|
|
WHERE topic_id = ' . (int) $id; |
329
|
|
|
$result = $this->db->sql_query_limit($sql, 1); |
330
|
|
|
$idea_id = (int) $this->db->sql_fetchfield('idea_id'); |
331
|
|
|
$this->db->sql_freeresult($result); |
332
|
|
|
|
333
|
|
|
return $this->get_idea($idea_id); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Do a live search on idea titles. Return any matches based on a given search query. |
338
|
|
|
* |
339
|
|
|
* @param string $search The string of characters to search using LIKE |
340
|
|
|
* @param int $limit The number of results to return |
341
|
|
|
* |
342
|
|
|
* @return array An array of matching idea id/key and title/values |
343
|
|
|
*/ |
344
|
|
|
public function ideas_title_livesearch($search, $limit = 10) |
345
|
|
|
{ |
346
|
|
|
$results = []; |
347
|
|
|
$sql = 'SELECT idea_title, idea_id |
348
|
|
|
FROM ' . $this->table_ideas . ' |
349
|
|
|
WHERE idea_title ' . $this->db->sql_like_expression($search . $this->db->get_any_char()); |
350
|
|
|
$result = $this->db->sql_query_limit($sql, $limit); |
351
|
|
|
while ($row = $this->db->sql_fetchrow($result)) |
352
|
|
|
{ |
353
|
|
|
$results[] = [ |
354
|
|
|
'idea_id' => $row['idea_id'], |
355
|
|
|
'result' => $row['idea_id'], |
356
|
|
|
'clean_title' => $row['idea_title'], |
357
|
|
|
'display' => "<span>{$row['idea_title']}</span>", // spans are expected in phpBB's live search JS |
358
|
|
|
]; |
359
|
|
|
} |
360
|
|
|
$this->db->sql_freeresult($result); |
361
|
|
|
|
362
|
|
|
return $results; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Returns the status name from the status ID specified. |
367
|
|
|
* |
368
|
|
|
* @param int $id ID of the status. |
369
|
|
|
* |
370
|
|
|
* @return string|bool The status name if it exists, false otherwise. |
371
|
|
|
*/ |
372
|
|
|
public function get_status_from_id($id) |
373
|
|
|
{ |
374
|
|
|
return $this->language->lang(array_search($id, self::$statuses)); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Updates the status of an idea. |
379
|
|
|
* |
380
|
|
|
* @param int $idea_id The ID of the idea. |
381
|
|
|
* @param int $status The ID of the status. |
382
|
|
|
* |
383
|
|
|
* @return void |
384
|
|
|
*/ |
385
|
|
|
public function change_status($idea_id, $status) |
386
|
|
|
{ |
387
|
|
|
$sql_ary = array( |
388
|
|
|
'idea_status' => (int) $status, |
389
|
|
|
); |
390
|
|
|
|
391
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Sets the ID of the duplicate for an idea. |
396
|
|
|
* |
397
|
|
|
* @param int $idea_id ID of the idea to be updated. |
398
|
|
|
* @param string $duplicate Idea ID of duplicate. |
399
|
|
|
* |
400
|
|
|
* @return bool True if set, false if invalid. |
401
|
|
|
*/ |
402
|
|
View Code Duplication |
public function set_duplicate($idea_id, $duplicate) |
|
|
|
|
403
|
|
|
{ |
404
|
|
|
if ($duplicate && !is_numeric($duplicate)) |
405
|
|
|
{ |
406
|
|
|
return false; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
$sql_ary = array( |
410
|
|
|
'duplicate_id' => (int) $duplicate, |
411
|
|
|
); |
412
|
|
|
|
413
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
414
|
|
|
|
415
|
|
|
return true; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* Sets the RFC link of an idea. |
420
|
|
|
* |
421
|
|
|
* @param int $idea_id ID of the idea to be updated. |
422
|
|
|
* @param string $rfc Link to the RFC. |
423
|
|
|
* |
424
|
|
|
* @return bool True if set, false if invalid. |
425
|
|
|
*/ |
426
|
|
View Code Duplication |
public function set_rfc($idea_id, $rfc) |
|
|
|
|
427
|
|
|
{ |
428
|
|
|
$match = '/^https?:\/\/area51\.phpbb\.com\/phpBB\/viewtopic\.php/'; |
429
|
|
|
if ($rfc && !preg_match($match, $rfc)) |
430
|
|
|
{ |
431
|
|
|
return false; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
$sql_ary = array( |
435
|
|
|
'rfc_link' => $rfc, // string is escaped by build_array() |
436
|
|
|
); |
437
|
|
|
|
438
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
439
|
|
|
|
440
|
|
|
return true; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* Sets the ticket ID of an idea. |
445
|
|
|
* |
446
|
|
|
* @param int $idea_id ID of the idea to be updated. |
447
|
|
|
* @param string $ticket Ticket ID. |
448
|
|
|
* |
449
|
|
|
* @return bool True if set, false if invalid. |
450
|
|
|
*/ |
451
|
|
View Code Duplication |
public function set_ticket($idea_id, $ticket) |
|
|
|
|
452
|
|
|
{ |
453
|
|
|
if ($ticket && !is_numeric($ticket)) |
454
|
|
|
{ |
455
|
|
|
return false; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
$sql_ary = array( |
459
|
|
|
'ticket_id' => (int) $ticket, |
460
|
|
|
); |
461
|
|
|
|
462
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
463
|
|
|
|
464
|
|
|
return true; |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Sets the implemented version of an idea. |
469
|
|
|
* |
470
|
|
|
* @param int $idea_id ID of the idea to be updated. |
471
|
|
|
* @param string $version Version of phpBB the idea was implemented in. |
472
|
|
|
* |
473
|
|
|
* @return bool True if set, false if invalid. |
474
|
|
|
*/ |
475
|
|
View Code Duplication |
public function set_implemented($idea_id, $version) |
|
|
|
|
476
|
|
|
{ |
477
|
|
|
$match = '/^\d\.\d\.\d+(\-\w+)?$/'; |
478
|
|
|
if ($version && !preg_match($match, $version)) |
479
|
|
|
{ |
480
|
|
|
return false; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
$sql_ary = array( |
484
|
|
|
'implemented_version' => $version, // string is escaped by build_array() |
485
|
|
|
); |
486
|
|
|
|
487
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
488
|
|
|
|
489
|
|
|
return true; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Sets the title of an idea. |
494
|
|
|
* |
495
|
|
|
* @param int $idea_id ID of the idea to be updated. |
496
|
|
|
* @param string $title New title. |
497
|
|
|
* |
498
|
|
|
* @return boolean True if updated, false if invalid length. |
499
|
|
|
*/ |
500
|
|
|
public function set_title($idea_id, $title) |
501
|
|
|
{ |
502
|
|
|
if (utf8_clean_string($title) === '') |
503
|
|
|
{ |
504
|
|
|
return false; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
$sql_ary = array( |
508
|
|
|
'idea_title' => truncate_string($title, self::SUBJECT_LENGTH), |
509
|
|
|
); |
510
|
|
|
|
511
|
|
|
$this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); |
512
|
|
|
|
513
|
|
|
return true; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Get the title of an idea. |
518
|
|
|
* |
519
|
|
|
* @param int $id ID of an idea |
520
|
|
|
* |
521
|
|
|
* @return string The idea's title |
522
|
|
|
*/ |
523
|
|
View Code Duplication |
public function get_title($id) |
|
|
|
|
524
|
|
|
{ |
525
|
|
|
$sql = 'SELECT idea_title |
526
|
|
|
FROM ' . $this->table_ideas . ' |
527
|
|
|
WHERE idea_id = ' . (int) $id; |
528
|
|
|
$result = $this->db->sql_query_limit($sql, 1); |
529
|
|
|
$idea_title = $this->db->sql_fetchfield('idea_title'); |
530
|
|
|
$this->db->sql_freeresult($result); |
531
|
|
|
|
532
|
|
|
return $idea_title; |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
/** |
536
|
|
|
* Submits a vote on an idea. |
537
|
|
|
* |
538
|
|
|
* @param array $idea The idea returned by get_idea(). |
539
|
|
|
* @param int $user_id The ID of the user voting. |
540
|
|
|
* @param int $value Up (1) or down (0)? |
541
|
|
|
* |
542
|
|
|
* @return array|string Array of information or string on error. |
543
|
|
|
*/ |
544
|
|
|
public function vote(&$idea, $user_id, $value) |
545
|
|
|
{ |
546
|
|
|
// Validate $vote - must be 0 or 1 |
547
|
|
|
if ($value !== 0 && $value !== 1) |
548
|
|
|
{ |
549
|
|
|
return 'INVALID_VOTE'; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
// Check whether user has already voted - update if they have |
553
|
|
|
$sql = 'SELECT idea_id, vote_value |
554
|
|
|
FROM ' . $this->table_votes . ' |
555
|
|
|
WHERE idea_id = ' . (int) $idea['idea_id'] . ' |
556
|
|
|
AND user_id = ' . (int) $user_id; |
557
|
|
|
$this->db->sql_query_limit($sql, 1); |
558
|
|
|
if ($row = $this->db->sql_fetchrow()) |
559
|
|
|
{ |
560
|
|
|
if ($row['vote_value'] != $value) |
561
|
|
|
{ |
562
|
|
|
$sql = 'UPDATE ' . $this->table_votes . ' |
563
|
|
|
SET vote_value = ' . $value . ' |
564
|
|
|
WHERE user_id = ' . (int) $user_id . ' |
565
|
|
|
AND idea_id = ' . (int) $idea['idea_id']; |
566
|
|
|
$this->db->sql_query($sql); |
567
|
|
|
|
568
|
|
|
if ($value == 1) |
569
|
|
|
{ |
570
|
|
|
// Change to upvote |
571
|
|
|
$idea['idea_votes_up']++; |
572
|
|
|
$idea['idea_votes_down']--; |
573
|
|
|
} |
574
|
|
|
else |
575
|
|
|
{ |
576
|
|
|
// Change to downvote |
577
|
|
|
$idea['idea_votes_up']--; |
578
|
|
|
$idea['idea_votes_down']++; |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
$sql_ary = array( |
582
|
|
|
'idea_votes_up' => $idea['idea_votes_up'], |
583
|
|
|
'idea_votes_down' => $idea['idea_votes_down'], |
584
|
|
|
); |
585
|
|
|
|
586
|
|
|
$this->update_idea_data($sql_ary, $idea['idea_id'], $this->table_ideas); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
return array( |
590
|
|
|
'message' => $this->language->lang('UPDATED_VOTE'), |
591
|
|
|
'votes_up' => $idea['idea_votes_up'], |
592
|
|
|
'votes_down' => $idea['idea_votes_down'], |
593
|
|
|
'points' => $this->language->lang('TOTAL_POINTS', $idea['idea_votes_up'] - $idea['idea_votes_down']), |
594
|
|
|
'voters' => $this->get_voters($idea['idea_id']), |
595
|
|
|
); |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
// Insert vote into votes table. |
599
|
|
|
$sql_ary = array( |
600
|
|
|
'idea_id' => (int) $idea['idea_id'], |
601
|
|
|
'user_id' => (int) $user_id, |
602
|
|
|
'vote_value' => (int) $value, |
603
|
|
|
); |
604
|
|
|
|
605
|
|
|
$this->insert_idea_data($sql_ary, $this->table_votes); |
606
|
|
|
|
607
|
|
|
// Update number of votes in ideas table |
608
|
|
|
$idea['idea_votes_' . ($value ? 'up' : 'down')]++; |
609
|
|
|
|
610
|
|
|
$sql_ary = array( |
611
|
|
|
'idea_votes_up' => $idea['idea_votes_up'], |
612
|
|
|
'idea_votes_down' => $idea['idea_votes_down'], |
613
|
|
|
); |
614
|
|
|
|
615
|
|
|
$this->update_idea_data($sql_ary, $idea['idea_id'], $this->table_ideas); |
616
|
|
|
|
617
|
|
|
return array( |
618
|
|
|
'message' => $this->language->lang('VOTE_SUCCESS'), |
619
|
|
|
'votes_up' => $idea['idea_votes_up'], |
620
|
|
|
'votes_down' => $idea['idea_votes_down'], |
621
|
|
|
'points' => $this->language->lang('TOTAL_POINTS', $idea['idea_votes_up'] - $idea['idea_votes_down']), |
622
|
|
|
'voters' => $this->get_voters($idea['idea_id']), |
623
|
|
|
); |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* Remove a user's vote from an idea |
628
|
|
|
* |
629
|
|
|
* @param array $idea The idea returned by get_idea(). |
630
|
|
|
* @param int $user_id The ID of the user voting. |
631
|
|
|
* |
632
|
|
|
* @return array Array of information. |
633
|
|
|
*/ |
634
|
|
|
public function remove_vote(&$idea, $user_id) |
635
|
|
|
{ |
636
|
|
|
// Only change something if user has already voted |
637
|
|
|
$sql = 'SELECT idea_id, vote_value |
638
|
|
|
FROM ' . $this->table_votes . ' |
639
|
|
|
WHERE idea_id = ' . (int) $idea['idea_id'] . ' |
640
|
|
|
AND user_id = ' . (int) $user_id; |
641
|
|
|
$this->db->sql_query_limit($sql, 1); |
642
|
|
|
if ($row = $this->db->sql_fetchrow()) |
643
|
|
|
{ |
644
|
|
|
$sql = 'DELETE FROM ' . $this->table_votes . ' |
645
|
|
|
WHERE idea_id = ' . (int) $idea['idea_id'] . ' |
646
|
|
|
AND user_id = ' . (int) $user_id; |
647
|
|
|
$this->db->sql_query($sql); |
648
|
|
|
|
649
|
|
|
$idea['idea_votes_' . ($row['vote_value'] == 1 ? 'up' : 'down')]--; |
650
|
|
|
|
651
|
|
|
$sql_ary = array( |
652
|
|
|
'idea_votes_up' => $idea['idea_votes_up'], |
653
|
|
|
'idea_votes_down' => $idea['idea_votes_down'], |
654
|
|
|
); |
655
|
|
|
|
656
|
|
|
$this->update_idea_data($sql_ary, $idea['idea_id'], $this->table_ideas); |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return array( |
660
|
|
|
'message' => $this->language->lang('UPDATED_VOTE'), |
661
|
|
|
'votes_up' => $idea['idea_votes_up'], |
662
|
|
|
'votes_down' => $idea['idea_votes_down'], |
663
|
|
|
'points' => $this->language->lang('TOTAL_POINTS', $idea['idea_votes_up'] - $idea['idea_votes_down']), |
664
|
|
|
'voters' => $this->get_voters($idea['idea_id']), |
665
|
|
|
); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
/** |
669
|
|
|
* Returns voter info on an idea. |
670
|
|
|
* |
671
|
|
|
* @param int $id ID of the idea. |
672
|
|
|
* |
673
|
|
|
* @return array Array of row data |
674
|
|
|
*/ |
675
|
|
|
public function get_voters($id) |
676
|
|
|
{ |
677
|
|
|
$sql = 'SELECT iv.user_id, iv.vote_value, u.username, u.user_colour |
678
|
|
|
FROM ' . $this->table_votes . ' as iv, |
679
|
|
|
' . USERS_TABLE . ' as u |
680
|
|
|
WHERE iv.idea_id = ' . (int) $id . ' |
681
|
|
|
AND iv.user_id = u.user_id |
682
|
|
|
ORDER BY u.username ASC'; |
683
|
|
|
$result = $this->db->sql_query($sql); |
684
|
|
|
$rows = $this->db->sql_fetchrowset($result); |
685
|
|
|
$this->db->sql_freeresult($result); |
686
|
|
|
|
687
|
|
|
// Process the username for the template now, so it is |
688
|
|
|
// ready to use in AJAX responses and DOM injections. |
689
|
|
|
foreach ($rows as &$row) |
690
|
|
|
{ |
691
|
|
|
$row['user'] = get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $this->profile_url()); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
return $rows; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Get a user's votes from a group of ideas |
699
|
|
|
* |
700
|
|
|
* @param int $user_id The user's id |
701
|
|
|
* @param array $ids An array of idea ids |
702
|
|
|
* |
703
|
|
|
* @return array An array of ideas the user voted on and their vote result, or empty otherwise. |
704
|
|
|
* example: [idea_id => vote_result] |
705
|
|
|
* 1 => 1, idea 1, voted up by the user |
706
|
|
|
* 2 => 0, idea 2, voted down by the user |
707
|
|
|
*/ |
708
|
|
|
public function get_users_votes($user_id, array $ids) |
709
|
|
|
{ |
710
|
|
|
$results = []; |
711
|
|
|
$sql = 'SELECT idea_id, vote_value |
712
|
|
|
FROM ' . $this->table_votes . ' |
713
|
|
|
WHERE user_id = ' . (int) $user_id . ' |
714
|
|
|
AND ' . $this->db->sql_in_set('idea_id', $ids, false, true); |
715
|
|
|
$result = $this->db->sql_query($sql); |
716
|
|
|
while ($row = $this->db->sql_fetchrow($result)) |
717
|
|
|
{ |
718
|
|
|
$results[$row['idea_id']] = $row['vote_value']; |
719
|
|
|
} |
720
|
|
|
$this->db->sql_freeresult($result); |
721
|
|
|
|
722
|
|
|
return $results; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
/** |
726
|
|
|
* Submit new idea data to the ideas table |
727
|
|
|
* |
728
|
|
|
* @param array $data An array of post data from a newly posted idea |
729
|
|
|
* |
730
|
|
|
* @return int The ID of the new idea. |
731
|
|
|
*/ |
732
|
|
|
public function submit($data) |
733
|
|
|
{ |
734
|
|
|
$sql_ary = [ |
735
|
|
|
'idea_title' => $data['topic_title'], |
736
|
|
|
'idea_author' => $data['poster_id'], |
737
|
|
|
'idea_date' => $data['post_time'], |
738
|
|
|
'topic_id' => $data['topic_id'], |
739
|
|
|
]; |
740
|
|
|
|
741
|
|
|
$idea_id = $this->insert_idea_data($sql_ary, $this->table_ideas); |
742
|
|
|
|
743
|
|
|
// Initial vote |
744
|
|
|
if (($idea = $this->get_idea($idea_id)) !== false) |
745
|
|
|
{ |
746
|
|
|
$this->vote($idea, $data['poster_id'], 1); |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
return $idea_id; |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
/** |
753
|
|
|
* Preview a new idea. |
754
|
|
|
* |
755
|
|
|
* @param string $message The description of the idea. |
756
|
|
|
* |
757
|
|
|
* @return string The idea parsed for display in preview. |
758
|
|
|
*/ |
759
|
|
|
public function preview($message) |
760
|
|
|
{ |
761
|
|
|
$uid = $bitfield = $flags = ''; |
762
|
|
|
generate_text_for_storage($message, $uid, $bitfield, $flags, true, true, true); |
763
|
|
|
return generate_text_for_display($message, $uid, $bitfield, $flags); |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
/** |
767
|
|
|
* Deletes an idea and the topic to go with it. |
768
|
|
|
* |
769
|
|
|
* @param int $id The ID of the idea to be deleted. |
770
|
|
|
* @param int $topic_id The ID of the idea topic. Optional, but preferred. |
771
|
|
|
* |
772
|
|
|
* @return boolean Whether the idea was deleted or not. |
773
|
|
|
*/ |
774
|
|
|
public function delete($id, $topic_id = 0) |
775
|
|
|
{ |
776
|
|
|
if (!$topic_id) |
777
|
|
|
{ |
778
|
|
|
$idea = $this->get_idea($id); |
779
|
|
|
$topic_id = $idea['topic_id']; |
780
|
|
|
} |
781
|
|
|
|
782
|
|
|
// Delete topic |
783
|
|
|
delete_posts('topic_id', $topic_id); |
784
|
|
|
|
785
|
|
|
// Delete idea |
786
|
|
|
$deleted = $this->delete_idea_data($id, $this->table_ideas); |
787
|
|
|
|
788
|
|
|
// Delete votes |
789
|
|
|
$this->delete_idea_data($id, $this->table_votes); |
790
|
|
|
|
791
|
|
|
return $deleted; |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
/** |
795
|
|
|
* Delete orphaned ideas. Orphaned ideas may exist after a |
796
|
|
|
* topic has been deleted or moved to another forum. |
797
|
|
|
* |
798
|
|
|
* @return int Number of rows affected |
799
|
|
|
*/ |
800
|
|
|
public function delete_orphans() |
801
|
|
|
{ |
802
|
|
|
// Find any orphans |
803
|
|
|
$sql = 'SELECT idea_id FROM ' . $this->table_ideas . ' |
804
|
|
|
WHERE topic_id NOT IN (SELECT t.topic_id |
805
|
|
|
FROM ' . $this->table_topics . ' t |
806
|
|
|
WHERE t.forum_id = ' . (int) $this->config['ideas_forum_id'] . ')'; |
807
|
|
|
$result = $this->db->sql_query($sql); |
808
|
|
|
$rows = $this->db->sql_fetchrowset($result); |
809
|
|
|
$this->db->sql_freeresult($result); |
810
|
|
|
|
811
|
|
|
if (empty($rows)) |
812
|
|
|
{ |
813
|
|
|
return 0; |
814
|
|
|
} |
815
|
|
|
|
816
|
|
|
$this->db->sql_transaction('begin'); |
817
|
|
|
|
818
|
|
|
foreach ($rows as $row) |
819
|
|
|
{ |
820
|
|
|
// Delete idea |
821
|
|
|
$this->delete_idea_data($row['idea_id'], $this->table_ideas); |
822
|
|
|
|
823
|
|
|
// Delete votes |
824
|
|
|
$this->delete_idea_data($row['idea_id'], $this->table_votes); |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
$this->db->sql_transaction('commit'); |
828
|
|
|
|
829
|
|
|
return count($rows); |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
/** |
833
|
|
|
* Helper method for inserting new idea data |
834
|
|
|
* |
835
|
|
|
* @param array $data The array of data to insert |
836
|
|
|
* @param string $table The name of the table |
837
|
|
|
* |
838
|
|
|
* @return int The ID of the inserted row |
839
|
|
|
*/ |
840
|
|
|
protected function insert_idea_data(array $data, $table) |
841
|
|
|
{ |
842
|
|
|
$sql = 'INSERT INTO ' . $table . ' |
843
|
|
|
' . $this->db->sql_build_array('INSERT', $data); |
844
|
|
|
$this->db->sql_query($sql); |
845
|
|
|
|
846
|
|
|
return (int) $this->db->sql_nextid(); |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
/** |
850
|
|
|
* Helper method for updating idea data |
851
|
|
|
* |
852
|
|
|
* @param array $data The array of data to insert |
853
|
|
|
* @param int $id The ID of the idea |
854
|
|
|
* @param string $table The name of the table |
855
|
|
|
* |
856
|
|
|
* @return void |
857
|
|
|
*/ |
858
|
|
|
protected function update_idea_data(array $data, $id, $table) |
859
|
|
|
{ |
860
|
|
|
$sql = 'UPDATE ' . $table . ' |
861
|
|
|
SET ' . $this->db->sql_build_array('UPDATE', $data) . ' |
862
|
|
|
WHERE idea_id = ' . (int) $id; |
863
|
|
|
$this->db->sql_query($sql); |
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* Helper method for deleting idea data |
868
|
|
|
* |
869
|
|
|
* @param int $id The ID of the idea |
870
|
|
|
* @param string $table The name of the table |
871
|
|
|
* |
872
|
|
|
* @return bool True if idea was deleted, false otherwise |
873
|
|
|
*/ |
874
|
|
|
protected function delete_idea_data($id, $table) |
875
|
|
|
{ |
876
|
|
|
$sql = 'DELETE FROM ' . $table . ' |
877
|
|
|
WHERE idea_id = ' . (int) $id; |
878
|
|
|
$this->db->sql_query($sql); |
879
|
|
|
|
880
|
|
|
return (bool) $this->db->sql_affectedrows(); |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
/** |
884
|
|
|
* Get the stored idea count |
885
|
|
|
* Note: this should only be called after get_ideas() |
886
|
|
|
* |
887
|
|
|
* @return int Count of ideas |
888
|
|
|
*/ |
889
|
|
|
public function get_idea_count() |
890
|
|
|
{ |
891
|
|
|
return isset($this->idea_count) ? $this->idea_count : 0; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
/** |
895
|
|
|
* Helper to generate the user profile URL with an |
896
|
|
|
* absolute URL, which helps avoid problems when |
897
|
|
|
* used in AJAX requests. |
898
|
|
|
* |
899
|
|
|
* @return string User profile URL |
900
|
|
|
*/ |
901
|
|
|
protected function profile_url() |
902
|
|
|
{ |
903
|
|
|
if (!isset($this->profile_url)) |
904
|
|
|
{ |
905
|
|
|
$this->profile_url = append_sid(generate_board_url() . "/memberlist.{$this->php_ext}", array('mode' => 'viewprofile')); |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
return $this->profile_url; |
909
|
|
|
} |
910
|
|
|
} |
911
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.