getBoardList()   F
last analyzed

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 191
Code Lines 92

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 222.2522

Importance

Changes 0
Metric Value
cc 36
eloc 92
nc 124416
nop 2
dl 0
loc 191
ccs 40
cts 84
cp 0.4762
crap 222.2522
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is mainly concerned with minor tasks relating to boards, such as
5
 * marking them read, collapsing categories, or quick moderation.
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
use ElkArte\BoardsTree;
19
use ElkArte\Cache\Cache;
20
use ElkArte\Helper\Util;
21
use ElkArte\User;
22
23
/**
24
 * Mark a board or multiple boards read.
25
 *
26
 * @param int[]|int $boards
27
 * @param bool $unread = false
28
 * @param bool $resetTopics = false
29
 * @package Boards
30
 */
31
function markBoardsRead($boards, $unread = false, $resetTopics = false)
32
{
33
	global $modSettings;
34 8
35
	$db = database();
36 8
37
	$boards = !is_array($boards) ? [$boards] : array_unique($boards);
38 8
39
	// No boards, nothing to mark as read.
40
	if (empty($boards))
41 8
	{
42
		return;
43
	}
44
45
	// Allow the user to mark a board as unread.
46
	if ($unread)
47 8
	{
48
		// Clear out all the places where this lovely info is stored.
49
		// @todo Maybe not log_mark_read?
50
		$db->query('', '
51
			DELETE FROM {db_prefix}log_mark_read
52
			WHERE id_board IN ({array_int:board_list})
53
				AND id_member = {int:current_member}',
54
			[
55
				'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
56
				'board_list' => $boards,
57
			]
58
		);
59
60
		$db->query('', '
61
			DELETE FROM {db_prefix}log_boards
62
			WHERE id_board IN ({array_int:board_list})
63
				AND id_member = {int:current_member}',
64
			[
65
				'current_member' => User::$info->id,
66
				'board_list' => $boards,
67
			]
68
		);
69
	}
70
	// Otherwise mark the board as read.
71
	else
72
	{
73
		$markRead = [];
74 8
		foreach ($boards as $board)
75 8
		{
76
			$markRead[] = [$modSettings['maxMsgID'], User::$info->id, $board];
77 8
		}
78
79
		$db->replace(
80 8
			'{db_prefix}log_boards',
81 8
			['id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'],
82 8
			$markRead,
83 4
			['id_board', 'id_member']
84 8
		);
85
	}
86
87
	// Get rid of useless log_topics data, because log_mark_read is better for it - even if marking unread - I think so...
88
	// @todo look at this...
89
	// The call to markBoardsRead() in Display() used to be simply
90
	// marking log_boards (the previous query only)
91
	// I'm adding a bool to control the processing of log_topics. We might want to just dissociate it from boards,
92
	// and call the log_topics clear-up only from the controller that needs it..
93
94
	// Notes (for read/unread rework)
95
	// MessageIndex::action_messageindex() does not update log_topics at all (only the above).
96
	// Display controller needed only to update log_boards.
97
98
	if ($resetTopics)
99 8
	{
100
		// Update log_mark_read and log_boards.
101
		// @todo check this condition <= I think I did, but better double check
102
		if (!$unread && !empty($markRead))
103 4
		{
104
			$db->replace(
105 4
				'{db_prefix}log_mark_read',
106 4
				['id_msg' => 'int', 'id_member' => 'int', 'id_board' => 'int'],
107 4
				$markRead,
108 2
				['id_board', 'id_member']
109 4
			);
110
		}
111
112
		$result = $db->query('', '
113 4
			SELECT 
114
				MIN(id_topic)
115
			FROM {db_prefix}log_topics
116
			WHERE id_member = {int:current_member}',
117
			[
118
				'current_member' => User::$info->id,
119 4
			]
120
		);
121
		list ($lowest_topic) = $result->fetch_row();
122 4
		$result->free_result();
123 4
124
		if (empty($lowest_topic))
125 4
		{
126
			return;
127
		}
128
129
		// @todo SLOW This query seems to eat it sometimes.
130
		$delete_topics = [];
131 4
		$update_topics = [];
132 4
		$db->fetchQuery('
133 4
			SELECT 
134
				lt.id_topic, lt.unwatched
135
			FROM {db_prefix}log_topics AS lt
136
				INNER JOIN {db_prefix}topics AS t /*!40000 USE INDEX (PRIMARY) */ ON (t.id_topic = lt.id_topic
137
					AND t.id_board IN ({array_int:board_list}))
138
			WHERE lt.id_member = {int:current_member}
139
				AND lt.id_topic >= {int:lowest_topic}',
140
			[
141
				'current_member' => User::$info->id,
142 4
				'board_list' => $boards,
143 4
				'lowest_topic' => $lowest_topic,
144 4
			]
145
		)->fetch_callback(
146 4
			function ($row) use (&$delete_topics, &$update_topics, $modSettings) {
147
				if (!empty($row['unwatched']))
148 4
				{
149
					$update_topics[] = [
150
						User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
151
						$modSettings['maxMsgID'],
152
						$row['id_topic'],
153
						1,
154
					];
155
				}
156
				else
157
				{
158
					$delete_topics[] = $row['id_topic'];
159 4
				}
160
			}
161 4
		);
162
163
		if (!empty($update_topics))
164 4
		{
165
			$db->replace(
166
				'{db_prefix}log_topics',
167
				[
168
					'id_member' => 'int',
169
					'id_msg' => 'int',
170
					'id_topic' => 'int',
171
					'unwatched' => 'int'
172
				],
173
				$update_topics,
174
				['id_topic', 'id_member']
175
			);
176
		}
177
178
		if (!empty($delete_topics))
179 4
		{
180
			$db->query('', '
181 4
				DELETE FROM {db_prefix}log_topics
182
				WHERE id_member = {int:current_member}
183
					AND id_topic IN ({array_int:topic_list})',
184
				[
185
					'current_member' => User::$info->id,
186 4
					'topic_list' => $delete_topics,
187 4
				]
188
			);
189
		}
190
	}
191
}
192 8
193
/**
194
 * Get the id_member associated with the specified message ID.
195
 *
196
 * @param int $messageID message ID
197
 * @return int the member id
198
 * @package Boards
199
 */
200
function getMsgMemberID($messageID)
201
{
202
	require_once(SUBSDIR . '/Messages.subs.php');
203
	$message_info = basicMessageInfo((int) $messageID, true);
204
205
	return empty($message_info['id_member']) ? 0 : (int) $message_info['id_member'];
206
}
207
208
/**
209
 * Modify the settings and position of a board.
210
 *
211
 * - Used by ManageBoards.controller.php to change the settings of a board.
212
 *
213
 * @param int $board_id
214
 * @param array $boardOptions
215
 *
216
 * @throws \ElkArte\Exceptions\Exception no_board, mboards_board_own_child_error
217
 * @package Boards
218
 *
219
 */
