Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
10.21% covered (danger)
10.21%
115 / 1126
5.26% covered (danger)
5.26%
1 / 19
CRAP
n/a
0 / 0
get_folder
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
306
clean_sentbox
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
check_rule
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
756
update_pm_counts
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
place_pm_into_folder
0.00% covered (danger)
0.00%
0 / 190
0.00% covered (danger)
0.00%
0 / 1
2550
move_pm
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
272
update_unread_status
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
mark_folder_read
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
handle_mark_actions
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
90
delete_pm
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
272
phpbb_delete_users_pms
94.55% covered (success)
94.55%
104 / 110
0.00% covered (danger)
0.00%
0 / 1
15.04
rebuild_header
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
write_pm_addresses
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
930
get_folder_status
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
submit_pm
0.00% covered (danger)
0.00%
0 / 193
0.00% covered (danger)
0.00%
0 / 1
3906
message_history
0.00% covered (danger)
0.00%
0 / 123
0.00% covered (danger)
0.00%
0 / 1
756
set_user_message_limit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
phpbb_get_max_setting_from_group
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
get_recipient_strings
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
342
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
14/**
15*/
16if (!defined('IN_PHPBB'))
17{
18    exit;
19}
20
21/*
22    Ability to simply add own rules by doing three things:
23        1) Add an appropriate constant
24        2) Add a new check array to the global_privmsgs_rules variable and the condition array (if one is required)
25        3) Implement the rule logic in the check_rule() function
26        4) Add a new language variable to ucp.php
27
28        The user is then able to select the new rule. It will be checked against and handled as specified.
29        To add new actions (yes, checks can be added here too) to the rule management, the core code has to be modified.
30*/
31
32define('RULE_IS_LIKE', 1);        // Is Like
33define('RULE_IS_NOT_LIKE', 2);    // Is Not Like
34define('RULE_IS', 3);            // Is
35define('RULE_IS_NOT', 4);        // Is Not
36define('RULE_BEGINS_WITH', 5);    // Begins with
37define('RULE_ENDS_WITH', 6);    // Ends with
38define('RULE_IS_FRIEND', 7);    // Is Friend
39define('RULE_IS_FOE', 8);        // Is Foe
40define('RULE_IS_USER', 9);        // Is User
41define('RULE_IS_GROUP', 10);    // Is In Usergroup
42define('RULE_ANSWERED', 11);    // Answered
43define('RULE_FORWARDED', 12);    // Forwarded
44define('RULE_TO_GROUP', 14);    // Usergroup
45define('RULE_TO_ME', 15);        // Me
46
47define('ACTION_PLACE_INTO_FOLDER', 1);
48define('ACTION_MARK_AS_READ', 2);
49define('ACTION_MARK_AS_IMPORTANT', 3);
50define('ACTION_DELETE_MESSAGE', 4);
51
52define('CHECK_SUBJECT', 1);
53define('CHECK_SENDER', 2);
54define('CHECK_MESSAGE', 3);
55define('CHECK_STATUS', 4);
56define('CHECK_TO', 5);
57
58/**
59* Global private message rules
60* These rules define what to do if a rule is hit
61*/
62$global_privmsgs_rules = array(
63    CHECK_SUBJECT    => array(
64        RULE_IS_LIKE        => array('check0' => 'message_subject'),
65        RULE_IS_NOT_LIKE    => array('check0' => 'message_subject'),
66        RULE_IS                => array('check0' => 'message_subject'),
67        RULE_IS_NOT            => array('check0' => 'message_subject'),
68        RULE_BEGINS_WITH    => array('check0' => 'message_subject'),
69        RULE_ENDS_WITH        => array('check0' => 'message_subject'),
70    ),
71
72    CHECK_SENDER    => array(
73        RULE_IS_LIKE        => array('check0' => 'username'),
74        RULE_IS_NOT_LIKE    => array('check0' => 'username'),
75        RULE_IS                => array('check0' => 'username'),
76        RULE_IS_NOT            => array('check0' => 'username'),
77        RULE_BEGINS_WITH    => array('check0' => 'username'),
78        RULE_ENDS_WITH        => array('check0' => 'username'),
79        RULE_IS_FRIEND        => array('check0' => 'friend'),
80        RULE_IS_FOE            => array('check0' => 'foe'),
81        RULE_IS_USER        => array('check0' => 'author_id'),
82        RULE_IS_GROUP        => array('check0' => 'author_in_group'),
83    ),
84
85    CHECK_MESSAGE    => array(
86        RULE_IS_LIKE        => array('check0' => 'message_text'),
87        RULE_IS_NOT_LIKE    => array('check0' => 'message_text'),
88        RULE_IS                => array('check0' => 'message_text'),
89        RULE_IS_NOT            => array('check0' => 'message_text'),
90    ),
91
92    CHECK_STATUS    => array(
93        RULE_ANSWERED        => array('check0' => 'pm_replied'),
94        RULE_FORWARDED        => array('check0' => 'pm_forwarded'),
95    ),
96
97    CHECK_TO        => array(
98        RULE_TO_GROUP        => array('check0' => 'to', 'check1' => 'bcc', 'check2' => 'user_in_group'),
99        RULE_TO_ME            => array('check0' => 'to', 'check1' => 'bcc'),
100    )
101);
102
103/**
104* This is for defining which condition fields to show for which Rule
105*/
106$global_rule_conditions = array(
107    RULE_IS_LIKE        => 'text',
108    RULE_IS_NOT_LIKE    => 'text',
109    RULE_IS                => 'text',
110    RULE_IS_NOT            => 'text',
111    RULE_BEGINS_WITH    => 'text',
112    RULE_ENDS_WITH        => 'text',
113    RULE_IS_USER        => 'user',
114    RULE_IS_GROUP        => 'group'
115);
116
117/**
118* Get all folder
119*/
120function get_folder($user_id, $folder_id = false)
121{
122    global $db, $user, $template;
123    global $phpbb_root_path, $phpEx;
124
125    $folder = array();
126
127    // Get folder information
128    $sql = 'SELECT folder_id, COUNT(msg_id) as num_messages, SUM(pm_unread) as num_unread
129        FROM ' . PRIVMSGS_TO_TABLE . "
130        WHERE user_id = $user_id
131            AND folder_id <> " . PRIVMSGS_NO_BOX . '
132        GROUP BY folder_id';
133    $result = $db->sql_query($sql);
134
135    $num_messages = $num_unread = array();
136    while ($row = $db->sql_fetchrow($result))
137    {
138        $num_messages[(int) $row['folder_id']] = $row['num_messages'];
139        $num_unread[(int) $row['folder_id']] = $row['num_unread'];
140    }
141    $db->sql_freeresult($result);
142
143    // Make sure the default boxes are defined
144    $available_folder = array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX);
145
146    foreach ($available_folder as $default_folder)
147    {
148        if (!isset($num_messages[$default_folder]))
149        {
150            $num_messages[$default_folder] = 0;
151        }
152
153        if (!isset($num_unread[$default_folder]))
154        {
155            $num_unread[$default_folder] = 0;
156        }
157    }
158
159    // Adjust unread status for outbox
160    $num_unread[PRIVMSGS_OUTBOX] = $num_messages[PRIVMSGS_OUTBOX];
161
162    $folder[PRIVMSGS_INBOX] = array(
163        'folder_name'        => $user->lang['PM_INBOX'],
164        'num_messages'        => $num_messages[PRIVMSGS_INBOX],
165        'unread_messages'    => $num_unread[PRIVMSGS_INBOX]
166    );
167
168    // Custom Folder
169    $sql = 'SELECT folder_id, folder_name, pm_count
170        FROM ' . PRIVMSGS_FOLDER_TABLE . "
171            WHERE user_id = $user_id";
172    $result = $db->sql_query($sql);
173
174    while ($row = $db->sql_fetchrow($result))
175    {
176        $folder[$row['folder_id']] = array(
177            'folder_name'        => $row['folder_name'],
178            'num_messages'        => $row['pm_count'],
179            'unread_messages'    => ((isset($num_unread[$row['folder_id']])) ? $num_unread[$row['folder_id']] : 0)
180        );
181    }
182    $db->sql_freeresult($result);
183
184    $folder[PRIVMSGS_OUTBOX] = array(
185        'folder_name'        => $user->lang['PM_OUTBOX'],
186        'num_messages'        => $num_messages[PRIVMSGS_OUTBOX],
187        'unread_messages'    => $num_unread[PRIVMSGS_OUTBOX]
188    );
189
190    $folder[PRIVMSGS_SENTBOX] = array(
191        'folder_name'        => $user->lang['PM_SENTBOX'],
192        'num_messages'        => $num_messages[PRIVMSGS_SENTBOX],
193        'unread_messages'    => $num_unread[PRIVMSGS_SENTBOX]
194    );
195
196    // Define Folder Array for template designers (and for making custom folders usable by the template too)
197    foreach ($folder as $f_id => $folder_ary)
198    {
199        $folder_id_name = ($f_id == PRIVMSGS_INBOX) ? 'inbox' : (($f_id == PRIVMSGS_OUTBOX) ? 'outbox' : 'sentbox');
200
201        $template->assign_block_vars('folder', array(
202            'FOLDER_ID'            => $f_id,
203            'FOLDER_NAME'        => $folder_ary['folder_name'],
204            'NUM_MESSAGES'        => $folder_ary['num_messages'],
205            'UNREAD_MESSAGES'    => $folder_ary['unread_messages'],
206
207            'U_FOLDER'            => ($f_id > 0) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=' . $f_id) : append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=' . $folder_id_name),
208
209            'S_CUR_FOLDER'        => ($f_id === $folder_id) ? true : false,
210            'S_UNREAD_MESSAGES'    => ($folder_ary['unread_messages']) ? true : false,
211            'S_CUSTOM_FOLDER'    => ($f_id > 0) ? true : false)
212        );
213    }
214
215    if ($folder_id !== false && $folder_id !== PRIVMSGS_HOLD_BOX && !isset($folder[$folder_id]))
216    {
217        trigger_error('UNKNOWN_FOLDER');
218    }
219
220    return $folder;
221}
222
223/**
224* Delete Messages From Sentbox
225* we are doing this here because this saves us a bunch of checks and queries
226*/
227function clean_sentbox($num_sentbox_messages)
228{
229    global $db, $user;
230
231    // Check Message Limit
232    if ($user->data['message_limit'] && $num_sentbox_messages > $user->data['message_limit'])
233    {
234        // Delete old messages
235        $sql = 'SELECT t.msg_id
236            FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p
237            WHERE t.msg_id = p.msg_id
238                AND t.user_id = ' . $user->data['user_id'] . '
239                AND t.folder_id = ' . PRIVMSGS_SENTBOX . '
240            ORDER BY p.message_time ASC';
241        $result = $db->sql_query_limit($sql, ($num_sentbox_messages - $user->data['message_limit']));
242
243        $delete_ids = array();
244        while ($row = $db->sql_fetchrow($result))
245        {
246            $delete_ids[] = $row['msg_id'];
247        }
248        $db->sql_freeresult($result);
249        delete_pm($user->data['user_id'], $delete_ids, PRIVMSGS_SENTBOX);
250    }
251}
252
253/**
254* Check Rule against Message Information
255*/
256function check_rule(&$rules, &$rule_row, &$message_row, $user_id)
257{
258    if (!isset($rules[$rule_row['rule_check']][$rule_row['rule_connection']]))
259    {
260        return false;
261    }
262
263    $check_ary = $rules[$rule_row['rule_check']][$rule_row['rule_connection']];
264
265    $result = false;
266
267    $check0 = $message_row[$check_ary['check0']];
268
269    switch ($rule_row['rule_connection'])
270    {
271        case RULE_IS_LIKE:
272            $result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0);
273        break;
274
275        case RULE_IS_NOT_LIKE:
276            $result = !preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0);
277        break;
278
279        case RULE_IS:
280            $result = ($check0 == $rule_row['rule_string']);
281        break;
282
283        case RULE_IS_NOT:
284            $result = ($check0 != $rule_row['rule_string']);
285        break;
286
287        case RULE_BEGINS_WITH:
288            $result = preg_match("/^" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0);
289        break;
290
291        case RULE_ENDS_WITH:
292            $result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '$/i', $check0);
293        break;
294
295        case RULE_IS_FRIEND:
296        case RULE_IS_FOE:
297        case RULE_ANSWERED:
298        case RULE_FORWARDED:
299            $result = ($check0 == 1);
300        break;
301
302        case RULE_IS_USER:
303            $result = ($check0 == $rule_row['rule_user_id']);
304        break;
305
306        case RULE_IS_GROUP:
307            $result = in_array($rule_row['rule_group_id'], $check0);
308        break;
309
310        case RULE_TO_GROUP:
311            $result = (in_array('g_' . $message_row[$check_ary['check2']], $check0) || in_array('g_' . $message_row[$check_ary['check2']], $message_row[$check_ary['check1']]));
312        break;
313
314        case RULE_TO_ME:
315            $result = (in_array('u_' . $user_id, $check0) || in_array('u_' . $user_id, $message_row[$check_ary['check1']]));
316        break;
317    }
318
319    if (!$result)
320    {
321        return false;
322    }
323
324    switch ($rule_row['rule_action'])
325    {
326        case ACTION_PLACE_INTO_FOLDER:
327            return array('action' => $rule_row['rule_action'], 'folder_id' => $rule_row['rule_folder_id']);
328        break;
329
330        case ACTION_MARK_AS_READ:
331        case ACTION_MARK_AS_IMPORTANT:
332            return array('action' => $rule_row['rule_action'], 'pm_unread' => $message_row['pm_unread'], 'pm_marked' => $message_row['pm_marked']);
333        break;
334
335        case ACTION_DELETE_MESSAGE:
336            global $db;
337
338            // Check for admins/mods - users are not allowed to remove those messages...
339            // We do the check here to make sure the data we use is consistent
340            $sql = 'SELECT user_id, user_type, user_permissions
341                FROM ' . USERS_TABLE . '
342                WHERE user_id = ' . (int) $message_row['author_id'];
343            $result = $db->sql_query($sql);
344            $userdata = $db->sql_fetchrow($result);
345            $db->sql_freeresult($result);
346
347            $auth2 = new \phpbb\auth\auth();
348            $auth2->acl($userdata);
349
350            if (!$auth2->acl_get('a_') && !$auth2->acl_get('m_') && !$auth2->acl_getf_global('m_'))
351            {
352                return array('action' => $rule_row['rule_action'], 'pm_unread' => $message_row['pm_unread'], 'pm_marked' => $message_row['pm_marked']);
353            }
354
355            return false;
356        break;
357
358        default:
359            return false;
360    }
361
362    return false;
363}
364
365/**
366* Update user PM count
367*/
368function update_pm_counts()
369{
370    global $user, $db;
371
372    // Update unread count
373    $sql = 'SELECT COUNT(msg_id) as num_messages
374        FROM ' . PRIVMSGS_TO_TABLE . '
375        WHERE pm_unread = 1
376            AND folder_id <> ' . PRIVMSGS_OUTBOX . '
377            AND user_id = ' . $user->data['user_id'];
378    $result = $db->sql_query($sql);
379    $user->data['user_unread_privmsg'] = (int) $db->sql_fetchfield('num_messages');
380    $db->sql_freeresult($result);
381
382    // Update new pm count
383    $sql = 'SELECT COUNT(msg_id) as num_messages
384        FROM ' . PRIVMSGS_TO_TABLE . '
385        WHERE pm_new = 1
386            AND folder_id IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ')
387            AND user_id = ' . $user->data['user_id'];
388    $result = $db->sql_query($sql);
389    $user->data['user_new_privmsg'] = (int) $db->sql_fetchfield('num_messages');
390    $db->sql_freeresult($result);
391
392    $db->sql_query('UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', array(
393        'user_unread_privmsg'    => (int) $user->data['user_unread_privmsg'],
394        'user_new_privmsg'        => (int) $user->data['user_new_privmsg'],
395    )) . ' WHERE user_id = ' . $user->data['user_id']);
396
397    // Ok, here we need to repair something, other boxes than privmsgs_no_box and privmsgs_hold_box should not carry the pm_new flag.
398    if (!$user->data['user_new_privmsg'])
399    {
400        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
401            SET pm_new = 0
402            WHERE pm_new = 1
403                AND folder_id NOT IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ')
404                AND user_id = ' . $user->data['user_id'];
405        $db->sql_query($sql);
406    }
407}
408
409/**
410* Place new messages into appropriate folder
411*/
412function place_pm_into_folder(&$global_privmsgs_rules, $release = false)
413{
414    global $db, $user, $config;
415
416    if (!$user->data['user_new_privmsg'])
417    {
418        return array('not_moved' => 0, 'removed' => 0);
419    }
420
421    $user_message_rules = (int) $user->data['user_message_rules'];
422    $user_id = (int) $user->data['user_id'];
423
424    $action_ary = $move_into_folder = array();
425    $num_removed = 0;
426
427    // Newly processing on-hold messages
428    if ($release)
429    {
430        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
431            SET folder_id = ' . PRIVMSGS_NO_BOX . '
432            WHERE folder_id = ' . PRIVMSGS_HOLD_BOX . "
433                AND user_id = $user_id";
434        $db->sql_query($sql);
435    }
436
437    // Get those messages not yet placed into any box
438    $retrieve_sql = 'SELECT t.*, p.*, u.username, u.user_id, u.group_id
439        FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . " u
440        WHERE t.user_id = $user_id
441            AND p.author_id = u.user_id
442            AND t.folder_id = " . PRIVMSGS_NO_BOX . '
443            AND t.msg_id = p.msg_id';
444
445    // Just place into the appropriate arrays if no rules need to be checked
446    if (!$user_message_rules)
447    {
448        $result = $db->sql_query($retrieve_sql);
449
450        while ($row = $db->sql_fetchrow($result))
451        {
452            $action_ary[$row['msg_id']][] = array('action' => false);
453        }
454        $db->sql_freeresult($result);
455    }
456    else
457    {
458        $zebra = $check_rows = array();
459        $user_ids = $memberships = array();
460
461        // First of all, grab all rules and retrieve friends/foes
462        $sql = 'SELECT *
463            FROM ' . PRIVMSGS_RULES_TABLE . "
464            WHERE user_id = $user_id";
465        $result = $db->sql_query($sql);
466        $user_rules = $db->sql_fetchrowset($result);
467        $db->sql_freeresult($result);
468
469        if (count($user_rules))
470        {
471            $sql = 'SELECT zebra_id, friend, foe
472                FROM ' . ZEBRA_TABLE . "
473                WHERE user_id = $user_id";
474            $result = $db->sql_query($sql);
475
476            while ($row = $db->sql_fetchrow($result))
477            {
478                $zebra[$row['zebra_id']] = $row;
479            }
480            $db->sql_freeresult($result);
481        }
482
483        // Now build a bare-bone check_row array
484        $result = $db->sql_query($retrieve_sql);
485
486        while ($row = $db->sql_fetchrow($result))
487        {
488            $check_rows[] = array_merge($row, array(
489                'to'                => explode(':', $row['to_address']),
490                'bcc'                => explode(':', $row['bcc_address']),
491                'friend'            => (isset($zebra[$row['author_id']])) ? $zebra[$row['author_id']]['friend'] : 0,
492                'foe'                => (isset($zebra[$row['author_id']])) ? $zebra[$row['author_id']]['foe'] : 0,
493                'user_in_group'        => $user->data['group_id'],
494                'author_in_group'    => array())
495            );
496
497            $user_ids[] = $row['user_id'];
498        }
499        $db->sql_freeresult($result);
500
501        // Retrieve user memberships
502        if (count($user_ids))
503        {
504            $sql = 'SELECT *
505                FROM ' . USER_GROUP_TABLE . '
506                WHERE ' . $db->sql_in_set('user_id', $user_ids) . '
507                    AND user_pending = 0';
508            $result = $db->sql_query($sql);
509
510            while ($row = $db->sql_fetchrow($result))
511            {
512                $memberships[$row['user_id']][] = $row['group_id'];
513            }
514            $db->sql_freeresult($result);
515        }
516
517        // Now place into the appropriate folder
518        foreach ($check_rows as $row)
519        {
520            // Add membership if set
521            if (isset($memberships[$row['author_id']]))
522            {
523                $row['author_in_group'] = $memberships[$row['user_id']];
524            }
525
526            // Check Rule - this should be very quick since we have all information we need
527            $is_match = false;
528            foreach ($user_rules as $rule_row)
529            {
530                if (($action = check_rule($global_privmsgs_rules, $rule_row, $row, $user_id)) !== false)
531                {
532                    $is_match = true;
533                    $action_ary[$row['msg_id']][] = $action;
534                }
535            }
536
537            if (!$is_match)
538            {
539                $action_ary[$row['msg_id']][] = array('action' => false);
540            }
541        }
542
543        unset($user_rules, $zebra, $check_rows, $user_ids, $memberships);
544    }
545
546    // We place actions into arrays, to save queries.
547    $unread_ids = $delete_ids = $important_ids = array();
548
549    foreach ($action_ary as $msg_id => $msg_ary)
550    {
551        // It is allowed to execute actions more than once, except placing messages into folder
552        $folder_action = $message_removed = false;
553
554        foreach ($msg_ary as $rule_ary)
555        {
556            if ($folder_action && $rule_ary['action'] == ACTION_PLACE_INTO_FOLDER)
557            {
558                continue;
559            }
560
561            switch ($rule_ary['action'])
562            {
563                case ACTION_PLACE_INTO_FOLDER:
564                    // Folder actions have precedence, so we will remove any other ones
565                    $folder_action = true;
566                    $move_into_folder[(int) $rule_ary['folder_id']][] = $msg_id;
567                break;
568
569                case ACTION_MARK_AS_READ:
570                    if ($rule_ary['pm_unread'])
571                    {
572                        $unread_ids[] = $msg_id;
573                    }
574                break;
575
576                case ACTION_DELETE_MESSAGE:
577                    $delete_ids[] = $msg_id;
578                    $message_removed = true;
579                break;
580
581                case ACTION_MARK_AS_IMPORTANT:
582                    if (!$rule_ary['pm_marked'])
583                    {
584                        $important_ids[] = $msg_id;
585                    }
586                break;
587            }
588        }
589
590        // We place this here because it could happen that the messages are doubled if a rule marks a message and then moves it into a specific
591        // folder. Here we simply move the message into the INBOX if it gets not removed and also not put into a custom folder.
592        if (!$folder_action && !$message_removed)
593        {
594            $move_into_folder[PRIVMSGS_INBOX][] = $msg_id;
595        }
596    }
597
598    // Do not change the order of processing
599    // The number of queries needed to be executed here highly depends on the defined rules and are
600    // only gone through if new messages arrive.
601
602    // Delete messages
603    if (count($delete_ids))
604    {
605        $num_removed += count($delete_ids);
606        delete_pm($user_id, $delete_ids, PRIVMSGS_NO_BOX);
607    }
608
609    // Set messages to Unread
610    if (count($unread_ids))
611    {
612        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
613            SET pm_unread = 0
614            WHERE ' . $db->sql_in_set('msg_id', $unread_ids) . "
615                AND user_id = $user_id
616                AND folder_id = " . PRIVMSGS_NO_BOX;
617        $db->sql_query($sql);
618    }
619
620    // mark messages as important
621    if (count($important_ids))
622    {
623        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
624            SET pm_marked = 1 - pm_marked
625            WHERE folder_id = ' . PRIVMSGS_NO_BOX . "
626                AND user_id = $user_id
627                AND " . $db->sql_in_set('msg_id', $important_ids);
628        $db->sql_query($sql);
629    }
630
631    // Move into folder
632    $folder = array();
633
634    if (count($move_into_folder))
635    {
636        // Determine Full Folder Action - we need the move to folder id later eventually
637        $full_folder_action = ($user->data['user_full_folder'] == FULL_FOLDER_NONE) ? ($config['full_folder_action'] - (FULL_FOLDER_NONE*(-1))) : $user->data['user_full_folder'];
638
639        $sql_folder = array_keys($move_into_folder);
640        if ($full_folder_action >= 0)
641        {
642            $sql_folder[] = $full_folder_action;
643        }
644
645        $sql = 'SELECT folder_id, pm_count
646            FROM ' . PRIVMSGS_FOLDER_TABLE . '
647            WHERE ' . $db->sql_in_set('folder_id', $sql_folder) . "
648                AND user_id = $user_id";
649        $result = $db->sql_query($sql);
650
651        while ($row = $db->sql_fetchrow($result))
652        {
653            $folder[(int) $row['folder_id']] = (int) $row['pm_count'];
654        }
655        $db->sql_freeresult($result);
656
657        unset($sql_folder);
658
659        if (isset($move_into_folder[PRIVMSGS_INBOX]))
660        {
661            $sql = 'SELECT COUNT(msg_id) as num_messages
662                FROM ' . PRIVMSGS_TO_TABLE . "
663                WHERE user_id = $user_id
664                    AND folder_id = " . PRIVMSGS_INBOX;
665            $result = $db->sql_query($sql);
666            $folder[PRIVMSGS_INBOX] = (int) $db->sql_fetchfield('num_messages');
667            $db->sql_freeresult($result);
668        }
669    }
670
671    // Here we have ideally only one folder to move into
672    foreach ($move_into_folder as $folder_id => $msg_ary)
673    {
674        $dest_folder = $folder_id;
675        $full_folder_action = FULL_FOLDER_NONE;
676
677        // Check Message Limit - we calculate with the complete array, most of the time it is one message
678        // But we are making sure that the other way around works too (more messages in queue than allowed to be stored)
679        if ($user->data['message_limit'] && $folder[$folder_id] && ($folder[$folder_id] + count($msg_ary)) > $user->data['message_limit'])
680        {
681            $full_folder_action = ($user->data['user_full_folder'] == FULL_FOLDER_NONE) ? ($config['full_folder_action'] - (FULL_FOLDER_NONE*(-1))) : $user->data['user_full_folder'];
682
683            // If destination folder itself is full...
684            if ($full_folder_action >= 0 && ($folder[$full_folder_action] + count($msg_ary)) > $user->data['message_limit'])
685            {
686                $full_folder_action = $config['full_folder_action'] - (FULL_FOLDER_NONE*(-1));
687            }
688
689            // If Full Folder Action is to move to another folder, we simply adjust the destination folder
690            if ($full_folder_action >= 0)
691            {
692                $dest_folder = $full_folder_action;
693            }
694            else if ($full_folder_action == FULL_FOLDER_DELETE)
695            {
696                // Delete some messages. NOTE: Ordered by msg_id here instead of message_time!
697                $sql = 'SELECT msg_id
698                    FROM ' . PRIVMSGS_TO_TABLE . "
699                    WHERE user_id = $user_id
700                        AND folder_id = $dest_folder
701                    ORDER BY msg_id ASC";
702                $result = $db->sql_query_limit($sql, (($folder[$dest_folder] + count($msg_ary)) - $user->data['message_limit']));
703
704                $delete_ids = array();
705                while ($row = $db->sql_fetchrow($result))
706                {
707                    $delete_ids[] = $row['msg_id'];
708                }
709                $db->sql_freeresult($result);
710
711                $num_removed += count($delete_ids);
712                delete_pm($user_id, $delete_ids, $dest_folder);
713            }
714        }
715
716        //
717        if ($full_folder_action == FULL_FOLDER_HOLD)
718        {
719            $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
720                SET folder_id = ' . PRIVMSGS_HOLD_BOX . '
721                WHERE folder_id = ' . PRIVMSGS_NO_BOX . "
722                    AND user_id = $user_id
723                    AND " . $db->sql_in_set('msg_id', $msg_ary);
724            $db->sql_query($sql);
725        }
726        else
727        {
728            $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . "
729                SET folder_id = $dest_folder, pm_new = 0
730                WHERE folder_id = " . PRIVMSGS_NO_BOX . "
731                    AND user_id = $user_id
732                    AND pm_new = 1
733                    AND " . $db->sql_in_set('msg_id', $msg_ary);
734            $db->sql_query($sql);
735
736            if ($dest_folder != PRIVMSGS_INBOX)
737            {
738                $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . '
739                    SET pm_count = pm_count + ' . (int) $db->sql_affectedrows() . "
740                    WHERE folder_id = $dest_folder
741                        AND user_id = $user_id";
742                $db->sql_query($sql);
743            }
744        }
745    }
746
747    if (count($action_ary))
748    {
749        // Move from OUTBOX to SENTBOX
750        // We are not checking any full folder status here... SENTBOX is a special treatment (old messages get deleted)
751        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
752            SET folder_id = ' . PRIVMSGS_SENTBOX . '
753            WHERE folder_id = ' . PRIVMSGS_OUTBOX . '
754                AND ' . $db->sql_in_set('msg_id', array_keys($action_ary));
755        $db->sql_query($sql);
756    }
757
758    // Update new/unread count
759    update_pm_counts();
760
761    // Now check how many messages got not moved...
762    $sql = 'SELECT COUNT(msg_id) as num_messages
763        FROM ' . PRIVMSGS_TO_TABLE . "
764        WHERE user_id = $user_id
765            AND folder_id = " . PRIVMSGS_HOLD_BOX;
766    $result = $db->sql_query($sql);
767    $num_not_moved = (int) $db->sql_fetchfield('num_messages');
768    $db->sql_freeresult($result);
769
770    return array('not_moved' => $num_not_moved, 'removed' => $num_removed);
771}
772
773/**
774* Move PM from one to another folder
775*/
776function move_pm($user_id, $message_limit, $move_msg_ids, $dest_folder, $cur_folder_id)
777{
778    global $db, $user;
779    global $phpbb_root_path, $phpEx;
780
781    $num_moved = 0;
782
783    if (!is_array($move_msg_ids))
784    {
785        $move_msg_ids = array($move_msg_ids);
786    }
787
788    if (count($move_msg_ids) && !in_array($dest_folder, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX)) &&
789        !in_array($cur_folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX)) && $cur_folder_id != $dest_folder)
790    {
791        // We have to check the destination folder ;)
792        if ($dest_folder != PRIVMSGS_INBOX)
793        {
794            $sql = 'SELECT folder_id, folder_name, pm_count
795                FROM ' . PRIVMSGS_FOLDER_TABLE . "
796                WHERE folder_id = $dest_folder
797                    AND user_id = $user_id";
798            $result = $db->sql_query($sql);
799            $row = $db->sql_fetchrow($result);
800            $db->sql_freeresult($result);
801
802            if (!$row)
803            {
804                send_status_line(403, 'Forbidden');
805                trigger_error('NOT_AUTHORISED');
806            }
807
808            if ($message_limit && $row['pm_count'] + count($move_msg_ids) > $message_limit)
809            {
810                $message = sprintf($user->lang['NOT_ENOUGH_SPACE_FOLDER'], $row['folder_name']) . '<br /><br />';
811                $message .= sprintf($user->lang['CLICK_RETURN_FOLDER'], '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=' . $row['folder_id']) . '">', '</a>', $row['folder_name']);
812                trigger_error($message);
813            }
814        }
815        else
816        {
817            $sql = 'SELECT COUNT(msg_id) as num_messages
818                FROM ' . PRIVMSGS_TO_TABLE . '
819                WHERE folder_id = ' . PRIVMSGS_INBOX . "
820                    AND user_id = $user_id";
821            $result = $db->sql_query($sql);
822            $num_messages = (int) $db->sql_fetchfield('num_messages');
823            $db->sql_freeresult($result);
824
825            if ($message_limit && $num_messages + count($move_msg_ids) > $message_limit)
826            {
827                $message = sprintf($user->lang['NOT_ENOUGH_SPACE_FOLDER'], $user->lang['PM_INBOX']) . '<br /><br />';
828                $message .= sprintf($user->lang['CLICK_RETURN_FOLDER'], '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox') . '">', '</a>', $user->lang['PM_INBOX']);
829                trigger_error($message);
830            }
831        }
832
833        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . "
834            SET folder_id = $dest_folder
835            WHERE folder_id = $cur_folder_id
836                AND user_id = $user_id
837                AND " . $db->sql_in_set('msg_id', $move_msg_ids);
838        $db->sql_query($sql);
839        $num_moved = $db->sql_affectedrows();
840
841        // Update pm counts
842        if ($num_moved)
843        {
844            if (!in_array($cur_folder_id, array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX)))
845            {
846                $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . "
847                    SET pm_count = pm_count - $num_moved
848                    WHERE folder_id = $cur_folder_id
849                        AND user_id = $user_id";
850                $db->sql_query($sql);
851            }
852
853            if ($dest_folder != PRIVMSGS_INBOX)
854            {
855                $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . "
856                    SET pm_count = pm_count + $num_moved
857                    WHERE folder_id = $dest_folder
858                        AND user_id = $user_id";
859                $db->sql_query($sql);
860            }
861        }
862    }
863    else if (in_array($cur_folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX)))
864    {
865        trigger_error('CANNOT_MOVE_SPECIAL');
866    }
867
868    return $num_moved;
869}
870
871/**
872* Update unread message status
873*/
874function update_unread_status($unread, $msg_id, $user_id, $folder_id)
875{
876    if (!$unread)
877    {
878        return;
879    }
880
881    global $db, $user, $phpbb_container;
882
883    /* @var $phpbb_notifications \phpbb\notification\manager */
884    $phpbb_notifications = $phpbb_container->get('notification_manager');
885
886    $phpbb_notifications->mark_notifications('notification.type.pm', $msg_id, $user_id);
887
888    $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . "
889        SET pm_unread = 0
890        WHERE msg_id = $msg_id
891            AND user_id = $user_id
892            AND folder_id = $folder_id
893            AND pm_unread = 1";
894    $db->sql_query($sql);
895
896    // If the message is already marked as read, we just skip the rest to avoid negative PM count
897    if (!$db->sql_affectedrows())
898    {
899        return;
900    }
901
902    $sql = 'UPDATE ' . USERS_TABLE . "
903        SET user_unread_privmsg = user_unread_privmsg - 1
904        WHERE user_id = $user_id";
905    $db->sql_query($sql);
906
907    if ($user->data['user_id'] == $user_id)
908    {
909        $user->data['user_unread_privmsg']--;
910
911        // Try to cope with previous wrong conversions...
912        if ($user->data['user_unread_privmsg'] < 0)
913        {
914            $sql = 'UPDATE ' . USERS_TABLE . "
915                SET user_unread_privmsg = 0
916                WHERE user_id = $user_id";
917            $db->sql_query($sql);
918
919            $user->data['user_unread_privmsg'] = 0;
920        }
921    }
922}
923
924function mark_folder_read($user_id, $folder_id)
925{
926    global $db;
927
928    $sql = 'SELECT msg_id
929        FROM ' . PRIVMSGS_TO_TABLE . '
930        WHERE folder_id = ' . ((int) $folder_id) . '
931            AND user_id = ' . ((int) $user_id) . '
932            AND pm_unread = 1';
933    $result = $db->sql_query($sql);
934
935    while ($row = $db->sql_fetchrow($result))
936    {
937        update_unread_status(true, $row['msg_id'], $user_id, $folder_id);
938    }
939    $db->sql_freeresult($result);
940}
941
942/**
943* Handle all actions possible with marked messages
944*/
945function handle_mark_actions($user_id, $mark_action)
946{
947    global $db, $user, $phpbb_root_path, $phpEx, $request;
948
949    $msg_ids        = $request->variable('marked_msg_id', array(0));
950    $cur_folder_id    = $request->variable('cur_folder_id', PRIVMSGS_NO_BOX);
951
952    if (!count($msg_ids))
953    {
954        return false;
955    }
956
957    switch ($mark_action)
958    {
959        case 'mark_important':
960
961            if (!check_form_key('ucp_pm_view'))
962            {
963                trigger_error('FORM_INVALID');
964            }
965
966            $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . "
967                SET pm_marked = 1 - pm_marked
968                WHERE folder_id = $cur_folder_id
969                    AND user_id = $user_id
970                    AND " . $db->sql_in_set('msg_id', $msg_ids);
971            $db->sql_query($sql);
972
973        break;
974
975        case 'delete_marked':
976
977            global $auth;
978
979            if (!$auth->acl_get('u_pm_delete'))
980            {
981                send_status_line(403, 'Forbidden');
982                trigger_error('NO_AUTH_DELETE_MESSAGE');
983            }
984
985            if (confirm_box(true))
986            {
987                delete_pm($user_id, $msg_ids, $cur_folder_id);
988
989                $success_msg = (count($msg_ids) == 1) ? 'MESSAGE_DELETED' : 'MESSAGES_DELETED';
990                $redirect = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=' . $cur_folder_id);
991
992                meta_refresh(3, $redirect);
993                trigger_error($user->lang[$success_msg] . '<br /><br />' . sprintf($user->lang['RETURN_FOLDER'], '<a href="' . $redirect . '">', '</a>'));
994            }
995            else
996            {
997                $s_hidden_fields = array(
998                    'cur_folder_id'    => $cur_folder_id,
999                    'mark_option'    => 'delete_marked',
1000                    'submit_mark'    => true,
1001                    'marked_msg_id'    => $msg_ids
1002                );
1003
1004                confirm_box(false, 'DELETE_MARKED_PM', build_hidden_fields($s_hidden_fields));
1005            }
1006
1007        break;
1008
1009        default:
1010            return false;
1011    }
1012
1013    return true;
1014}
1015
1016/**
1017* Delete PM(s)
1018*/
1019function delete_pm($user_id, $msg_ids, $folder_id)
1020{
1021    global $db, $user, $phpbb_container, $phpbb_dispatcher;
1022
1023    $user_id    = (int) $user_id;
1024    $folder_id    = (int) $folder_id;
1025
1026    if (!$user_id)
1027    {
1028        return false;
1029    }
1030
1031    if (!is_array($msg_ids))
1032    {
1033        if (!$msg_ids)
1034        {
1035            return false;
1036        }
1037        $msg_ids = array($msg_ids);
1038    }
1039
1040    if (!count($msg_ids))
1041    {
1042        return false;
1043    }
1044
1045    /**
1046    * Get all info for PM(s) before they are deleted
1047    *
1048    * @event core.delete_pm_before
1049    * @var    int    user_id     ID of the user requested the message delete
1050    * @var    array    msg_ids    array of all messages to be deleted
1051    * @var    int    folder_id    ID of the user folder where the messages are stored
1052    * @since 3.1.0-b5
1053    */
1054    $vars = array('user_id', 'msg_ids', 'folder_id');
1055    extract($phpbb_dispatcher->trigger_event('core.delete_pm_before', compact($vars)));
1056
1057    // Get PM Information for later deleting
1058    $sql = 'SELECT msg_id, pm_unread, pm_new
1059        FROM ' . PRIVMSGS_TO_TABLE . '
1060        WHERE ' . $db->sql_in_set('msg_id', array_map('intval', $msg_ids)) . "
1061            AND folder_id = $folder_id
1062            AND user_id = $user_id";
1063    $result = $db->sql_query($sql);
1064
1065    $delete_rows = array();
1066    $num_unread = $num_new = $num_deleted = 0;
1067    while ($row = $db->sql_fetchrow($result))
1068    {
1069        $num_unread += (int) $row['pm_unread'];
1070        $num_new += (int) $row['pm_new'];
1071
1072        $delete_rows[$row['msg_id']] = 1;
1073    }
1074    $db->sql_freeresult($result);
1075    unset($msg_ids);
1076
1077    if (!count($delete_rows))
1078    {
1079        return false;
1080    }
1081
1082    $db->sql_transaction('begin');
1083
1084    // if no one has read the message yet (meaning it is in users outbox)
1085    // then mark the message as deleted...
1086    if ($folder_id == PRIVMSGS_OUTBOX)
1087    {
1088        // Remove PM from Outbox
1089        $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . "
1090            WHERE user_id = $user_id AND folder_id = " . PRIVMSGS_OUTBOX . '
1091                AND ' . $db->sql_in_set('msg_id', array_keys($delete_rows));
1092        $db->sql_query($sql);
1093
1094        // Update PM Information for safety
1095        $sql = 'UPDATE ' . PRIVMSGS_TABLE . " SET message_text = ''
1096            WHERE " . $db->sql_in_set('msg_id', array_keys($delete_rows));
1097        $db->sql_query($sql);
1098
1099        // Set delete flag for those intended to receive the PM
1100        // We do not remove the message actually, to retain some basic information (sent time for example)
1101        $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
1102            SET pm_deleted = 1
1103            WHERE ' . $db->sql_in_set('msg_id', array_keys($delete_rows));
1104        $db->sql_query($sql);
1105
1106        $num_deleted = $db->sql_affectedrows();
1107    }
1108    else
1109    {
1110        // Delete private message data
1111        $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . "
1112            WHERE user_id = $user_id
1113                AND folder_id = $folder_id
1114                AND " . $db->sql_in_set('msg_id', array_keys($delete_rows));
1115        $db->sql_query($sql);
1116        $num_deleted = $db->sql_affectedrows();
1117    }
1118
1119    // if folder id is user defined folder then decrease pm_count
1120    if (!in_array($folder_id, array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX, PRIVMSGS_NO_BOX)))
1121    {
1122        $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . "
1123            SET pm_count = pm_count - $num_deleted
1124            WHERE folder_id = $folder_id";
1125        $db->sql_query($sql);
1126    }
1127
1128    // Update unread and new status field
1129    if ($num_unread || $num_new)
1130    {
1131        $set_sql = ($num_unread) ? 'user_unread_privmsg = user_unread_privmsg - ' . $num_unread : '';
1132
1133        if ($num_new)
1134        {
1135            $set_sql .= ($set_sql != '') ? ', ' : '';
1136            $set_sql .= 'user_new_privmsg = user_new_privmsg - ' . $num_new;
1137        }
1138
1139        $db->sql_query('UPDATE ' . USERS_TABLE . " SET $set_sql WHERE user_id = $user_id");
1140
1141        $user->data['user_new_privmsg'] -= $num_new;
1142        $user->data['user_unread_privmsg'] -= $num_unread;
1143    }
1144
1145    /* @var $phpbb_notifications \phpbb\notification\manager */
1146    $phpbb_notifications = $phpbb_container->get('notification_manager');
1147
1148    $phpbb_notifications->delete_notifications('notification.type.pm', array_keys($delete_rows));
1149
1150    // Now we have to check which messages we can delete completely
1151    $sql = 'SELECT msg_id
1152        FROM ' . PRIVMSGS_TO_TABLE . '
1153        WHERE ' . $db->sql_in_set('msg_id', array_keys($delete_rows));
1154    $result = $db->sql_query($sql);
1155
1156    while ($row = $db->sql_fetchrow($result))
1157    {
1158        unset($delete_rows[$row['msg_id']]);
1159    }
1160    $db->sql_freeresult($result);
1161
1162    $delete_ids = array_keys($delete_rows);
1163
1164    if (count($delete_ids))
1165    {
1166        // Check if there are any attachments we need to remove
1167        /** @var \phpbb\attachment\manager $attachment_manager */
1168        $attachment_manager = $phpbb_container->get('attachment.manager');
1169        $attachment_manager->delete('message', $delete_ids, false);
1170        unset($attachment_manager);
1171
1172        $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
1173            WHERE ' . $db->sql_in_set('msg_id', $delete_ids);
1174        $db->sql_query($sql);
1175    }
1176
1177    $db->sql_transaction('commit');
1178
1179    return true;
1180}
1181
1182/**
1183* Delete all PM(s) for given users and delete the ones without references
1184*
1185* @param    array        $user_ids    IDs of the users whose private messages we want to delete
1186*
1187* @return    boolean        False if there were no pms found, true otherwise.
1188*/
1189function phpbb_delete_users_pms($user_ids)
1190{
1191    global $db, $phpbb_container;
1192
1193    $user_id_sql = $db->sql_in_set('user_id', $user_ids);
1194    $author_id_sql = $db->sql_in_set('author_id', $user_ids);
1195
1196    // Get PM Information for later deleting
1197    // The two queries where split, so we can use our indexes
1198    $undelivered_msg = $delete_ids = array();
1199
1200    // Part 1: get PMs the user received
1201    $sql = 'SELECT msg_id
1202        FROM ' . PRIVMSGS_TO_TABLE . '
1203        WHERE ' . $user_id_sql;
1204    $result = $db->sql_query($sql);
1205
1206    while ($row = $db->sql_fetchrow($result))
1207    {
1208        $msg_id = (int) $row['msg_id'];
1209        $delete_ids[$msg_id] = $msg_id;
1210    }
1211    $db->sql_freeresult($result);
1212
1213    // Part 2: get PMs the users sent, but are yet to be received.
1214    // We cannot simply delete them. First we have to check
1215    // whether another user already received and read the message.
1216    $sql = 'SELECT msg_id
1217        FROM ' . PRIVMSGS_TO_TABLE . '
1218        WHERE ' . $author_id_sql . '
1219            AND folder_id = ' . PRIVMSGS_NO_BOX;
1220    $result = $db->sql_query($sql);
1221
1222    while ($row = $db->sql_fetchrow($result))
1223    {
1224        $msg_id = (int) $row['msg_id'];
1225        $undelivered_msg[$msg_id] = $msg_id;
1226    }
1227    $db->sql_freeresult($result);
1228
1229    if (empty($delete_ids) && empty($undelivered_msg))
1230    {
1231        return false;
1232    }
1233
1234    $db->sql_transaction('begin');
1235
1236    /* @var $phpbb_notifications \phpbb\notification\manager */
1237    $phpbb_notifications = $phpbb_container->get('notification_manager');
1238
1239    if (!empty($undelivered_msg))
1240    {
1241        // A pm is delivered, if for any recipient the message was moved
1242        // from their NO_BOX to another folder. We do not delete such
1243        // messages, but only delete them for users, who have not yet
1244        // received them.
1245        $sql = 'SELECT msg_id
1246            FROM ' . PRIVMSGS_TO_TABLE . '
1247            WHERE ' . $author_id_sql . '
1248                AND folder_id <> ' . PRIVMSGS_NO_BOX . '
1249                AND folder_id <> ' . PRIVMSGS_OUTBOX . '
1250                AND folder_id <> ' . PRIVMSGS_SENTBOX;
1251        $result = $db->sql_query($sql);
1252
1253        $delivered_msg = array();
1254        while ($row = $db->sql_fetchrow($result))
1255        {
1256            $msg_id = (int) $row['msg_id'];
1257            $delivered_msg[$msg_id] = $msg_id;
1258            unset($undelivered_msg[$msg_id]);
1259        }
1260        $db->sql_freeresult($result);
1261
1262        $undelivered_user = array();
1263
1264        // Count the messages we delete, so we can correct the user pm data
1265        $sql = 'SELECT user_id, COUNT(msg_id) as num_undelivered_privmsgs
1266            FROM ' . PRIVMSGS_TO_TABLE . '
1267            WHERE ' . $author_id_sql . '
1268                AND folder_id = ' . PRIVMSGS_NO_BOX . '
1269                    AND ' . $db->sql_in_set('msg_id', array_merge($undelivered_msg, $delivered_msg)) . '
1270            GROUP BY user_id';
1271        $result = $db->sql_query($sql);
1272
1273        while ($row = $db->sql_fetchrow($result))
1274        {
1275            $num_pms = (int) $row['num_undelivered_privmsgs'];
1276            $undelivered_user[$num_pms][] = (int) $row['user_id'];
1277
1278            if (count($undelivered_user[$num_pms]) > 50)
1279            {
1280                // If there are too many users affected the query might get
1281                // too long, so we update the value for the first bunch here.
1282                $sql = 'UPDATE ' . USERS_TABLE . '
1283                    SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ',
1284                        user_unread_privmsg = user_unread_privmsg - ' . $num_pms . '
1285                    WHERE ' . $db->sql_in_set('user_id', $undelivered_user[$num_pms]);
1286                $db->sql_query($sql);
1287                unset($undelivered_user[$num_pms]);
1288            }
1289        }
1290        $db->sql_freeresult($result);
1291
1292        foreach ($undelivered_user as $num_pms => $undelivered_user_set)
1293        {
1294            $sql = 'UPDATE ' . USERS_TABLE . '
1295                SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ',
1296                    user_unread_privmsg = user_unread_privmsg - ' . $num_pms . '
1297                WHERE ' . $db->sql_in_set('user_id', $undelivered_user_set);
1298            $db->sql_query($sql);
1299        }
1300
1301        if (!empty($delivered_msg))
1302        {
1303            $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . '
1304                WHERE folder_id = ' . PRIVMSGS_NO_BOX . '
1305                    AND ' . $db->sql_in_set('msg_id', $delivered_msg);
1306            $db->sql_query($sql);
1307
1308            $phpbb_notifications->delete_notifications('notification.type.pm', $delivered_msg);
1309        }
1310
1311        if (!empty($undelivered_msg))
1312        {
1313            $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . '
1314                WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg);
1315            $db->sql_query($sql);
1316
1317            $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
1318                WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg);
1319            $db->sql_query($sql);
1320
1321            $phpbb_notifications->delete_notifications('notification.type.pm', $undelivered_msg);
1322        }
1323    }
1324
1325    // Reset the user's pm count to 0
1326    $sql = 'UPDATE ' . USERS_TABLE . '
1327        SET user_new_privmsg = 0,
1328            user_unread_privmsg = 0
1329        WHERE ' . $user_id_sql;
1330    $db->sql_query($sql);
1331
1332    // Delete private message data of the user
1333    $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . '
1334        WHERE ' . $user_id_sql;
1335    $db->sql_query($sql);
1336
1337    if (!empty($delete_ids))
1338    {
1339        // Now we have to check which messages we can delete completely
1340        $sql = 'SELECT msg_id
1341            FROM ' . PRIVMSGS_TO_TABLE . '
1342            WHERE ' . $db->sql_in_set('msg_id', $delete_ids);
1343        $result = $db->sql_query($sql);
1344
1345        while ($row = $db->sql_fetchrow($result))
1346        {
1347            unset($delete_ids[$row['msg_id']]);
1348        }
1349        $db->sql_freeresult($result);
1350
1351        if (!empty($delete_ids))
1352        {
1353            // Check if there are any attachments we need to remove
1354            /** @var \phpbb\attachment\manager $attachment_manager */
1355            $attachment_manager = $phpbb_container->get('attachment.manager');
1356            $attachment_manager->delete('message', $delete_ids, false);
1357            unset($attachment_manager);
1358
1359            $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . '
1360                WHERE ' . $db->sql_in_set('msg_id', $delete_ids);
1361            $db->sql_query($sql);
1362
1363            $phpbb_notifications->delete_notifications('notification.type.pm', $delete_ids);
1364        }
1365    }
1366
1367    // Set the remaining author id to anonymous
1368    // This way users are still able to read messages from users being removed
1369    $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
1370        SET author_id = ' . ANONYMOUS . '
1371        WHERE ' . $author_id_sql;
1372    $db->sql_query($sql);
1373
1374    $sql = 'UPDATE ' . PRIVMSGS_TABLE . '
1375        SET author_id = ' . ANONYMOUS . '
1376        WHERE ' . $author_id_sql;
1377    $db->sql_query($sql);
1378
1379    $db->sql_transaction('commit');
1380
1381    return true;
1382}
1383
1384/**
1385* Rebuild message header
1386*/
1387function rebuild_header($check_ary)
1388{
1389    $address = array();
1390
1391    foreach ($check_ary as $check_type => $address_field)
1392    {
1393        // Split Addresses into users and groups
1394        preg_match_all('/:?(u|g)_([0-9]+):?/', $address_field, $match);
1395
1396        $u = $g = array();
1397        foreach ($match[1] as $id => $type)
1398        {
1399            ${$type}[] = (int) $match[2][$id];
1400        }
1401
1402        $_types = array('u', 'g');
1403        foreach ($_types as $type)
1404        {
1405            if (count(${$type}))
1406            {
1407                foreach (${$type} as $id)
1408                {
1409                    $address[$type][$id] = $check_type;
1410                }
1411            }
1412        }
1413    }
1414
1415    return $address;
1416}
1417
1418/**
1419* Print out/assign recipient information
1420*/
1421function write_pm_addresses($check_ary, $author_id, $plaintext = false)
1422{
1423    global $db, $user, $template, $phpbb_root_path, $phpEx, $phpbb_container;
1424
1425    /** @var \phpbb\group\helper $group_helper */
1426    $group_helper = $phpbb_container->get('group_helper');
1427
1428    $addresses = array();
1429
1430    foreach ($check_ary as $check_type => $address_field)
1431    {
1432        if (!is_array($address_field))
1433        {
1434            // Split Addresses into users and groups
1435            preg_match_all('/:?(u|g)_([0-9]+):?/', $address_field, $match);
1436
1437            $u = $g = array();
1438            foreach ($match[1] as $id => $type)
1439            {
1440                ${$type}[] = (int) $match[2][$id];
1441            }
1442        }
1443        else
1444        {
1445            $u = $address_field['u'];
1446            $g = $address_field['g'];
1447        }
1448
1449        $address = array();
1450        if (count($u))
1451        {
1452            $sql = 'SELECT user_id, username, user_colour
1453                FROM ' . USERS_TABLE . '
1454                WHERE ' . $db->sql_in_set('user_id', $u);
1455            $result = $db->sql_query($sql);
1456
1457            while ($row = $db->sql_fetchrow($result))
1458            {
1459                if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id'])
1460                {
1461                    if ($plaintext)
1462                    {
1463                        $address[] = $row['username'];
1464                    }
1465                    else
1466                    {
1467                        $address['user'][$row['user_id']] = array('name' => $row['username'], 'colour' => $row['user_colour']);
1468                    }
1469                }
1470            }
1471            $db->sql_freeresult($result);
1472        }
1473
1474        if (count($g))
1475        {
1476            if ($plaintext)
1477            {
1478                $sql = 'SELECT group_name, group_type
1479                    FROM ' . GROUPS_TABLE . '
1480                        WHERE ' . $db->sql_in_set('group_id', $g);
1481                $result = $db->sql_query($sql);
1482
1483                while ($row = $db->sql_fetchrow($result))
1484                {
1485                    if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id'])
1486                    {
1487                        $address[] = $group_helper->get_name($row['group_name']);
1488                    }
1489                }
1490                $db->sql_freeresult($result);
1491            }
1492            else
1493            {
1494                $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id
1495                    FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug
1496                        WHERE ' . $db->sql_in_set('g.group_id', $g) . '
1497                        AND g.group_id = ug.group_id
1498                        AND ug.user_pending = 0';
1499                $result = $db->sql_query($sql);
1500
1501                while ($row = $db->sql_fetchrow($result))
1502                {
1503                    if (!isset($address['group'][$row['group_id']]))
1504                    {
1505                        if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id'])
1506                        {
1507                            $row['group_name'] = $group_helper->get_name($row['group_name']);
1508                            $address['group'][$row['group_id']] = array('name' => $row['group_name'], 'colour' => $row['group_colour']);
1509                        }
1510                    }
1511
1512                    if (isset($address['user'][$row['user_id']]))
1513                    {
1514                        $address['user'][$row['user_id']]['in_group'] = $row['group_id'];
1515                    }
1516                }
1517                $db->sql_freeresult($result);
1518            }
1519        }
1520
1521        if (count($address) && !$plaintext)
1522        {
1523            $template->assign_var('S_' . strtoupper($check_type) . '_RECIPIENT', true);
1524
1525            foreach ($address as $type => $adr_ary)
1526            {
1527                foreach ($adr_ary as $id => $row)
1528                {
1529                    $tpl_ary = array(
1530                        'IS_GROUP'    => ($type == 'group') ? true : false,
1531                        'IS_USER'    => ($type == 'user') ? true : false,
1532                        'UG_ID'        => $id,
1533                        'NAME'        => $row['name'],
1534                        'COLOUR'    => ($row['colour']) ? '#' . $row['colour'] : '',
1535                        'TYPE'        => $type,
1536                    );
1537
1538                    if ($type == 'user')
1539                    {
1540                        $tpl_ary = array_merge($tpl_ary, array(
1541                            'U_VIEW'        => get_username_string('profile', $id, $row['name'], $row['colour']),
1542                            'NAME_FULL'        => get_username_string('full', $id, $row['name'], $row['colour']),
1543                        ));
1544                    }
1545                    else
1546                    {
1547                        $tpl_ary = array_merge($tpl_ary, array(
1548                            'U_VIEW'        => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&amp;g=' . $id),
1549                        ));
1550                    }
1551
1552                    $template->assign_block_vars($check_type . '_recipient', $tpl_ary);
1553                }
1554            }
1555        }
1556
1557        $addresses[$check_type] = $address;
1558    }
1559
1560    return $addresses;
1561}
1562
1563/**
1564* Get folder status
1565*/
1566function get_folder_status($folder_id, $folder)
1567{
1568    global $user;
1569
1570    if (isset($folder[$folder_id]))
1571    {
1572        $folder = $folder[$folder_id];
1573    }
1574    else
1575    {
1576        return false;
1577    }
1578
1579    $return = array(
1580        'folder_name'    => $folder['folder_name'],
1581        'cur'            => $folder['num_messages'],
1582        'remaining'        => ($user->data['message_limit']) ? $user->data['message_limit'] - $folder['num_messages'] : 0,
1583        'max'            => $user->data['message_limit'],
1584        'percent'        => ($user->data['message_limit']) ? (($user->data['message_limit'] > 0) ? floor(($folder['num_messages'] / $user->data['message_limit']) * 100) : 100) : 0,
1585    );
1586
1587    $return['message']    = $user->lang('FOLDER_STATUS_MSG', $user->lang('MESSAGES_COUNT', (int) $return['max']), (int) $return['cur'], $return['percent']);
1588
1589    return $return;
1590}
1591
1592//
1593// COMPOSE MESSAGES
1594//
1595
1596/**
1597* Submit PM
1598*/
1599function submit_pm($mode, $subject, &$data_ary, $put_in_outbox = true)
1600{
1601    global $db, $auth, $config, $user, $phpbb_container, $phpbb_dispatcher, $request;
1602
1603    $attachment_storage = $phpbb_container->get('storage.attachment');
1604
1605    // We do not handle erasing pms here
1606    if ($mode == 'delete')
1607    {
1608        return false;
1609    }
1610
1611    $current_time = time();
1612
1613    $data = $data_ary;
1614    /**
1615    * Get all parts of the PM that are to be submited to the DB.
1616    *
1617    * @event core.submit_pm_before
1618    * @var    string    mode    PM Post mode - post|reply|quote|quotepost|forward|edit
1619    * @var    string    subject    Subject of the private message
1620    * @var    array    data    The whole row data of the PM.
1621    * @since 3.1.0-b3
1622    */
1623    $vars = array('mode', 'subject', 'data');
1624    extract($phpbb_dispatcher->trigger_event('core.submit_pm_before', compact($vars)));
1625    $data_ary = $data;
1626    unset($data);
1627
1628    // Collect some basic information about which tables and which rows to update/insert
1629    $sql_data = array();
1630    $root_level = 0;
1631
1632    // Recipient Information
1633    $recipients = $to = $bcc = array();
1634
1635    if ($mode != 'edit')
1636    {
1637        // Build Recipient List
1638        // u|g => array($user_id => 'to'|'bcc')
1639        $_types = array('u', 'g');
1640        foreach ($_types as $ug_type)
1641        {
1642            if (isset($data_ary['address_list'][$ug_type]) && count($data_ary['address_list'][$ug_type]))
1643            {
1644                foreach ($data_ary['address_list'][$ug_type] as $id => $field)
1645                {
1646                    $id = (int) $id;
1647
1648                    // Do not rely on the address list being "valid"
1649                    if (!$id || ($ug_type == 'u' && $id == ANONYMOUS))
1650                    {
1651                        continue;
1652                    }
1653
1654                    $field = ($field == 'to') ? 'to' : 'bcc';
1655                    if ($ug_type == 'u')
1656                    {
1657                        $recipients[$id] = $field;
1658                    }
1659                    ${$field}[] = $ug_type . '_' . $id;
1660                }
1661            }
1662        }
1663
1664        if (isset($data_ary['address_list']['g']) && count($data_ary['address_list']['g']))
1665        {
1666            // We need to check the PM status of group members (do they want to receive PM's?)
1667            // Only check if not a moderator or admin, since they are allowed to override this user setting
1668            $sql_allow_pm = (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) ? ' AND u.user_allow_pm = 1' : '';
1669
1670            $sql = 'SELECT u.user_type, ug.group_id, ug.user_id
1671                FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug
1672                WHERE ' . $db->sql_in_set('ug.group_id', array_keys($data_ary['address_list']['g'])) . '
1673                    AND ug.user_pending = 0
1674                    AND u.user_id = ug.user_id
1675                    AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')' .
1676                    $sql_allow_pm;
1677            $result = $db->sql_query($sql);
1678
1679            while ($row = $db->sql_fetchrow($result))
1680            {
1681                $field = ($data_ary['address_list']['g'][$row['group_id']] == 'to') ? 'to' : 'bcc';
1682                $recipients[$row['user_id']] = $field;
1683            }
1684            $db->sql_freeresult($result);
1685        }
1686
1687        if (!count($recipients))
1688        {
1689            trigger_error('NO_RECIPIENT');
1690        }
1691    }
1692
1693    // First of all make sure the subject are having the correct length.
1694    $subject = truncate_string($subject, $mode === 'post' ? 120 : 124);
1695
1696    $db->sql_transaction('begin');
1697
1698    $sql = '';
1699
1700    switch ($mode)
1701    {
1702        case 'reply':
1703        case 'quote':
1704            $root_level = ($data_ary['reply_from_root_level']) ? $data_ary['reply_from_root_level'] : $data_ary['reply_from_msg_id'];
1705
1706            // Set message_replied switch for this user
1707            $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . '
1708                SET pm_replied = 1
1709                WHERE user_id = ' . $data_ary['from_user_id'] . '
1710                    AND msg_id = ' . $data_ary['reply_from_msg_id'];
1711
1712        // no break
1713
1714        case 'forward':
1715        case 'post':
1716        case 'quotepost':
1717            $sql_data = array(
1718                'root_level'        => $root_level,
1719                'author_id'            => $data_ary['from_user_id'],
1720                'icon_id'            => $data_ary['icon_id'],
1721                'author_ip'            => $data_ary['from_user_ip'],
1722                'message_time'        => $current_time,
1723                'enable_bbcode'        => $data_ary['enable_bbcode'],
1724                'enable_smilies'    => $data_ary['enable_smilies'],
1725                'enable_magic_url'    => $data_ary['enable_urls'],
1726                'enable_sig'        => $data_ary['enable_sig'],
1727                'message_subject'    => $subject,
1728                'message_text'        => $data_ary['message'],
1729                'message_attachment'=> (!empty($data_ary['attachment_data'])) ? 1 : 0,
1730                'bbcode_bitfield'    => $data_ary['bbcode_bitfield'],
1731                'bbcode_uid'        => $data_ary['bbcode_uid'],
1732                'to_address'        => implode(':', $to),
1733                'bcc_address'        => implode(':', $bcc),
1734                'message_reported'    => 0,
1735            );
1736        break;
1737
1738        case 'edit':
1739            $sql_data = array(
1740                'icon_id'            => $data_ary['icon_id'],
1741                'message_edit_time'    => $current_time,
1742                'enable_bbcode'        => $data_ary['enable_bbcode'],
1743                'enable_smilies'    => $data_ary['enable_smilies'],
1744                'enable_magic_url'    => $data_ary['enable_urls'],
1745                'enable_sig'        => $data_ary['enable_sig'],
1746                'message_subject'    => $subject,
1747                'message_text'        => $data_ary['message'],
1748                'message_attachment'=> (!empty($data_ary['attachment_data'])) ? 1 : 0,
1749                'bbcode_bitfield'    => $data_ary['bbcode_bitfield'],
1750                'bbcode_uid'        => $data_ary['bbcode_uid']
1751            );
1752        break;
1753    }
1754
1755    if (count($sql_data))
1756    {
1757        if ($mode == 'post' || $mode == 'reply' || $mode == 'quote' || $mode == 'quotepost' || $mode == 'forward')
1758        {
1759            $db->sql_query('INSERT INTO ' . PRIVMSGS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_data));
1760            $data_ary['msg_id'] = $db->sql_nextid();
1761        }
1762        else if ($mode == 'edit')
1763        {
1764            $sql = 'UPDATE ' . PRIVMSGS_TABLE . '
1765                SET message_edit_count = message_edit_count + 1, ' . $db->sql_build_array('UPDATE', $sql_data) . '
1766                WHERE msg_id = ' . $data_ary['msg_id'];
1767            $db->sql_query($sql);
1768        }
1769    }
1770
1771    if ($mode != 'edit')
1772    {
1773        if ($sql)
1774        {
1775            $db->sql_query($sql);
1776        }
1777        unset($sql);
1778
1779        $sql_ary = array();
1780        foreach ($recipients as $user_id => $type)
1781        {
1782            $sql_ary[] = array(
1783                'msg_id'        => (int) $data_ary['msg_id'],
1784                'user_id'        => (int) $user_id,
1785                'author_id'        => (int) $data_ary['from_user_id'],
1786                'folder_id'        => PRIVMSGS_NO_BOX,
1787                'pm_new'        => 1,
1788                'pm_unread'        => 1,
1789                'pm_forwarded'    => ($mode == 'forward') ? 1 : 0
1790            );
1791        }
1792
1793        $db->sql_multi_insert(PRIVMSGS_TO_TABLE, $sql_ary);
1794
1795        $sql = 'UPDATE ' . USERS_TABLE . '
1796            SET user_new_privmsg = user_new_privmsg + 1, user_unread_privmsg = user_unread_privmsg + 1, user_last_privmsg = ' . time() . '
1797            WHERE ' . $db->sql_in_set('user_id', array_keys($recipients));
1798        $db->sql_query($sql);
1799
1800        // Put PM into outbox
1801        if ($put_in_outbox)
1802        {
1803            $db->sql_query('INSERT INTO ' . PRIVMSGS_TO_TABLE . ' ' . $db->sql_build_array('INSERT', array(
1804                'msg_id'        => (int) $data_ary['msg_id'],
1805                'user_id'        => (int) $data_ary['from_user_id'],
1806                'author_id'        => (int) $data_ary['from_user_id'],
1807                'folder_id'        => PRIVMSGS_OUTBOX,
1808                'pm_new'        => 0,
1809                'pm_unread'        => 0,
1810                'pm_forwarded'    => ($mode == 'forward') ? 1 : 0))
1811            );
1812        }
1813    }
1814
1815    // Set user last post time
1816    if ($mode == 'reply' || $mode == 'quote' || $mode == 'quotepost' || $mode == 'forward' || $mode == 'post')
1817    {
1818        $sql = 'UPDATE ' . USERS_TABLE . "
1819            SET user_lastpost_time = $current_time
1820            WHERE user_id = " . $data_ary['from_user_id'];
1821        $db->sql_query($sql);
1822    }
1823
1824    // Submit Attachments
1825    if (!empty($data_ary['attachment_data']) && $data_ary['msg_id'] && in_array($mode, array('post', 'reply', 'quote', 'quotepost', 'edit', 'forward')))
1826    {
1827        $space_taken = $files_added = 0;
1828        $orphan_rows = array();
1829
1830        foreach ($data_ary['attachment_data'] as $attach_row)
1831        {
1832            $orphan_rows[(int) $attach_row['attach_id']] = array();
1833        }
1834
1835        if (count($orphan_rows))
1836        {
1837            $sql = 'SELECT attach_id, filesize, physical_filename
1838                FROM ' . ATTACHMENTS_TABLE . '
1839                WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan_rows)) . '
1840                    AND in_message = 1
1841                    AND is_orphan = 1
1842                    AND poster_id = ' . $user->data['user_id'];
1843            $result = $db->sql_query($sql);
1844
1845            $orphan_rows = array();
1846            while ($row = $db->sql_fetchrow($result))
1847            {
1848                $orphan_rows[$row['attach_id']] = $row;
1849            }
1850            $db->sql_freeresult($result);
1851        }
1852
1853        foreach ($data_ary['attachment_data'] as $attach_row)
1854        {
1855            if ($attach_row['is_orphan'] && !isset($orphan_rows[$attach_row['attach_id']]))
1856            {
1857                continue;
1858            }
1859
1860            if (!$attach_row['is_orphan'])
1861            {
1862                // update entry in db if attachment already stored in db and filespace
1863                $sql = 'UPDATE ' . ATTACHMENTS_TABLE . "
1864                    SET attach_comment = '" . $db->sql_escape($attach_row['attach_comment']) . "'
1865                    WHERE attach_id = " . (int) $attach_row['attach_id'] . '
1866                        AND is_orphan = 0';
1867                $db->sql_query($sql);
1868            }
1869            else
1870            {
1871                // insert attachment into db
1872                if (!$attachment_storage->exists(utf8_basename($orphan_rows[$attach_row['attach_id']]['physical_filename'])))
1873                {
1874                    continue;
1875                }
1876
1877                $space_taken += $orphan_rows[$attach_row['attach_id']]['filesize'];
1878                $files_added++;
1879
1880                $attach_sql = array(
1881                    'post_msg_id'        => $data_ary['msg_id'],
1882                    'topic_id'            => 0,
1883                    'is_orphan'            => 0,
1884                    'poster_id'            => $data_ary['from_user_id'],
1885                    'attach_comment'    => $attach_row['attach_comment'],
1886                );
1887
1888                $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $attach_sql) . '
1889                    WHERE attach_id = ' . $attach_row['attach_id'] . '
1890                        AND is_orphan = 1
1891                        AND poster_id = ' . $user->data['user_id'];
1892                $db->sql_query($sql);
1893            }
1894        }
1895
1896        if ($space_taken && $files_added)
1897        {
1898            $config->increment('upload_dir_size', $space_taken, false);
1899            $config->increment('num_files', $files_added, false);
1900        }
1901    }
1902
1903    // Delete draft if post was loaded...
1904    $draft_id = $request->variable('draft_loaded', 0);
1905    if ($draft_id)
1906    {
1907        $sql = 'DELETE FROM ' . DRAFTS_TABLE . "
1908            WHERE draft_id = $draft_id
1909                AND user_id = " . $data_ary['from_user_id'];
1910        $db->sql_query($sql);
1911    }
1912
1913    $db->sql_transaction('commit');
1914
1915    // Send Notifications
1916    $pm_data = array_merge($data_ary, array(
1917        'message_subject'        => $subject,
1918        'recipients'            => $recipients,
1919    ));
1920
1921    /* @var $phpbb_notifications \phpbb\notification\manager */
1922    $phpbb_notifications = $phpbb_container->get('notification_manager');
1923
1924    if ($mode == 'edit')
1925    {
1926        $phpbb_notifications->update_notifications('notification.type.pm', $pm_data);
1927    }
1928    else
1929    {
1930        $phpbb_notifications->add_notifications('notification.type.pm', $pm_data);
1931    }
1932
1933    $data = $data_ary;
1934    /**
1935    * Get PM message ID after submission to DB
1936    *
1937    * @event core.submit_pm_after
1938    * @var    string    mode    PM Post mode - post|reply|quote|quotepost|forward|edit
1939    * @var    string    subject    Subject of the private message
1940    * @var    array    data    The whole row data of the PM.
1941    * @var    array    pm_data    The data sent to notification class
1942    * @since 3.1.0-b5
1943    */
1944    $vars = array('mode', 'subject', 'data', 'pm_data');
1945    extract($phpbb_dispatcher->trigger_event('core.submit_pm_after', compact($vars)));
1946    $data_ary = $data;
1947    unset($data);
1948
1949    return $data_ary['msg_id'];
1950}
1951
1952/**
1953* Display Message History
1954*/
1955function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode = false)
1956{
1957    global $db, $user, $template, $phpbb_root_path, $phpEx, $auth, $phpbb_dispatcher;
1958
1959    // Select all receipts and the author from the pm we currently view, to only display their pm-history
1960    $sql = 'SELECT author_id, user_id
1961        FROM ' . PRIVMSGS_TO_TABLE . "
1962        WHERE msg_id = $msg_id
1963            AND folder_id <> " . PRIVMSGS_HOLD_BOX;
1964    $result = $db->sql_query($sql);
1965
1966    $recipients = array();
1967    while ($row = $db->sql_fetchrow($result))
1968    {
1969        $recipients[] = (int) $row['user_id'];
1970        $recipients[] = (int) $row['author_id'];
1971    }
1972    $db->sql_freeresult($result);
1973    $recipients = array_unique($recipients);
1974
1975    // Get History Messages (could be newer)
1976    $sql_where = 't.msg_id = p.msg_id
1977            AND p.author_id = u.user_id
1978            AND t.folder_id NOT IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ')
1979            AND ' . $db->sql_in_set('t.author_id', $recipients, false, true) . "
1980            AND t.user_id = $user_id";
1981
1982    // We no longer need those.
1983    unset($recipients);
1984
1985    if (!$message_row['root_level'])
1986    {
1987        $sql_where .= " AND (p.root_level = $msg_id OR (p.root_level = 0 AND p.msg_id = $msg_id))";
1988    }
1989    else
1990    {
1991        $sql_where .= " AND (p.root_level = " . $message_row['root_level'] . ' OR p.msg_id = ' . $message_row['root_level'] . ')';
1992    }
1993
1994    $sql_ary = array(
1995        'SELECT'    => 't.*, p.*, u.*',
1996        'FROM'        => array(
1997            PRIVMSGS_TABLE        => 'p',
1998            PRIVMSGS_TO_TABLE    => 't',
1999            USERS_TABLE            => 'u'
2000        ),
2001        'LEFT_JOIN'    => array(),
2002        'WHERE'        => $sql_where,
2003        'ORDER_BY'    => 'p.message_time DESC',
2004    );
2005
2006    /**
2007    * Event to modify the SQL query before the message history in private message is queried
2008    *
2009    * @event core.message_history_modify_sql_ary
2010    * @var    array    sql_ary        The SQL array to get the data of the message history in private message
2011    * @since 3.2.8-RC1
2012    */
2013    $vars = array('sql_ary');
2014    extract($phpbb_dispatcher->trigger_event('core.message_history_modify_sql_ary', compact($vars)));
2015
2016    $sql = $db->sql_build_query('SELECT', $sql_ary);
2017    unset($sql_ary);
2018
2019    $result = $db->sql_query($sql);
2020    $row = $db->sql_fetchrow($result);
2021
2022    if (!$row)
2023    {
2024        $db->sql_freeresult($result);
2025        return false;
2026    }
2027
2028    $title = $row['message_subject'];
2029
2030    $rowset = array();
2031    $folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm') . '&amp;folder=';
2032
2033    do
2034    {
2035        $folder_id = (int) $row['folder_id'];
2036
2037        $row['folder'][] = (isset($folder[$folder_id])) ? '<a href="' . $folder_url . $folder_id . '">' . $folder[$folder_id]['folder_name'] . '</a>' : $user->lang['UNKNOWN_FOLDER'];
2038
2039        if (isset($rowset[$row['msg_id']]))
2040        {
2041            $rowset[$row['msg_id']]['folder'][] = (isset($folder[$folder_id])) ? '<a href="' . $folder_url . $folder_id . '">' . $folder[$folder_id]['folder_name'] . '</a>' : $user->lang['UNKNOWN_FOLDER'];
2042        }
2043        else
2044        {
2045            $rowset[$row['msg_id']] = $row;
2046        }
2047    }
2048    while ($row = $db->sql_fetchrow($result));
2049    $db->sql_freeresult($result);
2050
2051    $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm');
2052
2053    /**
2054    * Modify message rows before displaying the history in private messages
2055    *
2056    * @event core.message_history_modify_rowset
2057    * @var int        msg_id            ID of the private message
2058    * @var int        user_id            ID of the message author
2059    * @var array    message_row        Array with message data
2060    * @var array    folder            Array with data of user's message folders
2061    * @var bool        in_post_mode    Whether or not we are viewing or composing
2062    * @var array    rowset            Array with message history data
2063    * @var string    url                Base URL used to generate links to private messages
2064    * @var string    title            Subject of the private message
2065    * @since 3.2.10-RC1
2066    * @since 3.3.1-RC1
2067    */
2068    $vars = [
2069        'msg_id',
2070        'user_id',
2071        'message_row',
2072        'folder',
2073        'in_post_mode',
2074        'rowset',
2075        'url',
2076        'title',
2077    ];
2078    extract($phpbb_dispatcher->trigger_event('core.message_history_modify_rowset', compact($vars)));
2079
2080    if (count($rowset) == 1 && !$in_post_mode)
2081    {
2082        return false;
2083    }
2084
2085    $title = censor_text($title);
2086
2087    $next_history_pm = $previous_history_pm = $prev_id = 0;
2088
2089    // Re-order rowset to be able to get the next/prev message rows...
2090    $rowset = array_values($rowset);
2091
2092    for ($i = 0, $size = count($rowset); $i < $size; $i++)
2093    {
2094        $row = &$rowset[$i];
2095        $id = (int) $row['msg_id'];
2096
2097        $author_id    = $row['author_id'];
2098        $folder_id    = (int) $row['folder_id'];
2099
2100        $subject    = $row['message_subject'];
2101        $message    = $row['message_text'];
2102
2103        $message = censor_text($message);
2104
2105        $decoded_message = false;
2106
2107        if ($in_post_mode && $auth->acl_get('u_sendpm') && $author_id != ANONYMOUS)
2108        {
2109            $decoded_message = $message;
2110            decode_message($decoded_message, $row['bbcode_uid']);
2111
2112            $decoded_message = bbcode_nl2br($decoded_message);
2113        }
2114
2115        $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0);
2116        $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0);
2117
2118        $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false);
2119
2120        $subject = censor_text($subject);
2121
2122        if ($id == $msg_id)
2123        {
2124            $next_history_pm = (isset($rowset[$i + 1])) ? (int) $rowset[$i + 1]['msg_id'] : 0;
2125            $previous_history_pm = $prev_id;
2126        }
2127
2128        $template_vars = array(
2129            'MESSAGE_AUTHOR_QUOTE'        => (($decoded_message) ? addslashes(get_username_string('username', $author_id, $row['username'], $row['user_colour'], $row['username'])) : ''),
2130            'MESSAGE_AUTHOR_FULL'        => get_username_string('full', $author_id, $row['username'], $row['user_colour'], $row['username']),
2131            'MESSAGE_AUTHOR_COLOUR'        => get_username_string('colour', $author_id, $row['username'], $row['user_colour'], $row['username']),
2132            'MESSAGE_AUTHOR'            => get_username_string('username', $author_id, $row['username'], $row['user_colour'], $row['username']),
2133            'U_MESSAGE_AUTHOR'            => get_username_string('profile', $author_id, $row['username'], $row['user_colour'], $row['username']),
2134
2135            'SUBJECT'            => $subject,
2136            'SENT_DATE'            => $user->format_date($row['message_time']),
2137            'MESSAGE'            => $message,
2138            'FOLDER'            => implode($user->lang['COMMA_SEPARATOR'], $row['folder']),
2139            'DECODED_MESSAGE'    => $decoded_message,
2140
2141            'S_CURRENT_MSG'        => ($row['msg_id'] == $msg_id),
2142            'S_AUTHOR_DELETED'    => ($author_id == ANONYMOUS) ? true : false,
2143            'S_IN_POST_MODE'    => $in_post_mode,
2144
2145            'MSG_ID'            => $row['msg_id'],
2146            'MESSAGE_TIME'        => $row['message_time'],
2147            'USER_ID'            => $row['user_id'],
2148            'U_VIEW_MESSAGE'    => "$url&amp;f=$folder_id&amp;p=" . $row['msg_id'],
2149            'U_QUOTE'            => (!$in_post_mode && $auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) ? "$url&amp;mode=compose&amp;action=quote&amp;f=" . $folder_id . "&amp;p=" . $row['msg_id'] : '',
2150            'U_POST_REPLY_PM'    => ($author_id != $user->data['user_id'] && $author_id != ANONYMOUS && $auth->acl_get('u_sendpm')) ? "$url&amp;mode=compose&amp;action=reply&amp;f=$folder_id&amp;p=" . $row['msg_id'] : ''
2151        );
2152
2153        /**
2154        * Modify the template vars for displaying the message history in private message
2155        *
2156        * @event core.message_history_modify_template_vars
2157        * @var array    template_vars        Array containing the query
2158        * @var array    row                    Array containing the action user row
2159        * @since 3.2.8-RC1
2160        */
2161        $vars = array(
2162            'template_vars',
2163            'row',
2164        );
2165        extract($phpbb_dispatcher->trigger_event('core.message_history_modify_template_vars', compact($vars)));
2166
2167        $template->assign_block_vars('history_row', $template_vars);
2168
2169        unset($rowset[$i]);
2170        $prev_id = $id;
2171    }
2172
2173    $template->assign_vars(array(
2174        'QUOTE_IMG'            => $user->img('icon_post_quote', $user->lang['REPLY_WITH_QUOTE']),
2175        'HISTORY_TITLE'        => $title,
2176
2177        'U_VIEW_NEXT_HISTORY'        => ($next_history_pm) ? "$url&amp;p=" . $next_history_pm : '',
2178        'U_VIEW_PREVIOUS_HISTORY'    => ($previous_history_pm) ? "$url&amp;p=" . $previous_history_pm : '',
2179    ));
2180
2181    return true;
2182}
2183
2184/**
2185* Set correct users max messages in PM folder.
2186* If several group memberships define different amount of messages, the highest will be chosen.
2187*/
2188function set_user_message_limit()
2189{
2190    global $user, $db, $config;
2191
2192    // Get maximum about from user memberships
2193    $message_limit = phpbb_get_max_setting_from_group($db, $user->data['user_id'], 'message_limit');
2194
2195    // If it is 0, there is no limit set and we use the maximum value within the config.
2196    $user->data['message_limit'] = (!$message_limit) ? $config['pm_max_msgs'] : $message_limit;
2197}
2198
2199/**
2200 * Get the maximum PM setting for the groups of the user
2201 *
2202 * @param \phpbb\db\driver\driver_interface $db
2203 * @param int $user_id
2204 * @param string $setting Only 'max_recipients' and 'message_limit' are supported
2205 * @return int The maximum setting for all groups of the user, unless one group has '0'
2206 * @throws \InvalidArgumentException If selected group setting is not supported
2207 */
2208function phpbb_get_max_setting_from_group(\phpbb\db\driver\driver_interface $db, $user_id, $setting)
2209{
2210    if ($setting !== 'max_recipients' && $setting !== 'message_limit')
2211    {
2212        throw new InvalidArgumentException('Setting "' . $setting . '" is not supported');
2213    }
2214
2215    // Get maximum number of allowed recipients
2216    $sql = 'SELECT MAX(g.group_' . $setting . ') as max_setting
2217        FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug
2218        WHERE ug.user_id = ' . (int) $user_id . '
2219            AND ug.user_pending = 0
2220            AND ug.group_id = g.group_id';
2221    $result = $db->sql_query($sql);
2222    $row = $db->sql_fetchrow($result);
2223    $db->sql_freeresult($result);
2224    $max_setting = (int) $row['max_setting'];
2225
2226    return $max_setting;
2227}
2228
2229/**
2230* Generates an array of coloured recipient names from a list of PMs - (groups & users)
2231*
2232* @param    array    $pm_by_id    An array of rows from PRIVMSGS_TABLE, keys are the msg_ids.
2233*
2234* @return    array                2D Array: array(msg_id => array('username or group string', ...), ...)
2235*                                Usernames are generated with {@link get_username_string get_username_string}
2236*                                Groups are coloured and have a link to the membership page
2237*/
2238function get_recipient_strings($pm_by_id)
2239{
2240    global $db, $phpbb_root_path, $phpEx, $user, $phpbb_container;
2241
2242    /** @var \phpbb\group\helper $group_helper */
2243    $group_helper = $phpbb_container->get('group_helper');
2244
2245    $address_list = $recipient_list = $address = array();
2246
2247    $_types = array('u', 'g');
2248
2249    foreach ($pm_by_id as $message_id => $row)
2250    {
2251        $address[$message_id] = rebuild_header(array('to' => $row['to_address'], 'bcc' => $row['bcc_address']));
2252
2253        foreach ($_types as $ug_type)
2254        {
2255            if (isset($address[$message_id][$ug_type]) && count($address[$message_id][$ug_type]))
2256            {
2257                foreach ($address[$message_id][$ug_type] as $ug_id => $in_to)
2258                {
2259                    $recipient_list[$ug_type][$ug_id] = array('name' => $user->lang['NA'], 'colour' => '');
2260                }
2261            }
2262        }
2263    }
2264
2265    foreach ($_types as $ug_type)
2266    {
2267        if (!empty($recipient_list[$ug_type]))
2268        {
2269            if ($ug_type == 'u')
2270            {
2271                $sql = 'SELECT user_id as id, username as name, user_colour as colour
2272                    FROM ' . USERS_TABLE . '
2273                    WHERE ';
2274            }
2275            else
2276            {
2277                $sql = 'SELECT group_id as id, group_name as name, group_colour as colour, group_type
2278                    FROM ' . GROUPS_TABLE . '
2279                    WHERE ';
2280            }
2281            $sql .= $db->sql_in_set(($ug_type == 'u') ? 'user_id' : 'group_id', array_map('intval', array_keys($recipient_list[$ug_type])));
2282
2283            $result = $db->sql_query($sql);
2284
2285            while ($row = $db->sql_fetchrow($result))
2286            {
2287                if ($ug_type == 'g')
2288                {
2289                    $row['name'] = $group_helper->get_name($row['name']);
2290                }
2291
2292                $recipient_list[$ug_type][$row['id']] = array('name' => $row['name'], 'colour' => $row['colour']);
2293            }
2294            $db->sql_freeresult($result);
2295        }
2296    }
2297
2298    foreach ($address as $message_id => $adr_ary)
2299    {
2300        foreach ($adr_ary as $type => $id_ary)
2301        {
2302            foreach ($id_ary as $ug_id => $_id)
2303            {
2304                if ($type == 'u')
2305                {
2306                    $address_list[$message_id][] = get_username_string('full', $ug_id, $recipient_list[$type][$ug_id]['name'], $recipient_list[$type][$ug_id]['colour']);
2307                }
2308                else
2309                {
2310                    $user_colour = ($recipient_list[$type][$ug_id]['colour']) ? ' style="font-weight: bold; color:#' . $recipient_list[$type][$ug_id]['colour'] . '"' : '';
2311                    $link = '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&amp;g=' . $ug_id) . '"' . $user_colour . '>';
2312                    $address_list[$message_id][] = $link . $recipient_list[$type][$ug_id]['name'] . (($link) ? '</a>' : '');
2313                }
2314            }
2315        }
2316    }
2317
2318    return $address_list;
2319}