Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.06% covered (warning)
63.06%
845 / 1340
21.43% covered (danger)
21.43%
9 / 42
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.75% covered (warning)
81.75%
103 / 126
0.00% covered (danger)
0.00%
0 / 1
24.94
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
81.48% covered (warning)
81.48%
22 / 27
0.00% covered (danger)
0.00%
0 / 1
10.64
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
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 / 14
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_allow_pm'            => 1,
250        'user_allow_viewonline'    => 1,
251        'user_allow_viewemail'    => 1,
252        'user_allow_massemail'    => 1,
253
254        'user_sig'                    => '',
255        'user_sig_bbcode_uid'        => '',
256        'user_sig_bbcode_bitfield'    => '',
257
258        'user_form_salt'            => unique_id(),
259    );
260
261    // Now fill the sql array with not required variables
262    foreach ($additional_vars as $key => $default_value)
263    {
264        $sql_ary[$key] = (isset($user_row[$key])) ? $user_row[$key] : $default_value;
265    }
266
267    // Any additional variables in $user_row not covered above?
268    $remaining_vars = array_diff(array_keys($user_row), array_keys($sql_ary));
269
270    // Now fill our sql array with the remaining vars
271    if (count($remaining_vars))
272    {
273        foreach ($remaining_vars as $key)
274        {
275            $sql_ary[$key] = $user_row[$key];
276        }
277    }
278
279    /**
280    * Use this event to modify the values to be inserted when a user is added
281    *
282    * @event core.user_add_modify_data
283    * @var array    user_row            Array of user details submitted to user_add
284    * @var array    cp_data                Array of Custom profile fields submitted to user_add
285    * @var array    sql_ary                Array of data to be inserted when a user is added
286    * @var array    notifications_data    Array of notification data to be inserted when a user is added
287    * @since 3.1.0-a1
288    * @changed 3.1.0-b5 Added user_row and cp_data
289    * @changed 3.1.11-RC1 Added notifications_data
290    */
291    $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data');
292    extract($phpbb_dispatcher->trigger_event('core.user_add_modify_data', compact($vars)));
293
294    $sql = 'INSERT INTO ' . USERS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
295    $db->sql_query($sql);
296
297    $user_id = $db->sql_nextid();
298
299    // Insert Custom Profile Fields
300    if ($cp_data !== false && count($cp_data))
301    {
302        $cp_data['user_id'] = (int) $user_id;
303
304        /* @var $cp \phpbb\profilefields\manager */
305        $cp = $phpbb_container->get('profilefields.manager');
306        $sql = 'INSERT INTO ' . PROFILE_FIELDS_DATA_TABLE . ' ' .
307            $db->sql_build_array('INSERT', $cp->build_insert_sql_array($cp_data));
308        $db->sql_query($sql);
309    }
310
311    // Place into appropriate group...
312    $sql = 'INSERT INTO ' . USER_GROUP_TABLE . ' ' . $db->sql_build_array('INSERT', array(
313        'user_id'        => (int) $user_id,
314        'group_id'        => (int) $user_row['group_id'],
315        'user_pending'    => 0)
316    );
317    $db->sql_query($sql);
318
319    // Now make it the users default group...
320    group_set_user_default($user_row['group_id'], array($user_id), false);
321
322    // Add to newly registered users group if user_new is 1
323    if ($config['new_member_post_limit'] && $sql_ary['user_new'])
324    {
325        $sql = 'SELECT group_id
326            FROM ' . GROUPS_TABLE . "
327            WHERE group_name = 'NEWLY_REGISTERED'
328                AND group_type = " . GROUP_SPECIAL;
329        $result = $db->sql_query($sql);
330        $add_group_id = (int) $db->sql_fetchfield('group_id');
331        $db->sql_freeresult($result);
332
333        if ($add_group_id)
334        {
335            global $phpbb_log;
336
337            // Because these actions only fill the log unnecessarily, we disable it
338            $phpbb_log->disable('admin');
339
340            // Add user to "newly registered users" group and set to default group if admin specified so.
341            if ($config['new_member_group_default'])
342            {
343                group_user_add($add_group_id, $user_id, false, false, true);
344                $user_row['group_id'] = $add_group_id;
345            }
346            else
347            {
348                group_user_add($add_group_id, $user_id);
349            }
350
351            $phpbb_log->enable('admin');
352        }
353    }
354
355    // set the newest user and adjust the user count if the user is a normal user and no activation mail is sent
356    if ($user_row['user_type'] == USER_NORMAL || $user_row['user_type'] == USER_FOUNDER)
357    {
358        $config->set('newest_user_id', $user_id, false);
359        $config->set('newest_username', $user_row['username'], false);
360        $config->increment('num_users', 1, false);
361
362        $sql = 'SELECT group_colour
363            FROM ' . GROUPS_TABLE . '
364            WHERE group_id = ' . (int) $user_row['group_id'];
365        $result = $db->sql_query_limit($sql, 1);
366        $row = $db->sql_fetchrow($result);
367        $db->sql_freeresult($result);
368
369        $config->set('newest_user_colour', $row['group_colour'], false);
370    }
371
372    // Use default notifications settings if notifications_data is not set
373    if ($notifications_data === null)
374    {
375        $notifications_data = array(
376            array(
377                'item_type'    => 'notification.type.post',
378                'method'    => 'notification.method.email',
379            ),
380            array(
381                'item_type'    => 'notification.type.topic',
382                'method'    => 'notification.method.email',
383            ),
384        );
385    }
386
387    /**
388    * Modify the notifications data to be inserted in the database when a user is added
389    *
390    * @event core.user_add_modify_notifications_data
391    * @var array    user_row            Array of user details submitted to user_add
392    * @var array    cp_data                Array of Custom profile fields submitted to user_add
393    * @var array    sql_ary                Array of data to be inserted when a user is added
394    * @var array    notifications_data    Array of notification data to be inserted when a user is added
395    * @since 3.2.2-RC1
396    */
397    $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data');
398    extract($phpbb_dispatcher->trigger_event('core.user_add_modify_notifications_data', compact($vars)));
399
400    // Subscribe user to notifications if necessary
401    if (!empty($notifications_data))
402    {
403        /* @var $phpbb_notifications \phpbb\notification\manager */
404        $phpbb_notifications = $phpbb_container->get('notification_manager');
405        foreach ($notifications_data as $subscription)
406        {
407            $phpbb_notifications->add_subscription($subscription['item_type'], 0, $subscription['method'], $user_id);
408        }
409    }
410
411    /**
412    * Event that returns user id, user details and user CPF of newly registered user
413    *
414    * @event core.user_add_after
415    * @var int        user_id            User id of newly registered user
416    * @var array    user_row        Array of user details submitted to user_add
417    * @var array    cp_data            Array of Custom profile fields submitted to user_add
418    * @since 3.1.0-b5
419    */
420    $vars = array('user_id', 'user_row', 'cp_data');
421    extract($phpbb_dispatcher->trigger_event('core.user_add_after', compact($vars)));
422
423    return $user_id;
424}
425
426/**
427 * Delete user(s) and their related data
428 *
429 * @param string    $mode                Mode of posts deletion (retain|remove)
430 * @param mixed        $user_ids            Either an array of integers or an integer
431 * @param bool        $retain_username    True if username should be retained, false otherwise
432 * @return bool
433 */
434function user_delete($mode, $user_ids, $retain_username = true)
435{
436    global $cache, $config, $db, $user, $phpbb_dispatcher, $phpbb_container;
437    global $phpbb_root_path, $phpEx;
438
439    $db->sql_transaction('begin');
440
441    $user_rows = array();
442    if (!is_array($user_ids))
443    {
444        $user_ids = array($user_ids);
445    }
446
447    $user_id_sql = $db->sql_in_set('user_id', $user_ids);
448
449    $sql = 'SELECT *
450        FROM ' . USERS_TABLE . '
451        WHERE ' . $user_id_sql;
452    $result = $db->sql_query($sql);
453    while ($row = $db->sql_fetchrow($result))
454    {
455        $user_rows[(int) $row['user_id']] = $row;
456    }
457    $db->sql_freeresult($result);
458
459    if (empty($user_rows))
460    {
461        return false;
462    }
463
464    /**
465     * Event before of the performing of the user(s) delete action
466     *
467     * @event core.delete_user_before
468     * @var string    mode                Mode of posts deletion (retain|remove)
469     * @var array    user_ids            ID(s) of the user(s) bound to be deleted
470     * @var bool    retain_username        True if username should be retained, false otherwise
471     * @var array    user_rows            Array containing data of the user(s) bound to be deleted
472     * @since 3.1.0-a1
473     * @changed 3.2.4-RC1 Added user_rows
474     */
475    $vars = array('mode', 'user_ids', 'retain_username', 'user_rows');
476    extract($phpbb_dispatcher->trigger_event('core.delete_user_before', compact($vars)));
477
478    // Before we begin, we will remove the reports the user issued.
479    $sql = 'SELECT r.post_id, p.topic_id
480        FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p
481        WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . '
482            AND p.post_id = r.post_id';
483    $result = $db->sql_query($sql);
484
485    $report_posts = $report_topics = array();
486    while ($row = $db->sql_fetchrow($result))
487    {
488        $report_posts[] = $row['post_id'];
489        $report_topics[] = $row['topic_id'];
490    }
491    $db->sql_freeresult($result);
492
493    if (count($report_posts))
494    {
495        $report_posts = array_unique($report_posts);
496        $report_topics = array_unique($report_topics);
497
498        // Get a list of topics that still contain reported posts
499        $sql = 'SELECT DISTINCT topic_id
500            FROM ' . POSTS_TABLE . '
501            WHERE ' . $db->sql_in_set('topic_id', $report_topics) . '
502                AND post_reported = 1
503                AND ' . $db->sql_in_set('post_id', $report_posts, true);
504        $result = $db->sql_query($sql);
505
506        $keep_report_topics = array();
507        while ($row = $db->sql_fetchrow($result))
508        {
509            $keep_report_topics[] = $row['topic_id'];
510        }
511        $db->sql_freeresult($result);
512
513        if (count($keep_report_topics))
514        {
515            $report_topics = array_diff($report_topics, $keep_report_topics);
516        }
517        unset($keep_report_topics);
518
519        // Now set the flags back
520        $sql = 'UPDATE ' . POSTS_TABLE . '
521            SET post_reported = 0
522            WHERE ' . $db->sql_in_set('post_id', $report_posts);
523        $db->sql_query($sql);
524
525        if (count($report_topics))
526        {
527            $sql = 'UPDATE ' . TOPICS_TABLE . '
528                SET topic_reported = 0
529                WHERE ' . $db->sql_in_set('topic_id', $report_topics);
530            $db->sql_query($sql);
531        }
532    }
533
534    // Remove reports
535    $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE ' . $user_id_sql);
536
537    $num_users_delta = 0;
538
539    // Get auth provider collection in case accounts might need to be unlinked
540    $provider_collection = $phpbb_container->get('auth.provider_collection');
541
542    // Some things need to be done in the loop (if the query changes based
543    // on which user is currently being deleted)
544    $added_guest_posts = 0;
545    foreach ($user_rows as $user_id => $user_row)
546    {
547        if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == 'avatar.driver.upload')
548        {
549            avatar_delete('user', $user_row);
550        }
551
552        // Unlink accounts
553        foreach ($provider_collection as $provider_name => $auth_provider)
554        {
555            $provider_data = $auth_provider->get_auth_link_data($user_id);
556
557            if ($provider_data !== null)
558            {
559                $link_data = array(
560                    'user_id' => $user_id,
561                    'link_method' => 'user_delete',
562                );
563
564                // BLOCK_VARS might contain hidden fields necessary for unlinking accounts
565                if (isset($provider_data['BLOCK_VARS']) && is_array($provider_data['BLOCK_VARS']))
566                {
567                    foreach ($provider_data['BLOCK_VARS'] as $provider_service)
568                    {
569                        if (!array_key_exists('HIDDEN_FIELDS', $provider_service))
570                        {
571                            $provider_service['HIDDEN_FIELDS'] = array();
572                        }
573
574                        $auth_provider->unlink_account(array_merge($link_data, $provider_service['HIDDEN_FIELDS']));
575                    }
576                }
577                else
578                {
579                    $auth_provider->unlink_account($link_data);
580                }
581            }
582        }
583
584        // Decrement number of users if this user is active
585        if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE)
586        {
587            --$num_users_delta;
588        }
589
590        switch ($mode)
591        {
592            case 'retain':
593                if ($retain_username === false)
594                {
595                    $post_username = $user->lang['GUEST'];
596                }
597                else
598                {
599                    $post_username = $user_row['username'];
600                }
601
602                // If the user is inactive and newly registered
603                // we assume no posts from the user, and save
604                // the queries
605                if ($user_row['user_type'] != USER_INACTIVE || $user_row['user_inactive_reason'] != INACTIVE_REGISTER || $user_row['user_posts'])
606                {
607                    // When we delete these users and retain the posts, we must assign all the data to the guest user
608                    $sql = 'UPDATE ' . FORUMS_TABLE . '
609                        SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = ''
610                        WHERE forum_last_poster_id = $user_id";
611                    $db->sql_query($sql);
612
613                    $sql = 'UPDATE ' . POSTS_TABLE . '
614                        SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "'
615                        WHERE poster_id = $user_id";
616                    $db->sql_query($sql);
617
618                    $sql = 'UPDATE ' . TOPICS_TABLE . '
619                        SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = ''
620                        WHERE topic_poster = $user_id";
621                    $db->sql_query($sql);
622
623                    $sql = 'UPDATE ' . TOPICS_TABLE . '
624                        SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = ''
625                        WHERE topic_last_poster_id = $user_id";
626                    $db->sql_query($sql);
627
628                    // Since we change every post by this author, we need to count this amount towards the anonymous user
629
630                    if ($user_row['user_posts'])
631                    {
632                        $added_guest_posts += $user_row['user_posts'];
633                    }
634                }
635            break;
636
637            case 'remove':
638                // there is nothing variant specific to deleting posts
639            break;
640        }
641    }
642
643    if ($num_users_delta != 0)
644    {
645        $config->increment('num_users', $num_users_delta, false);
646    }
647
648    // Now do the invariant tasks
649    // all queries performed in one call of this function are in a single transaction
650    // so this is kosher
651    if ($mode == 'retain')
652    {
653        // Assign more data to the Anonymous user
654        $sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
655            SET poster_id = ' . ANONYMOUS . '
656            WHERE ' . $db->sql_in_set('poster_id', $user_ids);
657        $db->sql_query($sql);
658
659        $sql = 'UPDATE ' . USERS_TABLE . '
660            SET user_posts = user_posts + ' . $added_guest_posts . '
661            WHERE user_id = ' . ANONYMOUS;
662        $db->sql_query($sql);
663    }
664    else if ($mode == 'remove')
665    {
666        if (!function_exists('delete_posts'))
667        {
668            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
669        }
670
671        // Delete posts, attachments, etc.
672        // delete_posts can handle any number of IDs in its second argument
673        delete_posts('poster_id', $user_ids);
674    }
675
676    $table_ary = [
677        USERS_TABLE,
678        USER_GROUP_TABLE,
679        TOPICS_WATCH_TABLE,
680        FORUMS_WATCH_TABLE,
681        ACL_USERS_TABLE,
682        TOPICS_TRACK_TABLE,
683        TOPICS_POSTED_TABLE,
684        FORUMS_TRACK_TABLE,
685        PROFILE_FIELDS_DATA_TABLE,
686        MODERATOR_CACHE_TABLE,
687        DRAFTS_TABLE,
688        BOOKMARKS_TABLE,
689        SESSIONS_KEYS_TABLE,
690        PRIVMSGS_FOLDER_TABLE,
691        PRIVMSGS_RULES_TABLE,
692        $phpbb_container->getParameter('tables.auth_provider_oauth_token_storage'),
693        $phpbb_container->getParameter('tables.auth_provider_oauth_states'),
694        $phpbb_container->getParameter('tables.auth_provider_oauth_account_assoc'),
695        $phpbb_container->getParameter('tables.user_notifications')
696    ];
697
698    // Ignore errors on deleting from non-existent tables, e.g. when migrating
699    $db->sql_return_on_error(true);
700    // Delete the miscellaneous (non-post) data for the user
701    foreach ($table_ary as $table)
702    {
703        $sql = "DELETE FROM $table
704            WHERE " . $user_id_sql;
705        $db->sql_query($sql);
706    }
707    $db->sql_return_on_error();
708
709    $cache->destroy('sql', MODERATOR_CACHE_TABLE);
710
711    // Change user_id to anonymous for posts edited by this user
712    $sql = 'UPDATE ' . POSTS_TABLE . '
713        SET post_edit_user = ' . ANONYMOUS . '
714        WHERE ' . $db->sql_in_set('post_edit_user', $user_ids);
715    $db->sql_query($sql);
716
717    // Change user_id to anonymous for pms edited by this user
718    $sql = 'UPDATE ' . PRIVMSGS_TABLE . '
719        SET message_edit_user = ' . ANONYMOUS . '
720        WHERE ' . $db->sql_in_set('message_edit_user', $user_ids);
721    $db->sql_query($sql);
722
723    // Change user_id to anonymous for posts deleted by this user
724    $sql = 'UPDATE ' . POSTS_TABLE . '
725        SET post_delete_user = ' . ANONYMOUS . '
726        WHERE ' . $db->sql_in_set('post_delete_user', $user_ids);
727    $db->sql_query($sql);
728
729    // Change user_id to anonymous for topics deleted by this user
730    $sql = 'UPDATE ' . TOPICS_TABLE . '
731        SET topic_delete_user = ' . ANONYMOUS . '
732        WHERE ' . $db->sql_in_set('topic_delete_user', $user_ids);
733    $db->sql_query($sql);
734
735    // Delete user log entries about this user
736    $sql = 'DELETE FROM ' . LOG_TABLE . '
737        WHERE ' . $db->sql_in_set('reportee_id', $user_ids);
738    $db->sql_query($sql);
739
740    // Change user_id to anonymous for this users triggered events
741    $sql = 'UPDATE ' . LOG_TABLE . '
742        SET user_id = ' . ANONYMOUS . '
743        WHERE ' . $user_id_sql;
744    $db->sql_query($sql);
745
746    // Delete the user_id from the zebra table
747    $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
748        WHERE ' . $user_id_sql . '
749            OR ' . $db->sql_in_set('zebra_id', $user_ids);
750    $db->sql_query($sql);
751
752    // Delete the user_id from the banlist
753    $sql = 'DELETE FROM ' . BANS_TABLE . "
754        WHERE ban_mode = 'user'
755            AND " . $db->sql_in_set('ban_userid', $user_ids);
756    $db->sql_query($sql);
757
758    // Delete the user_id from the session table
759    $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
760        WHERE ' . $db->sql_in_set('session_user_id', $user_ids);
761    $db->sql_query($sql);
762
763    // Clean the private messages tables from the user
764    if (!function_exists('phpbb_delete_users_pms'))
765    {
766        include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx);
767    }
768    phpbb_delete_users_pms($user_ids);
769
770    $phpbb_notifications = $phpbb_container->get('notification_manager');
771    $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_ids);
772
773    $db->sql_transaction('commit');
774
775    /**
776     * Event after the user(s) delete action has been performed
777     *
778     * @event core.delete_user_after
779     * @var string    mode                Mode of posts deletion (retain|remove)
780     * @var array    user_ids            ID(s) of the deleted user(s)
781     * @var bool    retain_username        True if username should be retained, false otherwise
782     * @var array    user_rows            Array containing data of the deleted user(s)
783     * @since 3.1.0-a1
784     * @changed 3.2.2-RC1 Added user_rows
785     */
786    $vars = array('mode', 'user_ids', 'retain_username', 'user_rows');
787    extract($phpbb_dispatcher->trigger_event('core.delete_user_after', compact($vars)));
788
789    // Reset newest user info if appropriate
790    if (in_array($config['newest_user_id'], $user_ids))
791    {
792        update_last_username();
793    }
794
795    return false;
796}
797
798/**
799* Flips user_type from active to inactive and vice versa, handles group membership updates
800*
801* @param string $mode can be flip for flipping from active/inactive, activate or deactivate
802* @param array $user_id_ary
803* @param int $reason
804*/
805function user_active_flip($mode, $user_id_ary, $reason = INACTIVE_MANUAL)
806{
807    global $config, $db, $user, $auth, $phpbb_dispatcher;
808
809    $deactivated = $activated = 0;
810    $sql_statements = array();
811
812    if (!is_array($user_id_ary))
813    {
814        $user_id_ary = array($user_id_ary);
815    }
816
817    if (!count($user_id_ary))
818    {
819        return;
820    }
821
822    $sql = 'SELECT user_id, group_id, user_type, user_inactive_reason
823        FROM ' . USERS_TABLE . '
824        WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
825    $result = $db->sql_query($sql);
826
827    while ($row = $db->sql_fetchrow($result))
828    {
829        $sql_ary = array();
830
831        if ($row['user_type'] == USER_IGNORE || $row['user_type'] == USER_FOUNDER ||
832            ($mode == 'activate' && $row['user_type'] != USER_INACTIVE) ||
833            ($mode == 'deactivate' && $row['user_type'] == USER_INACTIVE))
834        {
835            continue;
836        }
837
838        if ($row['user_type'] == USER_INACTIVE)
839        {
840            $activated++;
841        }
842        else
843        {
844            $deactivated++;
845
846            // Remove the users session key...
847            $user->reset_login_keys($row['user_id']);
848        }
849
850        $sql_ary += array(
851            'user_type'                => ($row['user_type'] == USER_NORMAL) ? USER_INACTIVE : USER_NORMAL,
852            'user_inactive_time'    => ($row['user_type'] == USER_NORMAL) ? time() : 0,
853            'user_inactive_reason'    => ($row['user_type'] == USER_NORMAL) ? $reason : 0,
854        );
855
856        $sql_statements[$row['user_id']] = $sql_ary;
857    }
858    $db->sql_freeresult($result);
859
860    /**
861    * Check or modify activated/deactivated users data before submitting it to the database
862    *
863    * @event core.user_active_flip_before
864    * @var    string    mode            User type changing mode, can be: flip|activate|deactivate
865    * @var    int        reason            Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND
866    * @var    int        activated        The number of users to be activated
867    * @var    int        deactivated        The number of users to be deactivated
868    * @var    array    user_id_ary        Array with user ids to change user type
869    * @var    array    sql_statements    Array with users data to submit to the database, keys: user ids, values: arrays with user data
870    * @since 3.1.4-RC1
871    */
872    $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements');
873    extract($phpbb_dispatcher->trigger_event('core.user_active_flip_before', compact($vars)));
874
875    if (count($sql_statements))
876    {
877        foreach ($sql_statements as $user_id => $sql_ary)
878        {
879            $sql = 'UPDATE ' . USERS_TABLE . '
880                SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
881                WHERE user_id = ' . $user_id;
882            $db->sql_query($sql);
883        }
884
885        $auth->acl_clear_prefetch(array_keys($sql_statements));
886    }
887
888    /**
889    * Perform additional actions after the users have been activated/deactivated
890    *
891    * @event core.user_active_flip_after
892    * @var    string    mode            User type changing mode, can be: flip|activate|deactivate
893    * @var    int        reason            Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND
894    * @var    int        activated        The number of users to be activated
895    * @var    int        deactivated        The number of users to be deactivated
896    * @var    array    user_id_ary        Array with user ids to change user type
897    * @var    array    sql_statements    Array with users data to submit to the database, keys: user ids, values: arrays with user data
898    * @since 3.1.4-RC1
899    */
900    $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements');
901    extract($phpbb_dispatcher->trigger_event('core.user_active_flip_after', compact($vars)));
902
903    if ($deactivated)
904    {
905        $config->increment('num_users', $deactivated * (-1), false);
906    }
907
908    if ($activated)
909    {
910        $config->increment('num_users', $activated, false);
911    }
912
913    // Update latest username
914    update_last_username();
915}
916
917/**
918* Add a ban or ban exclusion to the banlist. Bans either a user, an IP or an email address
919*
920* @deprecated 4.0.0-a1 (To be removed: 4.1.0)
921*
922* @param string $mode Type of ban. One of the following: user, ip, email
923* @param mixed $ban Banned entity. Either string or array with usernames, ips or email addresses
924* @param int $ban_len Ban length in minutes
925* @param string $ban_len_other Ban length as a date (YYYY-MM-DD)
926* @param string $ban_reason String describing the reason for this ban
927* @param string $ban_give_reason
928* @return boolean
929*/
930function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_reason, $ban_give_reason = '')
931{
932    global $phpbb_container, $user;
933
934    /** @var \phpbb\ban\manager $ban_manager */
935    $ban_manager = $phpbb_container->get('ban.manager');
936
937    $items = is_array($ban) ? $ban : [$ban];
938
939    $current_time = time();
940    // Set $ban_end to the unix time when the ban should end. 0 is a permanent ban.
941    if ($ban_len)
942    {
943        if ($ban_len != -1 || !$ban_len_other)
944        {
945            $ban_end = max($current_time, $current_time + ($ban_len) * 60);
946        }
947        else
948        {
949            $ban_other = explode('-', $ban_len_other);
950            if (count($ban_other) == 3 && ((int) $ban_other[0] < 9999) &&
951                (strlen($ban_other[0]) == 4) && (strlen($ban_other[1]) == 2) && (strlen($ban_other[2]) == 2))
952            {
953                $ban_end = max($current_time, $user->create_datetime()
954                        ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2])
955                        ->setTime(0, 0, 0)
956                        ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC')));
957            }
958            else
959            {
960                trigger_error('LENGTH_BAN_INVALID', E_USER_WARNING);
961            }
962        }
963    }
964    else
965    {
966        $ban_end = 0;
967    }
968
969    $start = new \DateTime();
970    $start->setTimestamp($current_time);
971    $end = new \DateTime();
972    $end->setTimestamp($ban_end);
973
974    return $ban_manager->ban($mode, $items, $start, $end, $ban_reason, $ban_give_reason);
975}
976
977/**
978* Unban User
979*
980* @deprecated 4.0.0-a1 (To be removed: 4.1.0)
981*/
982function user_unban($mode, $ban)
983{
984    global $phpbb_container;
985
986    $items = is_array($ban) ? $ban : [$ban];
987
988    /** @var \phpbb\ban\manager $ban_manager */
989    $ban_manager = $phpbb_container->get('ban.manager');
990    $ban_manager->unban($mode, $items);
991}
992
993/**
994* Internet Protocol Address Whois
995* RFC3912: WHOIS Protocol Specification
996*
997* @param string $ip        Ip address, either IPv4 or IPv6.
998*
999* @return string        Empty string if not a valid ip address.
1000*                        Otherwise make_clickable()'ed whois result.
1001*/
1002function user_ipwhois($ip)
1003{
1004    if (!filter_var($ip, FILTER_VALIDATE_IP))
1005    {
1006        return '';
1007    }
1008
1009    // IPv4 & IPv6 addresses
1010    $whois_host = 'whois.arin.net.';
1011
1012    $ipwhois = '';
1013
1014    if (($fsk = @fsockopen($whois_host, 43)))
1015    {
1016        // CRLF as per RFC3912
1017        // Z to limit the query to all possible flags (whois.arin.net)
1018        fputs($fsk, "$ip\r\n");
1019        while (!feof($fsk))
1020        {
1021            $ipwhois .= fgets($fsk, 1024);
1022        }
1023        @fclose($fsk);
1024    }
1025
1026    $match = array();
1027
1028    // Test for referrals from $whois_host to other whois databases, roll on rwhois
1029    // Search for referral servers with or without the "whois://" prefix
1030    if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match) || preg_match('#ReferralServer:[\x20]*([^/]+)$#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 hex colour value
1519*
1520* @param string $colour The hex colour value
1521* @param bool $optional Whether the colour value is optional. True if an empty
1522*            string will be accepted as correct input, false if not.
1523* @return bool|string Error message if colour value is incorrect, false if it
1524*            fits the hex colour code
1525*/
1526function phpbb_validate_hex_colour($colour, $optional = false)
1527{
1528    if ($colour === '')
1529    {
1530        return (($optional) ? false : 'WRONG_DATA');
1531    }
1532
1533    if (!preg_match('/^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/', $colour))
1534    {
1535        return 'WRONG_DATA';
1536    }
1537
1538    return false;
1539}
1540
1541/**
1542* Verifies whether a style ID corresponds to an active style.
1543*
1544* @param int $style_id The style_id of a style which should be checked if activated or not.
1545* @return boolean
1546*/
1547function phpbb_style_is_active($style_id)
1548{
1549    global $db;
1550
1551    $sql = 'SELECT style_active
1552        FROM ' . STYLES_TABLE . '
1553        WHERE style_id = '. (int) $style_id;
1554    $result = $db->sql_query($sql);
1555
1556    $style_is_active = (bool) $db->sql_fetchfield('style_active');
1557    $db->sql_freeresult($result);
1558
1559    return $style_is_active;
1560}
1561
1562/**
1563* Remove avatar
1564*/
1565function avatar_delete($mode, $row, $clean_db = false)
1566{
1567    global $phpbb_container;
1568
1569    $storage = $phpbb_container->get('storage.avatar');
1570
1571    // Check if the users avatar is actually *not* a group avatar
1572    if ($mode == 'user')
1573    {
1574        if (strpos($row['user_avatar'], 'g') === 0 || (((int) $row['user_avatar'] !== 0) && ((int) $row['user_avatar'] !== (int) $row['user_id'])))
1575        {
1576            return false;
1577        }
1578    }
1579
1580    if ($clean_db)
1581    {
1582        avatar_remove_db($row[$mode . '_avatar']);
1583    }
1584    $filename = get_avatar_filename($row[$mode . '_avatar']);
1585
1586    try
1587    {
1588        $storage->delete($filename);
1589
1590        return true;
1591    }
1592    catch (\phpbb\storage\exception\storage_exception $e)
1593    {
1594        // Fail is covered by return statement below
1595    }
1596
1597    return false;
1598}
1599
1600/**
1601* Generates avatar filename from the database entry
1602*/
1603function get_avatar_filename($avatar_entry)
1604{
1605    global $config;
1606
1607    if ($avatar_entry[0] === 'g')
1608    {
1609        $avatar_group = true;
1610        $avatar_entry = substr($avatar_entry, 1);
1611    }
1612    else
1613    {
1614        $avatar_group = false;
1615    }
1616    $ext             = substr(strrchr($avatar_entry, '.'), 1);
1617    $avatar_entry    = intval($avatar_entry);
1618    return $config['avatar_salt'] . '_' . (($avatar_group) ? 'g' : '') . $avatar_entry . '.' . $ext;
1619}
1620
1621/**
1622* Returns an explanation string with maximum avatar settings
1623*
1624* @return string
1625*/
1626function phpbb_avatar_explanation_string()
1627{
1628    global $config, $user;
1629
1630    return $user->lang(($config['avatar_filesize'] == 0) ? 'AVATAR_EXPLAIN_NO_FILESIZE' : 'AVATAR_EXPLAIN',
1631        $user->lang('PIXELS', (int) $config['avatar_max_width']),
1632        $user->lang('PIXELS', (int) $config['avatar_max_height']),
1633        round($config['avatar_filesize'] / 1024));
1634}
1635
1636//
1637// Usergroup functions
1638//
1639
1640/**
1641* Add or edit a group. If we're editing a group we only update user
1642* parameters such as rank, etc. if they are changed
1643*/
1644function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow_desc_bbcode = false, $allow_desc_urls = false, $allow_desc_smilies = false)
1645{
1646    global $db, $user, $phpbb_container, $phpbb_log;
1647
1648    /** @var \phpbb\group\helper $group_helper */
1649    $group_helper = $phpbb_container->get('group_helper');
1650
1651    $error = array();
1652
1653    // Attributes which also affect the users table
1654    $user_attribute_ary = array('group_colour', 'group_rank', 'group_avatar', 'group_avatar_type', 'group_avatar_width', 'group_avatar_height');
1655
1656    // Check data. Limit group name length.
1657    if (!utf8_strlen($name) || utf8_strlen($name) > 60)
1658    {
1659        $error[] = (!utf8_strlen($name)) ? $user->lang['GROUP_ERR_USERNAME'] : $user->lang['GROUP_ERR_USER_LONG'];
1660    }
1661
1662    $err = group_validate_groupname($group_id, $name);
1663    if (!empty($err))
1664    {
1665        $error[] = $user->lang[$err];
1666    }
1667
1668    if (!in_array($type, array(GROUP_OPEN, GROUP_CLOSED, GROUP_HIDDEN, GROUP_SPECIAL, GROUP_FREE)))
1669    {
1670        $error[] = $user->lang['GROUP_ERR_TYPE'];
1671    }
1672
1673    $group_teampage = !empty($group_attributes['group_teampage']);
1674    unset($group_attributes['group_teampage']);
1675
1676    if (!count($error))
1677    {
1678        $current_legend = \phpbb\groupposition\legend::GROUP_DISABLED;
1679        $current_teampage = \phpbb\groupposition\teampage::GROUP_DISABLED;
1680
1681        /* @var $legend \phpbb\groupposition\legend */
1682        $legend = $phpbb_container->get('groupposition.legend');
1683
1684        /* @var $teampage \phpbb\groupposition\teampage */
1685        $teampage = $phpbb_container->get('groupposition.teampage');
1686
1687        if ($group_id)
1688        {
1689            try
1690            {
1691                $current_legend = $legend->get_group_value($group_id);
1692                $current_teampage = $teampage->get_group_value($group_id);
1693            }
1694            catch (\phpbb\groupposition\exception $exception)
1695            {
1696                trigger_error($user->lang($exception->getMessage()));
1697            }
1698        }
1699
1700        if (!empty($group_attributes['group_legend']))
1701        {
1702            if (($group_id && ($current_legend == \phpbb\groupposition\legend::GROUP_DISABLED)) || !$group_id)
1703            {
1704                // Old group currently not in the legend or new group, add at the end.
1705                $group_attributes['group_legend'] = 1 + $legend->get_group_count();
1706            }
1707            else
1708            {
1709                // Group stayes in the legend
1710                $group_attributes['group_legend'] = $current_legend;
1711            }
1712        }
1713        else if ($group_id && ($current_legend != \phpbb\groupposition\legend::GROUP_DISABLED))
1714        {
1715            // Group is removed from the legend
1716            try
1717            {
1718                $legend->delete_group($group_id, true);
1719            }
1720            catch (\phpbb\groupposition\exception $exception)
1721            {
1722                trigger_error($user->lang($exception->getMessage()));
1723            }
1724            $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED;
1725        }
1726        else
1727        {
1728            $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED;
1729        }
1730
1731        // Unset the objects, we don't need them anymore.
1732        unset($legend);
1733
1734        $user_ary = array();
1735        $sql_ary = array(
1736            'group_name'            => (string) $name,
1737            'group_desc'            => (string) $desc,
1738            'group_desc_uid'        => '',
1739            'group_desc_bitfield'    => '',
1740            'group_type'            => (int) $type,
1741        );
1742
1743        // Parse description
1744        if ($desc)
1745        {
1746            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);
1747        }
1748
1749        if (count($group_attributes))
1750        {
1751            // Merge them with $sql_ary to properly update the group
1752            $sql_ary = array_merge($sql_ary, $group_attributes);
1753        }
1754
1755        // Setting the log message before we set the group id (if group gets added)
1756        $log = ($group_id) ? 'LOG_GROUP_UPDATED' : 'LOG_GROUP_CREATED';
1757
1758        if ($group_id)
1759        {
1760            $sql = 'SELECT user_id
1761                FROM ' . USERS_TABLE . '
1762                WHERE group_id = ' . $group_id;
1763            $result = $db->sql_query($sql);
1764
1765            while ($row = $db->sql_fetchrow($result))
1766            {
1767                $user_ary[] = $row['user_id'];
1768            }
1769            $db->sql_freeresult($result);
1770
1771            if (isset($sql_ary['group_avatar']))
1772            {
1773                remove_default_avatar($group_id, $user_ary);
1774            }
1775
1776            if (isset($sql_ary['group_rank']))
1777            {
1778                remove_default_rank($group_id, $user_ary);
1779            }
1780
1781            $sql = 'UPDATE ' . GROUPS_TABLE . '
1782                SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
1783                WHERE group_id = $group_id";
1784            $db->sql_query($sql);
1785
1786            // Since we may update the name too, we need to do this on other tables too...
1787            $sql = 'UPDATE ' . MODERATOR_CACHE_TABLE . "
1788                SET group_name = '" . $db->sql_escape($sql_ary['group_name']) . "'
1789                WHERE group_id = $group_id";
1790            $db->sql_query($sql);
1791
1792            // One special case is the group skip auth setting. If this was changed we need to purge permissions for this group
1793            if (isset($group_attributes['group_skip_auth']))
1794            {
1795                // Get users within this group...
1796                $sql = 'SELECT user_id
1797                    FROM ' . USER_GROUP_TABLE . '
1798                    WHERE group_id = ' . $group_id . '
1799                        AND user_pending = 0';
1800                $result = $db->sql_query($sql);
1801
1802                $user_id_ary = array();
1803                while ($row = $db->sql_fetchrow($result))
1804                {
1805                    $user_id_ary[] = $row['user_id'];
1806                }
1807                $db->sql_freeresult($result);
1808
1809                if (!empty($user_id_ary))
1810                {
1811                    global $auth;
1812
1813                    // Clear permissions cache of relevant users
1814                    $auth->acl_clear_prefetch($user_id_ary);
1815                }
1816            }
1817        }
1818        else
1819        {
1820            $sql = 'INSERT INTO ' . GROUPS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
1821            $db->sql_query($sql);
1822        }
1823
1824        // Remove the group from the teampage, only if unselected and we are editing a group,
1825        // which is currently displayed.
1826        if (!$group_teampage && $group_id && $current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED)
1827        {
1828            try
1829            {
1830                $teampage->delete_group($group_id);
1831            }
1832            catch (\phpbb\groupposition\exception $exception)
1833            {
1834                trigger_error($user->lang($exception->getMessage()));
1835            }
1836        }
1837
1838        if (!$group_id)
1839        {
1840            $group_id = $db->sql_nextid();
1841
1842            if (isset($sql_ary['group_avatar_type']) && $sql_ary['group_avatar_type'] == 'avatar.driver.upload')
1843            {
1844                group_correct_avatar($group_id, $sql_ary['group_avatar']);
1845            }
1846        }
1847
1848        try
1849        {
1850            if ($group_teampage && $current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED)
1851            {
1852                $teampage->add_group($group_id);
1853            }
1854
1855            if ($group_teampage)
1856            {
1857                if ($current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED)
1858                {
1859                    $teampage->add_group($group_id);
1860                }
1861            }
1862            else if ($group_id && ($current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED))
1863            {
1864                $teampage->delete_group($group_id);
1865            }
1866        }
1867        catch (\phpbb\groupposition\exception $exception)
1868        {
1869            trigger_error($user->lang($exception->getMessage()));
1870        }
1871        unset($teampage);
1872
1873        // Set user attributes
1874        $sql_ary = array();
1875        if (count($group_attributes))
1876        {
1877            // Go through the user attributes array, check if a group attribute matches it and then set it. ;)
1878            foreach ($user_attribute_ary as $attribute)
1879            {
1880                if (!isset($group_attributes[$attribute]))
1881                {
1882                    continue;
1883                }
1884
1885                // If we are about to set an avatar, we will not overwrite user avatars if no group avatar is set...
1886                if (strpos($attribute, 'group_avatar') === 0 && !$group_attributes[$attribute])
1887                {
1888                    continue;
1889                }
1890
1891                $sql_ary[$attribute] = $group_attributes[$attribute];
1892            }
1893        }
1894
1895        if (count($sql_ary) && count($user_ary))
1896        {
1897            group_set_user_default($group_id, $user_ary, $sql_ary);
1898        }
1899
1900        $name = $group_helper->get_name($name);
1901        $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($name));
1902
1903        group_update_listings($group_id);
1904    }
1905
1906    return (count($error)) ? $error : false;
1907}
1908
1909
1910/**
1911* Changes a group avatar's filename to conform to the naming scheme
1912*/
1913function group_correct_avatar($group_id, $old_entry)
1914{
1915    global $config, $db, $phpbb_container;
1916
1917    $storage = $phpbb_container->get('storage.avatar');
1918
1919    $group_id        = (int) $group_id;
1920    $ext             = substr(strrchr($old_entry, '.'), 1);
1921    $old_filename     = get_avatar_filename($old_entry);
1922    $new_filename     = $config['avatar_salt'] . "_g$group_id.$ext";
1923    $new_entry         = 'g' . $group_id . '_' . substr(time(), -5) . ".$ext";
1924
1925    try
1926    {
1927        $storage->write($new_filename, $storage->read($old_filename));
1928        $storage->delete($old_filename);
1929
1930        $sql = 'UPDATE ' . GROUPS_TABLE . '
1931            SET group_avatar = \'' . $db->sql_escape($new_entry) . "'
1932            WHERE group_id = $group_id";
1933        $db->sql_query($sql);
1934    }
1935    catch (\phpbb\storage\exception\storage_exception $e)
1936    {
1937        // If rename fail, dont execute the query
1938    }
1939}
1940
1941
1942/**
1943* Remove avatar also for users not having the group as default
1944*/
1945function avatar_remove_db($avatar_name)
1946{
1947    global $db;
1948
1949    $sql = 'UPDATE ' . USERS_TABLE . "
1950        SET user_avatar = '',
1951        user_avatar_type = ''
1952        WHERE user_avatar = '" . $db->sql_escape($avatar_name) . '\'';
1953    $db->sql_query($sql);
1954}
1955
1956
1957/**
1958* Group Delete
1959*/
1960function group_delete($group_id, $group_name = false)
1961{
1962    global $db, $cache, $auth, $user, $phpbb_root_path, $phpEx, $phpbb_dispatcher, $phpbb_container, $phpbb_log;
1963
1964    if (!$group_name)
1965    {
1966        $group_name = get_group_name($group_id);
1967    }
1968
1969    $start = 0;
1970
1971    do
1972    {
1973        $user_id_ary = $username_ary = array();
1974
1975        // Batch query for group members, call group_user_del
1976        $sql = 'SELECT u.user_id, u.username
1977            FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . " u
1978            WHERE ug.group_id = $group_id
1979                AND u.user_id = ug.user_id";
1980        $result = $db->sql_query_limit($sql, 200, $start);
1981
1982        if ($row = $db->sql_fetchrow($result))
1983        {
1984            do
1985            {
1986                $user_id_ary[] = $row['user_id'];
1987                $username_ary[] = $row['username'];
1988
1989                $start++;
1990            }
1991            while ($row = $db->sql_fetchrow($result));
1992
1993            group_user_del($group_id, $user_id_ary, $username_ary, $group_name);
1994        }
1995        else
1996        {
1997            $start = 0;
1998        }
1999        $db->sql_freeresult($result);
2000    }
2001    while ($start);
2002
2003    // Delete group from legend and teampage
2004    try
2005    {
2006        /* @var $legend \phpbb\groupposition\legend */
2007        $legend = $phpbb_container->get('groupposition.legend');
2008        $legend->delete_group($group_id);
2009        unset($legend);
2010    }
2011    catch (\phpbb\groupposition\exception $exception)
2012    {
2013        // The group we want to delete does not exist.
2014        // No reason to worry, we just continue the deleting process.
2015        //trigger_error($user->lang($exception->getMessage()));
2016    }
2017
2018    try
2019    {
2020        /* @var $teampage \phpbb\groupposition\teampage */
2021        $teampage = $phpbb_container->get('groupposition.teampage');
2022        $teampage->delete_group($group_id);
2023        unset($teampage);
2024    }
2025    catch (\phpbb\groupposition\exception $exception)
2026    {
2027        // The group we want to delete does not exist.
2028        // No reason to worry, we just continue the deleting process.
2029        //trigger_error($user->lang($exception->getMessage()));
2030    }
2031
2032    // Delete group
2033    $sql = 'DELETE FROM ' . GROUPS_TABLE . "
2034        WHERE group_id = $group_id";
2035    $db->sql_query($sql);
2036
2037    // Delete auth entries from the groups table
2038    $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . "
2039        WHERE group_id = $group_id";
2040    $db->sql_query($sql);
2041
2042    /**
2043    * Event after a group is deleted
2044    *
2045    * @event core.delete_group_after
2046    * @var    int        group_id    ID of the deleted group
2047    * @var    string    group_name    Name of the deleted group
2048    * @since 3.1.0-a1
2049    */
2050    $vars = array('group_id', 'group_name');
2051    extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars)));
2052
2053    // Re-cache moderators
2054    if (!function_exists('phpbb_cache_moderators'))
2055    {
2056        include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
2057    }
2058
2059    phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth);
2060
2061    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_GROUP_DELETE', false, array($group_name));
2062
2063    // Return false - no error
2064    return false;
2065}
2066
2067/**
2068* Add user(s) to group
2069*
2070* @return string|false false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER'
2071*/
2072function group_user_add($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $default = false, $leader = 0, $pending = 0, $group_attributes = false)
2073{
2074    global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher;
2075
2076    // We need both username and user_id info
2077    $result = user_get_id_name($user_id_ary, $username_ary);
2078
2079    if (empty($user_id_ary) || $result !== false)
2080    {
2081        return 'NO_USER';
2082    }
2083
2084    // Because the item that gets passed into the previous function is unset, the reference is lost and our original
2085    // array is retained - so we know there's a problem if there's a different number of ids to usernames now.
2086    if (count($user_id_ary) != count($username_ary))
2087    {
2088        return 'GROUP_USERS_INVALID';
2089    }
2090
2091    // Remove users who are already members of this group
2092    $sql = 'SELECT user_id, group_leader
2093        FROM ' . USER_GROUP_TABLE . '
2094        WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . "
2095            AND group_id = $group_id";
2096    $result = $db->sql_query($sql);
2097
2098    $add_id_ary = $update_id_ary = array();
2099    while ($row = $db->sql_fetchrow($result))
2100    {
2101        $add_id_ary[] = (int) $row['user_id'];
2102
2103        if ($leader && !$row['group_leader'])
2104        {
2105            $update_id_ary[] = (int) $row['user_id'];
2106        }
2107    }
2108    $db->sql_freeresult($result);
2109
2110    // Do all the users exist in this group?
2111    $add_id_ary = array_diff($user_id_ary, $add_id_ary);
2112
2113    // If we have no users
2114    if (!count($add_id_ary) && !count($update_id_ary))
2115    {
2116        return 'GROUP_USERS_EXIST';
2117    }
2118
2119    /**
2120     * Event before users are added to a group
2121     *
2122     * @event core.group_add_user_before
2123     * @var    int        group_id        ID of the group to which users are added
2124     * @var    string     group_name        Name of the group
2125     * @var    array    user_id_ary        IDs of the users to be added
2126     * @var    array    username_ary    Names of the users to be added
2127     * @var    int        pending            Pending setting, 1 if user(s) added are pending
2128     * @var    array    add_id_ary        IDs of the users to be added who are not members yet
2129     * @since 3.3.15-RC1
2130     */
2131    $vars = array(
2132        'group_id',
2133        'group_name',
2134        'user_id_ary',
2135        'username_ary',
2136        'pending',
2137        'add_id_ary',
2138    );
2139    extract($phpbb_dispatcher->trigger_event('core.group_add_user_before', compact($vars)));
2140
2141    $db->sql_transaction('begin');
2142
2143    // Insert the new users
2144    if (count($add_id_ary))
2145    {
2146        $sql_ary = array();
2147
2148        foreach ($add_id_ary as $user_id)
2149        {
2150            $sql_ary[] = array(
2151                'user_id'        => (int) $user_id,
2152                'group_id'        => (int) $group_id,
2153                'group_leader'    => (int) $leader,
2154                'user_pending'    => (int) $pending,
2155            );
2156        }
2157
2158        $db->sql_multi_insert(USER_GROUP_TABLE, $sql_ary);
2159    }
2160
2161    if (count($update_id_ary))
2162    {
2163        $sql = 'UPDATE ' . USER_GROUP_TABLE . '
2164            SET group_leader = 1
2165            WHERE ' . $db->sql_in_set('user_id', $update_id_ary) . "
2166                AND group_id = $group_id";
2167        $db->sql_query($sql);
2168    }
2169
2170    if ($default)
2171    {
2172        group_user_attributes('default', $group_id, $user_id_ary, false, $group_name, $group_attributes);
2173    }
2174
2175    $db->sql_transaction('commit');
2176
2177    // Clear permissions cache of relevant users
2178    $auth->acl_clear_prefetch($user_id_ary);
2179
2180    /**
2181    * Event after users are added to a group
2182    *
2183    * @event core.group_add_user_after
2184    * @var    int    group_id        ID of the group to which users are added
2185    * @var    string group_name        Name of the group
2186    * @var    array    user_id_ary        IDs of the users which are added
2187    * @var    array    username_ary    names of the users which are added
2188    * @var    int        pending            Pending setting, 1 if user(s) added are pending
2189    * @since 3.1.7-RC1
2190    */
2191    $vars = array(
2192        'group_id',
2193        'group_name',
2194        'user_id_ary',
2195        'username_ary',
2196        'pending',
2197    );
2198    extract($phpbb_dispatcher->trigger_event('core.group_add_user_after', compact($vars)));
2199
2200    if (!$group_name)
2201    {
2202        $group_name = get_group_name($group_id);
2203    }
2204
2205    $log = ($leader) ? 'LOG_MODS_ADDED' : (($pending) ? 'LOG_USERS_PENDING' : 'LOG_USERS_ADDED');
2206
2207    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2208
2209    group_update_listings($group_id);
2210
2211    if ($pending)
2212    {
2213        /* @var $phpbb_notifications \phpbb\notification\manager */
2214        $phpbb_notifications = $phpbb_container->get('notification_manager');
2215
2216        foreach ($add_id_ary as $user_id)
2217        {
2218            $phpbb_notifications->add_notifications('notification.type.group_request', array(
2219                'group_id'        => $group_id,
2220                'user_id'        => $user_id,
2221                'group_name'    => $group_name,
2222            ));
2223        }
2224    }
2225
2226    // Return false - no error
2227    return false;
2228}
2229
2230/**
2231* Remove a user/s from a given group. When we remove users we update their
2232* default group_id. We do this by examining which "special" groups they belong
2233* to. The selection is made based on a reasonable priority system
2234*
2235* @return false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER'
2236*/
2237function group_user_del($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $log_action = true)
2238{
2239    global $db, $auth, $config, $user, $phpbb_dispatcher, $phpbb_container, $phpbb_log;
2240
2241    if ($config['coppa_enable'])
2242    {
2243        $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED_COPPA', 'REGISTERED', 'BOTS', 'GUESTS');
2244    }
2245    else
2246    {
2247        $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED', 'BOTS', 'GUESTS');
2248    }
2249
2250    // We need both username and user_id info
2251    $result = user_get_id_name($user_id_ary, $username_ary);
2252
2253    if (empty($user_id_ary) || $result !== false)
2254    {
2255        return 'NO_USER';
2256    }
2257
2258    $sql = 'SELECT *
2259        FROM ' . GROUPS_TABLE . '
2260        WHERE ' . $db->sql_in_set('group_name', $group_order);
2261    $result = $db->sql_query($sql);
2262
2263    $group_order_id = $special_group_data = array();
2264    while ($row = $db->sql_fetchrow($result))
2265    {
2266        $group_order_id[$row['group_name']] = $row['group_id'];
2267
2268        $special_group_data[$row['group_id']] = array(
2269            'group_colour'            => $row['group_colour'],
2270            'group_rank'                => $row['group_rank'],
2271        );
2272
2273        // Only set the group avatar if one is defined...
2274        if ($row['group_avatar'])
2275        {
2276            $special_group_data[$row['group_id']] = array_merge($special_group_data[$row['group_id']], array(
2277                'group_avatar'            => $row['group_avatar'],
2278                'group_avatar_type'        => $row['group_avatar_type'],
2279                'group_avatar_width'        => $row['group_avatar_width'],
2280                'group_avatar_height'    => $row['group_avatar_height'])
2281            );
2282        }
2283    }
2284    $db->sql_freeresult($result);
2285
2286    // 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
2287    $sql = 'SELECT user_id, group_id
2288        FROM ' . USERS_TABLE . '
2289        WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
2290    $result = $db->sql_query($sql);
2291
2292    $default_groups = array();
2293    while ($row = $db->sql_fetchrow($result))
2294    {
2295        $default_groups[$row['user_id']] = $row['group_id'];
2296    }
2297    $db->sql_freeresult($result);
2298
2299    // What special group memberships exist for these users?
2300    $sql = 'SELECT g.group_id, g.group_name, ug.user_id
2301        FROM ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g
2302        WHERE ' . $db->sql_in_set('ug.user_id', $user_id_ary) . "
2303            AND g.group_id = ug.group_id
2304            AND g.group_id <> $group_id
2305            AND g.group_type = " . GROUP_SPECIAL . '
2306        ORDER BY ug.user_id, g.group_id';
2307    $result = $db->sql_query($sql);
2308
2309    $temp_ary = array();
2310    while ($row = $db->sql_fetchrow($result))
2311    {
2312        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']]))
2313        {
2314            $temp_ary[$row['user_id']] = $row['group_id'];
2315        }
2316    }
2317    $db->sql_freeresult($result);
2318
2319    // sql_where_ary holds the new default groups and their users
2320    $sql_where_ary = array();
2321    foreach ($temp_ary as $uid => $gid)
2322    {
2323        $sql_where_ary[$gid][] = $uid;
2324    }
2325    unset($temp_ary);
2326
2327    foreach ($special_group_data as $gid => $default_data_ary)
2328    {
2329        if (isset($sql_where_ary[$gid]) && count($sql_where_ary[$gid]))
2330        {
2331            remove_default_rank($group_id, $sql_where_ary[$gid]);
2332            remove_default_avatar($group_id, $sql_where_ary[$gid]);
2333            group_set_user_default($gid, $sql_where_ary[$gid], $default_data_ary);
2334        }
2335    }
2336    unset($special_group_data);
2337
2338    /**
2339    * Event before users are removed from a group
2340    *
2341    * @event core.group_delete_user_before
2342    * @var    int        group_id        ID of the group from which users are deleted
2343    * @var    string    group_name        Name of the group
2344    * @var    array    user_id_ary        IDs of the users which are removed
2345    * @var    array    username_ary    names of the users which are removed
2346    * @since 3.1.0-a1
2347    */
2348    $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary');
2349    extract($phpbb_dispatcher->trigger_event('core.group_delete_user_before', compact($vars)));
2350
2351    $sql = 'DELETE FROM ' . USER_GROUP_TABLE . "
2352        WHERE group_id = $group_id
2353            AND " . $db->sql_in_set('user_id', $user_id_ary);
2354    $db->sql_query($sql);
2355
2356    // Clear permissions cache of relevant users
2357    $auth->acl_clear_prefetch($user_id_ary);
2358
2359    /**
2360    * Event after users are removed from a group
2361    *
2362    * @event core.group_delete_user_after
2363    * @var    int        group_id        ID of the group from which users are deleted
2364    * @var    string    group_name        Name of the group
2365    * @var    array    user_id_ary        IDs of the users which are removed
2366    * @var    array    username_ary    names of the users which are removed
2367    * @since 3.1.7-RC1
2368    */
2369    $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary');
2370    extract($phpbb_dispatcher->trigger_event('core.group_delete_user_after', compact($vars)));
2371
2372    if ($log_action)
2373    {
2374        if (!$group_name)
2375        {
2376            $group_name = get_group_name($group_id);
2377        }
2378
2379        $log = 'LOG_GROUP_REMOVE';
2380
2381        if ($group_name)
2382        {
2383            $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2384        }
2385    }
2386
2387    group_update_listings($group_id);
2388
2389    /* @var $phpbb_notifications \phpbb\notification\manager */
2390    $phpbb_notifications = $phpbb_container->get('notification_manager');
2391
2392    $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id);
2393
2394    // Return false - no error
2395    return false;
2396}
2397
2398
2399/**
2400* Removes the group avatar of the default group from the users in user_ids who have that group as default.
2401*/
2402function remove_default_avatar($group_id, $user_ids)
2403{
2404    global $db;
2405
2406    if (!is_array($user_ids))
2407    {
2408        $user_ids = array($user_ids);
2409    }
2410    if (empty($user_ids))
2411    {
2412        return false;
2413    }
2414
2415    $user_ids = array_map('intval', $user_ids);
2416
2417    $sql = 'SELECT *
2418        FROM ' . GROUPS_TABLE . '
2419        WHERE group_id = ' . (int) $group_id;
2420    $result = $db->sql_query($sql);
2421    if (!$row = $db->sql_fetchrow($result))
2422    {
2423        $db->sql_freeresult($result);
2424        return false;
2425    }
2426    $db->sql_freeresult($result);
2427
2428    $sql = 'UPDATE ' . USERS_TABLE . "
2429        SET user_avatar = '',
2430            user_avatar_type = '',
2431            user_avatar_width = 0,
2432            user_avatar_height = 0
2433        WHERE group_id = " . (int) $group_id . "
2434            AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
2435            AND " . $db->sql_in_set('user_id', $user_ids);
2436
2437    $db->sql_query($sql);
2438}
2439
2440/**
2441* Removes the group rank of the default group from the users in user_ids who have that group as default.
2442* @return bool true if successful, false if not
2443*/
2444function remove_default_rank($group_id, $user_ids)
2445{
2446    global $db;
2447
2448    if (!is_array($user_ids))
2449    {
2450        $user_ids = array($user_ids);
2451    }
2452    if (empty($user_ids))
2453    {
2454        return false;
2455    }
2456
2457    $user_ids = array_map('intval', $user_ids);
2458
2459    $sql = 'SELECT *
2460        FROM ' . GROUPS_TABLE . '
2461        WHERE group_id = ' . (int) $group_id;
2462    $result = $db->sql_query($sql);
2463    if (!$row = $db->sql_fetchrow($result))
2464    {
2465        $db->sql_freeresult($result);
2466        return false;
2467    }
2468    $db->sql_freeresult($result);
2469
2470    $sql = 'UPDATE ' . USERS_TABLE . '
2471        SET user_rank = 0
2472        WHERE group_id = ' . (int) $group_id . '
2473            AND user_rank <> 0
2474            AND user_rank = ' . (int) $row['group_rank'] . '
2475            AND ' . $db->sql_in_set('user_id', $user_ids);
2476    $db->sql_query($sql);
2477
2478    return true;
2479}
2480
2481/**
2482* This is used to promote (to leader), demote or set as default a member/s
2483*/
2484function group_user_attributes($action, $group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $group_attributes = false)
2485{
2486    global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher;
2487
2488    // We need both username and user_id info
2489    $result = user_get_id_name($user_id_ary, $username_ary);
2490
2491    if (empty($user_id_ary) || $result !== false)
2492    {
2493        return 'NO_USERS';
2494    }
2495
2496    if (!$group_name)
2497    {
2498        $group_name = get_group_name($group_id);
2499    }
2500
2501    switch ($action)
2502    {
2503        case 'demote':
2504        case 'promote':
2505
2506            $sql = 'SELECT user_id
2507                FROM ' . USER_GROUP_TABLE . "
2508                WHERE group_id = $group_id
2509                    AND user_pending = 1
2510                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2511            $result = $db->sql_query_limit($sql, 1);
2512            $not_empty = ($db->sql_fetchrow($result));
2513            $db->sql_freeresult($result);
2514            if ($not_empty)
2515            {
2516                return 'NO_VALID_USERS';
2517            }
2518
2519            $sql = 'UPDATE ' . USER_GROUP_TABLE . '
2520                SET group_leader = ' . (($action == 'promote') ? 1 : 0) . "
2521                WHERE group_id = $group_id
2522                    AND user_pending = 0
2523                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2524            $db->sql_query($sql);
2525
2526            $log = ($action == 'promote') ? 'LOG_GROUP_PROMOTED' : 'LOG_GROUP_DEMOTED';
2527        break;
2528
2529        case 'approve':
2530            // Make sure we only approve those which are pending ;)
2531            $sql = 'SELECT u.user_id
2532                FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug
2533                WHERE ug.group_id = ' . $group_id . '
2534                    AND ug.user_pending = 1
2535                    AND ug.user_id = u.user_id
2536                    AND ' . $db->sql_in_set('ug.user_id', $user_id_ary);
2537            $result = $db->sql_query($sql);
2538
2539            $user_id_ary = array();
2540            while ($row = $db->sql_fetchrow($result))
2541            {
2542                $user_id_ary[] = $row['user_id'];
2543            }
2544            $db->sql_freeresult($result);
2545
2546            if (!count($user_id_ary))
2547            {
2548                return false;
2549            }
2550
2551            $sql = 'UPDATE ' . USER_GROUP_TABLE . "
2552                SET user_pending = 0
2553                WHERE group_id = $group_id
2554                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2555            $db->sql_query($sql);
2556
2557            /* @var $phpbb_notifications \phpbb\notification\manager */
2558            $phpbb_notifications = $phpbb_container->get('notification_manager');
2559
2560            $phpbb_notifications->add_notifications('notification.type.group_request_approved', array(
2561                'user_ids'        => $user_id_ary,
2562                'group_id'        => $group_id,
2563                'group_name'    => $group_name,
2564            ));
2565            $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id);
2566
2567            $log = 'LOG_USERS_APPROVED';
2568        break;
2569
2570        case 'default':
2571            // We only set default group for approved members of the group
2572            $sql = 'SELECT user_id
2573                FROM ' . USER_GROUP_TABLE . "
2574                WHERE group_id = $group_id
2575                    AND user_pending = 0
2576                    AND " . $db->sql_in_set('user_id', $user_id_ary);
2577            $result = $db->sql_query($sql);
2578
2579            $user_id_ary = $username_ary = array();
2580            while ($row = $db->sql_fetchrow($result))
2581            {
2582                $user_id_ary[] = $row['user_id'];
2583            }
2584            $db->sql_freeresult($result);
2585
2586            $result = user_get_id_name($user_id_ary, $username_ary);
2587            if (!count($user_id_ary) || $result !== false)
2588            {
2589                return 'NO_USERS';
2590            }
2591
2592            $sql = 'SELECT user_id, group_id
2593                FROM ' . USERS_TABLE . '
2594                WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true);
2595            $result = $db->sql_query($sql);
2596
2597            $groups = array();
2598            while ($row = $db->sql_fetchrow($result))
2599            {
2600                if (!isset($groups[$row['group_id']]))
2601                {
2602                    $groups[$row['group_id']] = array();
2603                }
2604                $groups[$row['group_id']][] = $row['user_id'];
2605            }
2606            $db->sql_freeresult($result);
2607
2608            foreach ($groups as $gid => $uids)
2609            {
2610                remove_default_rank($gid, $uids);
2611                remove_default_avatar($gid, $uids);
2612            }
2613            group_set_user_default($group_id, $user_id_ary, $group_attributes);
2614            $log = 'LOG_GROUP_DEFAULTS';
2615        break;
2616    }
2617
2618    /**
2619    * Event to perform additional actions on setting user group attributes
2620    *
2621    * @event core.user_set_group_attributes
2622    * @var    int        group_id            ID of the group
2623    * @var    string    group_name            Name of the group
2624    * @var    array    user_id_ary            IDs of the users to set group attributes
2625    * @var    array    username_ary        Names of the users to set group attributes
2626    * @var    array    group_attributes    Group attributes which were changed
2627    * @var    string    action                Action to perform over the group members
2628    * @since 3.1.10-RC1
2629    */
2630    $vars = array(
2631        'group_id',
2632        'group_name',
2633        'user_id_ary',
2634        'username_ary',
2635        'group_attributes',
2636        'action',
2637    );
2638    extract($phpbb_dispatcher->trigger_event('core.user_set_group_attributes', compact($vars)));
2639
2640    // Clear permissions cache of relevant users
2641    $auth->acl_clear_prefetch($user_id_ary);
2642
2643    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary)));
2644
2645    group_update_listings($group_id);
2646
2647    return false;
2648}
2649
2650/**
2651* A small version of validate_username to check for a group name's existence. To be called directly.
2652*/
2653function group_validate_groupname($group_id, $group_name)
2654{
2655    global $db;
2656
2657    $group_name =  utf8_clean_string($group_name);
2658
2659    if (!empty($group_id))
2660    {
2661        $sql = 'SELECT group_name
2662            FROM ' . GROUPS_TABLE . '
2663            WHERE group_id = ' . (int) $group_id;
2664        $result = $db->sql_query($sql);
2665        $row = $db->sql_fetchrow($result);
2666        $db->sql_freeresult($result);
2667
2668        if (!$row)
2669        {
2670            return false;
2671        }
2672
2673        $allowed_groupname = utf8_clean_string($row['group_name']);
2674
2675        if ($allowed_groupname == $group_name)
2676        {
2677            return false;
2678        }
2679    }
2680
2681    $sql = 'SELECT group_name
2682        FROM ' . GROUPS_TABLE . "
2683        WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($group_name)) . "'";
2684    $result = $db->sql_query($sql);
2685    $row = $db->sql_fetchrow($result);
2686    $db->sql_freeresult($result);
2687
2688    if ($row)
2689    {
2690        return 'GROUP_NAME_TAKEN';
2691    }
2692
2693    return false;
2694}
2695
2696/**
2697* Set users default group
2698*
2699* @access private
2700*/
2701function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false)
2702{
2703    global $config, $phpbb_container, $db, $phpbb_dispatcher;
2704
2705    if (empty($user_id_ary))
2706    {
2707        return;
2708    }
2709
2710    $attribute_ary = array(
2711        'group_colour'            => 'string',
2712        'group_rank'            => 'int',
2713        'group_avatar'            => 'string',
2714        'group_avatar_type'        => 'string',
2715        'group_avatar_width'    => 'int',
2716        'group_avatar_height'    => 'int',
2717    );
2718
2719    $sql_ary = array(
2720        'group_id'        => $group_id
2721    );
2722
2723    // Were group attributes passed to the function? If not we need to obtain them
2724    if ($group_attributes === false)
2725    {
2726        $sql = 'SELECT ' . implode(', ', array_keys($attribute_ary)) . '
2727            FROM ' . GROUPS_TABLE . "
2728            WHERE group_id = $group_id";
2729        $result = $db->sql_query($sql);
2730        $group_attributes = $db->sql_fetchrow($result);
2731        $db->sql_freeresult($result);
2732    }
2733
2734    foreach ($attribute_ary as $attribute => $type)
2735    {
2736        if (isset($group_attributes[$attribute]))
2737        {
2738            // 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
2739            if ((strpos($attribute, 'group_avatar') === 0 || strpos($attribute, 'group_rank') === 0) && !$group_attributes[$attribute])
2740            {
2741                continue;
2742            }
2743
2744            settype($group_attributes[$attribute], $type);
2745            $sql_ary[str_replace('group_', 'user_', $attribute)] = $group_attributes[$attribute];
2746        }
2747    }
2748
2749    $updated_sql_ary = $sql_ary;
2750
2751    // Before we update the user attributes, we will update the rank for users that don't have a custom rank
2752    if (isset($sql_ary['user_rank']))
2753    {
2754        $sql = 'UPDATE ' . USERS_TABLE . '
2755            SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . '
2756            WHERE user_rank = 0
2757                AND ' . $db->sql_in_set('user_id', $user_id_ary);
2758        $db->sql_query($sql);
2759        unset($sql_ary['user_rank']);
2760    }
2761
2762    // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar
2763    $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width');
2764
2765    if (isset($sql_ary['user_avatar']))
2766    {
2767        $avatar_sql_ary = array();
2768        foreach ($avatar_options as $avatar_option)
2769        {
2770            if (isset($sql_ary[$avatar_option]))
2771            {
2772                $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option];
2773            }
2774        }
2775
2776        $sql = 'UPDATE ' . USERS_TABLE . '
2777            SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . "
2778            WHERE user_avatar = ''
2779                AND " . $db->sql_in_set('user_id', $user_id_ary);
2780        $db->sql_query($sql);
2781    }
2782
2783    // Remove the avatar options, as we already updated them
2784    foreach ($avatar_options as $avatar_option)
2785    {
2786        unset($sql_ary[$avatar_option]);
2787    }
2788
2789    if (!empty($sql_ary))
2790    {
2791        $sql = 'UPDATE ' . USERS_TABLE . '
2792            SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2793            WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
2794        $db->sql_query($sql);
2795    }
2796
2797    if (isset($sql_ary['user_colour']))
2798    {
2799        // Update any cached colour information for these users
2800        $sql = 'UPDATE ' . FORUMS_TABLE . "
2801            SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
2802            WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary);
2803        $db->sql_query($sql);
2804
2805        $sql = 'UPDATE ' . TOPICS_TABLE . "
2806            SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
2807            WHERE " . $db->sql_in_set('topic_poster', $user_id_ary);
2808        $db->sql_query($sql);
2809
2810        $sql = 'UPDATE ' . TOPICS_TABLE . "
2811            SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
2812            WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary);
2813        $db->sql_query($sql);
2814
2815        if (in_array($config['newest_user_id'], $user_id_ary))
2816        {
2817            $config->set('newest_user_colour', $sql_ary['user_colour'], false);
2818        }
2819    }
2820
2821    // Make all values available for the event
2822    $sql_ary = $updated_sql_ary;
2823
2824    /**
2825    * Event when the default group is set for an array of users
2826    *
2827    * @event core.user_set_default_group
2828    * @var    int        group_id            ID of the group
2829    * @var    array    user_id_ary            IDs of the users
2830    * @var    array    group_attributes    Group attributes which were changed
2831    * @var    array    update_listing        Update the list of moderators and foes
2832    * @var    array    sql_ary                User attributes which were changed
2833    * @since 3.1.0-a1
2834    */
2835    $vars = array('group_id', 'user_id_ary', 'group_attributes', 'update_listing', 'sql_ary');
2836    extract($phpbb_dispatcher->trigger_event('core.user_set_default_group', compact($vars)));
2837
2838    if ($update_listing)
2839    {
2840        group_update_listings($group_id);
2841    }
2842
2843    // Because some tables/caches use usercolour-specific data we need to purge this here.
2844    $phpbb_container->get('cache.driver')->destroy('sql', MODERATOR_CACHE_TABLE);
2845}
2846
2847/**
2848* Get group name
2849*/
2850function get_group_name($group_id)
2851{
2852    global $db, $phpbb_container;
2853
2854    $sql = 'SELECT group_name, group_type
2855        FROM ' . GROUPS_TABLE . '
2856        WHERE group_id = ' . (int) $group_id;
2857    $result = $db->sql_query($sql);
2858    $row = $db->sql_fetchrow($result);
2859    $db->sql_freeresult($result);
2860
2861    if (!$row)
2862    {
2863        return '';
2864    }
2865
2866    /** @var \phpbb\group\helper $group_helper */
2867    $group_helper = $phpbb_container->get('group_helper');
2868
2869    return $group_helper->get_name($row['group_name']);
2870}
2871
2872/**
2873* Obtain either the members of a specified group, the groups the specified user is subscribed to
2874* or checking if a specified user is in a specified group. This function does not return pending memberships.
2875*
2876* Note: Never use this more than once... first group your users/groups
2877*/
2878function group_memberships($group_id_ary = false, $user_id_ary = false, $return_bool = false)
2879{
2880    global $db;
2881
2882    if (!$group_id_ary && !$user_id_ary)
2883    {
2884        return true;
2885    }
2886
2887    if ($user_id_ary)
2888    {
2889        $user_id_ary = (!is_array($user_id_ary)) ? array($user_id_ary) : $user_id_ary;
2890    }
2891
2892    if ($group_id_ary)
2893    {
2894        $group_id_ary = (!is_array($group_id_ary)) ? array($group_id_ary) : $group_id_ary;
2895    }
2896
2897    $sql = 'SELECT ug.*, u.username, u.username_clean, u.user_email
2898        FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u
2899        WHERE ug.user_id = u.user_id
2900            AND ug.user_pending = 0 AND ';
2901
2902    if ($group_id_ary)
2903    {
2904        $sql .= ' ' . $db->sql_in_set('ug.group_id', $group_id_ary);
2905    }
2906
2907    if ($user_id_ary)
2908    {
2909        $sql .= ($group_id_ary) ? ' AND ' : ' ';
2910        $sql .= $db->sql_in_set('ug.user_id', $user_id_ary);
2911    }
2912
2913    $result = ($return_bool) ? $db->sql_query_limit($sql, 1) : $db->sql_query($sql);
2914
2915    $row = $db->sql_fetchrow($result);
2916
2917    if ($return_bool)
2918    {
2919        $db->sql_freeresult($result);
2920        return ($row) ? true : false;
2921    }
2922
2923    if (!$row)
2924    {
2925        return false;
2926    }
2927
2928    $return = array();
2929
2930    do
2931    {
2932        $return[] = $row;
2933    }
2934    while ($row = $db->sql_fetchrow($result));
2935
2936    $db->sql_freeresult($result);
2937
2938    return $return;
2939}
2940
2941/**
2942* Re-cache moderators and foes if group has a_ or m_ permissions
2943*/
2944function group_update_listings($group_id)
2945{
2946    global $db, $cache, $auth;
2947
2948    $hold_ary = $auth->acl_group_raw_data($group_id, array('a_', 'm_'));
2949
2950    if (empty($hold_ary))
2951    {
2952        return;
2953    }
2954
2955    $mod_permissions = $admin_permissions = false;
2956
2957    foreach ($hold_ary as $g_id => $forum_ary)
2958    {
2959        foreach ($forum_ary as $forum_id => $auth_ary)
2960        {
2961            foreach ($auth_ary as $auth_option => $setting)
2962            {
2963                if ($mod_permissions && $admin_permissions)
2964                {
2965                    break 3;
2966                }
2967
2968                if ($setting != ACL_YES)
2969                {
2970                    continue;
2971                }
2972
2973                if ($auth_option == 'm_')
2974                {
2975                    $mod_permissions = true;
2976                }
2977
2978                if ($auth_option == 'a_')
2979                {
2980                    $admin_permissions = true;
2981                }
2982            }
2983        }
2984    }
2985
2986    if ($mod_permissions)
2987    {
2988        if (!function_exists('phpbb_cache_moderators'))
2989        {
2990            global $phpbb_root_path, $phpEx;
2991            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
2992        }
2993
2994        global $phpbb_container;
2995
2996        phpbb_cache_moderators($db, $phpbb_container->get('dbal.tools'), $cache, $auth);
2997    }
2998
2999    if ($mod_permissions || $admin_permissions)
3000    {
3001        if (!function_exists('phpbb_update_foes'))
3002        {
3003            global $phpbb_root_path, $phpEx;
3004            include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
3005        }
3006        phpbb_update_foes($db, $auth, array($group_id));
3007    }
3008}
3009
3010
3011
3012/**
3013* Funtion to make a user leave the NEWLY_REGISTERED system group.
3014* @access public
3015* @param int $user_id The id of the user to remove from the group
3016* @param mixed $user_data The id of the user to remove from the group
3017*/
3018function remove_newly_registered($user_id, $user_data = false)
3019{
3020    global $db;
3021
3022    if ($user_data === false)
3023    {
3024        $sql = 'SELECT *
3025            FROM ' . USERS_TABLE . '
3026            WHERE user_id = ' . $user_id;
3027        $result = $db->sql_query($sql);
3028        $user_row = $db->sql_fetchrow($result);
3029        $db->sql_freeresult($result);
3030
3031        if (!$user_row)
3032        {
3033            return false;
3034        }
3035        else
3036        {
3037            $user_data  = $user_row;
3038        }
3039    }
3040
3041    $sql = 'SELECT group_id
3042        FROM ' . GROUPS_TABLE . "
3043        WHERE group_name = 'NEWLY_REGISTERED'
3044            AND group_type = " . GROUP_SPECIAL;
3045    $result = $db->sql_query($sql);
3046    $group_id = (int) $db->sql_fetchfield('group_id');
3047    $db->sql_freeresult($result);
3048
3049    if (!$group_id)
3050    {
3051        return false;
3052    }
3053
3054    // We need to call group_user_del here, because this function makes sure everything is correctly changed.
3055    // Force function to not log the removal of users from newly registered users group
3056    group_user_del($group_id, $user_id, false, false, false);
3057
3058    // Set user_new to 0 to let this not be triggered again
3059    $sql = 'UPDATE ' . USERS_TABLE . '
3060        SET user_new = 0
3061        WHERE user_id = ' . $user_id;
3062    $db->sql_query($sql);
3063
3064    // The new users group was the users default group?
3065    if ($user_data['group_id'] == $group_id)
3066    {
3067        // Which group is now the users default one?
3068        $sql = 'SELECT group_id
3069            FROM ' . USERS_TABLE . '
3070            WHERE user_id = ' . $user_id;
3071        $result = $db->sql_query($sql);
3072        $user_data['group_id'] = $db->sql_fetchfield('group_id');
3073        $db->sql_freeresult($result);
3074    }
3075
3076    return $user_data['group_id'];
3077}
3078
3079/**
3080* Gets user ids of currently banned registered users.
3081*
3082* @param array $user_ids Array of users' ids to check for banning,
3083*                        leave empty to get complete list of banned ids
3084* @param bool|int $ban_end Bool True to get users currently banned
3085*                         Bool False to only get permanently banned users
3086*                         Int Unix timestamp to get users banned until that time
3087* @return array    Array of banned users' ids if any, empty array otherwise
3088*/
3089function phpbb_get_banned_user_ids($user_ids = array(), $ban_end = true)
3090{
3091    global $phpbb_container;
3092
3093    /** @var \phpbb\ban\manager $ban_manager */
3094    $ban_manager = $phpbb_container->get('ban.manager');
3095    $banned_users = $ban_manager->get_banned_users();
3096
3097    if ($ban_end === false)
3098    {
3099        $banned_users = array_filter($banned_users, function ($end) {
3100            return $end <= 0;
3101        });
3102    }
3103    else if ($ban_end !== true)
3104    {
3105        $banned_users = array_filter($banned_users, function ($end) use ($ban_end) {
3106            return $end <= 0 || $end > (int) $ban_end;
3107        });
3108    }
3109    else
3110    {
3111        $banned_users = array_filter($banned_users, function ($end) {
3112            return $end <= 0 || $end > time();
3113        });
3114    }
3115
3116    $result_array = [];
3117    foreach ($banned_users as $user_id => $_)
3118    {
3119        if (count($user_ids) && !in_array($user_id, $user_ids))
3120        {
3121            continue;
3122        }
3123
3124        $result_array[$user_id] = $user_id;
3125    }
3126
3127    return $result_array;
3128}
3129
3130/**
3131* Function for assigning a template var if the zebra module got included
3132*/
3133function phpbb_module_zebra($mode, &$module_row)
3134{
3135    global $template;
3136
3137    $template->assign_var('S_ZEBRA_ENABLED', true);
3138
3139    if ($mode == 'friends')
3140    {
3141        $template->assign_var('S_ZEBRA_FRIENDS_ENABLED', true);
3142    }
3143
3144    if ($mode == 'foes')
3145    {
3146        $template->assign_var('S_ZEBRA_FOES_ENABLED', true);
3147    }
3148}