220
function modifyBoard($board_id, &$boardOptions)
221
{
222
	$db = database();
223 2
224
	// Get some basic information about all boards and categories.
225
	$boardTree = new BoardsTree($db);
226 2
	$cat_tree = $boardTree->getCategories();
227 2
	$boards = $boardTree->getBoards();
228 2
229
	// Make sure given boards and categories exist.
230
	if (!isset($boards[$board_id]))
231 2
	{
232
		throw new \ElkArte\Exceptions\Exception('no_board');
233
	}
234
235
	if (isset($boardOptions['target_board']) && !isset($boards[$boardOptions['target_board']]))
236 2
	{
237
		throw new \ElkArte\Exceptions\Exception('no_board');
238
	}
239
240
	if (isset($boardOptions['target_category']) && !isset($cat_tree[$boardOptions['target_category']]))
241 2
	{
242
		throw new \ElkArte\Exceptions\Exception('no_board');
243
	}
244
245
	// All things that will be updated in the database will be in $boardUpdates.
246
	$boardUpdates = [];
247 2
	$boardUpdateParameters = [];
248 2
249
	// In case the board has to be moved
250
	if (isset($boardOptions['move_to']))
251 2
	{
252
		// Move the board to the top of a given category.
253
		if ($boardOptions['move_to'] === 'top')
254 2
		{
255
			$id_cat = $boardOptions['target_category'];
256
			$child_level = 0;
257
			$id_parent = 0;
258
			$after = $cat_tree[$id_cat]['last_board_order'];
259
		}
260
		// Move the board to the bottom of a given category.
261
		elseif ($boardOptions['move_to'] === 'bottom')
262
		{
263 2
			$id_cat = $boardOptions['target_category'];
264
			$child_level = 0;
265 2
			$id_parent = 0;
266 2
			$after = 0;
267 2
			foreach ($cat_tree[$id_cat]['children'] as $id_board => $dummy)
268 2
			{
269 2
				$after = max($after, $boards[$id_board]['order']);
270
			}
271 2
		}
272
		// Make the board a child of a given board.
273
		elseif ($boardOptions['move_to'] === 'child')
274
		{
275
			$id_cat = $boards[$boardOptions['target_board']]['category'];
276
			$child_level = $boards[$boardOptions['target_board']]['level'] + 1;
277
			$id_parent = $boardOptions['target_board'];
278
279
			// People can be creative, in many ways...
280
			if ($boardTree->isChildOf($id_parent, $board_id))
281
			{
282
				throw new \ElkArte\Exceptions\Exception('mboards_parent_own_child_error', false);
283
			}
284
285
			if ($id_parent == $board_id)
286
			{
287
				throw new \ElkArte\Exceptions\Exception('mboards_board_own_child_error', false);
288
			}
289
290
			$after = $boards[$boardOptions['target_board']]['order'];
291
292
			// Check if there are already children and (if so) get the max board order.
293
			if (!empty($boards[$id_parent]['tree']['children']) && empty($boardOptions['move_first_child']))
294
			{
295
				foreach ($boards[$id_parent]['tree']['children'] as $childBoard_id => $dummy)
296
				{
297
					$after = max($after, $boards[$childBoard_id]['order']);
298
				}
299
			}
300
		}
301
		// Place a board before or after another board, on the same child level.
302
		elseif (in_array($boardOptions['move_to'], ['before', 'after']))
303
		{
304
			$id_cat = $boards[$boardOptions['target_board']]['category'];
305
			$child_level = $boards[$boardOptions['target_board']]['level'];
306
			$id_parent = $boards[$boardOptions['target_board']]['parent'];
307
			$after = $boards[$boardOptions['target_board']]['order'] - ($boardOptions['move_to'] == 'before' ? 1 : 0);
308
		}
309
		// Oops...?
310
		else
311
		{
312
			trigger_error('modifyBoard(): The move_to value \'' . $boardOptions['move_to'] . '\' is incorrect', E_USER_ERROR);
313
		}
314
315
		// Get a list of children of this board.
316
		$childList = $boardTree->allChildsOf($board_id);
317
318
		// See if there are changes that affect children.
319
		$childUpdates = [];
320 2
		$levelDiff = $child_level - $boards[$board_id]['level'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $child_level does not seem to be defined for all execution paths leading up to this point.
Loading history...
321
		if ($levelDiff != 0)
322
		{
323 2
			$childUpdates[] = 'child_level = child_level ' . ($levelDiff > 0 ? '+ ' : '') . '{int:level_diff}';
324 2
		}
325 2
326
		if ($id_cat != $boards[$board_id]['category'])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_cat does not seem to be defined for all execution paths leading up to this point.
Loading history...
327
		{
328
			$childUpdates[] = 'id_cat = {int:category}';
329
		}
330 2
331
		// Fix the children of this board.
332
		if (!empty($childList) && !empty($childUpdates))
333
		{
334
			$db->query('', '
335
				UPDATE {db_prefix}boards
336 2
				SET 
337
					' . implode(', ', $childUpdates) . '
338
				WHERE id_board IN ({array_int:board_list})',
339
				[
340
					'board_list' => $childList,
341
					'category' => $id_cat,
342
					'level_diff' => $levelDiff,
343
				]
344
			);
345
		}
346
347
		// Make some room for this spot.
348
		$db->query('', '
349
			UPDATE {db_prefix}boards
350
			SET 
351
				board_order = board_order + {int:new_order}
352 2
			WHERE board_order > {int:insert_after}
353
				AND id_board != {int:selected_board}',
354
			[
355
				'insert_after' => $after,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $after does not seem to be defined for all execution paths leading up to this point.
Loading history...
356
				'selected_board' => $board_id,
357
				'new_order' => 1 + count($childList),
358
			]
359 2
		);
360 2
361 2
		$boardUpdates[] = 'id_cat = {int:id_cat}';
362
		$boardUpdates[] = 'id_parent = {int:id_parent}';
363
		$boardUpdates[] = 'child_level = {int:child_level}';
364
		$boardUpdates[] = 'board_order = {int:board_order}';
365 2
		$boardUpdateParameters += [
366 2
			'id_cat' => $id_cat,
367 2
			'id_parent' => $id_parent,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_parent does not seem to be defined for all execution paths leading up to this point.
Loading history...
368 2
			'child_level' => $child_level,
369
			'board_order' => $after + 1,
370 2
		];
371 2
	}
372 2
373 2
	// This setting is a little twisted in the database...
374
	if (isset($boardOptions['posts_count']))
375
	{
376
		$boardUpdates[] = 'count_posts = {int:count_posts}';
377
		$boardUpdateParameters['count_posts'] = $boardOptions['posts_count'] ? 0 : 1;
378 2
	}
379
380 2
	// Warn on old posts in this board
381 2
	if (isset($boardOptions['old_posts']))
382
	{
383
		$boardUpdates[] = 'old_posts = {int:old_posts}';
384
		$boardUpdateParameters['old_posts'] = $boardOptions['old_posts'] ? 0 : 1;
385 2
	}
386
387 2
	// Set the theme for this board.
388 2
	if (isset($boardOptions['board_theme']))
389
	{
390
		$boardUpdates[] = 'id_theme = {int:id_theme}';
391
		$boardUpdateParameters['id_theme'] = (int) $boardOptions['board_theme'];
392 2
	}
393
394 2
	// Should the board theme override the user preferred theme?
395 2
	if (isset($boardOptions['override_theme']))
396
	{
397
		$boardUpdates[] = 'override_theme = {int:override_theme}';
398
		$boardUpdateParameters['override_theme'] = $boardOptions['override_theme'] ? 1 : 0;
399 2
	}
400
401 2
	// Who's allowed to access this board.
402 2
	if (isset($boardOptions['access_groups']))
403
	{
404
		$boardUpdates[] = 'member_groups = {string:member_groups}';
405
		$boardUpdateParameters['member_groups'] = implode(',', $boardOptions['access_groups']);
406 2
	}
407
408 2
	// And who isn't.
409 2
	if (isset($boardOptions['deny_groups']))
410
	{
411
		$boardUpdates[] = 'deny_member_groups = {string:deny_groups}';
412 2
		$boardUpdateParameters['deny_groups'] = implode(',', $boardOptions['deny_groups']);
413
	}
414 2
415 2
	if (isset($boardOptions['board_name']))
416
	{
417
		$boardUpdates[] = 'name = {string:board_name}';
418 2
		$boardUpdateParameters['board_name'] = $boardOptions['board_name'];
419
	}
420 2
421 2
	if (isset($boardOptions['board_description']))
422
	{
423
		$boardUpdates[] = 'description = {string:board_description}';
424 2
		$boardUpdateParameters['board_description'] = $boardOptions['board_description'];
425
	}
426 2
427 2
	if (isset($boardOptions['profile']))
428
	{
429
		$boardUpdates[] = 'id_profile = {int:profile}';
430 2
		$boardUpdateParameters['profile'] = (int) $boardOptions['profile'];
431
	}
432 2
433 2
	if (isset($boardOptions['redirect']))
434
	{
435
		$boardUpdates[] = 'redirect = {string:redirect}';
436 2
		$boardUpdateParameters['redirect'] = $boardOptions['redirect'];
437
	}
438
439
	if (isset($boardOptions['num_posts']))
440
	{
441
		$boardUpdates[] = 'num_posts = {int:num_posts}';
442 2
		$boardUpdateParameters['num_posts'] = (int) $boardOptions['num_posts'];
443
	}
444
445 2
	call_integration_hook('integrate_modify_board', [$board_id, $boardOptions, &$boardUpdates, &$boardUpdateParameters]);
446
447 2
	// Do the updates (if any).
448
	if (!empty($boardUpdates))
449
	{
450 2
		$db->query('', '
451 2
			UPDATE {db_prefix}boards
452
			SET
453 2
				' . implode(',
454 2
				', $boardUpdates) . '
455
			WHERE id_board = {int:selected_board}',
456
			array_merge($boardUpdateParameters, [
457
				'selected_board' => $board_id,
458
			])
459
		);
460 2
	}
461
462
	// Set moderators of this board.
463 2
	if (isset($boardOptions['moderators']) || isset($boardOptions['moderator_string']))
464
	{
465
		// Reset current moderators for this board - if there are any!
466
		$db->query('', '
467 2
			DELETE FROM {db_prefix}moderators
468
			WHERE id_board = {int:board_list}',
469
			[
470
				'board_list' => $board_id,
471
			]
472 2
		);
473
474
		// Validate and get the IDs of the new moderators.
475
		if (isset($boardOptions['moderator_string']) && trim($boardOptions['moderator_string']) !== '')
476
		{
477
			// Divvy out the usernames, remove extra space.
478
			$moderator_string = strtr(Util::htmlspecialchars($boardOptions['moderator_string'], ENT_QUOTES), ['&quot;' => '"']);
479
			preg_match_all('~"([^"]+)"~', $moderator_string, $matches);
480
			$moderators = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $moderator_string)));
481
			foreach ($moderators as $k => $moderator)
482
			{
483
				$moderators[$k] = trim($moderator);
484
				if ($moderators[$k] === '')
485
				{
486
					unset($moderators[$k]);
487
				}
488
			}
489
490
			// Find all the id_member's for the member_name's in the list.
491
			if (empty($boardOptions['moderators']))
492
			{
493
				$boardOptions['moderators'] = [];
494
			}
495
496
			if (!empty($moderators))
497
			{
498
				$boardOptions['moderators'] = $db->fetchQuery('
499
					SELECT 
500
						id_member
501
					FROM {db_prefix}members
502
					WHERE member_name IN ({array_string:moderator_list}) OR real_name IN ({array_string:moderator_list})
503
					LIMIT ' . count($moderators),
504
					[
505
						'moderator_list' => $moderators,
506
					]
507
				)->fetch_callback(
508
					function ($row) {
509
						return $row['id_member'];
510
					}
511
				);
512
			}
513 2
		}
