Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.32% covered (success)
95.32%
367 / 385
66.67% covered (warning)
66.67%
10 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
log
95.32% covered (success)
95.32%
367 / 385
66.67% covered (warning)
66.67%
10 / 15
114
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 set_is_admin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_is_admin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set_log_table
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_enabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 disable
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
5.26
 enable
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
6.32
 add
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
1 / 1
17
 delete
84.62% covered (warning)
84.62%
33 / 39
0.00% covered (danger)
0.00%
0 / 1
16.93
 get_logs
97.35% covered (success)
97.35%
184 / 189
0.00% covered (danger)
0.00%
0 / 1
41
 generate_sql_keyword
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
10
 get_topic_auth
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
4
 get_reportee_data
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 get_log_count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 get_valid_offset
100.00% covered (success)
100.00%
1 / 1
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\log;
15
16/**
17* This class is used to add entries into the log table.
18*/
19class log implements \phpbb\log\log_interface
20{
21    /**
22    * If set, administrative user profile links will be returned and messages
23    * will not be censored.
24    * @var bool
25    */
26    protected $is_in_admin;
27
28    /**
29    * An array with the disabled log types. Logs of such types will not be
30    * added when add() is called.
31    * @var array
32    */
33    protected $disabled_types;
34
35    /**
36    * Keeps the total log count of the last call to get_logs()
37    * @var int
38    */
39    protected $entry_count;
40
41    /**
42    * Keeps the offset of the last valid page of the last call to get_logs()
43    * @var int
44    */
45    protected $last_page_offset;
46
47    /**
48    * The table we use to store our logs.
49    * @var string
50    */
51    protected $log_table;
52
53    /**
54    * Database object
55    * @var \phpbb\db\driver\driver
56    */
57    protected $db;
58
59    /**
60    * User object
61    * @var \phpbb\user
62    */
63    protected $user;
64
65    /**
66    * Auth object
67    * @var \phpbb\auth\auth
68    */
69    protected $auth;
70
71    /**
72    * Event dispatcher object
73    * @var \phpbb\event\dispatcher_interface
74    */
75    protected $dispatcher;
76
77    /**
78    * phpBB root path
79    * @var string
80    */
81    protected $phpbb_root_path;
82
83    /**
84    * Admin root path
85    * @var string
86    */
87    protected $phpbb_admin_path;
88
89    /**
90    * PHP Extension
91    * @var string
92    */
93    protected $php_ext;
94
95    /**
96    * Constructor
97    *
98    * @param    \phpbb\db\driver\driver_interface    $db        Database object
99    * @param    \phpbb\user        $user    User object
100    * @param    \phpbb\auth\auth        $auth    Auth object
101    * @param    \phpbb\event\dispatcher_interface    $phpbb_dispatcher    Event dispatcher
102    * @param    string        $phpbb_root_path        Root path
103    * @param    string        $relative_admin_path    Relative admin root path
104    * @param    string        $php_ext            PHP Extension
105    * @param    string        $log_table        Name of the table we use to store our logs
106    */
107    public function __construct($db, $user, $auth, $phpbb_dispatcher, $phpbb_root_path, $relative_admin_path, $php_ext, $log_table)
108    {
109        $this->db = $db;
110        $this->user = $user;
111        $this->auth = $auth;
112        $this->dispatcher = $phpbb_dispatcher;
113        $this->phpbb_root_path = $phpbb_root_path;
114        $this->phpbb_admin_path = $this->phpbb_root_path . $relative_admin_path;
115        $this->php_ext = $php_ext;
116        $this->log_table = $log_table;
117
118        /*
119        * IN_ADMIN is set after the session is created,
120        * so we need to take ADMIN_START into account as well, otherwise
121        * it will not work for the \phpbb\log\log object we create in common.php
122        */
123        $this->set_is_admin((defined('ADMIN_START') && ADMIN_START) || (defined('IN_ADMIN') && IN_ADMIN));
124        $this->enable();
125    }
126
127    /**
128    * Set is_in_admin in order to return administrative user profile links
129    * in get_logs()
130    *
131    * @param    bool    $is_in_admin        Are we called from within the acp?
132    * @return    void
133    */
134    public function set_is_admin($is_in_admin)
135    {
136        $this->is_in_admin = (bool) $is_in_admin;
137    }
138
139    /**
140    * Returns the is_in_admin option
141    *
142    * @return    bool
143    */
144    public function get_is_admin()
145    {
146        return $this->is_in_admin;
147    }
148
149    /**
150    * Set table name
151    *
152    * @param    string    $log_table        Can overwrite the table to use for the logs
153    * @return    void
154    */
155    public function set_log_table($log_table)
156    {
157        $this->log_table = $log_table;
158    }
159
160    /**
161    * {@inheritDoc}
162    */
163    public function is_enabled($type = '')
164    {
165        if ($type == '' || $type == 'all')
166        {
167            return !isset($this->disabled_types['all']);
168        }
169        return !isset($this->disabled_types[$type]) && !isset($this->disabled_types['all']);
170    }
171
172    /**
173    * {@inheritDoc}
174    */
175    public function disable($type = '')
176    {
177        if (is_array($type))
178        {
179            foreach ($type as $disable_type)
180            {
181                $this->disable($disable_type);
182            }
183            return;
184        }
185
186        // Empty string is an equivalent for all types.
187        if ($type == '')
188        {
189            $type = 'all';
190        }
191        $this->disabled_types[$type] = true;
192    }
193
194    /**
195    * {@inheritDoc}
196    */
197    public function enable($type = '')
198    {
199        if (is_array($type))
200        {
201            foreach ($type as $enable_type)
202            {
203                $this->enable($enable_type);
204            }
205            return;
206        }
207
208        if ($type == '' || $type == 'all')
209        {
210            $this->disabled_types = array();
211            return;
212        }
213        unset($this->disabled_types[$type]);
214    }
215
216    /**
217    * {@inheritDoc}
218    */
219    public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array())
220    {
221        if (!$this->is_enabled($mode))
222        {
223            return false;
224        }
225
226        if ($log_time === false)
227        {
228            $log_time = time();
229        }
230
231        $sql_ary = array(
232            'user_id'        => !empty($user_id) ? $user_id : ANONYMOUS,
233            'log_ip'        => !empty($log_ip) ? $log_ip : '',
234            'log_time'        => $log_time,
235            'log_operation'    => $log_operation,
236        );
237
238        switch ($mode)
239        {
240            case 'admin':
241                $sql_ary += array(
242                    'log_type'        => LOG_ADMIN,
243                    'log_data'        => (!empty($additional_data)) ? serialize($additional_data) : '',
244                );
245            break;
246
247            case 'mod':
248                $forum_id = isset($additional_data['forum_id']) ? (int) $additional_data['forum_id'] : 0;
249                unset($additional_data['forum_id']);
250                $topic_id = isset($additional_data['topic_id']) ? (int) $additional_data['topic_id'] : 0;
251                unset($additional_data['topic_id']);
252                $post_id = isset($additional_data['post_id']) ? (int) $additional_data['post_id'] : 0;
253                unset($additional_data['post_id']);
254                $sql_ary += array(
255                    'log_type'        => LOG_MOD,
256                    'forum_id'        => $forum_id,
257                    'topic_id'        => $topic_id,
258                    'post_id'        => $post_id,
259                    'log_data'        => (!empty($additional_data)) ? serialize($additional_data) : '',
260                );
261            break;
262
263            case 'user':
264                $reportee_id = (int) $additional_data['reportee_id'];
265                unset($additional_data['reportee_id']);
266
267                $sql_ary += array(
268                    'log_type'        => LOG_USERS,
269                    'reportee_id'    => $reportee_id,
270                    'log_data'        => (!empty($additional_data)) ? serialize($additional_data) : '',
271                );
272            break;
273
274            case 'critical':
275                $sql_ary += array(
276                    'log_type'        => LOG_CRITICAL,
277                    'log_data'        => (!empty($additional_data)) ? serialize($additional_data) : '',
278                );
279            break;
280        }
281
282        /**
283        * Allows to modify log data before we add it to the database
284        *
285        * NOTE: if sql_ary does not contain a log_type value, the entry will
286        * not be stored in the database. So ensure to set it, if needed.
287        *
288        * @event core.add_log
289        * @var    string    mode            Mode of the entry we log
290        * @var    int        user_id            ID of the user who triggered the log
291        * @var    string    log_ip            IP of the user who triggered the log
292        * @var    string    log_operation    Language key of the log operation
293        * @var    int        log_time        Timestamp, when the log was added
294        * @var    array    additional_data    Array with additional log data
295        * @var    array    sql_ary            Array with log data we insert into the
296        *                            database. If sql_ary[log_type] is not set,
297        *                            we won't add the entry to the database.
298        * @since 3.1.0-a1
299        */
300        $vars = array(
301            'mode',
302            'user_id',
303            'log_ip',
304            'log_operation',
305            'log_time',
306            'additional_data',
307            'sql_ary',
308        );
309        extract($this->dispatcher->trigger_event('core.add_log', compact($vars)));
310
311        // We didn't find a log_type, so we don't save it in the database.
312        if (!isset($sql_ary['log_type']))
313        {
314            return false;
315        }
316
317        $this->db->sql_query('INSERT INTO ' . $this->log_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary));
318
319        return $this->db->sql_nextid();
320    }
321
322    /**
323    * {@inheritDoc}
324    */
325    public function delete($mode, $conditions = array())
326    {
327        switch ($mode)
328        {
329            case 'admin':
330                $log_type = LOG_ADMIN;
331                break;
332
333            case 'mod':
334                $log_type = LOG_MOD;
335                break;
336
337            case 'user':
338                $log_type = LOG_USERS;
339                break;
340
341            case 'users':
342                $log_type = LOG_USERS;
343                break;
344
345            case 'critical':
346                $log_type = LOG_CRITICAL;
347                break;
348
349            default:
350                $log_type = false;
351        }
352
353        /**
354        * Allows to modify log data before we delete it from the database
355        *
356        * NOTE: if sql_ary does not contain a log_type value, the entry will
357        * not be deleted in the database. So ensure to set it, if needed.
358        *
359        * @event core.delete_log
360        * @var    string    mode            Mode of the entry we log
361        * @var    string    log_type        Type ID of the log (should be different than false)
362        * @var    array    conditions        An array of conditions, 3 different  forms are accepted
363        *                                 1) <key> => <value> transformed into 'AND <key> = <value>' (value should be an integer)
364        *                                2) <key> => array(<operator>, <value>) transformed into 'AND <key> <operator> <value>' (values can't be an array)
365        *                                3) <key> => array('IN' => array(<values>)) transformed into 'AND <key> IN <values>'
366        *                                A special field, keywords, can also be defined. In this case only the log entries that have the keywords in log_operation or log_data will be deleted.
367        * @since 3.1.0-b4
368        */
369        $vars = array(
370            'mode',
371            'log_type',
372            'conditions',
373        );
374        extract($this->dispatcher->trigger_event('core.delete_log', compact($vars)));
375
376        if ($log_type === false)
377        {
378            return;
379        }
380
381        $sql_where = 'WHERE log_type = ' . $log_type;
382
383        if (isset($conditions['keywords']))
384        {
385            $sql_where .= $this->generate_sql_keyword($conditions['keywords'], '');
386
387            unset($conditions['keywords']);
388        }
389
390        foreach ($conditions as $field => $field_value)
391        {
392            $sql_where .= ' AND ';
393
394            if (is_array($field_value) && count($field_value) == 2 && !is_array($field_value[1]))
395            {
396                $sql_where .= $field . ' ' . $field_value[0] . ' ' . $field_value[1];
397            }
398            else if (is_array($field_value) && isset($field_value['IN']) && is_array($field_value['IN']))
399            {
400                $sql_where .= $this->db->sql_in_set($field, $field_value['IN']);
401            }
402            else
403            {
404                $sql_where .= $field . ' = ' . $field_value;
405            }
406        }
407
408        $sql = 'DELETE FROM ' . $this->log_table . "
409                    $sql_where";
410        $this->db->sql_query($sql);
411
412        $this->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CLEAR_' . strtoupper($mode));
413    }
414
415    /**
416    * {@inheritDoc}
417    */
418    public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = '')
419    {
420        $this->entry_count = 0;
421        $this->last_page_offset = $offset;
422
423        $topic_id_list = $reportee_id_list = array();
424
425        $profile_url = ($this->get_is_admin() && $this->phpbb_admin_path) ? append_sid("{$this->phpbb_admin_path}index.{$this->php_ext}", 'i=users&amp;mode=overview') : append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=viewprofile');
426
427        switch ($mode)
428        {
429            case 'admin':
430                $log_type = LOG_ADMIN;
431                $sql_additional = '';
432            break;
433
434            case 'mod':
435                $log_type = LOG_MOD;
436                $sql_additional = '';
437
438                if ($topic_id)
439                {
440                    $sql_additional = 'AND l.topic_id = ' . (int) $topic_id;
441                }
442                else if (is_array($forum_id))
443                {
444                    $sql_additional = 'AND ' . $this->db->sql_in_set('l.forum_id', array_map('intval', $forum_id));
445                }
446                else if ($forum_id)
447                {
448                    $sql_additional = 'AND l.forum_id = ' . (int) $forum_id;
449                }
450            break;
451
452            case 'user':
453                $log_type = LOG_USERS;
454                $sql_additional = 'AND l.reportee_id = ' . (int) $user_id;
455            break;
456
457            case 'users':
458                $log_type = LOG_USERS;
459                $sql_additional = '';
460            break;
461
462            case 'critical':
463                $log_type = LOG_CRITICAL;
464                $sql_additional = '';
465            break;
466
467            default:
468                $log_type = false;
469                $sql_additional = '';
470        }
471
472        /**
473        * Overwrite log type and limitations before we count and get the logs
474        *
475        * NOTE: if log_type is false, no entries will be returned.
476        *
477        * @event core.get_logs_modify_type
478        * @var    string    mode        Mode of the entries we display
479        * @var    bool    count_logs    Do we count all matching entries?
480        * @var    int        limit        Limit the number of entries
481        * @var    int        offset        Offset when fetching the entries
482        * @var    mixed    forum_id    Limit entries to the forum_id,
483        *                            can also be an array of forum_ids
484        * @var    int        topic_id    Limit entries to the topic_id
485        * @var    int        user_id        Limit entries to the user_id
486        * @var    int        log_time    Limit maximum age of log entries
487        * @var    string    sort_by        SQL order option
488        * @var    string    keywords    Will only return entries that have the
489        *                            keywords in log_operation or log_data
490        * @var    string    profile_url    URL to the users profile
491        * @var    int        log_type    Limit logs to a certain type. If log_type
492        *                            is false, no entries will be returned.
493        * @var    string    sql_additional    Additional conditions for the entries,
494        *                                e.g.: 'AND l.forum_id = 1'
495        * @since 3.1.0-a1
496        */
497        $vars = array(
498            'mode',
499            'count_logs',
500            'limit',
501            'offset',
502            'forum_id',
503            'topic_id',
504            'user_id',
505            'log_time',
506            'sort_by',
507            'keywords',
508            'profile_url',
509            'log_type',
510            'sql_additional',
511        );
512        extract($this->dispatcher->trigger_event('core.get_logs_modify_type', compact($vars)));
513
514        if ($log_type === false)
515        {
516            $this->last_page_offset = 0;
517            return array();
518        }
519
520        $sql_keywords = '';
521        if (!empty($keywords))
522        {
523            // Get the SQL condition for our keywords
524            $sql_keywords = $this->generate_sql_keyword($keywords);
525        }
526
527        $get_logs_sql_ary = array(
528            'SELECT' => 'l.*, u.username, u.username_clean, u.user_colour',
529            'FROM' => array(
530                        $this->log_table => 'l',
531                        USERS_TABLE => 'u',
532                    ),
533            'WHERE' => 'l.log_type = ' . (int) $log_type . "
534                    AND l.user_id = u.user_id
535                    $sql_keywords
536                    $sql_additional",
537
538            'ORDER_BY' => $sort_by,
539        );
540
541        if ($log_time)
542        {
543            $get_logs_sql_ary['WHERE'] = 'l.log_time >= ' . (int) $log_time . '
544                    AND ' . $get_logs_sql_ary['WHERE'];
545        }
546
547        /**
548        * Modify the query to obtain the logs data
549        *
550        * @event core.get_logs_main_query_before
551        * @var    array    get_logs_sql_ary    The array in the format of the query builder with the query
552        *                                    to get the log count and the log list
553        * @var    string    mode                Mode of the entries we display
554        * @var    bool    count_logs            Do we count all matching entries?
555        * @var    int        limit                Limit the number of entries
556        * @var    int        offset                Offset when fetching the entries
557        * @var    mixed    forum_id            Limit entries to the forum_id,
558        *                                    can also be an array of forum_ids
559        * @var    int        topic_id            Limit entries to the topic_id
560        * @var    int        user_id                Limit entries to the user_id
561        * @var    int        log_time            Limit maximum age of log entries
562        * @var    string    sort_by                SQL order option
563        * @var    string    keywords            Will only return entries that have the
564        *                                    keywords in log_operation or log_data
565        * @var    string    profile_url            URL to the users profile
566        * @var    int        log_type            Limit logs to a certain type. If log_type
567        *                                    is false, no entries will be returned.
568        * @var    string    sql_additional        Additional conditions for the entries,
569        *                                    e.g.: 'AND l.forum_id = 1'
570        * @since 3.1.5-RC1
571        */
572        $vars = array(
573            'get_logs_sql_ary',
574            'mode',
575            'count_logs',
576            'limit',
577            'offset',
578            'forum_id',
579            'topic_id',
580            'user_id',
581            'log_time',
582            'sort_by',
583            'keywords',
584            'profile_url',
585            'log_type',
586            'sql_additional',
587        );
588        extract($this->dispatcher->trigger_event('core.get_logs_main_query_before', compact($vars)));
589
590        if ($count_logs)
591        {
592            $count_logs_sql_ary = $get_logs_sql_ary;
593
594            $count_logs_sql_ary['SELECT'] = 'COUNT(l.log_id) AS total_entries';
595            unset($count_logs_sql_ary['ORDER_BY']);
596
597            $sql = $this->db->sql_build_query('SELECT', $count_logs_sql_ary);
598            $result = $this->db->sql_query($sql);
599            $this->entry_count = (int) $this->db->sql_fetchfield('total_entries');
600            $this->db->sql_freeresult($result);
601
602            if ($this->entry_count == 0)
603            {
604                // Save the queries, because there are no logs to display
605                $this->last_page_offset = 0;
606                return array();
607            }
608
609            // Return the user to the last page that is valid
610            while ($this->last_page_offset >= $this->entry_count)
611            {
612                $this->last_page_offset = max(0, $this->last_page_offset - $limit);
613            }
614        }
615
616        $sql = $this->db->sql_build_query('SELECT', $get_logs_sql_ary);
617        $result = $this->db->sql_query_limit($sql, $limit, $this->last_page_offset);
618
619        $i = 0;
620        $log = array();
621        while ($row = $this->db->sql_fetchrow($result))
622        {
623            $row['forum_id'] = (int) $row['forum_id'];
624            if ($row['topic_id'])
625            {
626                $topic_id_list[] = (int) $row['topic_id'];
627            }
628
629            if ($row['reportee_id'])
630            {
631                $reportee_id_list[] = (int) $row['reportee_id'];
632            }
633
634            $log_entry_data = array(
635                'id'                => (int) $row['log_id'],
636
637                'reportee_id'            => (int) $row['reportee_id'],
638                'reportee_username'        => '',
639                'reportee_username_full'=> '',
640
641                'user_id'            => (int) $row['user_id'],
642                'username'            => $row['username'],
643                'username_full'        => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $profile_url),
644
645                'ip'                => $row['log_ip'],
646                'time'                => (int) $row['log_time'],
647                'forum_id'            => (int) $row['forum_id'],
648                'topic_id'            => (int) $row['topic_id'],
649                'post_id'            => (int) $row['post_id'],
650
651                'viewforum'            => ($row['forum_id'] && $this->auth->acl_get('f_read', $row['forum_id'])) ? append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}", 'f=' . $row['forum_id']) : false,
652                'action'            => (isset($this->user->lang[$row['log_operation']])) ? $row['log_operation'] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}',
653            );
654
655            /**
656            * Modify the entry's data before it is returned
657            *
658            * @event core.get_logs_modify_entry_data
659            * @var    array    row            Entry data from the database
660            * @var    array    log_entry_data    Entry's data which is returned
661            * @since 3.1.0-a1
662            */
663            $vars = array('row', 'log_entry_data');
664            extract($this->dispatcher->trigger_event('core.get_logs_modify_entry_data', compact($vars)));
665
666            $log[$i] = $log_entry_data;
667
668            if (!empty($row['log_data']))
669            {
670                $log_data_ary = unserialize($row['log_data']);
671                $log_data_ary = ($log_data_ary !== false) ? $log_data_ary : array();
672
673                if (isset($this->user->lang[$row['log_operation']]))
674                {
675                    // Check if there are more occurrences of % than
676                    // arguments, if there are we fill out the arguments
677                    // array. It doesn't matter if we add more arguments than
678                    // placeholders.
679                    $num_args = 0;
680                    if (!is_array($this->user->lang[$row['log_operation']]))
681                    {
682                        $num_args = substr_count($this->user->lang[$row['log_operation']], '%');
683                    }
684                    else
685                    {
686                        foreach ($this->user->lang[$row['log_operation']] as $case => $plural_string)
687                        {
688                            $num_args = max($num_args, substr_count($plural_string, '%'));
689                        }
690                    }
691
692                    if (($num_args - count($log_data_ary)) > 0)
693                    {
694                        $log_data_ary = array_merge($log_data_ary, array_fill(0, $num_args - count($log_data_ary), ''));
695                    }
696
697                    $lang_arguments = array_merge(array($log[$i]['action']), $log_data_ary);
698                    $log[$i]['action'] = call_user_func_array(array($this->user, 'lang'), array_values($lang_arguments));
699
700                    // If within the admin panel we do not censor text out
701                    if ($this->get_is_admin())
702                    {
703                        $log[$i]['action'] = bbcode_nl2br($log[$i]['action']);
704                    }
705                    else
706                    {
707                        $log[$i]['action'] = bbcode_nl2br(censor_text($log[$i]['action']));
708                    }
709                }
710                else if (!empty($log_data_ary))
711                {
712                    $log[$i]['action'] .= '<br />' . implode('', $log_data_ary);
713                }
714
715                /* Apply make_clickable... has to be seen if it is for good. :/
716                // Seems to be not for the moment, reconsider later...
717                $log[$i]['action'] = make_clickable($log[$i]['action']);
718                */
719            }
720            else
721            {
722                $log[$i]['action'] = $this->user->lang($log[$i]['action']);
723            }
724
725            $i++;
726        }
727        $this->db->sql_freeresult($result);
728
729        /**
730        * Get some additional data after we got all log entries
731        *
732        * @event core.get_logs_get_additional_data
733        * @var    array    log            Array with all our log entries
734        * @var    array    topic_id_list        Array of topic ids, for which we
735        *                                    get the permission data
736        * @var    array    reportee_id_list    Array of additional user IDs we
737        *                                    get the username strings for
738        * @since 3.1.0-a1
739        */
740        $vars = array('log', 'topic_id_list', 'reportee_id_list');
741        extract($this->dispatcher->trigger_event('core.get_logs_get_additional_data', compact($vars)));
742
743        if (count($topic_id_list))
744        {
745            $topic_auth = $this->get_topic_auth($topic_id_list);
746
747            foreach ($log as $key => $row)
748            {
749                $log[$key]['viewtopic'] = (isset($topic_auth['f_read'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 't=' . $row['topic_id']) : false;
750                $log[$key]['viewpost'] = (isset($topic_auth['f_read'][$row['topic_id']]) && $row['post_id']) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'p=' . $row['post_id'] . '#p' . $row['post_id']) : false;
751                $log[$key]['viewlogs'] = (isset($topic_auth['m_'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}mcp.{$this->php_ext}", 'i=logs&amp;mode=topic_logs&amp;t=' . $row['topic_id']) : false;
752            }
753        }
754
755        if (count($reportee_id_list))
756        {
757            $reportee_data_list = $this->get_reportee_data($reportee_id_list);
758
759            foreach ($log as $key => $row)
760            {
761                if (!isset($reportee_data_list[$row['reportee_id']]))
762                {
763                    continue;
764                }
765
766                $log[$key]['reportee_username'] = $reportee_data_list[$row['reportee_id']]['username'];
767                $log[$key]['reportee_username_full'] = get_username_string(
768                    'full',
769                    $row['reportee_id'],
770                    $reportee_data_list[$row['reportee_id']]['username'],
771                    $reportee_data_list[$row['reportee_id']]['user_colour'],
772                    false,
773                    $profile_url
774                );
775            }
776        }
777
778        /**
779        * Allow modifying or execute extra final filter on log entries
780        *
781        * @event core.get_logs_after
782        * @var    array    log            Array with all our log entries
783        * @var    array    topic_id_list        Array of topic ids, for which we
784        *                                    get the permission data
785        * @var    array    reportee_id_list    Array of additional user IDs we
786        *                                    get the username strings for
787        * @var    string    mode        Mode of the entries we display
788        * @var    bool    count_logs    Do we count all matching entries?
789        * @var    int        limit        Limit the number of entries
790        * @var    int        offset        Offset when fetching the entries
791        * @var    mixed    forum_id    Limit entries to the forum_id,
792        *                            can also be an array of forum_ids
793        * @var    int        topic_id    Limit entries to the topic_id
794        * @var    int        user_id        Limit entries to the user_id
795        * @var    int        log_time    Limit maximum age of log entries
796        * @var    string    sort_by        SQL order option
797        * @var    string    keywords    Will only return entries that have the
798        *                            keywords in log_operation or log_data
799        * @var    string    profile_url    URL to the users profile
800        * @var    int        log_type    The type of logs it was filtered
801        * @since 3.1.3-RC1
802        */
803        $vars = array(
804            'log',
805            'topic_id_list',
806            'reportee_id_list',
807            'mode',
808            'count_logs',
809            'limit',
810            'offset',
811            'forum_id',
812            'topic_id',
813            'user_id',
814            'log_time',
815            'sort_by',
816            'keywords',
817            'profile_url',
818            'log_type',
819        );
820        extract($this->dispatcher->trigger_event('core.get_logs_after', compact($vars)));
821
822        return $log;
823    }
824
825    /**
826    * Generates a sql condition for the specified keywords
827    *
828    * @param    string    $keywords            The keywords the user specified to search for
829    * @param    string    $table_alias        The alias of the logs' table ('l.' by default)
830    * @param    string    $statement_operator    The operator used to prefix the statement ('AND' by default)
831    *
832    * @return    string        Returns the SQL condition searching for the keywords
833    */
834    protected function generate_sql_keyword($keywords, $table_alias = 'l.', $statement_operator = 'AND')
835    {
836        // Use no preg_quote for $keywords because this would lead to sole
837        // backslashes being added. We also use an OR connection here for
838        // spaces and the | string. Currently, regex is not supported for
839        // searching (but may come later).
840        $keywords = preg_split('#[\s|]+#u', utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY);
841        $sql_keywords = '';
842
843        if (!empty($keywords))
844        {
845            $keywords_pattern = array();
846
847            // Build pattern and keywords...
848            for ($i = 0, $num_keywords = count($keywords); $i < $num_keywords; $i++)
849            {
850                $keywords_pattern[] = preg_quote($keywords[$i], '#');
851                $keywords[$i] = $this->db->sql_like_expression($this->db->get_any_char() . $keywords[$i] . $this->db->get_any_char());
852            }
853
854            $keywords_pattern = '#' . implode('|', $keywords_pattern) . '#ui';
855
856            $operations = array();
857            foreach ($this->user->lang as $key => $value)
858            {
859                if (substr($key, 0, 4) == 'LOG_')
860                {
861                    if (is_array($value))
862                    {
863                        foreach ($value as $plural_value)
864                        {
865                            if (preg_match($keywords_pattern, $plural_value))
866                            {
867                                $operations[] = $key;
868                                break;
869                            }
870                        }
871                    }
872                    else if (preg_match($keywords_pattern, $value))
873                    {
874                        $operations[] = $key;
875                    }
876                }
877            }
878
879            $sql_keywords = ' ' . $statement_operator . ' (';
880            if (!empty($operations))
881            {
882                $sql_keywords .= $this->db->sql_in_set($table_alias . 'log_operation', $operations) . ' OR ';
883            }
884            $sql_lower = $this->db->sql_lower_text($table_alias . 'log_data');
885            $sql_keywords .= " $sql_lower " . implode(" OR $sql_lower ", $keywords) . ')';
886        }
887
888        return $sql_keywords;
889    }
890
891    /**
892    * Determine whether the user is allowed to read and/or moderate the forum of the topic
893    *
894    * @param    array    $topic_ids    Array with the topic ids
895    *
896    * @return    array        Returns an array with two keys 'm_' and 'read_f' which are also an array of topic_id => forum_id sets when the permissions are given. Sample:
897    *                        array(
898    *                            'permission' => array(
899    *                                topic_id => forum_id
900    *                            ),
901    *                        ),
902    */
903    protected function get_topic_auth(array $topic_ids)
904    {
905        $forum_auth = array('f_read' => array(), 'm_' => array());
906        $topic_ids = array_unique($topic_ids);
907
908        $sql_ary = array(
909            'SELECT'    => 'topic_id, forum_id',
910            'FROM'        => array(
911                TOPICS_TABLE    => 't',
912            ),
913            'WHERE'        => $this->db->sql_in_set('topic_id', array_map('intval', $topic_ids)),
914        );
915
916        /**
917        * Allow modifying SQL query before topic data is retrieved.
918        *
919        * @event core.phpbb_log_get_topic_auth_sql_before
920        * @var    array    topic_ids    Array with unique topic IDs
921        * @var    array    sql_ary        SQL array
922        * @since 3.1.11-RC1
923        */
924        $vars = array(
925            'topic_ids',
926            'sql_ary',
927        );
928        extract($this->dispatcher->trigger_event('core.phpbb_log_get_topic_auth_sql_before', compact($vars)));
929
930        $sql = $this->db->sql_build_query('SELECT', $sql_ary);
931        $result = $this->db->sql_query($sql);
932
933        while ($row = $this->db->sql_fetchrow($result))
934        {
935            $row['topic_id'] = (int) $row['topic_id'];
936            $row['forum_id'] = (int) $row['forum_id'];
937
938            if ($this->auth->acl_get('f_read', $row['forum_id']))
939            {
940                $forum_auth['f_read'][$row['topic_id']] = $row['forum_id'];
941            }
942
943            /**
944             * Allow modifying SQL query after topic data is retrieved (inside loop).
945             *
946             * @event core.phpbb_log_get_topic_auth_sql_after
947             * @var    array    forum_auth    Forum permissions
948             * @var    array    row            One row of data from SQL query
949             * @since 3.2.2-RC1
950             */
951            $vars = array(
952                'forum_auth',
953                'row',
954            );
955            extract($this->dispatcher->trigger_event('core.phpbb_log_get_topic_auth_sql_after', compact($vars)));
956
957            if ($this->auth->acl_gets('a_', 'm_', $row['forum_id']))
958            {
959                $forum_auth['m_'][$row['topic_id']] = $row['forum_id'];
960            }
961        }
962        $this->db->sql_freeresult($result);
963
964        return $forum_auth;
965    }
966
967    /**
968    * Get the data for all reportee from the database
969    *
970    * @param    array    $reportee_ids    Array with the user ids of the reportees
971    *
972    * @return    array        Returns an array with the reportee data
973    */
974    protected function get_reportee_data(array $reportee_ids)
975    {
976        $reportee_ids = array_unique($reportee_ids);
977        $reportee_data_list = array();
978
979        $sql = 'SELECT user_id, username, user_colour
980            FROM ' . USERS_TABLE . '
981            WHERE ' . $this->db->sql_in_set('user_id', $reportee_ids);
982        $result = $this->db->sql_query($sql);
983
984        while ($row = $this->db->sql_fetchrow($result))
985        {
986            $reportee_data_list[$row['user_id']] = $row;
987        }
988        $this->db->sql_freeresult($result);
989
990        return $reportee_data_list;
991    }
992
993    /**
994    * {@inheritDoc}
995    */
996    public function get_log_count()
997    {
998        return ($this->entry_count) ? $this->entry_count : 0;
999    }
1000
1001    /**
1002    * {@inheritDoc}
1003    */
1004    public function get_valid_offset()
1005    {
1006        return ($this->last_page_offset) ? $this->last_page_offset : 0;
1007    }
1008}