Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.05% covered (danger)
18.05%
50 / 277
27.27% covered (danger)
27.27%
6 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
user
18.05% covered (danger)
18.05%
50 / 277
27.27% covered (danger)
27.27%
6 / 22
9003.58
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 is_setup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_token_expiration
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 __get
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 setup
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 1
4160
 lang
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_plural_form
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 add_lang
23.08% covered (danger)
23.08%
3 / 13
0.00% covered (danger)
0.00%
0 / 1
29.30
 set_lang
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
7.90
 add_lang_ext
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 format_date
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
3.00
 create_timezone
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 create_datetime
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 get_timestamp_from_format
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 get_iso_lang_id
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 get_profile_fields
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 img
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 optionget
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 optionset
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 leave_newly_registered
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 get_passworded_forums
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 get_ban_message
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
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
14namespace phpbb;
15
16/**
17* Base user class
18*
19* This is the overarching class which contains (through session extend)
20* all methods utilised for user functionality during a session.
21*/
22class user extends \phpbb\session
23{
24    /**
25     * @var \phpbb\language\language
26     */
27    protected $language;
28
29    var $style = array();
30    var $date_format;
31
32    /**
33    * DateTimeZone object holding the timezone of the user
34    */
35    public $timezone;
36
37    /**
38    * @var string Class name of datetime object
39    */
40    protected $datetime;
41
42    var $lang_name = false;
43    var $lang_id = false;
44    var $lang_path;
45    var $img_lang;
46    var $img_array = array();
47
48    /** @var bool */
49    protected $is_setup_flag;
50
51    // Able to add new options (up to id 31)
52    var $keyoptions = array('viewimg' => 0, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17);
53
54    public $profile_fields;
55
56    /**
57    * Constructor to set the lang path
58    *
59    * @param \phpbb\language\language    $lang            phpBB's Language loader
60    * @param string                        $datetime_class    Class name of datetime class
61    */
62    public function __construct(\phpbb\language\language $lang, $datetime_class)
63    {
64        global $phpbb_root_path;
65
66        $this->lang_path = $phpbb_root_path . 'language/';
67        $this->language = $lang;
68        $this->datetime = $datetime_class;
69
70        $this->is_setup_flag = false;
71    }
72
73    /**
74     * Returns whether user::setup was called
75     *
76     * @return bool
77     */
78    public function is_setup()
79    {
80        return $this->is_setup_flag;
81    }
82
83    /**
84     * Get expiration time for user tokens, e.g. activation or reset password tokens
85     *
86     * @return int Expiration for user tokens
87     */
88    public static function get_token_expiration(): int
89    {
90        return strtotime('+1 day') ?: 0;
91    }
92
93    /**
94     * Magic getter for BC compatibility
95     *
96     * Implement array access for user::lang.
97     *
98     * @param string    $param_name    Name of the BC component the user want to access
99     *
100     * @return array    The appropriate array
101     *
102     * @deprecated 3.2.0-dev (To be removed: 4.0.0)
103     */
104    public function __get($param_name)
105    {
106        if ($param_name === 'lang')
107        {
108            return $this->language->get_lang_array();
109        }
110        else if ($param_name === 'help')
111        {
112            $help_array = $this->language->get_lang_array();
113            return $help_array['__help'];
114        }
115
116        return array();
117    }
118
119    /**
120    * Setup basic user-specific items (style, language, ...)
121    *
122    * @param array|string|false $lang_set Lang set(s) to include, false if none shall be included
123    * @param int|false $style_id Style ID to load, false to load default style
124    *
125    * @return void
126    */
127    public function setup($lang_set = false, $style_id = false)
128    {
129        global $db, $request, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache;
130        global $phpbb_dispatcher, $phpbb_container;
131
132        $this->language->set_default_language($config['default_lang']);
133
134        if ($this->data['user_id'] != ANONYMOUS)
135        {
136            $user_lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']);
137            $user_date_format = $this->data['user_dateformat'];
138            $user_timezone = $this->data['user_timezone'];
139        }
140        else
141        {
142            $lang_override = $request->variable('language', '');
143            if ($lang_override)
144            {
145                $this->set_cookie('lang', $lang_override, 0, false);
146            }
147            else
148            {
149                $lang_override = $request->variable($config['cookie_name'] . '_lang', '', true, \phpbb\request\request_interface::COOKIE);
150            }
151
152            if ($lang_override)
153            {
154                $use_lang = basename($lang_override);
155                $user_lang_name = (file_exists($this->lang_path . $use_lang . "/common.$phpEx")) ? $use_lang : basename($config['default_lang']);
156                $this->data['user_lang'] = $user_lang_name;
157            }
158            else
159            {
160                $user_lang_name = basename($config['default_lang']);
161            }
162
163            $user_date_format = $config['default_dateformat'];
164            $user_timezone = $config['board_timezone'];
165
166            /**
167            * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language
168            * If re-enabled we need to make sure only those languages installed are checked
169            * Commented out so we do not loose the code.
170
171            if ($request->header('Accept-Language'))
172            {
173                $accept_lang_ary = explode(',', $request->header('Accept-Language'));
174
175                foreach ($accept_lang_ary as $accept_lang)
176                {
177                    // Set correct format ... guess full xx_YY form
178                    $accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2));
179                    $accept_lang = basename($accept_lang);
180
181                    if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx"))
182                    {
183                        $user_lang_name = $config['default_lang'] = $accept_lang;
184                        break;
185                    }
186                    else
187                    {
188                        // No match on xx_YY so try xx
189                        $accept_lang = substr($accept_lang, 0, 2);
190                        $accept_lang = basename($accept_lang);
191
192                        if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx"))
193                        {
194                            $user_lang_name = $config['default_lang'] = $accept_lang;
195                            break;
196                        }
197                    }
198                }
199            }
200            */
201        }
202
203        $user_data = $this->data;
204        $lang_set_ext = array();
205
206        /**
207        * Event to load language files and modify user data on every page
208        *
209        * Note: To load language file with this event, see description
210        * of lang_set_ext variable.
211        *
212        * @event core.user_setup
213        * @var    array    user_data            Array with user's data row
214        * @var    string    user_lang_name        Basename of the user's langauge
215        * @var    string    user_date_format    User's date/time format
216        * @var    string    user_timezone        User's timezone, should be one of
217        *                            http://www.php.net/manual/en/timezones.php
218        * @var    mixed    lang_set            String or array of language files
219        * @var    array    lang_set_ext        Array containing entries of format
220        *                     array(
221        *                         'ext_name' => (string) [extension name],
222        *                         'lang_set' => (string|array) [language files],
223        *                     )
224        *                     For performance reasons, only load translations
225        *                     that are absolutely needed globally using this
226        *                     event. Use local events otherwise.
227        * @var    mixed    style_id            Style we are going to display
228        * @since 3.1.0-a1
229        */
230        $vars = array(
231            'user_data',
232            'user_lang_name',
233            'user_date_format',
234            'user_timezone',
235            'lang_set',
236            'lang_set_ext',
237            'style_id',
238        );
239        extract($phpbb_dispatcher->trigger_event('core.user_setup', compact($vars)));
240
241        $this->data = $user_data;
242        $this->lang_name = $user_lang_name;
243        $this->date_format = $user_date_format;
244
245        $this->language->set_user_language($user_lang_name);
246
247        $this->create_timezone($user_timezone);
248
249        $this->add_lang($lang_set);
250        unset($lang_set);
251
252        foreach ($lang_set_ext as $ext_lang_pair)
253        {
254            $this->add_lang_ext($ext_lang_pair['ext_name'], $ext_lang_pair['lang_set']);
255        }
256        unset($lang_set_ext);
257
258        $style_request = $request->variable('style', 0);
259        if ($style_request && (!$config['override_user_style'] || $auth->acl_get('a_styles')) && !defined('ADMIN_START'))
260        {
261            global $SID, $_EXTRA_URL;
262
263            $style_id = $style_request;
264            $SID .= '&amp;style=' . $style_id;
265            $_EXTRA_URL = array('style=' . $style_id);
266        }
267        else
268        {
269            // Set up style
270            $style_id = ($style_id) ? $style_id : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']);
271        }
272
273        $sql = 'SELECT *
274            FROM ' . STYLES_TABLE . '
275            WHERE style_id = ' . (int) $style_id;
276        $result = $db->sql_query($sql, 3600);
277        $this->style = $db->sql_fetchrow($result);
278        $db->sql_freeresult($result);
279
280        // Fallback to user's standard style
281        if (!$this->style && $style_id != $this->data['user_style'])
282        {
283            $style_id = $this->data['user_style'];
284
285            $sql = 'SELECT *
286                FROM ' . STYLES_TABLE . '
287                WHERE style_id = ' . (int) $style_id;
288            $result = $db->sql_query($sql, 3600);
289            $this->style = $db->sql_fetchrow($result);
290            $db->sql_freeresult($result);
291        }
292
293        // Fallback to board's default style
294        if (!$this->style)
295        {
296            // Verify default style exists in the database
297            $sql = 'SELECT style_id
298                FROM ' . STYLES_TABLE . '
299                WHERE style_id = ' . (int) $config['default_style'];
300            $result = $db->sql_query($sql);
301            $style_id = (int) $db->sql_fetchfield('style_id');
302            $db->sql_freeresult($result);
303
304            if ($style_id > 0)
305            {
306                $db->sql_transaction('begin');
307
308                // Update $user row
309                $sql = 'SELECT *
310                    FROM ' . STYLES_TABLE . '
311                    WHERE style_id = ' . (int) $config['default_style'];
312                $result = $db->sql_query($sql);
313                $this->style = $db->sql_fetchrow($result);
314                $db->sql_freeresult($result);
315
316                // Update user style preference
317                $sql = 'UPDATE ' . USERS_TABLE . '
318                    SET user_style = ' . (int) $style_id . '
319                    WHERE user_id = ' . (int) $this->data['user_id'];
320                $db->sql_query($sql);
321
322                $db->sql_transaction('commit');
323            }
324        }
325
326        // This should never happen
327        if (!$this->style)
328        {
329            trigger_error($this->language->lang('NO_STYLE_DATA', $this->data['user_style'], $this->data['user_id']), E_USER_ERROR);
330        }
331
332        // Now parse the cfg file and cache it
333        $parsed_items = $cache->obtain_cfg_items($this->style);
334
335        $check_for = array(
336            'pagination_sep'    => (string) ', '
337        );
338
339        foreach ($check_for as $key => $default_value)
340        {
341            $this->style[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value;
342            settype($this->style[$key], gettype($default_value));
343
344            if (is_string($default_value))
345            {
346                $this->style[$key] = htmlspecialchars($this->style[$key], ENT_COMPAT);
347            }
348        }
349
350        $template->set_style();
351
352        $this->img_lang = $this->lang_name;
353
354        /**
355        * Execute code at the end of user setup
356        *
357        * @event core.user_setup_after
358        * @since 3.1.6-RC1
359        */
360        $phpbb_dispatcher->trigger_event('core.user_setup_after');
361
362        // If this function got called from the error handler we are finished here.
363        if (defined('IN_ERROR_HANDLER'))
364        {
365            return;
366        }
367
368        // Disable board if the install/ directory is still present
369        // For the brave development army we do not care about this, else we need to comment out this every time we develop locally
370        if (!$phpbb_container->getParameter('allow_install_dir') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install'))
371        {
372            // Adjust the message slightly according to the permissions
373            if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))
374            {
375                $message = 'REMOVE_INSTALL';
376            }
377            else
378            {
379                $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE';
380            }
381            trigger_error($message);
382        }
383
384        // Is board disabled and user not an admin or moderator?
385        // Check acp setting who has access: only admins "case: 0", plus global moderators "case: 1" and plus moderators "case: 2"
386        $board_disable_access = (int) $config['board_disable_access'];
387
388        switch ($board_disable_access)
389        {
390            case 0:
391                $access_disabled_board = $auth->acl_gets('a_');
392            break;
393
394            case 1:
395                $access_disabled_board = $auth->acl_gets('a_', 'm_');
396            break;
397
398            case 2:
399            default:
400                $access_disabled_board = $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_');
401            break;
402
403        }
404
405        if ($config['board_disable'] && !defined('IN_INSTALL') && !defined('IN_LOGIN') && !defined('SKIP_CHECK_DISABLED') && !$access_disabled_board)
406        {
407            if ($this->data['is_bot'])
408            {
409                send_status_line(503, 'Service Unavailable');
410            }
411
412            $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE';
413            trigger_error($message);
414        }
415
416        // Is load exceeded?
417        if ($config['limit_load'] && $this->load !== false)
418        {
419            if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN'))
420            {
421                // Set board disabled to true to let the admins/mods get the proper notification
422                $config['board_disable'] = '1';
423
424                if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_'))
425                {
426                    if ($this->data['is_bot'])
427                    {
428                        send_status_line(503, 'Service Unavailable');
429                    }
430                    trigger_error('BOARD_UNAVAILABLE');
431                }
432            }
433        }
434
435        if (isset($this->data['session_viewonline']))
436        {
437            // Make sure the user is able to hide his session
438            if (!$this->data['session_viewonline'])
439            {
440                // Reset online status if not allowed to hide the session...
441                if (!$auth->acl_get('u_hideonline'))
442                {
443                    $sql = 'UPDATE ' . SESSIONS_TABLE . '
444                        SET session_viewonline = 1
445                        WHERE session_user_id = ' . $this->data['user_id'];
446                    $db->sql_query($sql);
447                    $this->data['session_viewonline'] = 1;
448                }
449            }
450            else if (!$this->data['user_allow_viewonline'])
451            {
452                // the user wants to hide and is allowed to  -> cloaking device on.
453                if ($auth->acl_get('u_hideonline'))
454                {
455                    $sql = 'UPDATE ' . SESSIONS_TABLE . '
456                        SET session_viewonline = 0
457                        WHERE session_user_id = ' . $this->data['user_id'];
458                    $db->sql_query($sql);
459                    $this->data['session_viewonline'] = 0;
460                }
461            }
462        }
463
464        // Does the user need to change their password? If so, redirect to the
465        // ucp profile reg_details page ... of course do not redirect if we're already in the ucp
466        if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400))
467        {
468            if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx")
469            {
470                redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&amp;mode=reg_details'));
471            }
472        }
473
474        $this->is_setup_flag = true;
475    }
476
477    /**
478    * More advanced language substitution
479    * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms.
480    * Params are the language key and the parameters to be substituted.
481    * This function/functionality is inspired by SHS` and Ashe.
482    *
483    * Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp>
484    *
485    * If the first parameter is an array, the elements are used as keys and subkeys to get the language entry:
486    * Example: <samp>$user->lang(array('datetime', 'AGO'), 1)</samp> uses $user->lang['datetime']['AGO'] as language entry.
487    *
488    * @deprecated 3.2.0-dev (To be removed 4.0.0)
489    */
490    function lang()
491    {
492        $args = func_get_args();
493        return call_user_func_array(array($this->language, 'lang'), $args);
494    }
495
496    /**
497    * Determine which plural form we should use.
498    * For some languages this is not as simple as for English.
499    *
500    * @param $number        int|float   The number we want to get the plural case for. Float numbers are floored.
501    * @param $force_rule    mixed   False to use the plural rule of the language package
502    *                               or an integer to force a certain plural rule
503    * @return int|false     The plural-case we need to use for the number plural-rule combination, false if $force_rule
504    *                        was invalid.
505    *
506    * @deprecated: 3.2.0-dev (To be removed: 4.0.0)
507    */
508    function get_plural_form($number, $force_rule = false)
509    {
510        try {
511            return $this->language->get_plural_form($number, $force_rule);
512        } catch (\phpbb\language\exception\invalid_plural_rule_exception $e) {
513            return false;
514        }
515    }
516
517    /**
518    * Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion)
519    *
520    * @param mixed $lang_set specifies the language entries to include
521    * @param bool $use_db internal variable for recursion, do not use    @deprecated 3.2.0-dev (To be removed: 4.0.0)
522    * @param bool $use_help internal variable for recursion, do not use    @deprecated 3.2.0-dev (To be removed: 4.0.0)
523    * @param string $ext_name The extension to load language from, or empty for core files
524    *
525    * Examples:
526    * <code>
527    * $lang_set = array('posting', 'help' => 'faq');
528    * $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq'))
529    * $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq'))
530    * $lang_set = 'posting'
531    * $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting'))
532    * </code>
533    *
534    * Note: $use_db and $use_help should be removed. The old function was kept for BC purposes,
535    *         so the BC logic is handled here.
536    *
537    * @deprecated: 3.2.0-dev (To be removed: 4.0.0)
538    */
539    function add_lang($lang_set, $use_db = false, $use_help = false, $ext_name = '')
540    {
541        if (is_array($lang_set))
542        {
543            foreach ($lang_set as $key => $lang_file)
544            {
545                // Please do not delete this line.
546                // We have to force the type here, else [array] language inclusion will not work
547                $key = (string) $key;
548
549                if ($key == 'db')
550                {
551                    // This is never used
552                    $this->add_lang($lang_file, true, $use_help, $ext_name);
553                }
554                else if ($key == 'help')
555                {
556                    $this->add_lang($lang_file, $use_db, true, $ext_name);
557                }
558                else if (!is_array($lang_file))
559                {
560                    $this->set_lang($lang_file, $use_help, $ext_name);
561                }
562                else
563                {
564                    $this->add_lang($lang_file, $use_db, $use_help, $ext_name);
565                }
566            }
567            unset($lang_set);
568        }
569        else if ($lang_set)
570        {
571            $this->set_lang($lang_set, $use_help, $ext_name);
572        }
573    }
574
575    /**
576     * BC function for loading language files
577     *
578     * @deprecated 3.2.0-dev (To be removed: 4.0.0)
579     */
580    private function set_lang($lang_set, $use_help, $ext_name)
581    {
582        if (empty($ext_name))
583        {
584            $ext_name = null;
585        }
586
587        if ($use_help && strpos($lang_set, '/') !== false)
588        {
589            $component = dirname($lang_set) . '/help_' . basename($lang_set);
590
591            if ($component[0] === '/')
592            {
593                $component = substr($component, 1);
594            }
595        }
596        else
597        {
598            $component = (($use_help) ? 'help_' : '') . $lang_set;
599        }
600
601        $this->language->add_lang($component, $ext_name);
602    }
603
604    /**
605    * Add Language Items from an extension - use_db and use_help are assigned where needed (only use them to force inclusion)
606    *
607    * @param string $ext_name The extension to load language from, or empty for core files
608    * @param mixed $lang_set specifies the language entries to include
609    * @param bool $use_db internal variable for recursion, do not use
610    * @param bool $use_help internal variable for recursion, do not use
611    *
612    * Note: $use_db and $use_help should be removed. Kept for BC purposes.
613    *
614    * @deprecated: 3.2.0-dev (To be removed: 4.0.0)
615    */
616    function add_lang_ext($ext_name, $lang_set, $use_db = false, $use_help = false)
617    {
618        if ($ext_name === '/')
619        {
620            $ext_name = '';
621        }
622
623        $this->add_lang($lang_set, $use_db, $use_help, $ext_name);
624    }
625
626    /**
627    * Format user date
628    *
629    * @param int $gmepoch unix timestamp
630    * @param string|false $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i.
631    * @param bool $forcedate force non-relative date format.
632    *
633    * @return mixed translated date
634    */
635    function format_date($gmepoch, $format = false, $forcedate = false)
636    {
637        global $phpbb_dispatcher;
638        static $utc;
639
640        if (!isset($utc))
641        {
642            $utc = new \DateTimeZone('UTC');
643        }
644
645        $format_date_override = false;
646        $function_arguments = func_get_args();
647        /**
648        * Execute code and/or override format_date()
649        *
650        * To override the format_date() function generated value
651        * set $format_date_override to new return value
652        *
653        * @event core.user_format_date_override
654        * @var \DateTimeZone    utc Is DateTimeZone in UTC
655        * @var array function_arguments is array comprising a function's argument list
656        * @var string format_date_override Shall we return custom format (string) or not (false)
657        * @since 3.2.1-RC1
658        */
659        $vars = array('utc', 'function_arguments', 'format_date_override');
660        extract($phpbb_dispatcher->trigger_event('core.user_format_date_override', compact($vars)));
661
662        if (!$format_date_override)
663        {
664            $time = new $this->datetime($this, '@' . (int) $gmepoch, $utc);
665            $time->setTimezone($this->create_timezone());
666
667            return $time->format($format, $forcedate);
668        }
669        else
670        {
671            return $format_date_override;
672        }
673    }
674
675    /**
676     * Create a DateTimeZone object in the context of the current user
677     *
678     * @param string $user_timezone Time zone of the current user.
679     * @return \DateTimeZone DateTimeZone object linked to the current users locale
680     */
681    public function create_timezone($user_timezone = null)
682    {
683        if (!$this->timezone)
684        {
685            if (!$user_timezone)
686            {
687                global $config;
688                $user_timezone = ($this->data['user_id'] != ANONYMOUS) ? $this->data['user_timezone'] : $config['board_timezone'];
689            }
690
691            try
692            {
693                $this->timezone = new \DateTimeZone($user_timezone);
694            }
695            catch (\Exception $e)
696            {
697                // If the timezone the user has selected is invalid, we fall back to UTC.
698                $this->timezone = new \DateTimeZone('UTC');
699            }
700        }
701
702        return $this->timezone;
703    }
704
705    /**
706    * Create a \phpbb\datetime object in the context of the current user
707    *
708    * @param string $time String in a format accepted by strtotime().
709    * @param ?\DateTimeZone $timezone Time zone of the time.
710    * @return \phpbb\datetime Date time object linked to the current users locale
711    */
712    public function create_datetime(string $time = 'now', ?\DateTimeZone $timezone = null)
713    {
714        $timezone = $timezone ?: $this->create_timezone();
715        return new $this->datetime($this, $time, $timezone);
716    }
717
718    /**
719    * Get the UNIX timestamp for a datetime in the users timezone, so we can store it in the database.
720    *
721    * @param    string            $format        Format of the entered date/time
722    * @param    string            $time        Date/time with the timezone applied
723    * @param    ?\DateTimeZone    $timezone    Timezone of the date/time, falls back to timezone of current user
724    * @return    string|false            Returns the unix timestamp or false if date is invalid
725    */
726    public function get_timestamp_from_format($format, $time, ?\DateTimeZone $timezone = null)
727    {
728        $timezone = $timezone ?: $this->create_timezone();
729        $date = \DateTime::createFromFormat($format, $time, $timezone);
730        return $date !== false ? $date->format('U') : false;
731    }
732
733    /**
734    * Get language id currently used by the user
735    */
736    function get_iso_lang_id()
737    {
738        global $config, $db;
739
740        if (!empty($this->lang_id))
741        {
742            return $this->lang_id;
743        }
744
745        if (!$this->lang_name)
746        {
747            $this->lang_name = $config['default_lang'];
748        }
749
750        $sql = 'SELECT lang_id
751            FROM ' . LANG_TABLE . "
752            WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'";
753        $result = $db->sql_query($sql);
754        $this->lang_id = (int) $db->sql_fetchfield('lang_id');
755        $db->sql_freeresult($result);
756
757        return $this->lang_id;
758    }
759
760    /**
761    * Get users profile fields
762    */
763    function get_profile_fields($user_id)
764    {
765        global $db;
766
767        if (isset($this->profile_fields))
768        {
769            return;
770        }
771
772        $sql = 'SELECT *
773            FROM ' . PROFILE_FIELDS_DATA_TABLE . "
774            WHERE user_id = $user_id";
775        $result = $db->sql_query_limit($sql, 1);
776        $this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row;
777        $db->sql_freeresult($result);
778    }
779
780    /**
781    * Specify/Get image
782    */
783    function img($img, $alt = '')
784    {
785        $title = '';
786
787        if ($alt)
788        {
789            $alt = $this->language->lang($alt);
790            $title = ' title="' . $alt . '"';
791        }
792        return '<span class="imageset ' . $img . '"' . $title . '>' . $alt . '</span>';
793    }
794
795    /**
796    * Get option bit field from user options.
797    *
798    * @param string $key option key, as defined in $keyoptions property.
799    * @param int|false $data bit field value to use, or false to use $this->data['user_options']
800    * @return bool true if the option is set in the bit field, false otherwise
801    */
802    function optionget(string $key, $data = false)
803    {
804        $var = ($data !== false) ? $data : $this->data['user_options'];
805        return phpbb_optionget($this->keyoptions[$key], $var);
806    }
807
808    /**
809    * Set option bit field for user options.
810    *
811    * @param int $key Option key, as defined in $keyoptions property.
812    * @param bool $value True to set the option, false to clear the option.
813    * @param int|false $data Current bit field value, or false to use $this->data['user_options']
814    * @return int|bool If $data is false, the bit field is modified and
815    *                  written back to $this->data['user_options'], and
816    *                  return value is true if the bit field changed and
817    *                  false otherwise. If $data is not false, the new
818    *                  bitfield value is returned.
819    */
820    function optionset($key, $value, $data = false)
821    {
822        $var = ($data !== false) ? $data : $this->data['user_options'];
823
824        $new_var = phpbb_optionset($this->keyoptions[$key], $value, $var);
825
826        if ($data === false)
827        {
828            if ($new_var != $var)
829            {
830                $this->data['user_options'] = $new_var;
831                return true;
832            }
833            else
834            {
835                return false;
836            }
837        }
838        else
839        {
840            return $new_var;
841        }
842    }
843
844    /**
845    * Function to make the user leave the NEWLY_REGISTERED system group.
846    * @access public
847    */
848    function leave_newly_registered()
849    {
850        if (empty($this->data['user_new']))
851        {
852            return false;
853        }
854
855        if (!function_exists('remove_newly_registered'))
856        {
857            global $phpbb_root_path, $phpEx;
858
859            include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
860        }
861        if ($group = remove_newly_registered($this->data['user_id'], $this->data))
862        {
863            $this->data['group_id'] = $group;
864
865        }
866        $this->data['user_permissions'] = '';
867        $this->data['user_new'] = 0;
868
869        return true;
870    }
871
872    /**
873    * Returns all password protected forum ids the user is currently NOT authenticated for.
874    *
875    * @return array     Array of forum ids
876    * @access public
877    */
878    function get_passworded_forums()
879    {
880        global $db;
881
882        $sql = 'SELECT f.forum_id, fa.user_id
883            FROM ' . FORUMS_TABLE . ' f
884            LEFT JOIN ' . FORUMS_ACCESS_TABLE . " fa
885                ON (fa.forum_id = f.forum_id
886                    AND fa.session_id = '" . $db->sql_escape($this->session_id) . "')
887            WHERE f.forum_password <> ''";
888        $result = $db->sql_query($sql);
889
890        $forum_ids = array();
891        while ($row = $db->sql_fetchrow($result))
892        {
893            $forum_id = (int) $row['forum_id'];
894
895            if ($row['user_id'] != $this->data['user_id'])
896            {
897                $forum_ids[$forum_id] = $forum_id;
898            }
899        }
900        $db->sql_freeresult($result);
901
902        return $forum_ids;
903    }
904
905    /**
906     * {@inheritDoc}
907     */
908    protected function get_ban_message(array $ban_row, string $ban_triggered_by): string
909    {
910        global $config, $phpbb_root_path, $phpEx;
911
912        $till_date = ($ban_row['end']) ? $this->format_date($ban_row['end']) : '';
913        $message = ($ban_row['end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM';
914
915        $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx);
916        $message = $this->language->lang($message, $till_date, '<a href="' . $contact_link . '">', '</a>');
917        $message .= ($ban_row['reason']) ? '<br><br>' . $this->language->lang('BOARD_BAN_REASON', $ban_row['reason']) : '';
918        $message .= '<br><br><em>' . $this->language->lang('BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)) . '</em>';
919
920        return $message;
921    }
922}