514
515
		// Add the moderators to the board.
516
		if (!empty($boardOptions['moderators']))
517
		{
518
			$inserts = [];
519
			foreach ($boardOptions['moderators'] as $moderator)
520
			{
521
				$inserts[] = [$board_id, $moderator];
522
			}
523
524
			$db->insert('insert',
525
				'{db_prefix}moderators',
526
				['id_board' => 'int', 'id_member' => 'int'],
527
				$inserts,
528
				['id_board', 'id_member']
529
			);
530 2
		}
531
532
		// Note that caches can now be wrong!
533 2
		updateSettings(['settings_updated' => time()]);
534
	}
535 2
536
	if (isset($boardOptions['move_to']))
537
	{
538 2
		$boardTree->reorderBoards();
539
	}
540 2
541
	Cache::instance()->clean('data');
542
543
	if (empty($boardOptions['dont_log']))
544 2
	{
545
		logAction('edit_board', ['board' => $board_id], 'admin');
546
	}
547
}
548
549
/**
550
 * Create a new board and set its properties and position.
551
 *
552
 * - Allows (almost) the same options as the modifyBoard() function.
553
 * - With the option inherit_permissions set, the parent board permissions
554
 * will be inherited.
555
 *
556
 * @param array $boardOptions
557
 * @return int The new board id
558
 * @package Boards
559
 */
560 2
function createBoard($boardOptions)
561
{
562
	$db = database();
563 2
564
	// Trigger an error if one of the required values is not set.
565
	if (!isset($boardOptions['board_name'], $boardOptions['move_to'], $boardOptions['target_category']) || trim($boardOptions['board_name']) === '')
566
	{
567
		trigger_error('createBoard(): One or more of the required options is not set', E_USER_ERROR);
568 2
	}
569
570
	if (in_array($boardOptions['move_to'], ['child', 'before', 'after']) && !isset($boardOptions['target_board']))
571
	{
572
		trigger_error('createBoard(): Target board is not set', E_USER_ERROR);
573
	}
574
575 2
	// Set every optional value to its default value.
576
	$boardOptions += [
577
		'posts_count' => true,
578
		'old_posts' => true,
579
		'override_theme' => false,
580
		'board_theme' => 0,
581
		'access_groups' => [],
582
		'board_description' => '',
583
		'profile' => 1,
584
		'moderators' => '',
585
		'inherit_permissions' => true,
586 2
		'dont_log' => true,
587
	];
588
	$board_columns = [
589
		'id_cat' => 'int', 'name' => 'string-255', 'description' => 'string', 'board_order' => 'int',
590 2
		'member_groups' => 'string', 'redirect' => 'string',
591 2
	];
592
	$board_parameters = [
593
		$boardOptions['target_category'], $boardOptions['board_name'], '', 0,
594
		'-1,0', '',
595 2
	];
596 2
597 1
	// Insert a board, the settings are dealt with later.
598 1
	$board_id = $db->insert('',
599 2
		'{db_prefix}boards',
600 2
		$board_columns,
601
		$board_parameters,
602 2
		['id_board']
603
	)->insert_id();
604
605
	if (empty($board_id))
606
	{
607
		return 0;
608 2
	}
609
610
	// Change the board according to the given specifications.
611 2
	modifyBoard($board_id, $boardOptions);
612
613
	// Do we want the parent permissions to be inherited?
614
	if ($boardOptions['inherit_permissions'])
615
	{
616
		$boardTree = new BoardsTree($db);
617
618
		try
619
		{
620
			$board = $boardTree->getBoardById($board_id);
621
			$board_data = fetchBoardsInfo(['boards' => $board['parent']], ['selects' => 'permissions']);
622
623
			$db->query('', '
624
				UPDATE {db_prefix}boards
625
				SET 
626
					id_profile = {int:new_profile}
627
				WHERE id_board = {int:current_board}',
628
				[
629
					'new_profile' => $board_data[$board['parent']]['id_profile'],
630
					'current_board' => $board_id,
631
				]
632
			);
633
		}
634
		catch (Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
635
		{
636
		}
637 2
	}
638
639
	// Clean the data cache.
640 2
	Cache::instance()->clean('data');
641
642
	// Created it.
643 2
	logAction('add_board', ['board' => $board_id], 'admin');
644
645
	// Here you are, a new board, ready to be spammed.
646
	return $board_id;
647
}
648
649
/**
650
 * Generates the query to determine the list of available boards for a user
651
 *
652
 * - Executes the query and returns the list
653
 *
654
 * @param array $boardListOptions
655
 * @param bool $simple if true a simple array is returned containing some basic
656
 *                information regarding the board (id_board, board_name, child_level, id_cat, cat_name)
657
 *                if false the boards are returned in an array subdivided by categories including also
658
 *                additional data like the number of boards
659
 * @return array An array of boards sorted according to the normal boards order
660
 * @package Boards
661
 */
662 6
function getBoardList($boardListOptions = [], $simple = false)
663
{
664 6
	global $modSettings;
665
666 6
	$db = database();
667
668
	if ((isset($boardListOptions['excluded_boards']) || isset($boardListOptions['allowed_to'])) && isset($boardListOptions['included_boards']))
669
	{
670
		trigger_error('getBoardList(): Setting both excluded_boards and included_boards is not allowed.', E_USER_ERROR);
671 6
	}
672 6
673 6
	$where = [];
674 6
	$join = [];
675
	$select = '';
676
	$where_parameters = [];
677 6
678
	// Any boards to exclude
679
	if (isset($boardListOptions['excluded_boards']))
680
	{
681
		$where[] = 'b.id_board NOT IN ({array_int:excluded_boards})';
682
		$where_parameters['excluded_boards'] = $boardListOptions['excluded_boards'];
683
	}
684 6
685
	// Get list of boards to which they have specific permissions
686
	if (isset($boardListOptions['allowed_to']))
687
	{
688
		$boardListOptions['included_boards'] = boardsAllowedTo($boardListOptions['allowed_to']);
689
		if (in_array(0, $boardListOptions['included_boards']))
690
		{
691
			unset($boardListOptions['included_boards']);
692
		}
693
	}
694 6
695
	// Just want to include certain boards in the query
696 2
	if (isset($boardListOptions['included_boards']))
697 2
	{
698
		$where[] = 'b.id_board IN ({array_int:included_boards})';
699
		$where_parameters['included_boards'] = $boardListOptions['included_boards'];
700
	}
701 6
702
	// Determine if they can access a given board and return yea or nay in the results array
703
	if (isset($boardListOptions['access']))
704
	{
705
		$select .= ',
706
			FIND_IN_SET({string:current_group}, b.member_groups) != 0 AS can_access,
707
			FIND_IN_SET({string:current_group}, b.deny_member_groups) != 0 AS cannot_access';
708
		$where_parameters['current_group'] = $boardListOptions['access'];
709
	}
710 6
711
	// Leave out the boards that the user may be ignoring
712
	if (isset($boardListOptions['ignore']))
713
	{
714
		$select .= ',' . (!empty($boardListOptions['ignore']) ? 'b.id_board IN ({array_int:ignore_boards})' : '0') . ' AS is_ignored';
715
		$where_parameters['ignore_boards'] = $boardListOptions['ignore'];
716
	}
717 6
718
	// Want to check if the member is a moderators for any boards
719
	if (isset($boardListOptions['moderator']))
720
	{
721
		$join[] = '
722
			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})';
723
		$select .= ', b.id_profile, b.member_groups, COALESCE(mods.id_member, 0) AS is_mod';
724
		$where_parameters['current_member'] = $boardListOptions['moderator'];
725 6
	}
