Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.57% covered (danger)
43.57%
61 / 140
41.67% covered (danger)
41.67%
5 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
service
43.57% covered (danger)
43.57%
61 / 140
41.67% covered (danger)
41.67%
5 / 12
443.91
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 get_driver
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deferred_purge
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 set_driver
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __call
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 obtain_word_list
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 obtain_icons
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 obtain_ranks
71.43% covered (warning)
71.43%
10 / 14
0.00% covered (danger)
0.00%
0 / 1
4.37
 obtain_attach_extensions
82.98% covered (warning)
82.98%
39 / 47
0.00% covered (danger)
0.00%
0 / 1
16.11
 obtain_bots
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 obtain_cfg_items
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 obtain_disallowed_usernames
0.00% covered (danger)
0.00%
0 / 10
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
14namespace phpbb\cache;
15
16use phpbb\cache\driver\driver_interface;
17use phpbb\config\config;
18use phpbb\event\dispatcher;
19use phpbb\json\sanitizer as json_sanitizer;
20
21/**
22* Class for grabbing/handling cached entries
23*/
24class service
25{
26    /** @var string Name of event used for cache purging */
27    private const PURGE_DEFERRED_ON_EVENT = 'core.garbage_collection';
28
29    /** @var bool Flag whether cache purge has been deferred */
30    private $cache_purge_deferred = false;
31
32    /**
33    * Cache driver.
34    *
35    * @var driver_interface
36    */
37    protected $driver;
38
39    /**
40    * The config.
41    *
42    * @var config
43    */
44    protected $config;
45
46    /**
47    * Database connection.
48    *
49    * @var \phpbb\db\driver\driver_interface
50    */
51    protected $db;
52
53    /** @var dispatcher phpBB Event dispatcher */
54    protected $dispatcher;
55
56    /**
57    * Root path.
58    *
59    * @var string
60    */
61    protected $phpbb_root_path;
62
63    /**
64    * PHP file extension.
65    *
66    * @var string
67    */
68    protected $php_ext;
69
70    /**
71    * Creates a cache service around a cache driver
72    *
73    * @param driver_interface $driver The cache driver
74    * @param config $config The config
75    * @param \phpbb\db\driver\driver_interface $db Database connection
76    * @param dispatcher $dispatcher Event dispatcher
77    * @param string $phpbb_root_path Root path
78    * @param string $php_ext PHP file extension
79    */
80    public function __construct(driver_interface $driver, config $config, \phpbb\db\driver\driver_interface $db, dispatcher $dispatcher, $phpbb_root_path, $php_ext)
81    {
82        $this->set_driver($driver);
83        $this->config = $config;
84        $this->db = $db;
85        $this->dispatcher = $dispatcher;
86        $this->phpbb_root_path = $phpbb_root_path;
87        $this->php_ext = $php_ext;
88    }
89
90    /**
91    * Returns the cache driver used by this cache service.
92    *
93    * @return driver_interface The cache driver
94    */
95    public function get_driver()
96    {
97        return $this->driver;
98    }
99
100    /**
101     * Deferred purge of the cache.
102     *
103     * A deferred purge will be executed after rendering a page.
104     * It is recommended to be used in cases where an instant purge of the cache
105     * is not required, i.e. when the goal of a cache purge is to start from a
106     * clear cache at the next page load.
107     *
108     * @return void
109     */
110    public function deferred_purge(): void
111    {
112        if (!$this->cache_purge_deferred)
113        {
114            $this->dispatcher->addListener(self::PURGE_DEFERRED_ON_EVENT, [$this, 'purge']);
115            $this->cache_purge_deferred = true;
116        }
117    }
118
119    /**
120    * Replaces the cache driver used by this cache service.
121    *
122    * @param driver_interface $driver The cache driver
123    */
124    public function set_driver(driver_interface $driver)
125    {
126        $this->driver = $driver;
127    }
128
129    public function __call($method, $arguments)
130    {
131        return call_user_func_array(array($this->driver, $method), $arguments);
132    }
133
134    /**
135    * Obtain list of naughty words and build preg style replacement arrays for use by the
136    * calling script
137    */
138    function obtain_word_list()
139    {
140        if (($censors = $this->driver->get('_word_censors')) === false)
141        {
142            $sql = 'SELECT word, replacement
143                FROM ' . WORDS_TABLE;
144            $result = $this->db->sql_query($sql);
145
146            $censors = array();
147            while ($row = $this->db->sql_fetchrow($result))
148            {
149                $censors['match'][] = get_censor_preg_expression($row['word']);
150                $censors['replace'][] = $row['replacement'];
151            }
152            $this->db->sql_freeresult($result);
153
154            $this->driver->put('_word_censors', $censors);
155        }
156
157        return $censors;
158    }
159
160    /**
161    * Obtain currently listed icons
162    */
163    function obtain_icons()
164    {
165        if (($icons = $this->driver->get('_icons')) === false)
166        {
167            // Topic icons
168            $sql = 'SELECT *
169                FROM ' . ICONS_TABLE . '
170                ORDER BY icons_order';
171            $result = $this->db->sql_query($sql);
172
173            $icons = array();
174            while ($row = $this->db->sql_fetchrow($result))
175            {
176                $icons[$row['icons_id']]['img'] = $row['icons_url'];
177                $icons[$row['icons_id']]['width'] = (int) $row['icons_width'];
178                $icons[$row['icons_id']]['height'] = (int) $row['icons_height'];
179                $icons[$row['icons_id']]['alt'] = ($row['icons_alt']) ? $row['icons_alt'] : '';
180                $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting'];
181            }
182            $this->db->sql_freeresult($result);
183
184            $this->driver->put('_icons', $icons);
185        }
186
187        return $icons;
188    }
189
190    /**
191    * Obtain ranks
192    */
193    function obtain_ranks()
194    {
195        if (($ranks = $this->driver->get('_ranks')) === false)
196        {
197            $sql = 'SELECT *
198                FROM ' . RANKS_TABLE . '
199                ORDER BY rank_min DESC';
200            $result = $this->db->sql_query($sql);
201
202            $ranks = array();
203            while ($row = $this->db->sql_fetchrow($result))
204            {
205                if ($row['rank_special'])
206                {
207                    unset($row['rank_min']);
208                    $ranks['special'][$row['rank_id']] = $row;
209                }
210                else
211                {
212                    $ranks['normal'][$row['rank_id']] = $row;
213                }
214            }
215            $this->db->sql_freeresult($result);
216
217            $this->driver->put('_ranks', $ranks);
218        }
219
220        return $ranks;
221    }
222
223    /**
224    * Obtain allowed extensions
225    *
226    * @param mixed $forum_id If false then check for private messaging, if int then check for forum id. If true, then only return extension informations.
227    *
228    * @return array allowed extensions array.
229    */
230    function obtain_attach_extensions($forum_id)
231    {
232        if (($extensions = $this->driver->get('_extensions')) === false)
233        {
234            $extensions = array(
235                '_allowed_post'    => array(),
236                '_allowed_pm'    => array(),
237            );
238
239            // The rule is to only allow those extensions defined. ;)
240            $sql = 'SELECT e.extension, g.*
241                FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g
242                WHERE e.group_id = g.group_id
243                    AND (g.allow_group = 1 OR g.allow_in_pm = 1)';
244            $result = $this->db->sql_query($sql);
245
246            while ($row = $this->db->sql_fetchrow($result))
247            {
248                $extension = strtolower(trim($row['extension']));
249
250                $extensions[$extension] = array(
251                    'display_cat'    => (int) $row['cat_id'],
252                    'upload_icon'    => trim($row['upload_icon']),
253                    'max_filesize'    => (int) $row['max_filesize'],
254                    'allow_group'    => $row['allow_group'],
255                    'allow_in_pm'    => $row['allow_in_pm'],
256                    'group_name'    => $row['group_name'],
257                );
258
259                $allowed_forums = ($row['allowed_forums']) ? unserialize(trim($row['allowed_forums'])) : array();
260
261                // Store allowed extensions forum wise
262                if ($row['allow_group'])
263                {
264                    $extensions['_allowed_post'][$extension] = (!count($allowed_forums)) ? 0 : $allowed_forums;
265                }
266
267                if ($row['allow_in_pm'])
268                {
269                    $extensions['_allowed_pm'][$extension] = 0;
270                }
271            }
272            $this->db->sql_freeresult($result);
273
274            $this->driver->put('_extensions', $extensions);
275        }
276
277        // Forum post
278        if ($forum_id === false)
279        {
280            // We are checking for private messages, therefore we only need to get the pm extensions...
281            $return = array('_allowed_' => array());
282
283            foreach ($extensions['_allowed_pm'] as $extension => $check)
284            {
285                $return['_allowed_'][$extension] = 0;
286                $return[$extension] = $extensions[$extension];
287            }
288
289            $extensions = $return;
290        }
291        else if ($forum_id === true)
292        {
293            return $extensions;
294        }
295        else
296        {
297            $forum_id = (int) $forum_id;
298            $return = array('_allowed_' => array());
299
300            foreach ($extensions['_allowed_post'] as $extension => $check)
301            {
302                // Check for allowed forums
303                if (is_array($check))
304                {
305                    $allowed = (!in_array($forum_id, $check)) ? false : true;
306                }
307                else
308                {
309                    $allowed = true;
310                }
311
312                if ($allowed)
313                {
314                    $return['_allowed_'][$extension] = 0;
315                    $return[$extension] = $extensions[$extension];
316                }
317            }
318
319            $extensions = $return;
320        }
321
322        if (!isset($extensions['_allowed_']))
323        {
324            $extensions['_allowed_'] = array();
325        }
326
327        return $extensions;
328    }
329
330    /**
331    * Obtain active bots
332    */
333    function obtain_bots()
334    {
335        if (($bots = $this->driver->get('_bots')) === false)
336        {
337            switch ($this->db->get_sql_layer())
338            {
339                case 'mssql_odbc':
340                case 'mssqlnative':
341                    $sql = 'SELECT user_id, bot_agent, bot_ip
342                        FROM ' . BOTS_TABLE . '
343                        WHERE bot_active = 1
344                    ORDER BY LEN(bot_agent) DESC';
345                break;
346
347                // LENGTH supported by MySQL, IBM DB2 and Oracle for sure...
348                default:
349                    $sql = 'SELECT user_id, bot_agent, bot_ip
350                        FROM ' . BOTS_TABLE . '
351                        WHERE bot_active = 1
352                    ORDER BY LENGTH(bot_agent) DESC';
353                break;
354            }
355            $result = $this->db->sql_query($sql);
356
357            $bots = array();
358            while ($row = $this->db->sql_fetchrow($result))
359            {
360                $bots[] = $row;
361            }
362            $this->db->sql_freeresult($result);
363
364            $this->driver->put('_bots', $bots);
365        }
366
367        return $bots;
368    }
369
370    /**
371    * Obtain cfg file data
372    */
373    function obtain_cfg_items($style)
374    {
375        $parsed_array = $this->driver->get('_cfg_' . $style['style_path']);
376
377        if ($parsed_array === false)
378        {
379            $parsed_array = array();
380        }
381
382        $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/composer.json';
383
384        if (!file_exists($filename))
385        {
386            return $parsed_array;
387        }
388
389        if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime'])))
390        {
391            // Re-parse cfg file
392            $json = file_get_contents($filename);
393            $parsed_array = json_sanitizer::decode($json);
394            $parsed_array['filetime'] = @filemtime($filename);
395
396            $this->driver->put('_cfg_' . $style['style_path'], $parsed_array);
397        }
398
399        return $parsed_array;
400    }
401
402    /**
403    * Obtain disallowed usernames
404    */
405    function obtain_disallowed_usernames()
406    {
407        if (($usernames = $this->driver->get('_disallowed_usernames')) === false)
408        {
409            $sql = 'SELECT disallow_username
410                FROM ' . DISALLOW_TABLE;
411            $result = $this->db->sql_query($sql);
412
413            $usernames = array();
414            while ($row = $this->db->sql_fetchrow($result))
415            {
416                $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#'));
417            }
418            $this->db->sql_freeresult($result);
419
420            $this->driver->put('_disallowed_usernames', $usernames);
421        }
422
423        return $usernames;
424    }
425}