Completed
Branch master (b1a112)
by Matt
01:21
created

ideas::get_ideas()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 2 Features 0
Metric Value
eloc 16
c 8
b 2
f 0
dl 0
loc 32
rs 9.1111
cc 6
nc 4
nop 5
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;
1 ignored issue
show
Bug introduced by
The type phpbb\auth\auth was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use phpbb\config\config;
1 ignored issue
show
Bug introduced by
The type phpbb\config\config was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use phpbb\db\driver\driver_interface;
1 ignored issue
show
Bug introduced by
The type phpbb\db\driver\driver_interface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use phpbb\exception\runtime_exception;
1 ignored issue
show
Bug introduced by
The type phpbb\exception\runtime_exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use phpbb\language\language;
1 ignored issue
show
Bug introduced by
The type phpbb\language\language was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use phpbb\user;
1 ignored issue
show
Bug introduced by
The type phpbb\user was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
	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);
1 ignored issue
show
Bug introduced by
The function get_complete_topic_tracking was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
			$topic_tracking_info = /** @scrutinizer ignore-call */ get_complete_topic_tracking((int) $this->config['ideas_forum_id'], $topic_ids);
Loading history...
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;
1 ignored issue
show
Bug introduced by
The constant phpbb\ideas\factory\ITEM_APPROVED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
	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
	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($this->db->get_any_char() . $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
	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
	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
	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
	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) === '')
1 ignored issue
show
Bug introduced by
The function utf8_clean_string was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

502
		if (/** @scrutinizer ignore-call */ utf8_clean_string($title) === '')
Loading history...
503
		{
504
			return false;
505
		}
506
507
		$sql_ary = array(
508
			'idea_title' => truncate_string($title, self::SUBJECT_LENGTH),
1 ignored issue
show
Bug introduced by
The function truncate_string was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

508
			'idea_title' => /** @scrutinizer ignore-call */ truncate_string($title, self::SUBJECT_LENGTH),
Loading history...
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, empty string if not found
522
	 */
523
	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
1 ignored issue
show
Bug introduced by
The constant phpbb\ideas\factory\USERS_TABLE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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());
1 ignored issue
show
Bug introduced by
The function get_username_string was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

691
			$row['user'] = /** @scrutinizer ignore-call */ get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $this->profile_url());
Loading history...
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);
0 ignored issues
show
Bug introduced by
The function generate_text_for_storage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

762
		/** @scrutinizer ignore-call */ 
763
  generate_text_for_storage($message, $uid, $bitfield, $flags, true, true, true);
Loading history...
763
		return generate_text_for_display($message, $uid, $bitfield, $flags);
0 ignored issues
show
Bug introduced by
The function generate_text_for_display was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

763
		return /** @scrutinizer ignore-call */ generate_text_for_display($message, $uid, $bitfield, $flags);
Loading history...
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);
1 ignored issue
show
Bug introduced by
The function delete_posts was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

783
		/** @scrutinizer ignore-call */ 
784
  delete_posts('topic_id', $topic_id);
Loading history...
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'));
2 ignored issues
show
Bug introduced by
The function generate_board_url was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

905
			$this->profile_url = append_sid(/** @scrutinizer ignore-call */ generate_board_url() . "/memberlist.{$this->php_ext}", array('mode' => 'viewprofile'));
Loading history...
Bug introduced by
The function append_sid was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

905
			$this->profile_url = /** @scrutinizer ignore-call */ append_sid(generate_board_url() . "/memberlist.{$this->php_ext}", array('mode' => 'viewprofile'));
Loading history...
906
		}
907
908
		return $this->profile_url;
909
	}
910
}
911