726
727
	if (!empty($boardListOptions['ignore_boards']) && empty($boardListOptions['override_permissions']))
728
	{
729 6
		$where[] = '{query_wanna_see_board}';
730
	}
731 2
	elseif (empty($boardListOptions['override_permissions']))
732
	{
733
		$where[] = '{query_see_board}';
734 6
	}
735
736 4
	if (!empty($boardListOptions['not_redirection']))
737 4
	{
738
		$where[] = 'b.redirect = {string:blank_redirect}';
739
		$where_parameters['blank_redirect'] = '';
740
	}
741 6
742
	// Bring all the options together and make the query
743
	$request = $db->query('', '
744 6
		SELECT 
745
			c.name AS cat_name, c.id_cat, 
746 6
			b.id_board, b.name AS board_name, b.child_level' . $select . '
747 6
		FROM {db_prefix}boards AS b
748 6
			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)' . (empty($join) ? '' : implode(' ', $join)) . (empty($where) ? '' : '
749
		WHERE ' . implode('
750 3
			AND ', $where)) . '
751
		ORDER BY c.cat_order, b.board_order',
752
		$where_parameters
753
	);
754 6
755
	// Build our output arrays, simple or complete
756 6
	if ($simple)
757 6
	{
758
		$return_value = [];
759 6
		while (($row = $request->fetch_assoc()))
760 6
		{
761 6
			$return_value[$row['id_board']] = [
762 6
				'id_cat' => (int) $row['id_cat'],
763 6
				'cat_name' => $row['cat_name'],
764 6
				'id_board' => (int) $row['id_board'],
765
				'board_name' => $row['board_name'],
766
				'child_level' => (int) $row['child_level'],
767
			];
768 6
769
			// Do we want access information?
770
			if (isset($boardListOptions['access']) && $boardListOptions['access'] !== false)
771
			{
772
				$return_value[$row['id_board']]['allow'] = !(empty($row['can_access']) || $row['can_access'] === 'f');
773
				$return_value[$row['id_board']]['deny'] = !(empty($row['cannot_access']) || $row['cannot_access'] === 'f');
774
			}
775 6
776
			// Do we want moderation information?
777
			if (!empty($boardListOptions['moderator']))
778
			{
779
				$return_value[$row['id_board']] += [
780
					'id_profile' => (int) $row['id_profile'],
781
					'member_groups' => $row['member_groups'],
782
					'is_mod' => $row['is_mod'],
783
				];
784
			}
785
		}
786
	}
787
	else
788
	{
789
		$return_value = [
790
			'num_boards' => $request->num_rows(),
791
			'boards_check_all' => true,
792
			'boards_current_disabled' => true,
793
			'categories' => [],
794
		];
795
		while (($row = $request->fetch_assoc()))
796
		{
797
			// This category hasn't been set up yet..
798
			if (!isset($return_value['categories'][$row['id_cat']]))
799
			{
800
				$return_value['categories'][$row['id_cat']] = [
801
					'id' => (int) $row['id_cat'],
802
					'name' => $row['cat_name'],
803
					'boards' => [],
804
				];
805
			}
806
807
			// Shortcuts are useful to keep things simple
808
			$this_cat = &$return_value['categories'][$row['id_cat']];
809
810
			$this_cat['boards'][(int) $row['id_board']] = [
811
				'id' => (int) $row['id_board'],
812
				'name' => $row['board_name'],
813
				'child_level' => (int) $row['child_level'],
814
				'allow' => false,
815
				'deny' => false,
816
				'selected' => isset($boardListOptions['selected_board']) && $boardListOptions['selected_board'] == $row['id_board'],
817
			];
818
			// Do we want access information?
819
820
			if (!empty($boardListOptions['access']))
821
			{
822
				$this_cat['boards'][$row['id_board']]['allow'] = !(empty($row['can_access']) || $row['can_access'] === 'f');
823
				$this_cat['boards'][$row['id_board']]['deny'] = !(empty($row['cannot_access']) || $row['cannot_access'] === 'f');
824
			}
825
826
			// If is_ignored is set, it means we could have to deselect a board
827
			if (isset($row['is_ignored']))
828
			{
829
				$this_cat['boards'][$row['id_board']]['selected'] = $row['is_ignored'];
830
831
				// If a board wasn't checked that probably should have been ensure the board selection is selected, yo!
832
				if (!empty($this_cat['boards'][$row['id_board']]['selected']) && (empty($modSettings['recycle_enable']) || $row['id_board'] != $modSettings['recycle_board']))
833
				{
834
					$return_value['boards_check_all'] = false;
835
				}
836
			}
837
838
			// Do we want moderation information?
839
			if (!empty($boardListOptions['moderator']))
840
			{
841
				$this_cat['boards'][(int) $row['id_board']] += [
842
					'id_profile' => (int) $row['id_profile'],
843
					'member_groups' => $row['member_groups'],
844
					'is_mod' => $row['is_mod'],
845
				];
846
			}
847
		}
848 6
	}
849
850 6
	$request->free_result();
851
852
	return $return_value;
853
}
854
855
/**
856
 * Recursively get a list of boards.
857
 *
858
 * @param array $tree the board tree
859
 * @return array list of child boards id
860
 * @package Boards
861
 */
862
function recursiveBoards($tree)
863
{
864
	if (empty($tree['children']))
865
	{
866
		return [];
867
	}
868
869
	$boardsList = [];
870
	foreach ($tree['children'] as $id => $node)
871
	{
872
		$boardsList[] = $id;
873
		$boardsList = array_merge($boardsList, recursiveBoards($node));
874
	}
875
876
	return $boardsList;
877
}
878
879
/**
880
 * Returns whether this member has notification turned on for the specified board.
881
 *
882
 * @param int $id_member the member id
883
 * @param int $id_board the board to check
884
 * @return bool if they have notifications turned on for the board
885
 */
886
function hasBoardNotification($id_member, $id_board)
887
{
888
	$db = database();
889
890
	// Find out if they have notification set for this board already.
891
	$request = $db->query('', '
892
		SELECT 
893
			id_member
894
		FROM {db_prefix}log_notify
895
		WHERE id_member = {int:current_member}
896
			AND id_board = {int:current_board}
897
		LIMIT 1',
898
		[
899
			'current_board' => $id_board,
900
			'current_member' => $id_member,
901
		]
902
	);
903
	$hasNotification = $request->num_rows() !== 0;
904
	$request->free_result();
905
906
	return $hasNotification;
907
}
908
909
/**
910
 * Set board notification on or off for the given member.
911
 *
912
 * @param int $id_member
913
 * @param int $id_board
914
 * @param bool $on = false
915
 * @package Boards
916
 */
917
function setBoardNotification($id_member, $id_board, $on = false)
918
{
919
	$db = database();
920
921
	if ($on)
922
	{
923
		// Turn notification on.  (note this just blows smoke if it's already on.)
924
		$db->insert('ignore',
925
			'{db_prefix}log_notify',
926
			['id_member' => 'int', 'id_board' => 'int'],
927
			[$id_member, $id_board],
928
			['id_member', 'id_board']
929
		);
930
	}
931
	else
932
	{
933
		// Turn notification off for this board.
934
		$db->query('', '
935
			DELETE FROM {db_prefix}log_notify
936
			WHERE id_member = {int:current_member}
937
				AND id_board = {int:current_board}',
938
			[
939
				'current_board' => $id_board,
940
				'current_member' => $id_member,
941
			]
942
		);
943
	}
944
}
945
946
/**
947
 * Reset sent status for board notifications.
948
 *
949
 * This function returns a boolean equivalent with hasBoardNotification().
950
 * This is unexpected, but it's done this way to avoid any extra-query is executed on MessageIndex::action_messageindex().
951
 * Just ignore the return value for normal use.
952
 *
953
 * @param int $id_member
954
 * @param int $id_board
955
 * @param bool $check = true check if the user has notifications enabled for the board
956
 * @return bool if the board was marked for notifications
957
 * @package Boards
958
 */
