Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.51% covered (warning)
65.51%
963 / 1470
20.93% covered (danger)
20.93%
9 / 43
CRAP
n/a
0 / 0
user_get_id_name
81.48% covered (warning)
81.48%
22 / 27
0.00% covered (danger)
0.00%
0 / 1
15.24
update_last_username
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
user_update_name
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
user_add
81.89% covered (warning)
81.89%
104 / 127
0.00% covered (danger)
0.00%
0 / 1
24.87
user_delete
95.14% covered (success)
95.14%
176 / 185
0.00% covered (danger)
0.00%
0 / 1
34
user_active_flip
95.45% covered (success)
95.45%
42 / 44
0.00% covered (danger)
0.00%
0 / 1
18
user_ban
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
110
user_unban
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
user_ipwhois
44.44% covered (danger)
44.44%
12 / 27
0.00% covered (danger)
0.00%
0 / 1
22.89
validate_data
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
8
validate_string
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
7
validate_num
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
validate_date
50.00% covered (danger)
50.00%
7 / 14
0.00% covered (danger)
0.00%
0 / 1
22.50
validate_match
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
validate_language_iso_name
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
phpbb_validate_timezone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
validate_username
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
21
validate_password
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
9
phpbb_validate_email
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
6.97
validate_user_email
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
8
validate_jabber
97.69% covered (success)
97.69%
127 / 130
0.00% covered (danger)
0.00%
0 / 1
20
phpbb_validate_hex_colour
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
phpbb_style_is_active
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
avatar_delete
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
get_avatar_filename
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
phpbb_avatar_explanation_string
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
group_create
48.25% covered (danger)
48.25%
55 / 114
0.00% covered (danger)
0.00%
0 / 1
367.39
group_correct_avatar
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
avatar_remove_db
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
group_delete
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
42
group_user_add
84.51% covered (warning)
84.51%
60 / 71
0.00% covered (danger)
0.00%
0 / 1
19.20
group_user_del
0.00% covered (danger)
0.00%
0 / 75
0.00% covered (danger)
0.00%
0 / 1
342
remove_default_avatar
78.95% covered (warning)
78.95%
15 / 19
0.00% covered (danger)
0.00%
0 / 1
4.15
remove_default_rank
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
4.13
group_user_attributes
79.12% covered (warning)
79.12%
72 / 91
0.00% covered (danger)
0.00%
0 / 1
22.29
group_validate_groupname
47.83% covered (danger)
47.83%
11 / 23
0.00% covered (danger)
0.00%
0 / 1
8.55
group_set_user_default
95.83% covered (success)
95.83%
69 / 72
0.00% covered (danger)
0.00%
0 / 1
17
get_group_name
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
2.00
group_memberships
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
210
group_update_listings
14.81% covered (danger)
14.81%
4 / 27
0.00% covered (danger)
0.00%
0 / 1
154.08
remove_newly_registered
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
phpbb_get_banned_user_ids
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
72
phpbb_module_zebra
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3*
4* This file is part of 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* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14use phpbb\messenger\method\messenger_interface;
15
16/**
17* @ignore
18*/
19if (!defined('IN_PHPBB'))
20{
21    exit;
22}
23
24/**
25* Obtain user_ids from usernames or vice versa. Returns false on
26* success else the error string
27*
28* @param array &$user_id_ary The user ids to check or empty if usernames used
29* @param array &$username_ary The usernames to check or empty if user ids used
30* @param mixed $user_type Array of user types to check, false if not restricting by user type
31* @param boolean $update_references If false, the supplied array is unset and appears unchanged from where it was called
32* @return boolean|string Returns false on success, error string on failure
33*/
34function user_get_id_name(&$user_id_ary, &$username_ary, $user_type = false, $update_references = false)
35{
36    global $db;
37
38    // Are both arrays already filled? Yep, return else
39    // are neither array filled?
40    if ($user_id_ary && $username_ary)
41    {
42        return false;
43    }
44    else if (!$user_id_ary && !$username_ary)
45    {
46        return 'NO_USERS';
47    }
48
49    $which_ary = ($user_id_ary) ? 'user_id_ary' : 'username_ary';
50
51    if (${$which_ary} && !is_array(${$which_ary}))
52    {
53        ${$which_ary} = array(${$which_ary});
54    }
55
56    $sql_in = ($which_ary == 'user_id_ary') ? array_map('intval', ${$which_ary}) : array_map('utf8_clean_string', ${$which_ary});
57
58    // By unsetting the array here, the values passed in at the point user_get_id_name() was called will be retained.
59    // Otherwise, if we don't unset (as the array was passed by reference) the original array will be updated below.
60    if ($update_references === false)
61    {
62        unset(${$which_ary});
63    }
64
65    $user_id_ary = $username_ary = array();
66
67    // Grab the user id/username records
68    $sql_where = ($which_ary == 'user_id_ary') ? 'user_id' : 'username_clean';
69    $sql = 'SELECT user_id, username
70        FROM ' . USERS_TABLE . '
71        WHERE ' . $db->sql_in_set($sql_where, $sql_in);
72
73    if ($user_type !== false && !empty($user_type))
74    {
75        $sql .= ' AND ' . $db->sql_in_set('user_type', $user_type);
76    }
77
78    $result = $db->sql_query($sql);
79
80    if (!($row = $db->sql_fetchrow($result)))
81    {
82        $db->sql_freeresult($result);
83        return 'NO_USERS';
84    }
85
86    do
87    {
88        $username_ary[$row['user_id']] = $row['username'];
89        $user_id_ary[] = $row['user_id'];
90    }
91    while ($row = $db->sql_fetchrow($result));
92    $db->sql_freeresult($result);
93
94    return false;
95}
96
97/**
98* Get latest registered username and update database to reflect it
99*/
100function update_last_username()
101{
102    global $config, $db;
103
104    // Get latest username
105    $sql = 'SELECT user_id, username, user_colour
106        FROM ' . USERS_TABLE . '
107        WHERE user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')
108        ORDER BY user_id DESC';
109    $result = $db->sql_query_limit($sql, 1);
110    $row = $db->sql_fetchrow($result);
111    $db->sql_freeresult($result);
112
113    if ($row)
114    {
115        $config->set('newest_user_id', $row['user_id'], false);
116        $config->set('newest_username', $row['username'], false);
117        $config->set('newest_user_colour', $row['user_colour'], false);
118    }
119}
120
121/**
122* Updates a username across all relevant tables/fields
123*
124* @param string $old_name the old/current username
125* @param string $new_name the new username
126*/
127function user_update_name($old_name, $new_name)
128{
129    global $config, $db, $cache, $phpbb_dispatcher;
130
131    $update_ary = array(
132        FORUMS_TABLE            => array(
133            'forum_last_poster_id'    => 'forum_last_poster_name',
134        ),
135        MODERATOR_CACHE_TABLE    => array(
136            'user_id'    => 'username',
137        ),
138        POSTS_TABLE                => array(
139            'poster_id'    => 'post_username',
140        ),
141        TOPICS_TABLE            => array(
142            'topic_poster'            => 'topic_first_poster_name',
143            'topic_last_poster_id'    => 'topic_last_poster_name',
144        ),
145    );
146
147    foreach ($update_ary as $table => $field_ary)
148    {
149        foreach ($field_ary as $id_field => $name_field)
150        {
151            $sql = "UPDATE $table
152                SET $name_field = '" . $db->sql_escape($new_name) . "'
153                WHERE $name_field = '" . $db->sql_escape($old_name) . "'
154                    AND $id_field <> " . ANONYMOUS;
155            $db->sql_query($sql);
156        }
157    }
158
159    if ($config['newest_username'] == $old_name)
160    {
161        $config->set('newest_username', $new_name, false);
162    }
163
164    /**
165    * Update a username when it is changed
166    *
167    * @event core.update_username
168    * @var    string    old_name    The old username that is replaced
169    * @var    string    new_name    The new username
170    * @since 3.1.0-a1
171    */
172    $vars = array('old_name', 'new_name');
173    extract($phpbb_dispatcher->trigger_event('core.update_username', compact($vars)));
174
175    // Because some tables/caches use username-specific data we need to purge this here.
176    $cache->destroy('sql', MODERATOR_CACHE_TABLE);
177}
178
179/**
180* Adds an user
181*
182* @param mixed $user_row An array containing the following keys (and the appropriate values): username, group_id (the group to place the user in), user_email and the user_type(usually 0). Additional entries not overridden by defaults will be forwarded.
183* @param array $cp_data custom profile fields, see custom_profile::build_insert_sql_array
184* @param array $notifications_data The notifications settings for the new user
185* @return int  The new user's ID.
186*/
187function user_add($user_row, $cp_data = false, $notifications_data = null)
188{
189    global $db, $config;
190    global $phpbb_dispatcher, $phpbb_container;
191
192    if (empty($user_row['username']) || !isset($user_row['group_id']) || !isset($user_row['user_email']) || !isset($user_row['user_type']))
193    {
194        return false;
195    }
196
197    $username_clean = utf8_clean_string($user_row['username']);
198
199    if (empty($username_clean))
200    {
201        return false;
202    }
203
204    $sql_ary = array(
205        'username'            => $user_row['username'],
206        'username_clean'    => $username_clean,
207        'user_password'        => (isset($user_row['user_password'])) ? $user_row['user_password'] : '',
208        'user_email'        => strtolower($user_row['user_email']),
209        'group_id'            => $user_row['group_id'],
210        'user_type'            => $user_row['user_type'],
211    );
212
213    // These are the additional vars able to be specified
214    $additional_vars = array(
215        'user_permissions'            => '',
216        'user_timezone'                => $config['board_timezone'],
217        'user_dateformat'            => $config['default_dateformat'],
218        'user_lang'                    => $config['default_lang'],
219        'user_style'                => (int) $config['default_style'],
220        'user_actkey'                => '',
221        'user_ip'                    => '',
222        'user_regdate'                => time(),
223        'user_passchg'                => time(),
224        'user_options'                => 230271,
225        // We do not set the new flag here - registration scripts need to specify it
226        'user_new'                    => 0,
227
228        'user_inactive_reason'    => 0,
229        'user_inactive_time'    => 0,
230        'user_lastmark'            => time(),
231        'user_lastvisit'        => 0,
232        'user_lastpost_time'    => 0,
233        'user_lastpage'            => '',
234        'user_posts'            => 0,
235        'user_colour'            => '',
236        'user_avatar'            => '',
237        'user_avatar_type'        => '',
238        'user_avatar_width'        => 0,
239        'user_avatar_height'    => 0,
240        'user_new_privmsg'        => 0,
241        'user_unread_privmsg'    => 0,
242        'user_last_privmsg'        => 0,
243        'user_message_rules'    => 0,
244        'user_full_folder'        => PRIVMSGS_NO_BOX,
245        'user_emailtime'        => 0,
246
247        'user_notify'            => 0,
248        'user_notify_pm'        => 1,
249        'user_notify_type'        => messenger_interface::NOTIFY_EMAIL,
250        'user_allow_pm'            => 1,
251        'user_allow_viewonline'    => 1,
252        'user_allow_viewemail'    => 1,
253        'user_allow_massemail'    => 1,
254
255        'user_sig'                    => '',
256        'user_sig_bbcode_uid'        => '',
257        'user_sig_bbcode_bitfield'    => '',
258
259        'user_form_salt'            => unique_id(),
260    );
261
262    // Now fill the sql array with not required variables
263    foreach ($additional_vars as $key => $default_value)
264    {
265        $sql_ary[$key] = (isset($user_row[$key])) ? $user_row[$key] : $default_value;
266    }
267
268    // Any additional variables in $user_row not covered above?
269    $remaining_vars = array_diff(array_keys($user_row), array_keys($sql_ary));
270
271    // Now fill our sql array with the remaining vars
272    if (count($remaining_vars))
273    {
274        foreach ($remaining_vars as $key)
275        {
276            $sql_ary[$key] = $user_row[$key];
277        }
278    }
279
280    /**
281    * Use this event to modify the values to be inserted when a user is added
282    *
283    * @event core.user_add_modify_data
284    * @var array    user_row            Array of user details submitted to user_add
285    * @var array    cp_data                Array of Custom profile fields submitted to user_add
286    * @var array    sql_ary                Array of data to be inserted when a user is added
287    * @var array    notifications_data    Array of notification data to be inserted when a user is added
288    * @since 3.1.0-a1
289    * @changed 3.1.0-b5 Added user_row and cp_data
290    * @changed 3.1.11-RC1 Added notifications_data
291    */
292    $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data');
293    extract($phpbb_dispatcher->trigger_event('core.user_add_modify_data', compact($vars)));
294
295    $sql = 'INSERT INTO ' . USERS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
296    $db->sql_query($sql);
297
298    $user_id = $db->sql_nextid();
299
300    // Insert Custom Profile Fields
301    if ($cp_data !== false && count($cp_data))
302    {
303        $cp_data['user_id'] = (int) $user_id;
304
305        /* @var $cp \phpbb\profilefields\manager */
306        $cp = $phpbb_container->get('profilefields.manager');
307        $sql = 'INSERT INTO ' . PROFILE_FIELDS_DATA_TABLE . ' ' .
308            $db->sql_build_array('INSERT', $cp->build_insert_sql_array($cp_data));
309        $db->sql_query($sql);
310    }
311
312    // Place into appropriate group...
313    $sql = 'INSERT INTO ' . USER_GROUP_TABLE . ' ' . $db->sql_build_array('INSERT', array(
314        'user_id'        => (int) $user_id,
315        'group_id'        => (int) $user_row['group_id'],
316        'user_pending'    => 0)
317    );
318    $db->sql_query($sql);
319
320    // Now make it the users default group...
321    group_set_user_default($user_row['group_id'], array($user_id), false);
322
323    // Add to newly registered users group if user_new is 1
324    if ($config['new_member_post_limit'] && $sql_ary['user_new'])
325    {
326        $sql = 'SELECT group_id
327            FROM ' . GROUPS_TABLE . "
328            WHERE group_name = 'NEWLY_REGISTERED'
329                AND group_type = " . GROUP_SPECIAL;
330        $result = $db->sql_query($sql);
331        $add_group_id = (int) $db->sql_fetchfield('group_id');
332        $db->sql_freeresult($result);
333
334        if ($add_group_id)
335        {
336            global $phpbb_log;
337
338            // Because these actions only fill the log unnecessarily, we disable it
339            $phpbb_log->disable('admin');
340
341            // Add user to "newly registered users" group and set to default group if admin specified so.
342            if ($config['new_member_group_default'])
343            {
344                group_user_add($add_group_id, $user_id, false, false, true);
345                $user_row['group_id'] = $add_group_id;
346            }
347            else
348            {
349                group_user_add($add_group_id, $user_id);
350            }
351
352            $phpbb_log->enable('admin');
353        }
354    }
355
356    // set the newest user and adjust the user count if the user is a normal user and no activation mail is sent
357    if ($user_row['user_type'] == USER_NORMAL || $user_row['user_type'] == USER_FOUNDER)
358    {
359        $config->set('newest_user_id', $user_id, false);
360        $config->set('newest_username', $user_row['username'], false);
361        $config->increment('num_users', 1, false);
362
363        $sql = 'SELECT group_colour
364            FROM ' . GROUPS_TABLE . '
365            WHERE group_id = ' . (int) $user_row['group_id'];
366        $result = $db->sql_query_limit($sql, 1);
367        $row = $db->sql_fetchrow($result);
368        $db->sql_freeresult($result);
369
370        $config->set('newest_user_colour', $row['group_colour'], false);
371    }
372
373    // Use default notifications settings if notifications_data is not set
374    if ($notifications_data === null)
375    {
376        $notifications_data = array(
377            array(
378                'item_type'    => 'notification.type.post',
379                'method'    => 'notification.method.email',
380            ),
381            array(
382                'item_type'    => 'notification.type.topic',
383                'method'    => 'notification.method.email',
384            ),
385        );
386    }
387
388    /**
389    * Modify the notifications data to be inserted in the database when a user is added
390    *
391    * @event core.user_add_modify_notifications_data
392    * @var array    user_row            Array of user details submitted to user_add
393    * @var array    cp_data                Array of Custom profile fields submitted to user_add
394    * @var array    sql_ary                Array of data to be inserted when a user is added
395    * @var array    notifications_data    Array of notification data to be inserted when a user is added
396    * @since 3.2.2-RC1
397    */
398    $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data');
399    extract($phpbb_dispatcher->trigger_event('core.user_add_modify_notifications_data', compact($vars)));
400
401    // Subscribe user to notifications if necessary
402    if (!empty($notifications_data))
403    {
404        /* @var $phpbb_notifications \phpbb\notification\manager */
405        $phpbb_notifications = $phpbb_container->get('notification_manager');
406        foreach ($notifications_data as $subscription)
407        {
408            $phpbb_notifications->add_subscription($subscription['item_type'], 0, $subscription['method'], $user_id);
409        }
410    }
411
412    /**
413    * Event that returns user id, user details and user CPF of newly registered user
414    *
415    * @event core.user_add_after
416    * @var int        user_id            User id of newly registered user
417    * @var array    user_row        Array of user details submitted to user_add
418    * @var array    cp_data            Array of Custom profile fields submitted to user_add
419    * @since 3.1.0-b5
420    */
421    $vars = array('user_id', 'user_row', 'cp_data');
422    extract($phpbb_dispatcher->trigger_event('core.user_add_after', compact($vars)));
423
424    return $user_id;
425}
426
427/**
428 * Delete user(s) and their related data
429 *
430 * @param string    $mode                Mode of posts deletion (retain|remove)
431 * @param mixed        $user_ids            Either an array of integers or an integer
432 * @param bool        $retain_username    True if username should be retained, false otherwise
433 * @return bool
434 */
435function user_delete($mode, $user_ids, $retain_username = true)
436{
437    global $cache, $config, $db, $user, $phpbb_dispatcher, $phpbb_container;
438    global $phpbb_root_path, $phpEx;
439
440    $db->sql_transaction('begin');
441
442    $user_rows = array();
443    if (!is_array($user_ids))
444    {
445        $user_ids = array($user_ids);
446    }
447
448    $user_id_sql = $db->sql_in_set('user_id', $user_ids);
449
450    $sql = 'SELECT *
451        FROM ' . USERS_TABLE . '
452        WHERE ' . $user_id_sql;
453    $result = $db->sql_query($sql);
454    while ($row = $db->sql_fetchrow($result))
455    {
456        $user_rows[(int) $row['user_id']] = $row;
457    }
458    $db->sql_freeresult($result);
459
460    if (empty($user_rows))
461    {
462        return false;
463    }
464
465    /**
466     * Event before of the performing of the user(s) delete action
467     *
468     * @event core.delete_user_before
469     * @var string    mode                Mode of posts deletion (retain|remove)
470     * @var array    user_ids            ID(s) of the user(s) bound to be deleted
471     * @var bool    retain_username        True if username should be retained, false otherwise
472     * @var array    user_rows            Array containing data of the user(s) bound to be deleted
473     * @since 3.1.0-a1
474     * @changed 3.2.4-RC1 Added user_rows
475     */
476    $vars = array('mode', 'user_ids', 'retain_username', 'user_rows');
477    extract($phpbb_dispatcher->trigger_event('core.delete_user_before', compact($vars)));
478
479    // Before we begin, we will remove the reports the user issued.
480    $sql = 'SELECT r.post_id, p.topic_id
481        FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p
482        WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . '
483            AND p.post_id = r.post_id';
484    $result = $db->sql_query($sql);
485
486    $report_posts = $report_topics = array();
487    while ($row = $db->sql_fetchrow($result))
488    {
489        $report_posts[] = $row['post_id'];
490        $report_topics[] = $row['topic_id'];
491    }
492    $db->sql_freeresult($result);
493
494    if (count($report_posts))
495    {
496        $report_posts = array_unique($report_posts);
497        $report_topics = array_unique($report_topics);
498
499        // Get a list of topics that still contain reported posts
500        $sql = 'SELECT DISTINCT topic_id
501            FROM ' . POSTS_TABLE . '
502            WHERE ' . $db->sql_in_set('topic_id', $report_topics) . '
503                AND post_reported = 1
504                AND ' . $db->sql_in_set('post_id', $report_posts, true);
505        $result = $db->sql_query($sql);
506
507        $keep_report_topics = array();
508        while ($row = $db->sql_fetchrow($result))
509        {
510            $keep_report_topics[] = $row['topic_id'];
511        }
512        $db->sql_freeresult($result);
513
514        if (count($keep_report_topics))
515        {
516            $report_topics = array_diff($report_topics, $keep_report_topics);
517        }
518        unset($keep_report_topics);
519
520        // Now set the flags back
521        $sql = 'UPDATE ' . POSTS_TABLE . '
522            SET post_reported = 0
523            WHERE ' . $db->sql_in_set('post_id', $report_posts);
524        $db->sql_query($sql);
525
526        if (count($report_topics))
527        {
528            $sql = 'UPDATE ' . TOPICS_TABLE . '
529                SET topic_reported = 0
530                WHERE ' . $db->sql_in_set('topic_id', $report_topics);
531            $db->sql_query($sql);
532        }
533    }
534
535    // Remove reports
536    $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE ' . $user_id_sql);
537
538    $num_users_delta = 0;
539
540    // Get auth provider collection in case accounts might need to be unlinked
541    $provider_collection = $phpbb_container->get('auth.provider_collection');
542
543    // Some things need to be done in the loop (if the query changes based
544    // on which user is currently being deleted)
545    $added_guest_posts = 0;
546    foreach ($user_rows as $user_id => $user_row)
547    {
548        if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == 'avatar.driver.upload')
549        {
550            avatar_delete('user', $user_row);
551        }
552
553        // Unlink accounts
554        foreach ($provider_collection as $provider_name => $auth_provider)
555        {
556            $provider_data = $auth_provider->get_auth_link_data($user_id);
557
558            if ($provider_data !== null)
559            {
560                $link_data = array(
561                    'user_id' => $user_id,
562                    'link_method' => 'user_delete',
563                );
564
565                // BLOCK_VARS might contain hidden fields necessary for unlinking accounts
566                if (isset($provider_data['BLOCK_VARS']) && is_array($provider_data['BLOCK_VARS']))
567                {
568                    foreach ($provider_data['BLOCK_VARS'] as $provider_service)
569                    {
570                        if (!array_key_exists('HIDDEN_FIELDS', $provider_service))
571                        {
572                            $provider_service['HIDDEN_FIELDS'] = array();
573                        }
574
575                        $auth_provider->unlink_account(array_merge($link_data, $provider_service['HIDDEN_FIELDS']));
576                    }
577                }
578                else
579                {
580                    $auth_provider->unlink_account($link_data);
581                }
582            }
583        }
584
585        // Decrement number of users if this user is active
586        if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE)
587        {
588            --$num_users_delta;
589        }
590
591        switch ($mode)
592        {
593            case 'retain':
594                if ($retain_username === false)
595                {
596                    $post_username = $user->lang['GUEST'];
597                }
598                else
599                {
600                    $post_username = $user_row['username'];
601                }
602
603                // If the user is inactive and newly registered
604                // we assume no posts from the user, and save
605                // the queries
606                if ($user_row['user_type'] != USER_INACTIVE || $user_row['user_inactive_reason'] != INACTIVE_REGISTER || $user_row['user_posts'])
607                {
608                    // When we delete these users and retain the posts, we must assign all the data to the guest user
609                    $sql = 'UPDATE ' . FORUMS_TABLE . '
610                        SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = ''
611                        WHERE forum_last_poster_id = $user_id";
612                    $db->sql_query($sql);
613
614                    $sql = 'UPDATE ' . POSTS_TABLE . '
615                        SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "'
616                        WHERE poster_id = $user_id";
617                    $db->sql_query($sql);
618
619                    $sql = 'UPDATE ' . TOPICS_TABLE . '
620                        SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = ''
621                        WHERE topic_poster = $user_id";
622                    $db->sql_query($sql);
623
624                    $sql = 'UPDATE ' . TOPICS_TABLE . '
625                        SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = ''
626                        WHERE topic_last_poster_id = $user_id";
627                    $db->sql_query($sql);
628
629                    // Since we change every post by this author, we need to count this amount towards the anonymous user
630
631                    if ($user_row['user_posts'])
632                    {
633                        $added_guest_posts += $user_row['user_posts'];
634                    }
635                }
636            break;
637
638            case 'remove':
639                // there is nothing variant specific to deleting posts
640            break;
641        }
642    }
643
644    if ($num_users_delta != 0)
645    {
646        $config->increment('num_users', $num_users_delta, false);
647    }
648
649    // Now do the invariant tasks
650    // all queries performed in one call of this function are in a single transaction
651    // so this is kosher
652    if ($mode == 'retain')
653    {
654        // Assign more data to the Anonymous user
655        $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
656            SET poster_id = ' . ANONYMOUS . '
657            WHERE ' . $db->sql_in_set('poster_id', $user_ids);
658        $db->sql_query($sql);
659
660        $sql = 'UPDATE ' . USERS_TABLE . '
661            SET user_posts = user_posts + ' . $added_guest_posts . '
662            WHERE user_id = ' . ANONYMOUS;
663        $db->sql_query($sql);
664    }
665    else if ($mode == 'remove')
666    {
667        if (!function_exists('delete_posts'))
668        {
669            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
670        }
671
672        // Delete posts, attachments, etc.
673        // delete_posts can handle any number of IDs in its second argument
674        delete_posts('poster_id', $user_ids);
675    }
676
677    $table_ary = [
678        USERS_TABLE,
679        USER_GROUP_TABLE,
680        TOPICS_WATCH_TABLE,
681        FORUMS_WATCH_TABLE,
682        ACL_USERS_TABLE,
683        TOPICS_TRACK_TABLE,
684        TOPICS_POSTED_TABLE,
685        FORUMS_TRACK_TABLE,
686        PROFILE_FIELDS_DATA_TABLE,
687        MODERATOR_CACHE_TABLE,
688        DRAFTS_TABLE,
689        BOOKMARKS_TABLE,
690        SESSIONS_KEYS_TABLE,
691        PRIVMSGS_FOLDER_TABLE,
692        PRIVMSGS_RULES_TABLE,
693        $phpbb_container->getParameter('tables.auth_provider_oauth_token_storage'),
694        $phpbb_container->getParameter('tables.auth_provider_oauth_states'),
695        $phpbb_container->getParameter('tables.auth_provider_oauth_account_assoc'),
696        $phpbb_container->getParameter('tables.user_notifications')
697    ];
698
699    // Ignore errors on deleting from non-existent tables, e.g. when migrating
700    $db->sql_return_on_error(true);
701    // Delete the miscellaneous (non-post) data for the user
702    foreach ($table_ary as $table)
703    {
704        $sql = "DELETE FROM $table
705            WHERE " . $user_id_sql;
706        $db->sql_query($sql);
707    }
708    $db->sql_return_on_error();
709
710    $cache->destroy('sql', MODERATOR_CACHE_TABLE);
711
712    // Change user_id to anonymous for posts edited by this user
713    $sql = 'UPDATE ' . POSTS_TABLE . '
714        SET post_edit_user = ' . ANONYMOUS . '
715        WHERE ' . $db->sql_in_set('post_edit_user', $user_ids);
716    $db->sql_query($sql);
717
718    // Change user_id to anonymous for pms edited by this user
719    $sql = 'UPDATE ' . PRIVMSGS_TABLE . '
720        SET message_edit_user = ' . ANONYMOUS . '
721        WHERE ' . $db->sql_in_set('message_edit_user', $user_ids);
722    $db->sql_query($sql);
723
724    // Change user_id to anonymous for posts deleted by this user
725    $sql = 'UPDATE ' . POSTS_TABLE . '
726        SET post_delete_user = ' . ANONYMOUS . '
727        WHERE ' . $db->sql_in_set('post_delete_user', $user_ids);
728    $db->sql_query($sql);
729
730    // Change user_id to anonymous for topics deleted by this user
731    $sql = 'UPDATE ' . TOPICS_TABLE . '
732        SET topic_delete_user = ' . ANONYMOUS . '
733        WHERE ' . $db->sql_in_set('topic_delete_user', $user_ids);
734    $db->sql_query($sql);
735
736    // Delete user log entries about this user
737    $sql = 'DELETE FROM ' . LOG_TABLE . '
738        WHERE ' . $db->sql_in_set('reportee_id', $user_ids);
739    $db->sql_query($sql);
740
741    // Change user_id to anonymous for this users triggered events
742    $sql = 'UPDATE ' . LOG_TABLE . '
743        SET user_id = ' . ANONYMOUS . '
744        WHERE ' . $user_id_sql;
745    $db->sql_query($sql);
746
747    // Delete the user_id from the zebra table
748    $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
749        WHERE ' . $user_id_sql . '
750            OR ' . $db->sql_in_set('zebra_id', $user_ids);
751    $db->sql_query($sql);
752
753    // Delete the user_id from the banlist
754    $sql = 'DELETE FROM ' . BANS_TABLE . "
755        WHERE ban_mode = 'user'
756            AND " . $db->sql_in_set('ban_userid', $user_ids);
757    $db->sql_query($sql);
758
759    // Delete the user_id from the session table
760    $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
761        WHERE ' . $db->sql_in_set('session_user_id', $user_ids);
762    $db->sql_query($sql);
763
764    // Clean the private messages tables from the user
765    if (!function_exists('phpbb_delete_users_pms'))
766    {
767        include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx);
768    }
769    phpbb_delete_users_pms($user_ids);
770
771    $phpbb_notifications = $phpbb_container->get('notification_manager');
772    $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_ids);
773
774    $db->sql_transaction('commit');
775
776    /**
777     * Event after the user(s) delete action has been performed
778     *
779     * @event core.delete_user_after
780     * @var string    mode                Mode of posts deletion (retain|remove)
781     * @var array    user_ids            ID(s) of the deleted user(s)
782     * @var bool    retain_username        True if username should be retained, false otherwise
783     * @var array    user_rows            Array containing data of the deleted user(s)
784     * @since 3.1.0-a1
785     * @changed 3.2.2-RC1 Added user_rows
786     */
787    $vars = array('mode', 'user_ids', 'retain_username', 'user_rows');
788    extract($phpbb_dispatcher->trigger_event('core.delete_user_after', compact($vars)));
789
790    // Reset newest user info if appropriate
791    if (in_array($config['newest_user_id'], $user_ids))
792    {
793        update_last_username();
794    }
795
796    return false;
797}
798
799/**
800* Flips user_type from active to inactive and vice versa, handles group membership updates
801*
802* @param string $mode can be flip for flipping from active/inactive, activate or deactivate
803* @param array $user_id_ary
804* @param int $reason
805*/
806function user_active_flip($mode, $user_id_ary, $reason = INACTIVE_MANUAL)
807{
808    global $config, $db, $user, $auth, $phpbb_dispatcher;
809
810    $deactivated = $activated = 0;
811    $sql_statements = array();
812
813    if (!is_array($user_id_ary))
814    {
815        $user_id_ary = array($user_id_ary);
816    }
817
818    if (!count($user_id_ary))
819    {
820        return;
821    }
822
823    $sql = 'SELECT user_id, group_id, user_type, user_inactive_reason
824        FROM ' . USERS_TABLE . '
825        WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
826    $result = $db->sql_query($sql);
827
828    while ($row = $db->sql_fetchrow($result))
829    {
830        $sql_ary = array();
831
832        if ($row['user_type'] == USER_IGNORE || $row['user_type'] == USER_FOUNDER ||
833            ($mode == 'activate' && $row['user_type'] != USER_INACTIVE) ||
834            ($mode == 'deactivate' && $row['user_type'] == USER_INACTIVE))
835        {
836            continue;
837        }
838
839        if ($row['user_type'] == USER_INACTIVE)
840        {
841            $activated++;
842        }
843        else
844        {
845            $deactivated++;
846
847            // Remove the users session key...
848            $user->reset_login_keys($row['user_id']);
849        }
850
851        $sql_ary += array(
852            'user_type'                => ($row['user_type'] == USER_NORMAL) ? USER_INACTIVE : USER_NORMAL,
853            'user_inactive_time'    => ($row['user_type'] == USER_NORMAL) ? time() : 0,
854            'user_inactive_reason'    => ($row['user_type'] == USER_NORMAL) ? $reason : 0,
855        );
856
857        $sql_statements[$row['user_id']] = $sql_ary;
858    }
859    $db->sql_freeresult($result);
860
861    /**
862    * Check or modify activated/deactivated users data before submitting it to the database
863    *
864    * @event core.user_active_flip_before
865    * @var    string    mode            User type changing mode, can be: flip|activate|deactivate
866    * @var    int        reason            Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND
867    * @var    int        activated        The number of users to be activated
868    * @var    int        deactivated        The number of users to be deactivated
869    * @var    array    user_id_ary        Array with user ids to change user type
870    * @var    array    sql_statements    Array with users data to submit to the database, keys: user ids, values: arrays with user data
871    * @since 3.1.4-RC1
872    */
873    $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements');
874    extract($phpbb_dispatcher->trigger_event('core.user_active_flip_before', compact($vars)));
875
876    if (count($sql_statements))
877    {
878        foreach ($sql_statements as $user_id => $sql_ary)
879        {
880            $sql = 'UPDATE ' . USERS_TABLE . '
881                SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
882                WHERE user_id = ' . $user_id;
883            $db->sql_query($sql);
884        }
885
886        $auth->acl_clear_prefetch(array_keys($sql_statements));
887    }
888
889    /**
890    * Perform additional actions after the users have been activated/deactivated
891    *
892    * @event core.user_active_flip_after
893    * @var    string    mode            User type changing mode, can be: flip|activate|deactivate
894    * @var    int        reason            Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND
895    * @var    int        activated        The number of users to be activated
896    * @var    int        deactivated        The number of users to be deactivated
897    * @var    array    user_id_ary        Array with user ids to change user type
898    * @var    array    sql_statements    Array with users data to submit to the database, keys: user ids, values: arrays with user data
899    * @since 3.1.4-RC1
900    */
901    $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements');
902    extract($phpbb_dispatcher->trigger_event('core.user_active_flip_after', compact($vars)));
903
904    if ($deactivated)
905    {
906        $config->increment('num_users', $deactivated * (-1), false);
907    }
908
909    if ($activated)
910    {
911        $config->increment('num_users', $activated, false);
912    }
913
914    // Update latest username
915    update_last_username();
916}
917
918/**
919* Add a ban or ban exclusion to the banlist. Bans either a user, an IP or an email address
920*
921* @deprecated 4.0.0-a1 (To be removed: 4.1.0)
922*
923* @param string $mode Type of ban. One of the following: user, ip, email
924* @param mixed $ban Banned entity. Either string or array with usernames, ips or email addresses
925* @param int $ban_len Ban length in minutes
926* @param string $ban_len_other Ban length as a date (YYYY-MM-DD)
927* @param string $ban_reason String describing the reason for this ban
928* @param string $ban_give_reason
929* @return boolean
930*/
931function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_reason, $ban_give_reason = '')
932{
933    global $phpbb_container, $user;
934
935    /** @var \phpbb\ban\manager $ban_manager */
936    $ban_manager = $phpbb_container->get('ban.manager');
937
938    $items = is_array($ban) ? $ban : [$ban];
939
940    $current_time = time();
941    // Set $ban_end to the unix time when the ban should end. 0 is a permanent ban.
942    if ($ban_len)
943    {
944        if ($ban_len != -1 || !$ban_len_other)
945        {
946            $ban_end = max($current_time, $current_time + ($ban_len) * 60);
947        }
948        else
949        {
950            $ban_other = explode('-', $ban_len_other);
951            if (count($ban_other) == 3 && ((int) $ban_other[0] < 9999) &&
952                (strlen($ban_other[0]) == 4) && (strlen($ban_other[1]) == 2) && (strlen($ban_other[2]) == 2))
953            {
954                $ban_end = max($current_time, $user->create_datetime()
955                        ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2])
956                        ->setTime(0, 0, 0)
957                        ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC')));
958            }
959            else
960            {
961                trigger_error('LENGTH_BAN_INVALID', E_USER_WARNING);
962            }
963        }
964    }
965    else
966    {
967        $ban_end = 0;
968    }
969
970    $start = new \DateTime();
971    $start->setTimestamp($current_time);
972    $end = new \DateTime();
973    $end->setTimestamp($ban_end);
974
975    return $ban_manager->ban($mode, $items, $start, $end, $ban_reason, $ban_give_reason);
976}
977
978/**
979* Unban User
980*
981* @deprecated 4.0.0-a1 (To be removed: 4.1.0)
982*/
983function user_unban($mode, $ban)
984{
985    global $phpbb_container;
986
987    $items = is_array($ban) ? $ban : [$ban];
988
989    /** @var \phpbb\ban\manager $ban_manager */
990    $ban_manager = $phpbb_container->get('ban.manager');
991    $ban_manager->unban($mode, $items);
992}
993
994/**
995* Internet Protocol Address Whois
996* RFC3912: WHOIS Protocol Specification
997*
998* @param string $ip        Ip address, either IPv4 or IPv6.
999*
1000* @return string        Empty string if not a valid ip address.
1001*                        Otherwise make_clickable()'ed whois result.
1002*/
1003function user_ipwhois($ip)
1004{
1005    if (!filter_var($ip, FILTER_VALIDATE_IP))
1006    {
1007        return '';
1008    }
1009
1010    // IPv4 & IPv6 addresses
1011    $whois_host = 'whois.arin.net.';
1012
1013    $ipwhois = '';
1014
1015    if (($fsk = @fsockopen($whois_host, 43)))
1016    {
1017        // CRLF as per RFC3912
1018        // Z to limit the query to all possible flags (whois.arin.net)
1019        fputs($fsk, "$ip\r\n");
1020        while (!feof($fsk))
1021        {
1022            $ipwhois .= fgets($fsk, 1024);
1023        }
1024        @fclose($fsk);
1025    }
1026
1027    $match = array();
1028
1029    // Test for referrals from $whois_host to other whois databases, roll on rwhois
1030    if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match))
1031    {
1032        if (strpos($match[1], ':') !== false)
1033        {
1034            $pos    = strrpos($match[1], ':');
1035            $server    = substr($match[1], 0, $pos);
1036            $port    = (int) substr($match[1], $pos + 1);
1037            unset($pos);
1038        }
1039        else
1040        {
1041            $server    = $match[1];
1042            $port    = 43;
1043        }
1044
1045        $buffer = '';
1046
1047        if (($fsk = @fsockopen($server, $port)))
1048        {
1049            fputs($fsk, "$ip\r\n");
1050            while (!feof($fsk))
1051            {
1052                $buffer .= fgets($fsk, 1024);
1053            }
1054            @fclose($fsk);
1055        }
1056
1057        // Use the result from $whois_host if we don't get any result here
1058        $ipwhois = (empty($buffer)) ? $ipwhois : $buffer;
1059    }
1060
1061    $ipwhois = htmlspecialchars($ipwhois, ENT_COMPAT);
1062
1063    // Magic URL ;)
1064    return trim(make_clickable($ipwhois, false, ''));
1065}
1066
1067/**
1068* Data validation ... used primarily but not exclusively by ucp modules
1069*
1070* "Master" function for validating a range of data types
1071*/
1072function validate_data($data, $val_ary)
1073{
1074    global $user;
1075
1076    $error = array();
1077
1078    foreach ($val_ary as $var => $val_seq)
1079    {
1080        if (!is_array($val_seq[0]))
1081        {
1082            $val_seq = array($val_seq);
1083        }
1084
1085        foreach ($val_seq as $validate)
1086        {
1087            $function = array_shift($validate);
1088            array_unshift($validate, $data[$var]);
1089
1090            if (is_array($function))
1091            {
1092                $result = call_user_func_array(array($function[0], 'validate_' . $function[1]), $validate);
1093            }
1094            else
1095            {
1096                $function_prefix = (function_exists('phpbb_validate_' . $function)) ? 'phpbb_validate_' : 'validate_';
1097                $result = call_user_func_array($function_prefix . $function, $validate);
1098            }
1099
1100            if ($result)
1101            {
1102                // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted.
1103                $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var);
1104            }
1105        }
1106    }
1107
1108    return $error;
1109}
1110
1111/**
1112* Validate String
1113*
1114* @return    boolean|string    Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1115*/
1116function validate_string($string, $optional = false, $min = 0, $max = 0)
1117{
1118    if (empty($string) && $optional)
1119    {
1120        return false;
1121    }
1122
1123    if ($min && utf8_strlen(html_entity_decode($string, ENT_COMPAT)) < $min)
1124    {
1125        return 'TOO_SHORT';
1126    }
1127    else if ($max && utf8_strlen(html_entity_decode($string, ENT_COMPAT)) > $max)
1128    {
1129        return 'TOO_LONG';
1130    }
1131
1132    return false;
1133}
1134
1135/**
1136* Validate Number
1137*
1138* @return    boolean|string    Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1139*/
1140function validate_num($num, $optional = false, $min = 0, $max = 1E99)
1141{
1142    if (empty($num) && $optional)
1143    {
1144        return false;
1145    }
1146
1147    if ($num < $min)
1148    {
1149        return 'TOO_SMALL';
1150    }
1151    else if ($num > $max)
1152    {
1153        return 'TOO_LARGE';
1154    }
1155
1156    return false;
1157}
1158
1159/**
1160* Validate Date
1161* @param    string $date_string a date in the dd-mm-yyyy format
1162* @param    bool $optional
1163* @return    boolean
1164*/
1165function validate_date($date_string, $optional = false)
1166{
1167    $date = explode('-', $date_string);
1168    if ((empty($date) || count($date) != 3) && $optional)
1169    {
1170        return false;
1171    }
1172    else if ($optional)
1173    {
1174        for ($field = 0; $field <= 1; $field++)
1175        {
1176            $date[$field] = (int) $date[$field];
1177            if (empty($date[$field]))
1178            {
1179                $date[$field] = 1;
1180            }
1181        }
1182        $date[2] = (int) $date[2];
1183        // assume an arbitrary leap year
1184        if (empty($date[2]))
1185        {
1186            $date[2] = 1980;
1187        }
1188    }
1189
1190    if (count($date) != 3 || !checkdate($date[1], $date[0], $date[2]))
1191    {
1192        return 'INVALID';
1193    }
1194
1195    return false;
1196}
1197
1198/**
1199* Validate Match
1200*
1201* @return    boolean|string    Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1202*/
1203function validate_match($string, $optional = false, $match = '')
1204{
1205    if (empty($string) && $optional)
1206    {
1207        return false;
1208    }
1209
1210    if (empty($match))
1211    {
1212        return false;
1213    }
1214
1215    if (!preg_match($match, $string))
1216    {
1217        return 'WRONG_DATA';
1218    }
1219
1220    return false;
1221}
1222
1223/**
1224* Validate Language Pack ISO Name
1225*
1226* Tests whether a language name is valid and installed
1227*
1228* @param string $lang_iso    The language string to test
1229*
1230* @return bool|string        Either false if validation succeeded or
1231*                            a string which will be used as the error message
1232*                            (with the variable name appended)
1233*/
1234function validate_language_iso_name($lang_iso)
1235{
1236    global $db;
1237
1238    $sql = 'SELECT lang_id
1239        FROM ' . LANG_TABLE . "
1240        WHERE lang_iso = '" . $db->sql_escape($lang_iso) . "'";
1241    $result = $db->sql_query($sql);
1242    $lang_id = (int) $db->sql_fetchfield('lang_id');
1243    $db->sql_freeresult($result);
1244
1245    return ($lang_id) ? false : 'WRONG_DATA';
1246}
1247
1248/**
1249* Validate Timezone Name
1250*
1251* Tests whether a timezone name is valid
1252*
1253* @param string $timezone    The timezone string to test
1254*
1255* @return bool|string        Either false if validation succeeded or
1256*                            a string which will be used as the error message
1257*                            (with the variable name appended)
1258*/
1259function phpbb_validate_timezone($timezone)
1260{
1261    return (in_array($timezone, phpbb_get_timezone_identifiers($timezone))) ? false : 'TIMEZONE_INVALID';
1262}
1263
1264/***
1265 * Validate Username
1266 *
1267 * Check to see if the username has been taken, or if it is disallowed.
1268 * Also checks if it includes the " character or the 4-bytes Unicode ones
1269 * (aka emojis) which we don't allow in usernames.
1270 * Used for registering, changing names, and posting anonymously with a username
1271 *
1272 * @param string    $username                The username to check
1273 * @param string    $allowed_username        An allowed username, default being $user->data['username']
1274 *
1275 * @return string|false                        Either false if validation succeeded or a string which will be
1276 *                                            used as the error message (with the variable name appended)
1277 */
1278function validate_username($username, $allowed_username = false, $allow_all_names = false)
1279{
1280    global $config, $db, $user, $cache;
1281
1282    $clean_username = utf8_clean_string($username);
1283    $allowed_username = ($allowed_username === false) ? $user->data['username_clean'] : utf8_clean_string($allowed_username);
1284
1285    if ($allowed_username == $clean_username)
1286    {
1287        return false;
1288    }
1289
1290    // The very first check is for
1291    // out-of-bounds characters that are currently
1292    // not supported by utf8_bin in MySQL
1293    if (preg_match('/[\x{10000}-\x{10FFFF}]/u', $username))
1294    {
1295        return 'INVALID_EMOJIS';
1296    }
1297
1298    // ... fast checks first.
1299    if (strpos($username, '&quot;') !== false || strpos($username, '"') !== false || empty($clean_username)
1300        || preg_match('/[\x{180E}\x{2005}-\x{200D}\x{202F}\x{205F}\x{2060}\x{FEFF}]/u', $username))
1301    {
1302        return 'INVALID_CHARS';
1303    }
1304
1305    switch ($config['allow_name_chars'])
1306    {
1307        case 'USERNAME_CHARS_ANY':
1308            $regex = '.+';
1309        break;
1310
1311        case 'USERNAME_ALPHA_ONLY':
1312            $regex = '[A-Za-z0-9]+';
1313        break;
1314
1315        case 'USERNAME_ALPHA_SPACERS':
1316            $regex = '[A-Za-z0-9-[\]_+ ]+';
1317        break;
1318
1319        case 'USERNAME_LETTER_NUM':
1320            $regex = '[\p{Lu}\p{Ll}\p{N}]+';
1321        break;
1322
1323        case 'USERNAME_LETTER_NUM_SPACERS':
1324            $regex = '[-\]_+ [\p{Lu}\p{Ll}\p{N}]+';
1325        break;
1326
1327        case 'USERNAME_ASCII':
1328        default:
1329            $regex = '[\x01-\x7F]+';
1330        break;
1331    }
1332
1333    if (!preg_match('#^' . $regex . '$#u', $username))
1334    {
1335        return 'INVALID_CHARS';
1336    }
1337
1338    $sql = 'SELECT username
1339        FROM ' . USERS_TABLE . "
1340        WHERE username_clean = '" . $db->sql_escape($clean_username) . "'";
1341    $result = $db->sql_query($sql);
1342    $row = $db->sql_fetchrow($result);
1343    $db->sql_freeresult($result);
1344
1345    if ($row)
1346    {
1347        return 'USERNAME_TAKEN';
1348    }
1349
1350    $sql = 'SELECT group_name
1351        FROM ' . GROUPS_TABLE . "
1352        WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($username)) . "'";
1353    $result = $db->sql_query($sql);
1354    $row = $db->sql_fetchrow($result);
1355    $db->sql_freeresult($result);
1356
1357    if ($row)
1358    {
1359        return 'USERNAME_TAKEN';
1360    }
1361
1362    if (!$allow_all_names)
1363    {
1364        $bad_usernames = $cache->obtain_disallowed_usernames();
1365
1366        foreach ($bad_usernames as $bad_username)
1367        {
1368            if (preg_match('#^' . $bad_username . '$#', $clean_username))
1369            {
1370                return 'USERNAME_DISALLOWED';
1371            }
1372        }
1373    }
1374
1375    return false;
1376}
1377
1378/**
1379* Check to see if the password meets the complexity settings
1380*
1381* @return    boolean|string    Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1382*/
1383function validate_password($password)
1384{
1385    global $config;
1386
1387    if ($password === '' || $config['pass_complex'] === 'PASS_TYPE_ANY')
1388    {
1389        // Password empty or no password complexity required.
1390        return false;
1391    }
1392
1393    $upp = '\p{Lu}';
1394    $low = '\p{Ll}';
1395    $num = '\p{N}';
1396    $sym = '[^\p{Lu}\p{Ll}\p{N}]';
1397    $chars = array();
1398
1399    switch ($config['pass_complex'])
1400    {
1401        // No break statements below ...
1402        // We require strong passwords in case pass_complex is not set or is invalid
1403        default:
1404
1405        // Require mixed case letters, numbers and symbols
1406        case 'PASS_TYPE_SYMBOL':
1407            $chars[] = $sym;
1408
1409        // Require mixed case letters and numbers
1410        case 'PASS_TYPE_ALPHA':
1411            $chars[] = $num;
1412
1413        // Require mixed case letters
1414        case 'PASS_TYPE_CASE':
1415            $chars[] = $low;
1416            $chars[] = $upp;
1417    }
1418
1419    foreach ($chars as $char)
1420    {
1421        if (!preg_match('#' . $char . '#u', $password))
1422        {
1423            return 'INVALID_CHARS';
1424        }
1425    }
1426
1427    return false;
1428}
1429
1430/**
1431* Check to see if email address is a valid address and contains a MX record
1432*
1433* @param string $email The email to check
1434* @param $config
1435*
1436* @return mixed Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1437*/
1438function phpbb_validate_email($email, $config = null)
1439{
1440    if ($config === null)
1441    {
1442        global $config;
1443    }
1444
1445    $email = strtolower($email);
1446
1447    if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
1448    {
1449        return 'EMAIL_INVALID';
1450    }
1451
1452    // Check MX record.
1453    // The idea for this is from reading the UseBB blog/announcement. :)
1454    if ($config['email_check_mx'])
1455    {
1456        list(, $domain) = explode('@', $email);
1457
1458        if (checkdnsrr($domain, 'A') === false && checkdnsrr($domain, 'MX') === false)
1459        {
1460            return 'DOMAIN_NO_MX_RECORD';
1461        }
1462    }
1463
1464    return false;
1465}
1466
1467/**
1468* Check to see if email address is banned or already present in the DB
1469*
1470* @param string $email The email to check
1471* @param string $allowed_email An allowed email, default being $user->data['user_email']
1472*
1473* @return mixed Either false if validation succeeded or a string which will be used as the error message (with the variable name appended)
1474*/
1475function validate_user_email($email, $allowed_email = false)
1476{
1477    global $config, $db, $user;
1478
1479    $email = strtolower($email);
1480    $allowed_email = ($allowed_email === false) ? strtolower($user->data['user_email']) : strtolower($allowed_email);
1481
1482    if ($allowed_email == $email)
1483    {
1484        return false;
1485    }
1486
1487    $validate_email = phpbb_validate_email($email, $config);
1488    if ($validate_email)
1489    {
1490        return $validate_email;
1491    }
1492
1493    $ban = $user->check_ban(false, false, $email, true);
1494    if (!empty($ban))
1495    {
1496        return !empty($ban['reason']) ? $ban['reason'] : 'EMAIL_BANNED';
1497    }
1498
1499    if (!$config['allow_emailreuse'])
1500    {
1501        $sql = 'SELECT user_email
1502            FROM ' . USERS_TABLE . "
1503            WHERE user_email = '" . $db->sql_escape($email) . "'";
1504        $result = $db->sql_query($sql);
1505        $row = $db->sql_fetchrow($result);
1506        $db->sql_freeresult($result);
1507
1508        if ($row)
1509        {
1510            return 'EMAIL_TAKEN';
1511        }
1512    }
1513
1514    return false;
1515}
1516
1517/**
1518* Validate jabber address
1519* Taken from the jabber class within flyspray (see author notes)
1520*
1521* @author flyspray.org
1522*/
1523function validate_jabber($jid)
1524{
1525    if (!$jid)
1526    {
1527        return false;
1528    }
1529
1530    $separator_pos = strpos($jid, '@');
1531
1532    if ($separator_pos === false)
1533    {
1534        return 'WRONG_DATA';
1535    }
1536
1537    $username = substr($jid, 0, $separator_pos);
1538    $realm = substr($jid, $separator_pos + 1);
1539
1540    if (strlen($username) == 0 || strlen($realm) < 3)
1541    {
1542        return 'WRONG_DATA';
1543    }
1544
1545    $arr = explode('.', $realm);
1546
1547    if (count($arr) == 0)
1548    {
1549        return 'WRONG_DATA';
1550    }
1551
1552    foreach ($arr as $part)
1553    {
1554        if (substr($part, 0, 1) == '-' || substr($part, -1, 1) == '-')
1555        {
1556            return 'WRONG_DATA';
1557        }
1558
1559        if (!preg_match("@^[a-zA-Z0-9-.]+$@", $part))
1560        {
1561            return 'WRONG_DATA';
1562        }
1563    }
1564
1565    $boundary = array(array(0, 127), array(192, 223), array(224, 239), array(240, 247), array(248, 251), array(252, 253));
1566
1567    // Prohibited Characters RFC3454 + RFC3920
1568    $prohibited = array(
1569        // Table C.1.1
1570        array(0x0020, 0x0020),        // SPACE
1571        // Table C.1.2
1572        array(0x00A0, 0x00A0),        // NO-BREAK SPACE
1573        array(0x1680, 0x1680),        // OGHAM SPACE MARK
1574        array(0x2000, 0x2001),        // EN QUAD
1575        array(0x2001, 0x2001),        // EM QUAD
1576        array(0x2002, 0x2002),        // EN SPACE
1577        array(0x2003, 0x2003),        // EM SPACE
1578        array(0x2004, 0x2004),        // THREE-PER-EM SPACE
1579        array(0x2005, 0x2005),        // FOUR-PER-EM SPACE
1580        array(0x2006, 0x2006),        // SIX-PER-EM SPACE
1581        array(0x2007, 0x2007),        // FIGURE SPACE
1582        array(0x2008, 0x2008),        // PUNCTUATION SPACE
1583        array(0x2009, 0x2009),        // THIN SPACE
1584        array(0x200A, 0x200A),        // HAIR SPACE
1585        array(0x200B, 0x200B),        // ZERO WIDTH SPACE
1586        array(0x202F, 0x202F),        // NARROW NO-BREAK SPACE
1587        array(0x205F, 0x205F),        // MEDIUM MATHEMATICAL SPACE
1588        array(0x3000, 0x3000),        // IDEOGRAPHIC SPACE
1589        // Table C.2.1
1590        array(0x0000, 0x001F),        // [CONTROL CHARACTERS]
1591        array(0x007F, 0x007F),        // DELETE
1592        // Table C.2.2
1593        array(0x0080, 0x009F),        // [CONTROL CHARACTERS]
1594        array(0x06DD, 0x06DD),        // ARABIC END OF AYAH
1595        array(0x070F, 0x070F),        // SYRIAC ABBREVIATION MARK
1596        array(0x180E, 0x180E),        // MONGOLIAN VOWEL SEPARATOR
1597        array(0x200C, 0x200C),         // ZERO WIDTH NON-JOINER
1598        array(0x200D, 0x200D),        // ZERO WIDTH JOINER
1599        array(0x2028, 0x2028),        // LINE SEPARATOR
1600        array(0x2029, 0x2029),        // PARAGRAPH SEPARATOR
1601        array(0x2060, 0x2060),        // WORD JOINER
1602        array(0x2061, 0x2061),        // FUNCTION APPLICATION
1603        array(0x2062, 0x2062),        // INVISIBLE TIMES
1604        array(0x2063, 0x2063),        // INVISIBLE SEPARATOR
1605        array(0x206A, 0x206F),        // [CONTROL CHARACTERS]
1606        array(0xFEFF, 0xFEFF),        // ZERO WIDTH NO-BREAK SPACE
1607        array(0xFFF9, 0xFFFC),        // [CONTROL CHARACTERS]
1608        array(0x1D173, 0x1D17A),    // [MUSICAL CONTROL CHARACTERS]
1609        // Table C.3
1610        array(0xE000, 0xF8FF),        // [PRIVATE USE, PLANE 0]
1611        array(0xF0000, 0xFFFFD),    // [PRIVATE USE, PLANE 15]
1612        array(0x100000, 0x10FFFD),    // [PRIVATE USE, PLANE 16]
1613        // Table C.4
1614        array(0xFDD0, 0xFDEF),        // [NONCHARACTER CODE POINTS]
1615        array(0xFFFE, 0xFFFF),        // [NONCHARACTER CODE POINTS]
1616        array(0x1FFFE, 0x1FFFF),    // [NONCHARACTER CODE POINTS]
1617        array(0x2FFFE, 0x2FFFF),    // [NONCHARACTER CODE POINTS]
1618        array(0x3FFFE, 0x3FFFF),    // [NONCHARACTER CODE POINTS]
1619        array(0x4FFFE, 0x4FFFF),    // [NONCHARACTER CODE POINTS]
1620        array(0x5FFFE, 0x5FFFF),    // [NONCHARACTER CODE POINTS]
1621        array(0x6FFFE, 0x6FFFF),    // [NONCHARACTER CODE POINTS]
1622        array(0x7FFFE, 0x7FFFF),    // [NONCHARACTER CODE POINTS]
1623        array(0x8FFFE, 0x8FFFF),    // [NONCHARACTER CODE POINTS]
1624        array(0x9FFFE, 0x9FFFF),    // [NONCHARACTER CODE POINTS]
1625        array(0xAFFFE, 0xAFFFF),    // [NONCHARACTER CODE POINTS]
1626        array(0xBFFFE, 0xBFFFF),    // [NONCHARACTER CODE POINTS]
1627        array(0xCFFFE, 0xCFFFF),    // [NONCHARACTER CODE POINTS]
1628        array(0xDFFFE, 0xDFFFF),    // [NONCHARACTER CODE POINTS]
1629        array(0xEFFFE, 0xEFFFF),    // [NONCHARACTER CODE POINTS]
1630        array(0xFFFFE, 0xFFFFF),    // [NONCHARACTER CODE POINTS]
1631        array(0x10FFFE, 0x10FFFF),    // [NONCHARACTER CODE POINTS]
1632        // Table C.5
1633        array(0xD800, 0xDFFF),        // [SURROGATE CODES]
1634        // Table C.6
1635        array(0xFFF9, 0xFFF9),        // INTERLINEAR ANNOTATION ANCHOR
1636        array(0xFFFA, 0xFFFA),        // INTERLINEAR ANNOTATION SEPARATOR
1637        array(0xFFFB, 0xFFFB),        // INTERLINEAR ANNOTATION TERMINATOR
1638        array(0xFFFC, 0xFFFC),        // OBJECT REPLACEMENT CHARACTER
1639        array(0xFFFD, 0xFFFD),        // REPLACEMENT CHARACTER
1640        // Table C.7
1641        array(0x2FF0, 0x2FFB),        // [IDEOGRAPHIC DESCRIPTION CHARACTERS]
1642        // Table C.8
1643        array(0x0340, 0x0340),        // COMBINING GRAVE TONE MARK
1644        array(0x0341, 0x0341),        // COMBINING ACUTE TONE MARK
1645        array(0x200E, 0x200E),        // LEFT-TO-RIGHT MARK
1646        array(0x200F, 0x200F),        // RIGHT-TO-LEFT MARK
1647        array(0x202A, 0x202A),        // LEFT-TO-RIGHT EMBEDDING
1648        array(0x202B, 0x202B),        // RIGHT-TO-LEFT EMBEDDING
1649        array(0x202C, 0x202C),        // POP DIRECTIONAL FORMATTING
1650        array(0x202D, 0x202D),        // LEFT-TO-RIGHT OVERRIDE
1651        array(0x202E, 0x202E),        // RIGHT-TO-LEFT OVERRIDE
1652        array(0x206A, 0x206A),        // INHIBIT SYMMETRIC SWAPPING
1653        array(0x206B, 0x206B),        // ACTIVATE SYMMETRIC SWAPPING
1654        array(0x206C, 0x206C),        // INHIBIT ARABIC FORM SHAPING
1655        array(0x206D, 0x206D),        // ACTIVATE ARABIC FORM SHAPING
1656        array(0x206E, 0x206E),        // NATIONAL DIGIT SHAPES
1657        array(0x206F, 0x206F),        // NOMINAL DIGIT SHAPES
1658        // Table C.9
1659        array(0xE0001, 0xE0001),    // LANGUAGE TAG
1660        array(0xE0020, 0xE007F),    // [TAGGING CHARACTERS]
1661        // RFC3920
1662        array(0x22, 0x22),            // "
1663        array(0x26, 0x26),            // &
1664        array(0x27, 0x27),            // '
1665        array(0x2F, 0x2F),            // /
1666        array(0x3A, 0x3A),            // :
1667        array(0x3C, 0x3C),            // <
1668        array(0x3E, 0x3E),            // >
1669        array(0x40, 0x40)            // @
1670    );
1671
1672    $pos = 0;
1673    $result = true;
1674
1675    while ($pos < strlen($username))
1676    {
1677        $len = $uni = 0;
1678        for ($i = 0; $i <= 5; $i++)
1679        {
1680            if (ord($username[$pos]) >= $boundary[$i][0] && ord($username[$pos]) <= $boundary[$i][1])
1681            {
1682                $len = $i + 1;
1683                $uni = (ord($username[$pos]) - $boundary[$i][0]) * pow(2, $i * 6);
1684
1685                for ($k = 1; $k < $len; $k++)
1686                {
1687                    $uni += (ord($username[$pos + $k]) - 128) * pow(2, ($i - $k) * 6);
1688                }
1689
1690                break;
1691            }
1692        }
1693
1694        if ($len == 0)
1695        {
1696            return 'WRONG_DATA';
1697        }
1698
1699        foreach ($prohibited as $pval)
1700        {
1701            if ($uni >= $pval[0] && $uni <= $pval[1])
1702            {
1703                $result = false;
1704                break 2;
1705            }
1706        }
1707
1708        $pos = $pos + $len;
1709    }
1710
1711    if (!$result)
1712    {
1713        return 'WRONG_DATA';
1714    }
1715
1716    return false;
1717}
1718
1719/**
1720* Validate hex colour value
1721*
1722* @param string $colour The hex colour value
1723* @param bool $optional Whether the colour value is optional. True if an empty
1724*            string will be accepted as correct input, false if not.
1725* @return bool|string Error message if colour value is incorrect, false if it
1726*            fits the hex colour code
1727*/
1728function phpbb_validate_hex_colour($colour, $optional = false)
1729{
1730    if ($colour === '')
1731    {
1732        return (($optional) ? false : 'WRONG_DATA');
1733    }
1734
1735    if (!preg_match('/^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/', $colour))
1736    {
1737        return 'WRONG_DATA';
1738    }
1739
1740    return false;
1741}
1742
1743/**
1744* Verifies whether a style ID corresponds to an active style.
1745*
1746* @param int $style_id The style_id of a style which should be checked if activated or not.
1747* @return boolean
1748*/
1749function phpbb_style_is_active($style_id)
1750{
1751    global $db;
1752
1753    $sql = 'SELECT style_active
1754        FROM ' . STYLES_TABLE . '
1755        WHERE style_id = '. (int) $style_id;
1756    $result = $db->sql_query($sql);
1757
1758    $style_is_active = (bool) $db->sql_fetchfield('style_active');
1759    $db->sql_freeresult($result);
1760
1761    return $style_is_active;
1762}
1763
1764/**
1765* Remove avatar
1766*/
1767function avatar_delete($mode, $row, $clean_db = false)
1768{
1769    global $phpbb_container;
1770
1771    $storage = $phpbb_container->get('storage.avatar');
1772
1773    // Check if the users avatar is actually *not* a group avatar
1774    if ($mode == 'user')
1775    {
1776        if (strpos($row['user_avatar'], 'g') === 0 || (((int) $row['user_avatar'] !== 0) && ((int) $row['user_avatar'] !== (int) $row['user_id'])))
1777        {
1778            return false;
1779        }
1780    }
1781
1782    if ($clean_db)
1783    {
1784        avatar_remove_db($row[$mode . '_avatar']);
1785    }
1786    $filename = get_avatar_filename($row[$mode . '_avatar']);
1787
1788    try
1789    {
1790        $storage->delete($filename);
1791
1792        return true;
1793    }
1794    catch (\phpbb\storage\exception\storage_exception $e)
1795    {
1796        // Fail is covered by return statement below
1797    }
1798
1799    return false;
1800}
1801
1802/**
1803* Generates avatar filename from the database entry
1804*/
1805function get_avatar_filename($avatar_entry)
1806{
1807    global $config;
1808
1809    if ($avatar_entry[0] === 'g')
1810    {
1811        $avatar_group = true;
1812        $avatar_entry = substr($avatar_entry, 1);
1813    }
1814    else
1815    {
1816        $avatar_group = false;
1817    }
1818    $ext             = substr(strrchr($avatar_entry, '.'), 1);
1819    $avatar_entry    = intval($avatar_entry);
1820    return $config['avatar_salt'] . '_' . (($avatar_group) ? 'g' : '') . $avatar_entry . '.' . $ext;
1821}
1822
1823/**
1824* Returns an explanation string with maximum avatar settings
1825*
1826* @return string
1827*/
1828function phpbb_avatar_explanation_string()
1829{
1830    global $config, $user;
1831
1832    return $user->lang(($config['avatar_filesize'] == 0) ? 'AVATAR_EXPLAIN_NO_FILESIZE' : 'AVATAR_EXPLAIN',
1833        $user->lang('PIXELS', (int) $config['avatar_max_width']),
1834        $user->lang('PIXELS', (int) $config['avatar_max_height']),
1835        round($config['avatar_filesize'] / 1024));
1836}
1837
1838//
1839// Usergroup functions
1840//
1841
1842/**
1843* Add or edit a group. If we're editing a group we only update user
1844* parameters such as rank, etc. if they are changed
1845*/
1846function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow_desc_bbcode = false, $allow_desc_urls = false, $allow_desc_smilies = false)
1847{
1848    global $db, $user, $phpbb_container, $phpbb_log;
1849
1850    /** @var \phpbb\group\helper $group_helper */
1851    $group_helper = $phpbb_container->get('group_helper');
1852
1853    $error = array();
1854
1855    // Attributes which also affect the users table
1856    $user_attribute_ary = array('group_colour', 'group_rank', 'group_avatar', 'group_avatar_type', 'group_avatar_width', 'group_avatar_height');
1857
1858    // Check data. Limit group name length.
1859    if (!utf8_strlen($name) || utf8_strlen($name) > 60)
1860    {
1861        $error[] = (!utf8_strlen($name)) ? $user->lang['GROUP_ERR_USERNAME'] : $user->lang['GROUP_ERR_USER_LONG'];
1862    }
1863
1864    $err = group_validate_groupname($group_id, $name);
1865    if (!empty($err))
1866    {
1867        $error[] = $user->lang[$err];
1868    }
1869
1870    if (!in_array($type, array(GROUP_OPEN, GROUP_CLOSED, GROUP_HIDDEN, GROUP_SPECIAL, GROUP_FREE)))
1871    {
1872        $error[] = $user->lang['GROUP_ERR_TYPE'];
1873    }
1874
1875    $group_teampage = !empty($group_attributes['group_teampage']);
1876    unset($group_attributes['group_teampage']);
1877
1878    if (!count($error))
1879    {
1880        $current_legend = \phpbb\groupposition\legend::GROUP_DISABLED;
1881        $current_teampage = \phpbb\groupposition\teampage::GROUP_DISABLED;
1882
1883        /* @var $legend \phpbb\groupposition\legend */
1884        $legend = $phpbb_container->get('groupposition.legend');
1885
1886        /* @var $teampage \phpbb\groupposition\teampage */
1887        $teampage = $phpbb_container->get('groupposition.teampage');
1888
1889        if ($group_id)
1890        {
1891            try
1892            {
1893                $current_legend = $legend->get_group_value($group_id);
1894                $current_teampage = $teampage->get_group_value($group_id);
1895            }
1896            catch (\phpbb\groupposition\exception $exception)
1897            {
1898                trigger_error($user->lang($exception->getMessage()));
1899            }
1900        }
1901
1902        if (!empty($group_attributes['group_legend']))
1903        {
1904            if (($group_id && ($current_legend == \phpbb\groupposition\legend::GROUP_DISABLED)) || !$group_id)
1905            {
1906                // Old group currently not in the legend or new group, add at the end.
1907                $group_attributes['group_legend'] = 1 + $legend->get_group_count();
1908            }
1909            else
1910            {
1911                // Group stayes in the legend
1912                $group_attributes['group_legend'] = $current_legend;
1913            }
1914        }
1915        else if ($group_id && ($current_legend != \phpbb\groupposition\legend::GROUP_DISABLED))
1916        {
1917            // Group is removed from the legend
1918            try
1919            {
1920                $legend->delete_group($group_id, true);
1921            }
1922            catch (\phpbb\groupposition\exception $exception)
1923            {
1924                trigger_error($user->lang($exception->getMessage()));
1925            }
1926            $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED;
1927        }
1928        else
1929        {
1930            $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED;
1931        }
1932
1933        // Unset the objects, we don't need them anymore.
1934        unset($legend);
1935
1936        $user_ary = array();
1937        $sql_ary = array(
1938            'group_name'            => (string) $name,
1939            'group_desc'            => (string) $desc,
1940            'group_desc_uid'        => '',
1941            'group_desc_bitfield'    => '',
1942            'group_type'            => (int) $type,
1943        );
1944
1945        // Parse description
1946        if ($desc)
1947        {
1948            generate_text_for_storage($sql_ary['group_desc'], $sql_ary['group_desc_uid'], $sql_ary['group_desc_bitfield'], $sql_ary['group_desc_options'], $allow_desc_bbcode, $allow_desc_urls, $allow_desc_smilies);
1949        }
1950
1951        if (count($group_attributes))
1952        {
1953            // Merge them with $sql_ary to properly update the group
1954            $sql_ary = array_merge($sql_ary, $group_attributes);
1955        }
1956
1957        // Setting the log message before we set the group id (if group gets added)
1958        $log = ($group_id) ? 'LOG_GROUP_UPDATED' : 'LOG_GROUP_CREATED';
1959
1960        if ($group_id)
1961        {
1962            $sql = 'SELECT user_id
1963                FROM ' . USERS_TABLE . '
1964                WHERE group_id = ' . $group_id;
1965            $result = $db->sql_query($sql);
1966
1967            while ($row = $db->sql_fetchrow($result))
1968            {
1969                $user_ary[] = $row['user_id'];
1970            }
1971            $db->sql_freeresult($result);
1972
1973            if (isset($sql_ary['group_avatar']))
1974            {
1975                remove_default_avatar($group_id, $user_ary);
1976            }
1977
1978            if (isset($sql_ary['group_rank']))
1979            {
1980                remove_default_rank($group_id, $user_ary);
1981            }
1982
1983            $sql = 'UPDATE ' . GROUPS_TABLE . '
1984                SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
1985                WHERE group_id = $group_id";
1986            $db->sql_query($sql);
1987
1988            // Since we may update the name too, we need to do this on other tables too...
1989            $sql = 'UPDATE ' . MODERATOR_CACHE_TABLE . "
1990                SET group_name = '" . $db->sql_escape($sql_ary['group_name']) . "'
1991                WHERE group_id = $group_id";
1992            $db->sql_query($sql);
1993
1994            // One special case is the group skip auth setting. If this was changed we need to purge permissions for this group
1995            if (isset($group_attributes['group_skip_auth']))
1996            {
1997                // Get users within this group...
1998                $sql = 'SELECT user_id
1999                    FROM ' . USER_GROUP_TABLE . '
2000                    WHERE group_id = ' . $group_id . '
2001                        AND user_pending = 0';
2002                $result = $db->sql_query($sql);
2003
2004                $user_id_ary = array();
2005                while ($row = $db->sql_fetchrow($result))
2006                {
2007                    $user_id_ary[] = $row['user_id'];
2008                }
2009                $db->sql_freeresult($result);
2010
2011                if (!empty($user_id_ary))
2012                {
2013                    global $auth;
2014
2015                    // Clear permissions cache of relevant users
2016                    $auth->acl_clear_prefetch($user_id_ary);
2017                }
2018            }
2019        }
2020        else
2021        {
2022            $sql = 'INSERT INTO ' . GROUPS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
2023            $db->sql_query($sql);
2024        }
2025
2026        // Remove the group from the teampage, only if unselected and we are editing a group,
2027        // which is currently displayed.
2028        if (!$group_teampage && $group_id && $current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED)
2029        {
2030            try
2031            {
2032                $teampage->delete_group($group_id);
2033            }
2034            catch (\phpbb\groupposition\exception $exception)
2035            {
2036                trigger_error($user->lang($exception->getMessage()));
2037            }
2038        }
2039
2040        if (!$group_id)
2041        {
2042            $group_id = $db->sql_nextid();
2043
2044            if (isset($sql_ary['group_avatar_type']) && $sql_ary['group_avatar_type'] == 'avatar.driver.upload')
2045            {
2046                group_correct_avatar($group_id, $sql_ary['group_avatar']);
2047            }
2048        }
2049
2050        try
2051        {
2052            if ($group_teampage && $current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED)
2053            {
2054                $teampage->add_group($group_id);
2055            }
2056
2057            if ($group_teampage)
2058            {
2059                if ($current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED)
2060                {
2061                    $teampage->add_group($group_id);
2062                }
2063            }
2064            else if ($group_id && ($current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED))
2065            {
2066                $teampage->delete_group($group_id);
2067            }
2068        }
2069        catch (\phpbb\groupposition\exception $exception)
2070        {
2071            trigger_error($user->lang($exception->getMessage()));
2072        }
2073        unset($teampage);
2074
2075        // Set user attributes
2076        $sql_ary = array();
2077        if (count($group_attributes))
2078        {
2079            // Go through the user attributes array, check if a group attribute matches it and then set it. ;)
2080            foreach ($user_attribute_ary as $attribute)
2081            {
2082                if (!isset($group_attributes[$attribute]))
2083                {
2084                    continue;
2085                }
2086
2087                // If we are about to set an avatar, we will not overwrite user avatars if no group avatar is set...
2088                if (strpos($attribute, 'group_avatar') === 0 && !$group_attributes[$attribute])
2089                {
2090                    continue;
2091                }
2092
2093                $sql_ary[$attribute] = $group_attributes[$attribute];
2094            }
2095        }
2096
2097        if (count($sql_ary) && count($user_ary))
2098        {
2099            group_set_user_default($group_id, $user_ary, $sql_ary);
2100        }
2101
2102        $name = $group_helper->get_name($name);
2103        $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($name));
2104
2105        group_update_listings($group_id);
2106    }
2107
2108    return (count($error)) ? $error : false;
2109}
2110
2111
2112/**
2113* Changes a group avatar's filename to conform to the naming scheme
2114*/
2115function group_correct_avatar($group_id, $old_entry)
2116{
2117    global $config, $db, $phpbb_container;
2118
2119    $storage = $phpbb_container->get('storage.avatar');
2120
2121    $group_id        = (int) $group_id;
2122    $ext             = substr(strrchr($old_entry, '.'), 1);
2123    $old_filename     = get_avatar_filename($old_entry);
2124    $new_filename     = $config['avatar_salt'] . "_g$group_id.$ext";
2125    $new_entry         = 'g' . $group_id . '_' . substr(time(), -5) . ".$ext";
2126
2127    try
2128    {
2129        $storage->rename($old_filename, $new_filename);
2130
2131        $sql = 'UPDATE ' . GROUPS_TABLE . '
2132            SET group_avatar = \'' . $db->sql_escape($new_entry) . "'
2133            WHERE group_id = $group_id";
2134        $db->sql_query($sql);
2135    }
2136    catch (\phpbb\storage\exception\storage_exception $e)
2137    {
2138        // If rename fail, dont execute the query
2139    }
2140}
2141
2142
2143/**
2144* Remove avatar also for users not having the group as default
2145*/
2146function avatar_remove_db($avatar_name)
2147{
2148    global $db;
2149
2150    $sql = 'UPDATE ' . USERS_TABLE . "
2151        SET user_avatar = '',
2152        user_avatar_type = ''
2153        WHERE user_avatar = '" . $db->sql_escape($avatar_name) . '\'';
2154    $db->sql_query($sql);
2155}
2156
2157
2158/**
2159* Group Delete
2160*/
2161function group_delete($group_id, $group_name = false)
2162{
2163    global $db, $cache, $auth, $user, $phpbb_root_path, $phpEx, $phpbb_dispatcher, $phpbb_container, $phpbb_log;
2164
2165    if (!$group_name)
2166    {
2167        $group_name = get_group_name($group_id);
2168    }
2169
2170    $start = 0;
2171
2172    do
2173    {
2174        $user_id_ary = $username_ary = array();
2175
2176        // Batch query for group members, call group_user_del
2177        $sql = 'SELECT u.user_id, u.username
2178            FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . " u
2179            WHERE ug.group_id = $group_id
2180                AND u.user_id = ug.user_id";
2181        $result = $db->sql_query_limit($sql, 200, $start);
2182
2183        if ($row = $db->sql_fetchrow($result))
2184        {
2185            do
2186            {
2187                $user_id_ary[] = $row['user_id'];
2188                $username_ary[] = $row['username'];
2189
2190                $start++;
2191            }
2192            while ($row = $db->sql_fetchrow($result));
2193
2194            group_user_del($group_id, $user_id_ary, $username_ary, $group_name);
2195        }
2196        else
2197        {
2198            $start = 0;
2199        }
2200        $db->sql_freeresult($result);
2201    }
2202    while ($start);
2203
2204    // Delete group from legend and teampage
2205    try
2206    {
2207        /* @var $legend \phpbb\groupposition\legend */
2208        $legend = $phpbb_container->get('groupposition.legend');
2209        $legend->delete_group($group_id);
2210        unset($legend);
2211    }
2212    catch (\phpbb\groupposition\exception $exception)
2213    {
2214        // The group we want to delete does not exist.
2215        // No reason to worry, we just continue the deleting process.
2216        //trigger_error($user->lang($exception->getMessage()));
2217    }
2218
2219    try
2220    {
2221        /* @var $teampage \phpbb\groupposition\teampage */
2222        $teampage = $phpbb_container->get('groupposition.teampage');
2223        $teampage->delete_group($group_id);
2224        unset($teampage);
2225    }
2226    catch (\phpbb\groupposition\exception $exception)
2227    {
2228        // The group we want to delete does not exist.
2229        // No reason to worry, we just continue the deleting process.
2230        //trigger_error($user->lang($exception->getMessage()));
2231    }
2232
2233    // Delete group
2234    $sql = 'DELETE FROM ' . GROUPS_TABLE . "
2235        WHERE group_id = $group_id";
2236    $db->sql_query($sql);
2237
2238    // Delete auth entries from the groups table
2239    $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . "
2240        WHERE group_id = $group_id";
2241    $db->sql_query($sql);
2242
2243    /**
2244    * Event after a group is deleted
2245    *
2246    * @event core.delete_group_after
2247    * @var    int        group_id    ID of the deleted group
2248    * @var    string    group_name    Name of the deleted group
2249    * @since 3.1.0-a1
2250    */
2251    $vars = array('group_id', 'group_name');
2252    extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars)));
2253
2254    // Re-cache moderators
2255    if (!function_exists('phpbb_cache_moderators'))
2256    {
2257        include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
2258    }
2259
2260    phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth);
2261
2262    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_GROUP_DELETE', false, array($group_name));
2263
2264    // Return false - no error
2265    return false;
2266}
2267
2268/**
2269* Add user(s) to group
2270*
2271* @return string|false false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER'
2272*/
2273function group_user_add($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $default = false, $leader = 0, $pending = 0, $group_attributes = false)
2274{
2275    global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher;
2276
2277    // We need both username and user_id info
2278    $result = user_get_id_name($user_id_ary, $username_ary);
2279
2280    if (empty($user_id_ary) || $result !== false)
2281    {
2282        return 'NO_USER';
2283    }
2284
2285    // Because the item that gets passed into the previous function is unset, the reference is lost and our original
2286    // array is retained - so we know there's a problem if there's a different number of ids to usernames now.
2287    if (count($user_id_ary) != count($username_ary))
2288    {
2289        return 'GROUP_USERS_INVALID';
2290    }
2291
2292    // Remove users who are already members of this group
2293    $sql = 'SELECT user_id, group_leader
2294        FROM ' . USER_GROUP_TABLE . '
2295        WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . "
2296            AND group_id = $group_id";
2297    $result = $db->sql_query($sql);
2298
2299    $add_id_ary = $update_id_ary = array();
2300    while ($row = $db->sql_fetchrow($result))
2301    {
2302        $add_id_ary[] = (int) $row['user_id'];
2303
2304        if ($leader && !$row['group_leader'])
2305        {
2306            $update_id_ary[] = (int) $row['user_id'];
2307        }
2308    }
2309    $db->sql_freeresult($result);
2310
2311    // Do all the users exist in this group?
2312    $add_id_ary = array_diff($user_id_ary, $add_id_ary);
2313
2314    // If we have no users
2315    if (!count($add_id_ary) && !count($update_id_ary))
2316    {
2317        return 'GROUP_USERS_EXIST';
2318    }
2319
2320    /**
2321     * Event before users are added to a group
2322     *
2323     * @event core.group_add_user_before
2324     * @var    int        group_id        ID of the group to which users are added
2325     * @var    string     group_name        Name of the group
2326     * @var    array    user_id_ary        IDs of the users to be added
2327     * @var    array    username_ary    Names of the users to be added
2328     * @var    int        pending            Pending setting, 1 if user(s) added are pending
2329     * @var    array    add_id_ary        IDs of the users to be added who are not members yet
2330     * @since 3.3.15-RC1
2331     */
2332    $vars = array(
2333        'group_id',
2334        'group_name',
2335        'user_id_ary',
2336        'username_ary',
2337        'pending',
2338        'add_id_ary',
2339    );
2340    extract($phpbb_dispatcher->trigger_event('core.group_add_user_before', compact($vars)));
2341
2342    $db->sql_transaction('begin');
2343
2344    // Insert the new users
2345    if (count($add_id_ary))
2346    {
2347        $sql_ary = array();
2348
2349        foreach ($add_id_ary as $user_id)
2350        {
2351            $sql_ary[] = array(
2352                'user_id'        => (int) $user_id,
2353                'group_id'        => (int) $group_id,
2354                'group_leader'    => (int) $leader,
2355                'user_pending'    => (int) $pending,
2356            );
2357        }
2358
2359        $db->sql_multi_insert(USER_GROUP_TABLE, $sql_ary);
2360    }
2361
2362    if (count($update_id_ary))
2363    {
2364        $sql = 'UPDATE ' . USER_GROUP_TABLE . '
2365            SET group_leader = 1
2366            WHERE ' . $db->sql_in_set('user_id', $update_id_ary) . "
2367                AND group_id = $group_id";
2368        $db->sql_query($sql);
2369    }
2370
2371    if ($default)
2372    {
2373        group_user_attributes('default', $group_id, $user_id_ary, false, $group_name, $group_attributes);
2374    }
2375
2376    $db->sql_transaction('commit');
2377
2378    // Clear permissions cache of relevant users
2379    $auth->acl_clear_prefetch($user_id_ary);
2380
2381    /**
2382    * Event after users are added to a group
2383    *
2384    * @event core.group_add_user_after
2385    * @var    int    group_id        ID of the group to which users are added
2386    * @var    string group_name        Name of the group
2387    * @var    array    user_id_ary        IDs of the users which are added
2388    * @var    array    username_ary    names of the users which are added
2389    * @var    int        pending            Pending setting, 1 if user(s) added are pending
2390    * @since 3.1.7-RC1
2391    */
2392    $vars = array(
2393        'group_id',
2394        'group_name',
2395        'user_id_ary',
2396        'username_ary',
2397        'pending',
2398    );
2399    extract($phpbb_dispatcher->trigger_event('core.group_add_user_after', compact($vars)));
2400
2401    if (!$group_name)
2402    {
2403        $group_name = get_group_name($group_id);
2404    }
2405
2406    $log = ($leader) ? 'LOG_MODS_ADDED' : (($pending) ? 'LOG_USERS_PENDING' : 'LOG_USERS_ADDED');
2407
2408    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2409
2410    group_update_listings($group_id);
2411
2412    if ($pending)
2413    {
2414        /* @var $phpbb_notifications \phpbb\notification\manager */
2415        $phpbb_notifications = $phpbb_container->get('notification_manager');
2416
2417        foreach ($add_id_ary as $user_id)
2418        {
2419            $phpbb_notifications->add_notifications('notification.type.group_request', array(
2420                'group_id'        => $group_id,
2421                'user_id'        => $user_id,
2422                'group_name'    => $group_name,
2423            ));
2424        }
2425    }
2426
2427    // Return false - no error
2428    return false;
2429}
2430
2431/**
2432* Remove a user/s from a given group. When we remove users we update their
2433* default group_id. We do this by examining which "special" groups they belong
2434* to. The selection is made based on a reasonable priority system
2435*
2436* @return false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER'
2437*/
2438function group_user_del($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $log_action = true)
2439{
2440    global $db, $auth, $config, $user, $phpbb_dispatcher, $phpbb_container, $phpbb_log;
2441
2442    if ($config['coppa_enable'])
2443    {
2444        $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED_COPPA', 'REGISTERED', 'BOTS', 'GUESTS');
2445    }
2446    else
2447    {
2448        $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED', 'BOTS', 'GUESTS');
2449    }
2450
2451    // We need both username and user_id info
2452    $result = user_get_id_name($user_id_ary, $username_ary);
2453
2454    if (empty($user_id_ary) || $result !== false)
2455    {
2456        return 'NO_USER';
2457    }
2458
2459    $sql = 'SELECT *
2460        FROM ' . GROUPS_TABLE . '
2461        WHERE ' . $db->sql_in_set('group_name', $group_order);
2462    $result = $db->sql_query($sql);
2463
2464    $group_order_id = $special_group_data = array();
2465    while ($row = $db->sql_fetchrow($result))
2466    {
2467        $group_order_id[$row['group_name']] = $row['group_id'];
2468
2469        $special_group_data[$row['group_id']] = array(
2470            'group_colour'            => $row['group_colour'],
2471            'group_rank'                => $row['group_rank'],
2472        );
2473
2474        // Only set the group avatar if one is defined...
2475        if ($row['group_avatar'])
2476        {
2477            $special_group_data[$row['group_id']] = array_merge($special_group_data[$row['group_id']], array(
2478                'group_avatar'            => $row['group_avatar'],
2479                'group_avatar_type'        => $row['group_avatar_type'],
2480                'group_avatar_width'        => $row['group_avatar_width'],
2481                'group_avatar_height'    => $row['group_avatar_height'])
2482            );
2483        }
2484    }
2485    $db->sql_freeresult($result);
2486
2487    // Get users default groups - we only need to reset default group membership if the group from which the user gets removed is set as default
2488    $sql = 'SELECT user_id, group_id
2489        FROM ' . USERS_TABLE . '
2490        WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
2491    $result = $db->sql_query($sql);
2492
2493    $default_groups = array();
2494    while ($row = $db->sql_fetchrow($result))
2495    {
2496        $default_groups[$row['user_id']] = $row['group_id'];
2497    }
2498    $db->sql_freeresult($result);
2499
2500    // What special group memberships exist for these users?
2501    $sql = 'SELECT g.group_id, g.group_name, ug.user_id
2502        FROM ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g
2503        WHERE ' . $db->sql_in_set('ug.user_id', $user_id_ary) . "
2504            AND g.group_id = ug.group_id
2505            AND g.group_id <> $group_id
2506            AND g.group_type = " . GROUP_SPECIAL . '
2507        ORDER BY ug.user_id, g.group_id';
2508    $result = $db->sql_query($sql);
2509
2510    $temp_ary = array();
2511    while ($row = $db->sql_fetchrow($result))
2512    {
2513        if ($default_groups[$row['user_id']] == $group_id && (!isset($temp_ary[$row['user_id']]) || $group_order_id[$row['group_name']] < $temp_ary[$row['user_id']]))
2514        {
2515            $temp_ary[$row['user_id']] = $row['group_id'];
2516        }
2517    }
2518    $db->sql_freeresult($result);
2519
2520    // sql_where_ary holds the new default groups and their users
2521    $sql_where_ary = array();
2522    foreach ($temp_ary as $uid => $gid)
2523    {
2524        $sql_where_ary[$gid][] = $uid;
2525    }
2526    unset($temp_ary);
2527
2528    foreach ($special_group_data as $gid => $default_data_ary)
2529    {
2530        if (isset($sql_where_ary[$gid]) && count($sql_where_ary[$gid]))
2531        {
2532            remove_default_rank($group_id, $sql_where_ary[$gid]);
2533            remove_default_avatar($group_id, $sql_where_ary[$gid]);
2534            group_set_user_default($gid, $sql_where_ary[$gid], $default_data_ary);
2535        }
2536    }
2537    unset($special_group_data);
2538
2539    /**
2540    * Event before users are removed from a group
2541    *
2542    * @event core.group_delete_user_before
2543    * @var    int        group_id        ID of the group from which users are deleted
2544    * @var    string    group_name        Name of the group
2545    * @var    array    user_id_ary        IDs of the users which are removed
2546    * @var    array    username_ary    names of the users which are removed
2547    * @since 3.1.0-a1
2548    */
2549    $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary');
2550    extract($phpbb_dispatcher->trigger_event('core.group_delete_user_before', compact($vars)));
2551
2552    $sql = 'DELETE FROM ' . USER_GROUP_TABLE . "
2553        WHERE group_id = $group_id
2554            AND " . $db->sql_in_set('user_id', $user_id_ary);
2555    $db->sql_query($sql);
2556
2557    // Clear permissions cache of relevant users
2558    $auth->acl_clear_prefetch($user_id_ary);
2559
2560    /**
2561    * Event after users are removed from a group
2562    *
2563    * @event core.group_delete_user_after
2564    * @var    int        group_id        ID of the group from which users are deleted
2565    * @var    string    group_name        Name of the group
2566    * @var    array    user_id_ary        IDs of the users which are removed
2567    * @var    array    username_ary    names of the users which are removed
2568    * @since 3.1.7-RC1
2569    */
2570    $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary');
2571    extract($phpbb_dispatcher->trigger_event('core.group_delete_user_after', compact($vars)));
2572
2573    if ($log_action)
2574    {
2575        if (!$group_name)
2576        {
2577            $group_name = get_group_name($group_id);
2578        }
2579
2580        $log = 'LOG_GROUP_REMOVE';
2581
2582        if ($group_name)
2583        {
2584            $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2585        }
2586    }
2587
2588    group_update_listings($group_id);
2589
2590    /* @var $phpbb_notifications \phpbb\notification\manager */
2591    $phpbb_notifications = $phpbb_container->get('notification_manager');
2592
2593    $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id);
2594
2595    // Return false - no error
2596    return false;
2597}
2598
2599
2600/**
2601* Removes the group avatar of the default group from the users in user_ids who have that group as default.
2602*/
2603function remove_default_avatar($group_id, $user_ids)
2604{
2605    global $db;
2606
2607    if (!is_array($user_ids))
2608    {
2609        $user_ids = array($user_ids);
2610    }
2611    if (empty($user_ids))
2612    {
2613        return false;
2614    }
2615
2616    $user_ids = array_map('intval', $user_ids);
2617
2618    $sql = 'SELECT *
2619        FROM ' . GROUPS_TABLE . '
2620        WHERE group_id = ' . (int) $group_id;
2621    $result = $db->sql_query($sql);
2622    if (!$row = $db->sql_fetchrow($result))
2623    {
2624        $db->sql_freeresult($result);
2625        return false;
2626    }
2627    $db->sql_freeresult($result);
2628
2629    $sql = 'UPDATE ' . USERS_TABLE . "
2630        SET user_avatar = '',
2631            user_avatar_type = '',
2632            user_avatar_width = 0,
2633            user_avatar_height = 0
2634        WHERE group_id = " . (int) $group_id . "
2635            AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
2636            AND " . $db->sql_in_set('user_id', $user_ids);
2637
2638    $db->sql_query($sql);
2639}
2640
2641/**
2642* Removes the group rank of the default group from the users in user_ids who have that group as default.
2643* @return bool true if successful, false if not
2644*/
2645function remove_default_rank($group_id, $user_ids)
2646{
2647    global $db;
2648
2649    if (!is_array($user_ids))
2650    {
2651        $user_ids = array($user_ids);
2652    }
2653    if (empty($user_ids))
2654    {
2655        return false;
2656    }
2657
2658    $user_ids = array_map('intval', $user_ids);
2659
2660    $sql = 'SELECT *
2661        FROM ' . GROUPS_TABLE . '
2662        WHERE group_id = ' . (int) $group_id;
2663    $result = $db->sql_query($sql);
2664    if (!$row = $db->sql_fetchrow($result))
2665    {
2666        $db->sql_freeresult($result);
2667        return false;
2668    }
2669    $db->sql_freeresult($result);
2670
2671    $sql = 'UPDATE ' . USERS_TABLE . '
2672        SET user_rank = 0
2673        WHERE group_id = ' . (int) $group_id . '
2674            AND user_rank <> 0
2675            AND user_rank = ' . (int) $row['group_rank'] . '
2676            AND ' . $db->sql_in_set('user_id', $user_ids);
2677    $db->sql_query($sql);
2678
2679    return true;
2680}
2681
2682/**
2683* This is used to promote (to leader), demote or set as default a member/s
2684*/
2685function group_user_attributes($action, $group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $group_attributes = false)
2686{
2687    global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher;
2688
2689    // We need both username and user_id info
2690    $result = user_get_id_name($user_id_ary, $username_ary);
2691
2692    if (empty($user_id_ary) || $result !== false)
2693    {
2694        return 'NO_USERS';
2695    }
2696
2697    if (!$group_name)
2698    {
2699        $group_name = get_group_name($group_id);
2700    }
2701
2702    switch ($action)
2703    {
2704        case 'demote':
2705        case 'promote':
2706
2707            $sql = 'SELECT user_id
2708                FROM ' . USER_GROUP_TABLE . "
2709                WHERE group_id = $group_id
2710                    AND user_pending = 1
2711                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2712            $result = $db->sql_query_limit($sql, 1);
2713            $not_empty = ($db->sql_fetchrow($result));
2714            $db->sql_freeresult($result);
2715            if ($not_empty)
2716            {
2717                return 'NO_VALID_USERS';
2718            }
2719
2720            $sql = 'UPDATE ' . USER_GROUP_TABLE . '
2721                SET group_leader = ' . (($action == 'promote') ? 1 : 0) . "
2722                WHERE group_id = $group_id
2723                    AND user_pending = 0
2724                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2725            $db->sql_query($sql);
2726
2727            $log = ($action == 'promote') ? 'LOG_GROUP_PROMOTED' : 'LOG_GROUP_DEMOTED';
2728        break;
2729
2730        case 'approve':
2731            // Make sure we only approve those which are pending ;)
2732            $sql = 'SELECT u.user_id, u.user_email, u.username, u.username_clean, u.user_notify_type, u.user_jabber, u.user_lang
2733                FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug
2734                WHERE ug.group_id = ' . $group_id . '
2735                    AND ug.user_pending = 1
2736                    AND ug.user_id = u.user_id
2737                    AND ' . $db->sql_in_set('ug.user_id', $user_id_ary);
2738            $result = $db->sql_query($sql);
2739
2740            $user_id_ary = array();
2741            while ($row = $db->sql_fetchrow($result))
2742            {
2743                $user_id_ary[] = $row['user_id'];
2744            }
2745            $db->sql_freeresult($result);
2746
2747            if (!count($user_id_ary))
2748            {
2749                return false;
2750            }
2751
2752            $sql = 'UPDATE ' . USER_GROUP_TABLE . "
2753                SET user_pending = 0
2754                WHERE group_id = $group_id
2755                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2756            $db->sql_query($sql);
2757
2758            /* @var $phpbb_notifications \phpbb\notification\manager */
2759            $phpbb_notifications = $phpbb_container->get('notification_manager');
2760
2761            $phpbb_notifications->add_notifications('notification.type.group_request_approved', array(
2762                'user_ids'        => $user_id_ary,
2763                'group_id'        => $group_id,
2764                'group_name'    => $group_name,
2765            ));
2766            $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id);
2767
2768            $log = 'LOG_USERS_APPROVED';
2769        break;
2770
2771        case 'default':
2772            // We only set default group for approved members of the group
2773            $sql = 'SELECT user_id
2774                FROM ' . USER_GROUP_TABLE . "
2775                WHERE group_id = $group_id
2776                    AND user_pending = 0
2777                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2778            $result = $db->sql_query($sql);
2779
2780            $user_id_ary = $username_ary = array();
2781            while ($row = $db->sql_fetchrow($result))
2782            {
2783                $user_id_ary[] = $row['user_id'];
2784            }
2785            $db->sql_freeresult($result);
2786
2787            $result = user_get_id_name($user_id_ary, $username_ary);
2788            if (!count($user_id_ary) || $result !== false)
2789            {
2790                return 'NO_USERS';
2791            }
2792
2793            $sql = 'SELECT user_id, group_id
2794                FROM ' . USERS_TABLE . '
2795                WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true);
2796            $result = $db->sql_query($sql);
2797
2798            $groups = array();
2799            while ($row = $db->sql_fetchrow($result))
2800            {
2801                if (!isset($groups[$row['group_id']]))
2802                {
2803                    $groups[$row['group_id']] = array();
2804                }
2805                $groups[$row['group_id']][] = $row['user_id'];
2806            }
2807            $db->sql_freeresult($result);
2808
2809            foreach ($groups as $gid => $uids)
2810            {
2811                remove_default_rank($gid, $uids);
2812                remove_default_avatar($gid, $uids);
2813            }
2814            group_set_user_default($group_id, $user_id_ary, $group_attributes);
2815            $log = 'LOG_GROUP_DEFAULTS';
2816        break;
2817    }
2818
2819    /**
2820    * Event to perform additional actions on setting user group attributes
2821    *
2822    * @event core.user_set_group_attributes
2823    * @var    int        group_id            ID of the group
2824    * @var    string    group_name            Name of the group
2825    * @var    array    user_id_ary            IDs of the users to set group attributes
2826    * @var    array    username_ary        Names of the users to set group attributes
2827    * @var    array    group_attributes    Group attributes which were changed
2828    * @var    string    action                Action to perform over the group members
2829    * @since 3.1.10-RC1
2830    */
2831    $vars = array(
2832        'group_id',
2833        'group_name',
2834        'user_id_ary',
2835        'username_ary',
2836        'group_attributes',
2837        'action',
2838    );
2839    extract($phpbb_dispatcher->trigger_event('core.user_set_group_attributes', compact($vars)));
2840
2841    // Clear permissions cache of relevant users
2842    $auth->acl_clear_prefetch($user_id_ary);
2843
2844    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2845
2846    group_update_listings($group_id);
2847
2848    return false;
2849}
2850
2851/**
2852* A small version of validate_username to check for a group name's existence. To be called directly.
2853*/
2854function group_validate_groupname($group_id, $group_name)
2855{
2856    global $db;
2857
2858    $group_name =  utf8_clean_string($group_name);
2859
2860    if (!empty($group_id))
2861    {
2862        $sql = 'SELECT group_name
2863            FROM ' . GROUPS_TABLE . '
2864            WHERE group_id = ' . (int) $group_id;
2865        $result = $db->sql_query($sql);
2866        $row = $db->sql_fetchrow($result);
2867        $db->sql_freeresult($result);
2868
2869        if (!$row)
2870        {
2871            return false;
2872        }
2873
2874        $allowed_groupname = utf8_clean_string($row['group_name']);
2875
2876        if ($allowed_groupname == $group_name)
2877        {
2878            return false;
2879        }
2880    }
2881
2882    $sql = 'SELECT group_name
2883        FROM ' . GROUPS_TABLE . "
2884        WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($group_name)) . "'";
2885    $result = $db->sql_query($sql);
2886    $row = $db->sql_fetchrow($result);
2887    $db->sql_freeresult($result);
2888
2889    if ($row)
2890    {
2891        return 'GROUP_NAME_TAKEN';
2892    }
2893
2894    return false;
2895}
2896
2897/**
2898* Set users default group
2899*
2900* @access private
2901*/
2902function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false)
2903{
2904    global $config, $phpbb_container, $db, $phpbb_dispatcher;
2905
2906    if (empty($user_id_ary))
2907    {
2908        return;
2909    }
2910
2911    $attribute_ary = array(
2912        'group_colour'            => 'string',
2913        'group_rank'            => 'int',
2914        'group_avatar'            => 'string',
2915        'group_avatar_type'        => 'string',
2916        'group_avatar_width'    => 'int',
2917        'group_avatar_height'    => 'int',
2918    );
2919
2920    $sql_ary = array(
2921        'group_id'        => $group_id
2922    );
2923
2924    // Were group attributes passed to the function? If not we need to obtain them
2925    if ($group_attributes === false)
2926    {
2927        $sql = 'SELECT ' . implode(', ', array_keys($attribute_ary)) . '
2928            FROM ' . GROUPS_TABLE . "
2929            WHERE group_id = $group_id";
2930        $result = $db->sql_query($sql);
2931        $group_attributes = $db->sql_fetchrow($result);
2932        $db->sql_freeresult($result);
2933    }
2934
2935    foreach ($attribute_ary as $attribute => $type)
2936    {
2937        if (isset($group_attributes[$attribute]))
2938        {
2939            // If we are about to set an avatar or rank, we will not overwrite with empty, unless we are not actually changing the default group
2940            if ((strpos($attribute, 'group_avatar') === 0 || strpos($attribute, 'group_rank') === 0) && !$group_attributes[$attribute])
2941            {
2942                continue;
2943            }
2944
2945            settype($group_attributes[$attribute], $type);
2946            $sql_ary[str_replace('group_', 'user_', $attribute)] = $group_attributes[$attribute];
2947        }
2948    }
2949
2950    $updated_sql_ary = $sql_ary;
2951
2952    // Before we update the user attributes, we will update the rank for users that don't have a custom rank
2953    if (isset($sql_ary['user_rank']))
2954    {
2955        $sql = 'UPDATE ' . USERS_TABLE . '
2956            SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . '
2957            WHERE user_rank = 0
2958                AND ' . $db->sql_in_set('user_id', $user_id_ary);
2959        $db->sql_query($sql);
2960        unset($sql_ary['user_rank']);
2961    }
2962
2963    // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar
2964    $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width');
2965
2966    if (isset($sql_ary['user_avatar']))
2967    {
2968        $avatar_sql_ary = array();
2969        foreach ($avatar_options as $avatar_option)
2970        {
2971            if (isset($sql_ary[$avatar_option]))
2972            {
2973                $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option];
2974            }
2975        }
2976
2977        $sql = 'UPDATE ' . USERS_TABLE . '
2978            SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . "
2979            WHERE user_avatar = ''
2980                AND " . $db->sql_in_set('user_id', $user_id_ary);
2981        $db->sql_query($sql);
2982    }
2983
2984    // Remove the avatar options, as we already updated them
2985    foreach ($avatar_options as $avatar_option)
2986    {
2987        unset($sql_ary[$avatar_option]);
2988    }
2989
2990    if (!empty($sql_ary))
2991    {
2992        $sql = 'UPDATE ' . USERS_TABLE . '
2993            SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2994            WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
2995        $db->sql_query($sql);
2996    }
2997
2998    if (isset($sql_ary['user_colour']))
2999    {
3000        // Update any cached colour information for these users
3001        $sql = 'UPDATE ' . FORUMS_TABLE . "
3002            SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
3003            WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary);
3004        $db->sql_query($sql);
3005
3006        $sql = 'UPDATE ' . TOPICS_TABLE . "
3007            SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
3008            WHERE " . $db->sql_in_set('topic_poster', $user_id_ary);
3009        $db->sql_query($sql);
3010
3011        $sql = 'UPDATE ' . TOPICS_TABLE . "
3012            SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
3013            WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary);
3014        $db->sql_query($sql);
3015
3016        if (in_array($config['newest_user_id'], $user_id_ary))
3017        {
3018            $config->set('newest_user_colour', $sql_ary['user_colour'], false);
3019        }
3020    }
3021
3022    // Make all values available for the event
3023    $sql_ary = $updated_sql_ary;
3024
3025    /**
3026    * Event when the default group is set for an array of users
3027    *
3028    * @event core.user_set_default_group
3029    * @var    int        group_id            ID of the group
3030    * @var    array    user_id_ary            IDs of the users
3031    * @var    array    group_attributes    Group attributes which were changed
3032    * @var    array    update_listing        Update the list of moderators and foes
3033    * @var    array    sql_ary                User attributes which were changed
3034    * @since 3.1.0-a1
3035    */
3036    $vars = array('group_id', 'user_id_ary', 'group_attributes', 'update_listing', 'sql_ary');
3037    extract($phpbb_dispatcher->trigger_event('core.user_set_default_group', compact($vars)));
3038
3039    if ($update_listing)
3040    {
3041        group_update_listings($group_id);
3042    }
3043
3044    // Because some tables/caches use usercolour-specific data we need to purge this here.
3045    $phpbb_container->get('cache.driver')->destroy('sql', MODERATOR_CACHE_TABLE);
3046}
3047
3048/**
3049* Get group name
3050*/
3051function get_group_name($group_id)
3052{
3053    global $db, $phpbb_container;
3054
3055    $sql = 'SELECT group_name, group_type
3056        FROM ' . GROUPS_TABLE . '
3057        WHERE group_id = ' . (int) $group_id;
3058    $result = $db->sql_query($sql);
3059    $row = $db->sql_fetchrow($result);
3060    $db->sql_freeresult($result);
3061
3062    if (!$row)
3063    {
3064        return '';
3065    }
3066
3067    /** @var \phpbb\group\helper $group_helper */
3068    $group_helper = $phpbb_container->get('group_helper');
3069
3070    return $group_helper->get_name($row['group_name']);
3071}
3072
3073/**
3074* Obtain either the members of a specified group, the groups the specified user is subscribed to
3075* or checking if a specified user is in a specified group. This function does not return pending memberships.
3076*
3077* Note: Never use this more than once... first group your users/groups
3078*/
3079function group_memberships($group_id_ary = false, $user_id_ary = false, $return_bool = false)
3080{
3081    global $db;
3082
3083    if (!$group_id_ary && !$user_id_ary)
3084    {
3085        return true;
3086    }
3087
3088    if ($user_id_ary)
3089    {
3090        $user_id_ary = (!is_array($user_id_ary)) ? array($user_id_ary) : $user_id_ary;
3091    }
3092
3093    if ($group_id_ary)
3094    {
3095        $group_id_ary = (!is_array($group_id_ary)) ? array($group_id_ary) : $group_id_ary;
3096    }
3097
3098    $sql = 'SELECT ug.*, u.username, u.username_clean, u.user_email
3099        FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u
3100        WHERE ug.user_id = u.user_id
3101            AND ug.user_pending = 0 AND ';
3102
3103    if ($group_id_ary)
3104    {
3105        $sql .= ' ' . $db->sql_in_set('ug.group_id', $group_id_ary);
3106    }
3107
3108    if ($user_id_ary)
3109    {
3110        $sql .= ($group_id_ary) ? ' AND ' : ' ';
3111        $sql .= $db->sql_in_set('ug.user_id', $user_id_ary);
3112    }
3113
3114    $result = ($return_bool) ? $db->sql_query_limit($sql, 1) : $db->sql_query($sql);
3115
3116    $row = $db->sql_fetchrow($result);
3117
3118    if ($return_bool)
3119    {
3120        $db->sql_freeresult($result);
3121        return ($row) ? true : false;
3122    }
3123
3124    if (!$row)
3125    {
3126        return false;
3127    }
3128
3129    $return = array();
3130
3131    do
3132    {
3133        $return[] = $row;
3134    }
3135    while ($row = $db->sql_fetchrow($result));
3136
3137    $db->sql_freeresult($result);
3138
3139    return $return;
3140}
3141
3142/**
3143* Re-cache moderators and foes if group has a_ or m_ permissions
3144*/
3145function group_update_listings($group_id)
3146{
3147    global $db, $cache, $auth;
3148
3149    $hold_ary = $auth->acl_group_raw_data($group_id, array('a_', 'm_'));
3150
3151    if (empty($hold_ary))
3152    {
3153        return;
3154    }
3155
3156    $mod_permissions = $admin_permissions = false;
3157
3158    foreach ($hold_ary as $g_id => $forum_ary)
3159    {
3160        foreach ($forum_ary as $forum_id => $auth_ary)
3161        {
3162            foreach ($auth_ary as $auth_option => $setting)
3163            {
3164                if ($mod_permissions && $admin_permissions)
3165                {
3166                    break 3;
3167                }
3168
3169                if ($setting != ACL_YES)
3170                {
3171                    continue;
3172                }
3173
3174                if ($auth_option == 'm_')
3175                {
3176                    $mod_permissions = true;
3177                }
3178
3179                if ($auth_option == 'a_')
3180                {
3181                    $admin_permissions = true;
3182                }
3183            }
3184        }
3185    }
3186
3187    if ($mod_permissions)
3188    {
3189        if (!function_exists('phpbb_cache_moderators'))
3190        {
3191            global $phpbb_root_path, $phpEx;
3192            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
3193        }
3194
3195        global $phpbb_container;
3196
3197        phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth);
3198    }
3199
3200    if ($mod_permissions || $admin_permissions)
3201    {
3202        if (!function_exists('phpbb_update_foes'))
3203        {
3204            global $phpbb_root_path, $phpEx;
3205            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
3206        }
3207        phpbb_update_foes($db, $auth, array($group_id));
3208    }
3209}
3210
3211
3212
3213/**
3214* Funtion to make a user leave the NEWLY_REGISTERED system group.
3215* @access public
3216* @param int $user_id The id of the user to remove from the group
3217* @param mixed $user_data The id of the user to remove from the group
3218*/
3219function remove_newly_registered($user_id, $user_data = false)
3220{
3221    global $db;
3222
3223    if ($user_data === false)
3224    {
3225        $sql = 'SELECT *
3226            FROM ' . USERS_TABLE . '
3227            WHERE user_id = ' . $user_id;
3228        $result = $db->sql_query($sql);
3229        $user_row = $db->sql_fetchrow($result);
3230        $db->sql_freeresult($result);
3231
3232        if (!$user_row)
3233        {
3234            return false;
3235        }
3236        else
3237        {
3238            $user_data  = $user_row;
3239        }
3240    }
3241
3242    $sql = 'SELECT group_id
3243        FROM ' . GROUPS_TABLE . "
3244        WHERE group_name = 'NEWLY_REGISTERED'
3245            AND group_type = " . GROUP_SPECIAL;
3246    $result = $db->sql_query($sql);
3247    $group_id = (int) $db->sql_fetchfield('group_id');
3248    $db->sql_freeresult($result);
3249
3250    if (!$group_id)
3251    {
3252        return false;
3253    }
3254
3255    // We need to call group_user_del here, because this function makes sure everything is correctly changed.
3256    // Force function to not log the removal of users from newly registered users group
3257    group_user_del($group_id, $user_id, false, false, false);
3258
3259    // Set user_new to 0 to let this not be triggered again
3260    $sql = 'UPDATE ' . USERS_TABLE . '
3261        SET user_new = 0
3262        WHERE user_id = ' . $user_id;
3263    $db->sql_query($sql);
3264
3265    // The new users group was the users default group?
3266    if ($user_data['group_id'] == $group_id)
3267    {
3268        // Which group is now the users default one?
3269        $sql = 'SELECT group_id
3270            FROM ' . USERS_TABLE . '
3271            WHERE user_id = ' . $user_id;
3272        $result = $db->sql_query($sql);
3273        $user_data['group_id'] = $db->sql_fetchfield('group_id');
3274        $db->sql_freeresult($result);
3275    }
3276
3277    return $user_data['group_id'];
3278}
3279
3280/**
3281* Gets user ids of currently banned registered users.
3282*
3283* @param array $user_ids Array of users' ids to check for banning,
3284*                        leave empty to get complete list of banned ids
3285* @param bool|int $ban_end Bool True to get users currently banned
3286*                         Bool False to only get permanently banned users
3287*                         Int Unix timestamp to get users banned until that time
3288* @return array    Array of banned users' ids if any, empty array otherwise
3289*/
3290function phpbb_get_banned_user_ids($user_ids = array(), $ban_end = true)
3291{
3292    global $phpbb_container;
3293
3294    /** @var \phpbb\ban\manager $ban_manager */
3295    $ban_manager = $phpbb_container->get('ban.manager');
3296    $banned_users = $ban_manager->get_banned_users();
3297
3298    if ($ban_end === false)
3299    {
3300        $banned_users = array_filter($banned_users, function ($end) {
3301            return $end <= 0;
3302        });
3303    }
3304    else if ($ban_end !== true)
3305    {
3306        $banned_users = array_filter($banned_users, function ($end) use ($ban_end) {
3307            return $end <= 0 || $end > (int) $ban_end;
3308        });
3309    }
3310    else
3311    {
3312        $banned_users = array_filter($banned_users, function ($end) {
3313            return $end <= 0 || $end > time();
3314        });
3315    }
3316
3317    $result_array = [];
3318    foreach ($banned_users as $user_id => $_)
3319    {
3320        if (count($user_ids) && !in_array($user_id, $user_ids))
3321        {
3322            continue;
3323        }
3324
3325        $result_array[$user_id] = $user_id;
3326    }
3327
3328    return $result_array;
3329}
3330
3331/**
3332* Function for assigning a template var if the zebra module got included
3333*/
3334function phpbb_module_zebra($mode, &$module_row)
3335{
3336    global $template;
3337
3338    $template->assign_var('S_ZEBRA_ENABLED', true);
3339
3340    if ($mode == 'friends')
3341    {
3342        $template->assign_var('S_ZEBRA_FRIENDS_ENABLED', true);
3343    }
3344
3345    if ($mode == 'foes')
3346    {
3347        $template->assign_var('S_ZEBRA_FOES_ENABLED', true);
3348    }
3349}