Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.73% covered (success)
98.73%
78 / 79
92.86% covered (success)
92.86%
13 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
manager
98.73% covered (success)
98.73%
78 / 79
92.86% covered (success)
92.86%
13 / 14
40
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 register_avatar_drivers
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 get_driver
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 load_enabled_drivers
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 get_all_drivers
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 get_enabled_drivers
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 clean_row
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 clean_driver_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare_driver_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_enabled
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_avatar_settings
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 localize_errors
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 handle_avatar_delete
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 prefix_avatar_columns
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
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\avatar;
15
16class manager
17{
18    /**
19    * phpBB configuration
20    * @var \phpbb\config\config
21    */
22    protected $config;
23
24    /**
25    * phpBB event dispatcher
26    * @var \phpbb\event\dispatcher_interface
27    */
28    protected $phpbb_dispatcher;
29
30    /**
31    * Array that contains a list of enabled drivers
32    * @var array|false
33    */
34    protected static $enabled_drivers = false;
35
36    /**
37    * Array that contains all available avatar drivers which are passed via the
38    * service container
39    * @var array
40    */
41    protected $avatar_drivers;
42
43    /**
44    * Default avatar data row
45    * @var array
46    */
47    protected static $default_row = array(
48        'avatar'        => '',
49        'avatar_type'    => '',
50        'avatar_width'    => 0,
51        'avatar_height'    => 0,
52    );
53
54    /**
55    * Construct an avatar manager object
56    *
57    * @param \phpbb\config\config $config phpBB configuration
58    * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher phpBB event dispatcher
59    * @param array $avatar_drivers Avatar drivers passed via the service container
60    */
61    public function __construct(\phpbb\config\config $config, \phpbb\event\dispatcher_interface $phpbb_dispatcher, $avatar_drivers)
62    {
63        $this->config = $config;
64        $this->phpbb_dispatcher = $phpbb_dispatcher;
65        $this->register_avatar_drivers($avatar_drivers);
66    }
67
68    /**
69    * Register avatar drivers
70    *
71    * @param array $avatar_drivers Service collection of avatar drivers
72    */
73    protected function register_avatar_drivers($avatar_drivers)
74    {
75        if (!empty($avatar_drivers))
76        {
77            foreach ($avatar_drivers as $driver)
78            {
79                $this->avatar_drivers[$driver->get_name()] = $driver;
80            }
81        }
82    }
83
84    /**
85    * Get the driver object specified by the avatar type
86    *
87    * @param string $avatar_type Avatar type; by default an avatar's service container name
88    * @param bool $load_enabled Load only enabled avatars
89    *
90    * @return object|null Avatar driver object
91    */
92    public function get_driver($avatar_type, $load_enabled = true)
93    {
94        if (self::$enabled_drivers === false)
95        {
96            $this->load_enabled_drivers();
97        }
98
99        $avatar_drivers = ($load_enabled) ? self::$enabled_drivers : $this->get_all_drivers();
100
101        // Legacy stuff...
102        switch ($avatar_type)
103        {
104            case AVATAR_GALLERY:
105                $avatar_type = 'avatar.driver.local';
106            break;
107
108            case AVATAR_UPLOAD:
109                $avatar_type = 'avatar.driver.upload';
110            break;
111        }
112
113        if (!isset($avatar_drivers[$avatar_type]))
114        {
115            return null;
116        }
117
118        /*
119        * There is no need to handle invalid avatar types as the following code
120        * will cause a ServiceNotFoundException if the type does not exist
121        */
122        $driver = $this->avatar_drivers[$avatar_type];
123
124        return $driver;
125    }
126
127    /**
128    * Load the list of enabled drivers
129    * This is executed once and fills self::$enabled_drivers
130    */
131    protected function load_enabled_drivers()
132    {
133        if (!empty($this->avatar_drivers))
134        {
135            self::$enabled_drivers = array();
136            foreach ($this->avatar_drivers as $driver)
137            {
138                if ($this->is_enabled($driver))
139                {
140                    self::$enabled_drivers[$driver->get_name()] = $driver->get_name();
141                }
142            }
143            asort(self::$enabled_drivers);
144        }
145    }
146
147    /**
148    * Get a list of all avatar drivers
149    *
150    * As this function will only be called in the ACP avatar settings page, it
151    * doesn't make much sense to cache the list of all avatar drivers like the
152    * list of the enabled drivers.
153    *
154    * @return array Array containing a list of all avatar drivers
155    */
156    public function get_all_drivers()
157    {
158        $drivers = array();
159
160        if (!empty($this->avatar_drivers))
161        {
162            foreach ($this->avatar_drivers as $driver)
163            {
164                $drivers[$driver->get_name()] = $driver->get_name();
165            }
166            asort($drivers);
167        }
168
169        return $drivers;
170    }
171
172    /**
173    * Get a list of enabled avatar drivers
174    *
175    * @return array Array containing a list of the enabled avatar drivers
176    */
177    public function get_enabled_drivers()
178    {
179        if (self::$enabled_drivers === false)
180        {
181            $this->load_enabled_drivers();
182        }
183
184        return self::$enabled_drivers ?: [];
185    }
186
187    /**
188    * Strip out user_, group_, or other prefixes from array keys
189    *
190    * @param array    $row            User data or group data
191    * @param string $prefix            Prefix of data keys (e.g. user), should not include the trailing underscore
192    *
193    * @return array    User or group data with keys that have been
194    *            stripped from the preceding "user_" or "group_"
195    *            Also the group id is prefixed with g, when the prefix group is removed.
196    */
197    public static function clean_row($row, $prefix = '')
198    {
199        // Upon creation of a user/group $row might be empty
200        if (empty($row))
201        {
202            return self::$default_row;
203        }
204
205        $required_keys = ['user_id', 'group_id', 'username'];
206        $output = [];
207        foreach ($row as $key => $value)
208        {
209            if (!str_starts_with($key, $prefix ? "{$prefix}_avatar": 'avatar') && !in_array($key, $required_keys))
210            {
211                continue;
212            }
213
214            $key = preg_replace("#^(?:{$prefix}_)#", '', (string) $key);
215            $output[$key] = $value;
216        }
217
218        if ($prefix === 'group' && isset($output['id']))
219        {
220            $output['id'] = 'g' . $output['id'];
221        }
222
223        return $output;
224    }
225
226    /**
227    * Clean driver names that are returned from template files
228    * Underscores are replaced with dots
229    *
230    * @param string $name Driver name
231    *
232    * @return string Cleaned driver name
233    */
234    public static function clean_driver_name($name)
235    {
236        return str_replace(array('\\', '_'), '.', $name);
237    }
238
239    /**
240    * Prepare driver names for use in template files
241    * Dots are replaced with underscores
242    *
243    * @param string $name Clean driver name
244    *
245    * @return string Prepared driver name
246    */
247    public static function prepare_driver_name($name)
248    {
249        return str_replace('.', '_', $name);
250    }
251
252    /**
253    * Check if avatar is enabled
254    *
255    * @param object $driver Avatar driver object
256    *
257    * @return bool True if avatar is enabled, false if it's disabled
258    */
259    public function is_enabled($driver)
260    {
261        $config_name = $driver->get_config_name();
262
263        return (bool) $this->config["allow_avatar_{$config_name}"];
264    }
265
266    /**
267    * Get the settings array for enabling/disabling an avatar driver
268    *
269    * @param object $driver Avatar driver object
270    *
271    * @return array Array of configuration options as consumed by acp_board
272    */
273    public function get_avatar_settings($driver)
274    {
275        $config_name = $driver->get_config_name();
276
277        return array(
278            'allow_avatar_' . $config_name    => array('lang' => 'ALLOW_' . strtoupper(str_replace('\\', '_', $config_name)),        'validate' => 'bool',    'type' => 'radio:yes_no', 'explain' => true),
279        );
280    }
281
282    /**
283    * Replace "error" strings with their real, localized form
284    *
285    * @param \phpbb\user phpBB User object
286    * @param array    $error Array containing error strings
287    *        Key values can either be a string with a language key or an array
288    *        that will be passed to vsprintf() with the language key in the
289    *        first array key.
290    *
291    * @return array Array containing the localized error strings
292    */
293    public function localize_errors(\phpbb\user $user, $error)
294    {
295        foreach ($error as $key => $lang)
296        {
297            if (is_array($lang))
298            {
299                $lang_key = array_shift($lang);
300                $error[$key] = vsprintf($user->lang($lang_key), $lang);
301            }
302            else
303            {
304                $error[$key] = $user->lang("$lang");
305            }
306        }
307
308        return $error;
309    }
310
311    /**
312    * Handle deleting avatars
313    *
314    * @param \phpbb\db\driver\driver_interface $db phpBB dbal
315    * @param \phpbb\user    $user phpBB user object
316    * @param array          $avatar_data Cleaned user data containing the user's
317    *                               avatar data
318    * @param string         $table Database table from which the avatar should be deleted
319    * @param string         $prefix Prefix of user data columns in database
320    * @return void
321    */
322    public function handle_avatar_delete(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $avatar_data, $table, $prefix)
323    {
324        if ($driver = $this->get_driver($avatar_data['avatar_type']))
325        {
326            $driver->delete($avatar_data);
327        }
328
329        $result = $this->prefix_avatar_columns($prefix, self::$default_row);
330
331        $sql = 'UPDATE ' . $table . '
332            SET ' . $db->sql_build_array('UPDATE', $result) . '
333            WHERE ' . $prefix . 'id = ' . (int) $avatar_data['id'];
334        $db->sql_query($sql);
335
336        // Make sure we also delete this avatar from the users
337        if ($prefix === 'group_')
338        {
339            $result = $this->prefix_avatar_columns('user_', self::$default_row);
340
341            $sql = 'UPDATE ' . USERS_TABLE . '
342                SET ' . $db->sql_build_array('UPDATE', $result) . "
343                WHERE user_avatar = '" . $db->sql_escape($avatar_data['avatar']) . "'";
344            $db->sql_query($sql);
345        }
346
347        /**
348        * Event is triggered after user avatar has been deleted
349        *
350        * @event core.avatar_manager_avatar_delete_after
351        * @var    \phpbb\user    user        phpBB user object
352        * @var    array        avatar_data    Normalised avatar-related user data
353        * @var    string        table        Table to delete avatar from
354        * @var    string        prefix        Column prefix to delete avatar from
355        * @since 3.2.4-RC1
356        */
357        $vars = array('user', 'avatar_data', 'table', 'prefix');
358        extract($this->phpbb_dispatcher->trigger_event('core.avatar_manager_avatar_delete_after', compact($vars)));
359    }
360
361    /**
362     * Prefix avatar columns
363     *
364     * @param string $prefix Column prefix
365     * @param array $data Column data
366     *
367     * @return array Column data with prefixed column names
368     */
369    public function prefix_avatar_columns($prefix, $data)
370    {
371        foreach ($data as $key => $value)
372        {
373            $data[$prefix . $key] = $value;
374            unset($data[$key]);
375        }
376
377        return $data;
378    }
379}