959
function resetSentBoardNotification($id_member, $id_board, $check = true)
960
{
961
	$db = database();
962 2
963
	// Check if notifications are enabled for this user on the board?
964
	if ($check)
965 2
	{
966
		// check if the member has notifications enabled for this board
967
		$request = $db->query('', '
968 2
			SELECT 
969
				sent
970
			FROM {db_prefix}log_notify
971
			WHERE id_board = {int:current_board}
972
				AND id_member = {int:current_member}
973
			LIMIT 1',
974
			[
975
				'current_board' => $id_board,
976 2
				'current_member' => $id_member,
977 2
			]
978
		);
979
		// nothing to do
980
		if ($request->num_rows() === 0)
981 2
		{
982
			return false;
983 2
		}
984
		$sent = $request->fetch_row();
985
		$request->free_result();
986
987
		// not sent already? No need to stay around then
988
		if (empty($sent))
989
		{
990
			return true;
991
		}
992
	}
993
994
	// Reset 'sent' status.
995
	$db->query('', '
996
		UPDATE {db_prefix}log_notify
997
		SET 
998
			sent = {int:is_sent}
999
		WHERE id_board = {int:current_board}
1000
			AND id_member = {int:current_member}',
1001
		[
1002
			'current_board' => $id_board,
1003
			'current_member' => $id_member,
1004
			'is_sent' => 0,
1005
		]
1006
	);
1007
1008
	return true;
1009
}
1010
1011
/**
1012
 * Counts the board notification for a given member.
1013
 *
1014
 * @param int $memID
1015
 * @return int
1016
 * @package Boards
1017
 */
1018
function getBoardNotificationsCount($memID)
1019
{
1020
	$db = database();
1021
1022 2
	// All the boards that you have notification enabled
1023
	$request = $db->query('', '
1024
		SELECT 
1025 2
			COUNT(*)
1026
		FROM {db_prefix}log_notify AS ln
1027
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board)
1028
			LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
1029
		WHERE ln.id_member = {int:selected_member}
1030
			AND {query_see_board}',
1031
		[
1032
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1033
			'selected_member' => $memID,
1034 2
		]
1035 2
	);
1036
	list ($totalNotifications) = $request->fetch_row();
1037
	$request->free_result();
1038 2
1039 2
	return $totalNotifications;
1040
}
1041 2
1042
/**
1043
 * Returns all the boards accessible to the current user.
1044
 *
1045
 * - If $id_parents is given, return only the sub-boards of those boards.
1046
 * - If $id_boards is given, filters the boards to only those accessible.
1047
 * - The function doesn't guarantee the boards are properly sorted
1048
 *
1049
 * @param int[]|null $id_parents array of ints representing board ids
1050
 * @param int[]|null $id_boards
1051
 *
1052
 * @return array
1053
 * @package Boards
1054
 *
1055
 */
1056
function accessibleBoards($id_boards = null, $id_parents = null)
1057
{
1058
	$db = database();
1059
1060
	$boards = [];
1061 4
	if (!empty($id_parents))
1062
	{
1063 4
		// Find all boards down from $id_parent
1064 4
		$request = $db->query('', '
1065
			SELECT 
1066
				b.id_board
1067 2
			FROM {db_prefix}boards AS b
1068
			WHERE b.id_parent IN ({array_int:parent_list})
1069
				AND {query_see_board}',
1070
			[
1071
				'parent_list' => $id_parents,
1072
			]
1073
		);
1074 2
	}
1075
	elseif (!empty($id_boards))
1076
	{
1077
		// Find all the boards this user can see between those selected
1078 2
		$request = $db->query('', '
1079
			SELECT 
1080
				b.id_board
1081
			FROM {db_prefix}boards AS b
1082
			WHERE b.id_board IN ({array_int:board_list})
1083
				AND {query_see_board}',
1084
			[
1085
				'board_list' => $id_boards,
1086
			]
1087
		);
1088
	}
1089
	else
1090
	{
1091
		// Find all the boards this user can see.
1092
		$request = $db->query('', '
1093
			SELECT
1094
			 	b.id_board
1095 2
			FROM {db_prefix}boards AS b
1096
			WHERE {query_see_board}',
1097
			[]
1098
		);
1099
	}
1100 2
	while (($row = $request->fetch_assoc()))
1101
	{
1102
		$boards[] = (int) $row['id_board'];
1103
	}
1104 4
1105
	$request->free_result();
1106 2
1107
	return $boards;
1108 4
}
1109
1110 4
/**
1111
 * Returns the boards the current user wants to see.
1112
 *
1113
 * @param string $see_board either 'query_see_board' or 'query_wanna_see_board'
1114
 * @param bool $hide_recycle is tru the recycle bin is not returned
1115
 *
1116
 * @return array
1117
 * @package Boards
1118
 *
1119
 */
1120
function wantedBoards($see_board, $hide_recycle = true)
1121
{
1122
	global $modSettings;
1123
1124
	$db = database();
1125
	$allowed_see = [
1126
		'query_see_board',
1127
		'query_wanna_see_board'
1128
	];
1129
1130
	// Find all boards down from $id_parent
1131
	return $db->fetchQuery('
1132
		SELECT 
1133
			b.id_board
1134
		FROM {db_prefix}boards AS b
1135
		WHERE ' . User::$info->{in_array($see_board, $allowed_see) ? $see_board : $allowed_see[0]} . ($hide_recycle && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1136
			AND b.id_board != {int:recycle_board}' : ''),
1137
		[
1138
			'recycle_board' => (int) $modSettings['recycle_board'],
1139
		]
1140
	)->fetch_callback(
1141
		function ($row) {
1142
			return $row['id_board'];
1143
		}
1144
	);
1145
}
1146
1147
/**
1148
 * Returns the post count and name of a board
1149
 *
1150
 * - if supplied a topic id will also return the message subject
1151
 * - honors query_see_board to ensure a user can see the information
1152
 *
1153
 * @param int $board_id
1154
 * @param int|null $topic_id
1155
 * @return array
1156
 * @package Boards
1157
 */
1158
function boardInfo($board_id, $topic_id = null)
1159
{
1160
	$db = database();
1161
1162
	if (!empty($topic_id))
1163
	{
1164
		$request = $db->query('', '
1165 2
			SELECT 
1166
				b.count_posts, b.name, m.subject
1167 2
			FROM {db_prefix}boards AS b
1168
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
1169
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1170
			WHERE {query_see_board}
1171
				AND b.id_board = {int:board}
1172
				AND b.redirect = {string:blank_redirect}
1173
			LIMIT 1',
1174
			[
1175
				'current_topic' => $topic_id,
1176
				'board' => $board_id,
1177
				'blank_redirect' => '',
1178
			]
1179
		);
1180
	}
1181
	else
1182
	{
1183
		$request = $db->query('', '
1184
			SELECT 
1185
				b.count_posts, b.name
1186
			FROM {db_prefix}boards AS b
1187
			WHERE {query_see_board}
1188 2
				AND b.id_board = {int:board}
1189
				AND b.redirect = {string:blank_redirect}
1190
			LIMIT 1',
1191
			[
1192
				'board' => $board_id,
1193
				'blank_redirect' => '',
1194
			]
1195
		);
1196
	}
1197 2
1198 2
	$returns = $request->fetch_assoc();
1199
	$request->free_result();
1200
1201
	return $returns;
1202
}
1203 2
1204 2
/**
1205
 * Loads properties from non-standard groups
1206 2
 *
1207
 * @param int $curBoard
1208
 * @param bool $new_board = false Whether this is a new board
1209
 * @return array
1210
 * @package Boards
1211
 */
1212
function getOtherGroups($curBoard, $new_board = false)
1213
{
1214
	$db = database();
1215
1216
	$groups = [];
1217
1218
	// Load membergroups.
1219
	$db->fetchQuery('
1220
		SELECT 
1221
			group_name, id_group, min_posts
1222
		FROM {db_prefix}membergroups
1223
		WHERE id_group > {int:moderator_group} OR id_group = {int:global_moderator}
1224
		ORDER BY min_posts, id_group != {int:global_moderator}, group_name',
1225
		[
1226
			'moderator_group' => 3,
1227
			'global_moderator' => 2,
1228
		]
1229
	)->fetch_callback(
1230
		function ($row) use (&$new_board, &$groups, &$curBoard) {
1231
			if ($new_board && $row['min_posts'] == -1)
1232
			{
1233
				$curBoard['member_groups'][] = $row['id_group'];
1234
			}
1235
1236
			$groups[(int) $row['id_group']] = [
1237
				'id' => $row['id_group'],
1238
				'name' => trim($row['group_name']),
1239
				'allow' => in_array($row['id_group'], $curBoard['member_groups']),
1240
				'deny' => in_array($row['id_group'], $curBoard['deny_groups']),
1241
				'is_post_group' => $row['min_posts'] != -1,
1242
			];
1243
		}
1244
	);
1245
1246
	return $groups;
1247
}
1248
1249
/**
1250
 * Get a list of moderators from a specific board
1251
 *
1252
 * @param int $idboard
1253
 * @param bool $only_id return only the id of the moderators instead of id and name (default false)
1254
 * @return array
1255
 * @package Boards
1256
 */
1257
function getBoardModerators($idboard, $only_id = false)
1258
{
1259
	$db = database();
1260
1261
	$moderators = [];
1262
1263
	if ($only_id)
1264
	{
1265
		$db->fetchQuery('
1266 2
			SELECT 
1267
				id_member
1268 2
			FROM {db_prefix}moderators
1269
			WHERE id_board = {int:current_board}',
1270 2
			[
1271
				'current_board' => $idboard,
1272 2
			]
1273
		)->fetch_callback(
1274
			function ($row) use (&$moderators) {
1275
				$moderators[] = $row['id_member'];
1276
			}
1277
		);
1278 2
	}
1279
	else
1280 2
	{
1281
		$db->fetchQuery('
1282
			SELECT mem.id_member, mem.real_name
1283 2
			FROM {db_prefix}moderators AS mods
1284
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1285
			WHERE mods.id_board = {int:current_board}',
1286
			[
1287
				'current_board' => $idboard,
1288
			]
1289
		)->fetch_callback(
1290
			function ($row) use (&$moderators) {
1291
				$moderators[$row['id_member']] = $row['real_name'];
1292
			}
1293
		);
1294
	}
1295
1296
	return $moderators;
1297
}
1298
1299
/**
1300
 * Get a list of all the board moderators (every board)
1301
 *
1302
 * @param bool $only_id return array with key of id_member of the moderator(s)
1303 2
 * otherwise array with key of id_board id (default false)
1304
 * @return array
1305
 * @package Boards
1306
 */
1307
function allBoardModerators($only_id = false)
1308
{
1309
	$db = database();
1310
1311
	$moderators = [];
1312
1313
	if ($only_id)
1314
	{
1315
		$request = $db->query('', '
1316
			SELECT 
1317
				id_board, id_member
1318
			FROM {db_prefix}moderators',
1319
			[]
1320
		);
1321
	}
1322
	else
1323
	{
1324
		$request = $db->query('', '
1325
			SELECT 
1326
				mods.id_board, mods.id_member, mem.real_name
1327
			FROM {db_prefix}moderators AS mods
1328
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)',
1329
			[]
1330
		);
1331
	}
1332
1333
	while (($row = $request->fetch_assoc()))
1334
	{
1335
		if ($only_id)
1336
		{
1337
			$moderators[$row['id_member']][] = $row;
1338
		}
1339
		else
1340
		{
1341
			$moderators[$row['id_board']][] = $row;
1342
		}
1343
	}
1344
	$request->free_result();
1345
1346
	return $moderators;
1347
}
1348
1349
/**
1350
 * Get a list of all the board moderated by a certain user
1351
 *
1352
 * @param int $id_member the id of a member
1353
 * @return array
1354
 * @package Boards
1355
 */
1356
function boardsModerated($id_member)
1357
{
1358
	$db = database();
1359
1360
	return $db->fetchQuery('
1361
		SELECT 
1362
			id_board
1363
		FROM {db_prefix}moderators
1364
		WHERE id_member = {int:current_member}',
1365
		[
1366
			'current_member' => $id_member,
1367
		]
1368
	)->fetch_callback(
1369
		function ($row) {
1370
			return $row['id_board'];
1371
		}
1372
	);
1373
}
1374
1375
/**
1376
 * Get all available themes
1377
 *
1378
 * @return array
1379
 * @package Boards
1380
 */
1381
function getAllThemes()
1382
{
1383
	$db = database();
1384
1385
	// Get all the themes...
1386
	return $db->fetchQuery('
1387
		SELECT 
1388
			id_theme AS id, value AS name
1389
		FROM {db_prefix}themes
1390
		WHERE variable = {string:name}',
1391
		[
1392
			'name' => 'name',
1393
		]
1394
	)->fetch_all();
1395
}
1396
1397
/**
1398
 * Gets redirect infos and post count from a selected board.
1399
 *
1400
 * @param int $idboard
1401
 * @return array
1402
 * @package Boards
1403
 */
1404
function getBoardProperties($idboard)
1405
{
1406
	$db = database();
1407
1408
	$properties = [];
1409
1410
	$request = $db->query('', '
1411
		SELECT 
1412
			redirect, num_posts
1413
		FROM {db_prefix}boards
1414
		WHERE id_board = {int:current_board}',
1415
		[
1416
			'current_board' => $idboard,
1417
		]
1418
	);
1419
	list ($properties['oldRedirect'], $properties['numPosts']) = $request->fetch_row();
1420
	$request->free_result();
1421
1422
	return $properties;
1423
}
1424
1425
/**
1426
 * Fetch the number of posts in an array of boards based on board IDs or category IDs
1427
 *
1428
 * @param int[]|null $boards an array of board IDs
1429
 * @param int[]|null $categories an array of category IDs
1430
 * @param bool $wanna_see_board if true uses {query_wanna_see_board}, otherwise {query_see_board}
1431
 * @param bool $include_recycle if false excludes any results from the recycle board (if enabled)
1432
 *
1433
 * @return array
1434
 * @package Boards
1435
 *
1436
 */
1437
function boardsPosts($boards, $categories, $wanna_see_board = false, $include_recycle = true)
1438
{
1439
	global $modSettings;
1440
1441
	$db = database();
1442
1443
	$clauses = [];
1444
	$removals = [];
1445
	$clauseParameters = [];
1446
1447
	if (!empty($categories))
1448
	{
1449
		$clauses[] = 'id_cat IN ({array_int:category_list})';
1450
		$clauseParameters['category_list'] = $categories;
1451 6
	}
1452
1453 6
	if (!empty($boards))
1454
	{
1455 6
		$clauses[] = 'id_board IN ({array_int:board_list})';
1456 6
		$clauseParameters['board_list'] = $boards;
1457 6
	}
1458
1459 6
	if (empty($include_recycle) && (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0))
1460
	{
1461 4
		$removals[] = 'id_board != {int:recycle_board}';
1462 4
		$clauseParameters['recycle_board'] = (int) $modSettings['recycle_board'];
1463
	}
1464
1465 6
	if (empty($clauses))
1466
	{
1467 4
		return [];
1468 4
	}
1469
1470
	$return = [];
1471 6
	$db->fetchQuery('
1472
		SELECT 
1473
			b.id_board, b.num_posts
1474
		FROM {db_prefix}boards AS b
1475
		WHERE ' . ($wanna_see_board ? '{query_wanna_see_board}' : '{query_see_board}') . '
1476
			AND b.' . implode(' OR b.', $clauses) . (!empty($removals) ? '
1477 6
			AND b.' . implode(' AND b.', $removals) : ''),
1478
		$clauseParameters
1479
	)->fetch_callback(
1480
		function ($row) use (&$return) {
1481
			$return[$row['id_board']] = $row['num_posts'];
1482 6
		}
1483 6
	);
1484
1485
	return $return;
1486
}
1487 6
1488 6
/**
1489 6
 * Returns the total sum of posts in the boards defined by query_wanna_see_board
1490 3
 * Excludes the count of any boards defined as a recycle board from the sum
1491 6
 */
1492
function sumRecentPosts()
1493 6
{
1494 6
	$db = database();
1495
1496
	global $modSettings;
1497 6
1498
	$request = $db->query('', '
1499
		SELECT 
1500
			COALESCE(SUM(num_posts), 0)
1501
		FROM {db_prefix}boards as b
1502
		WHERE {query_wanna_see_board}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1503
			AND b.id_board != {int:recycle_board}' : ''),
1504
		[
1505
			'recycle_board' => $modSettings['recycle_board']
1506
		]
1507
	);
1508
	list ($result) = $request->fetch_row();
1509
	$request->free_result();
1510
1511
	return $result;
1512
}
1513
1514
/**
1515
 * Returns information of a set of boards based on board IDs or category IDs
1516
 *
1517
 * @param array|string $conditions is an associative array that holds the board or the cat IDs
1518
 *              'categories' => an array of category IDs (it accepts a single ID too)
1519
 *              'boards' => an array of board IDs (it accepts a single ID too)
1520
 *              if conditions is set to 'all' (not an array) all the boards are queried
1521
 * @param array $params is an optional array that allows to control the results returned:
1522
 *              'sort_by' => (string) defines the sorting of the results (allowed: id_board, name)
1523
 *              'selects' => (string) determines what information are retrieved and returned
1524
 *                           Allowed values: 'name', 'posts', 'detailed', 'permissions', 'reports';
1525
 *                           default: 'name';
1526
 *                           see the function for details on the fields associated to each value
1527
 *              'override_permissions' => (bool) if true doesn't use neither {query_wanna_see_board} nor
1528
 *     {query_see_board} (default false)
1529
 *              'wanna_see_board' => (bool) if true uses {query_wanna_see_board}, otherwise {query_see_board}
1530
 *              'include_recycle' => (bool) recycle board is included (default true)
1531
 *              'include_redirects' => (bool) redirects are included (default true)
1532
 *
1533
 * @return array
1534
 * @package Boards
1535
 *
1536
 * @todo unify the two queries?
1537
 */
1538
function fetchBoardsInfo($conditions = 'all', $params = [])
1539
{
1540
	global $modSettings;
1541
1542
	$db = database();
1543
1544
	// Ensure default values are set
1545
	$params = array_merge(['override_permissions' => false, 'wanna_see_board' => false, 'include_recycle' => true, 'include_redirects' => true], $params);
1546
1547
	$clauses = [];
1548
	$clauseParameters = [];
1549
	$allowed_sort = [
1550
		'id_board',
1551
		'name'
1552
	];
1553 4
1554
	if (!empty($params['sort_by']) && in_array($params['sort_by'], $allowed_sort))
1555 4
	{
1556
		$sort_by = 'ORDER BY ' . $params['sort_by'];
1557
	}
1558 4
	else
1559
	{
1560 4
		$sort_by = '';
1561 4
	}
1562
1563 4
	// @todo: memos for optimization
1564
	/*
1565
		id_board    => MergeTopic + MergeTopic + MessageIndex + Search + ScheduledTasks
1566
		name        => MergeTopic + ScheduledTasks + News
1567 4
		count_posts => MessageIndex
1568
		num_posts   => News
1569
	*/
1570
	$known_selects = [
1571
		'name' => 'b.id_board, b.name',
1572
		'posts' => 'b.id_board, b.count_posts, b.old_posts, b.num_posts',
1573 4
		'detailed' => 'b.id_board, b.name, b.count_posts, b.old_posts, b.num_posts',
1574
		'permissions' => 'b.id_board, b.name, b.member_groups, b.id_profile',
1575
		'reports' => 'b.id_board, b.name, b.member_groups, b.id_profile, b.deny_member_groups',
1576
	];
1577
1578
	$select = $known_selects[empty($params['selects']) || !isset($known_selects[$params['selects']]) ? 'name' : $params['selects']];
1579
1580
	// If $conditions wasn't set or is 'all', get all boards
1581
	if (!is_array($conditions) && $conditions === 'all')
1582
	{
1583
		// id_board, name, id_profile => used in admin/Reports.controller.php
1584 4
		$request = $db->query('', '
1585
			SELECT ' . $select . '
1586
			FROM {db_prefix}boards AS b
1587
			' . $sort_by,
1588
			[]
1589
		);
1590
	}
1591 4
	else
1592
	{
1593
		// Only some categories?
1594 4
		if (!empty($conditions['categories']))
1595
		{
1596
			$clauses[] = 'id_cat IN ({array_int:category_list})';
1597
			$clauseParameters['category_list'] = is_array($conditions['categories']) ? $conditions['categories'] : [$conditions['categories']];
1598
		}
1599
1600
		// Only a few boards, perhaps!
1601
		if (!empty($conditions['boards']))
1602
		{
1603
			$clauses[] = 'id_board IN ({array_int:board_list})';
1604
			$clauseParameters['board_list'] = is_array($conditions['boards']) ? $conditions['boards'] : [$conditions['boards']];
1605
		}
1606
1607 4
		if ($params['override_permissions'])
1608
		{
1609
			$security = '1=1';
1610
		}
1611
		else
1612
		{
1613
			$security = $params['wanna_see_board'] ? '{query_wanna_see_board}' : '{query_see_board}';
1614 4
		}
1615
1616 2
		// Prevent a bad query, from a triggered scheduled task or some other issue.
1617 2
		if (empty($security))
1618
		{
1619
			return [];
1620 4
		}
1621
1622
		$request = $db->query('', '
1623
			SELECT ' . $select . '
1624
			FROM {db_prefix}boards AS b
1625
			WHERE ' . $security . (!empty($clauses) ? '
1626 4
				AND b.' . implode(' OR b.', $clauses) : '') . ($params['include_recycle'] ? '' : '
1627
				AND b.id_board != {int:recycle_board}') . ($params['include_redirects'] ? '' : '
1628
				AND b.redirect = {string:empty_string}
1629 4
			' . $sort_by),
1630 4
			array_merge($clauseParameters, [
1631
				'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
1632 4
				'empty_string' => '',
1633 4
			])
1634 4
		);
1635
	}
1636 4
	$return = [];
1637 4
	while (($row = $request->fetch_assoc()))
1638 4
	{
1639 4
		$return[$row['id_board']] = $row;
1640
	}
1641
	$request->free_result();
1642
1643 4
	return $return;
1644 4
}
1645
1646 4
/**
1647
 * Retrieve the all the sub-boards of an array of boards and add the ids to the same array
1648 4
 *
1649
 * @param int[]|int $boards an array of board IDs (it accepts a single board too).
1650 4
 * NOTE: the $boards param is deprecated since 1.1 - The param is passed by ref in 1.0 and the result
1651
 * is returned through the param itself, starting from 1.1 the expected behaviour
1652
 * is that the result is returned.
1653
 * @return bool|int[]
1654
 * @package Boards
1655
 */
1656
function addChildBoards($boards)
1657
{
1658
	$db = database();
1659
1660
	if (empty($boards))
1661
	{
1662
		return false;
1663
	}
1664
1665
	if (!is_array($boards))
1666 2
	{
1667
		$boards = [$boards];
1668 2
	}
1669
1670
	$db->fetchQuery('
1671
		SELECT
1672
			b.id_board, b.id_parent
1673 2
		FROM {db_prefix}boards AS b
1674
		WHERE {query_see_board}
1675
			AND b.child_level > {int:no_parents}
1676
			AND b.id_board NOT IN ({array_int:board_list})
1677
		ORDER BY child_level ASC
1678 2
		',
1679
		[
1680
			'no_parents' => 0,
1681
			'board_list' => $boards,
1682
		]
1683
	)->fetch_callback(
1684
		function ($row) use (&$boards) {
1685
			if (in_array($row['id_parent'], $boards))
1686
			{
1687
				$boards[] = $row['id_board'];
1688 2
			}
1689 2
		}
1690
	);
1691 2
1692
	return $boards;
1693
}
1694
1695
/**
1696
 * Increment a board stat field, for example num_posts.
1697 2
 *
1698
 * @param int $id_board
1699
 * @param array|string $values an array of index => value of a string representing the index to increment
1700 2
 * @package Boards
1701
 */
1702
function incrementBoard($id_board, $values)
1703
{
1704
	$db = database();
1705
1706
	$knownInts = [
1707
		'child_level', 'board_order', 'num_topics', 'num_posts', 'count_posts',
1708
		'unapproved_posts', 'unapproved_topics'
1709
	];
1710
1711
	call_integration_hook('integrate_board_fields', [&$knownInts]);
1712
1713
	$set = [];
1714
	$params = ['id_board' => $id_board];
1715
	$values = is_array($values) ? $values : [$values => 1];
1716
1717
	foreach ($values as $key => $val)
1718
	{
1719
		if (in_array($key, $knownInts))
1720
		{
1721
			$set[] = $key . ' = ' . $key . ' + {int:' . $key . '}';
1722
			$params[$key] = $val;
1723
		}
1724
	}
1725
1726
	if (empty($set))
1727
	{
1728
		return;
1729
	}
1730
1731
	$db->query('', '
1732
		UPDATE {db_prefix}boards
1733
		SET
1734
			' . implode(',
1735
			', $set) . '
1736
		WHERE id_board = {int:id_board}',
1737
		$params
1738
	);
1739
}
1740
1741
/**
1742
 * Decrement a board stat field, for example num_posts.
1743
 *
1744
 * @param int $id_board
1745
 * @param array|string $values an array of index => value of a string representing the index to decrement
1746
 * @package Boards
1747
 */
1748
function decrementBoard($id_board, $values)
1749
{
1750
	$db = database();
1751
1752
	$knownInts = [
1753
		'child_level', 'board_order', 'num_topics', 'num_posts', 'count_posts',
1754
		'unapproved_posts', 'unapproved_topics'
1755
	];
1756
1757
	call_integration_hook('integrate_board_fields', [&$knownInts]);
1758
1759
	$set = [];
1760
	$params = ['id_board' => $id_board];
1761
	$values = is_array($values) ? $values : [$values => 1];
1762
1763
	foreach ($values as $key => $val)
1764
	{
1765
		if (in_array($key, $knownInts))
1766
		{
1767
			$set[] = $key . ' = CASE WHEN {int:' . $key . '} > ' . $key . ' THEN 0 ELSE ' . $key . ' - {int:' . $key . '} END';
1768
			$params[$key] = $val;
1769
		}
1770
	}
1771
1772
	if (empty($set))
1773
	{
1774
		return;
1775
	}
1776
1777
	$db->query('', '
1778
		UPDATE {db_prefix}boards
1779
		SET
1780
			' . implode(',
1781
			', $set) . '
1782
		WHERE id_board = {int:id_board}',
1783
		$params
1784
	);
1785
}
1786
1787
/**
1788
 * Retrieve all the boards the user can see and their notification status:
1789
 *
1790
 * - if they're subscribed to notifications for new topics in each of them
1791
 * or they're not.
1792
 * - (used by createList() callbacks)
1793
 *
1794
 * @param string $sort A string indicating how to sort the results
1795
 * @param int $memID id_member
1796
 *
1797
 * @return array
1798
 * @package Boards
1799
 *
1800
 */
1801
function boardNotifications($sort, $memID)
1802
{
1803
	global $modSettings;
1804
1805
	$db = database();
1806
1807
	// All the boards that you have notification enabled
1808
	$notification_boards = $db->fetchQuery('
1809
		SELECT 
1810
			b.id_board, b.name, COALESCE(lb.id_msg, 0) AS board_read, b.id_msg_updated
1811
		FROM {db_prefix}log_notify AS ln
1812
		INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board)
1813
		LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
1814 2
		WHERE ln.id_member = {int:selected_member}
1815
			AND {query_see_board}
1816 2
		ORDER BY ' . $sort,
1817
		[
1818
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1819 2
			'selected_member' => $memID,
1820
		]
1821
	)->fetch_callback(
1822
		function ($row) {
1823
			$href = getUrl('board', ['board' => $row['id_board'], 'start' => '0', 'name' => $row['name']]);
1824
1825
			return [
1826
				'id' => $row['id_board'],
1827 2
				'name' => $row['name'],
1828
				'href' => $href,
1829 2
				'link' => '<a href="' . $href . '"><strong>' . $row['name'] . '</strong></a>',
1830 2
				'new' => $row['board_read'] < $row['id_msg_updated'],
1831
				'checked' => 'checked="checked"',
1832 2
			];
1833
		}
1834
	);
1835
1836
	// and all the boards that you can see but don't have notify turned on for
1837
	$db->fetchQuery('
1838
		SELECT 
1839
			b.id_board, b.name, COALESCE(lb.id_msg, 0) AS board_read, b.id_msg_updated
1840
		FROM {db_prefix}boards AS b
1841
			LEFT JOIN {db_prefix}log_notify AS ln ON (ln.id_board = b.id_board AND ln.id_member = {int:selected_member})
1842
			LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
1843
		WHERE {query_see_board}
1844 2
			AND ln.id_board is null ' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
1845
			AND b.id_board != {int:recycle_board}' : '') . '
1846
		ORDER BY ' . $sort,
1847
		[
1848 2
			'selected_member' => $memID,
1849
			'current_member' => User::$info->id,
1850
			'recycle_board' => $modSettings['recycle_board'],
1851
		]
1852
	)->fetch_callback(
1853
		function ($row) use (&$notification_boards) {
1854
			$href = getUrl('board', ['board' => $row['id_board'], 'start' => '0', 'name' => $row['name']]);
1855 2
			$notification_boards[] = [
1856 2
				'id' => $row['id_board'],
1857 2
				'name' => $row['name'],
1858
				'href' => $href,
1859 2
				'link' => '<a href="' . $href . '">' . $row['name'] . '</a>',
1860 2
				'new' => $row['board_read'] < $row['id_msg_updated'],
1861 2
				'checked' => '',
1862
			];
1863 2
		}
1864
	);
1865 2
1866 2
	return $notification_boards;
1867 2
}
1868 2
1869 2
/**
1870 2
 * Count boards all or specific depending on argument, redirect boards excluded by default.
1871 2
 *
1872 2
 * @param array|string $conditions is an associative array that holds the board or the cat IDs
1873
 *              'categories' => an array of category IDs (it accepts a single ID too)
1874 2
 *              'boards' => an array of board IDs (it accepts a single ID too)
1875
 *              if conditions is set to 'all' (not an array) all the boards are queried
1876
 * @param array|null $params is an optional array that allows to control the results returned if $conditions is not set to 'all':
1877 2
 *              'wanna_see_board' => (bool) if true uses {query_wanna_see_board}, otherwise {query_see_board}
1878
 *              'include_recycle' => (bool) recycle board is included (default true)
1879
 *              'include_redirects' => (bool) redirects are included (default true)
1880
 * @return int
1881
 * @package Boards
1882
 */
1883
function countBoards($conditions = 'all', $params = [])
1884
{
1885
	global $modSettings;
1886
1887
	$db = database();
1888
1889
	// Ensure default values are set
1890
	$params = array_merge(['wanna_see_board' => false, 'include_recycle' => true, 'include_redirects' => true], $params);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type null; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1890
	$params = array_merge(['wanna_see_board' => false, 'include_recycle' => true, 'include_redirects' => true], /** @scrutinizer ignore-type */ $params);
Loading history...
1891
1892
	$clauses = [];
1893
	$clauseParameters = [];
1894
1895
	// if $conditions wasn't set or is 'all', get all boards
1896
	if (!is_array($conditions) && $conditions === 'all')
1897 2
	{
1898
		// id_board, name, id_profile => used in admin/Reports.controller.php
1899 2
		$request = $db->query('', '
1900
			SELECT 
1901
				COUNT(*)
1902 2
			FROM {db_prefix}boards AS b',
1903
			[]
1904 2
		);
1905 2
	}
1906
	else
1907
	{
1908 2
		// only some categories?
1909
		if (!empty($conditions['categories']))
1910
		{
1911 2
			$clauses[] = 'id_cat IN ({array_int:category_list})';
1912
			$clauseParameters['category_list'] = is_array($conditions['categories']) ? $conditions['categories'] : [$conditions['categories']];
1913
		}
1914
1915 2
		// only a few boards, perhaps!
1916
		if (!empty($conditions['boards']))
1917
		{
1918
			$clauses[] = 'id_board IN ({array_int:board_list})';
1919
			$clauseParameters['board_list'] = is_array($conditions['boards']) ? $conditions['boards'] : [$conditions['boards']];
1920
		}
1921
1922
		$request = $db->query('', '
1923
			SELECT 
1924
				COUNT(*)
1925
			FROM {db_prefix}boards AS b
1926
			WHERE ' . ($params['wanna_see_board'] ? '{query_wanna_see_board}' : '{query_see_board}') . (!empty($clauses) ? '
1927
				AND b.' . implode(' OR b.', $clauses) : '') . ($params['include_recycle'] ? '' : '
1928
				AND b.id_board != {int:recycle_board}') . ($params['include_redirects'] ? '' : '
1929
				AND b.redirect = {string:empty_string}'),
1930
			array_merge($clauseParameters, [
1931
				'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
1932
				'empty_string' => '',
1933
			])
1934
		);
1935
	}
1936
1937
	list ($num_boards) = $request->fetch_row();
1938
	$request->free_result();
1939
1940
	return $num_boards;
1941
}
1942