1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* All the vital helper functions for use in email posting, formatting and conversion |
5
|
|
|
* and boy are there a bunch ! |
6
|
|
|
* |
7
|
|
|
* @package ElkArte Forum |
8
|
|
|
* @copyright ElkArte Forum contributors |
9
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
10
|
|
|
* |
11
|
|
|
* @version 2.0 dev |
12
|
|
|
* |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
use BBC\PreparseCode; |
16
|
|
|
use ElkArte\Cache\Cache; |
17
|
|
|
use ElkArte\Converters\Html2BBC; |
18
|
|
|
use ElkArte\Converters\Html2Md; |
19
|
|
|
use ElkArte\Helper\Util; |
20
|
|
|
use ElkArte\Languages\Txt; |
21
|
|
|
use ElkArte\Mail\PreparseMail; |
22
|
|
|
use ElkArte\Maillist\EmailFormat; |
23
|
|
|
use ElkArte\Maillist\EmailParse; |
24
|
|
|
use ElkArte\MembersList; |
25
|
|
|
use ElkArte\Notifications\Notifications; |
26
|
|
|
use ElkArte\Notifications\NotificationsTask; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Converts text / HTML to BBC |
30
|
|
|
* |
31
|
|
|
* What it does: |
32
|
|
|
* |
33
|
|
|
* - protects certain tags from conversion |
34
|
|
|
* - strips original message from the reply if possible |
35
|
|
|
* - If the email is html based, this will convert basic html tags to bbc tags |
36
|
|
|
* - If the email is plain text it will convert it to html based on markdown text |
37
|
|
|
* conventions and then that will be converted to bbc. |
38
|
|
|
* |
39
|
|
|
* @param string $text plain or html text |
40
|
|
|
* @param bool $html |
41
|
|
|
* |
42
|
|
|
* @return string |
43
|
|
|
* @uses Html2BBC.class.php for the html to bbc conversion |
44
|
|
|
* @uses markdown.php for text to html conversions |
45
|
|
|
* @package Maillist |
46
|
|
|
*/ |
47
|
|
|
function pbe_email_to_bbc($text, $html) |
48
|
|
|
{ |
49
|
|
|
// Define some things that need to be converted/modified, outside normal html or markup |
50
|
|
|
$tags = [ |
51
|
|
|
'~\*\*\s?(.*?)\*\*~is' => '**$1**', // set as markup bold |
52
|
|
|
'~<\*>~' => '<*>', // <*> as set in default Mailist Templates |
53
|
|
|
'~^-{3,}~' => '<hr>', // 3+ --- to hr |
54
|
|
|
'~#([0-9a-fA-F]{4,6}\b)~' => '#$1', // HTML entities |
55
|
|
|
]; |
56
|
|
|
|
57
|
|
|
// We are starting with HTML, our goal is to convert the best parts of it to BBC, |
58
|
|
|
$text = pbe_run_parsers($text); |
59
|
|
|
|
60
|
|
|
if ($html) |
61
|
|
|
{ |
62
|
|
|
// upfront pre-process $tags, mostly for the email template strings |
63
|
|
|
$text = preg_replace(array_keys($tags), array_values($tags), $text); |
64
|
|
|
} |
65
|
|
|
// Starting with plain text, possibly even markdown style ;) |
66
|
|
|
else |
67
|
|
|
{ |
68
|
|
|
// Set a gmail flag for special quote processing since its quotes are strange |
69
|
|
|
$gmail = (bool) preg_match('~<div class="gmail_quote">~i', $text); |
70
|
|
|
|
71
|
|
|
// Attempt to fix textual ('>') quotes so we also fix wrapping issues first! |
72
|
|
|
$text = pbe_fix_email_quotes($text, $gmail); |
73
|
|
|
$text = str_replace(['[quote]', '[/quote]'], ['>blockquote>', '>/blockquote>'], $text); |
74
|
|
|
|
75
|
|
|
// Convert this (markup) text to html |
76
|
|
|
require_once(EXTDIR . '/markdown/markdown.php'); |
77
|
|
|
|
78
|
|
|
$text = preg_replace(array_keys($tags), array_values($tags), $text); |
79
|
|
|
$text = Markdown($text); |
|
|
|
|
80
|
|
|
$text = str_replace(['>blockquote>', '>/blockquote>'], ['<blockquote>', '</blockquote>'], $text); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
// Convert the resulting HTML to BBC |
84
|
|
|
$bbc_converter = new Html2BBC($text, $html); |
85
|
|
|
$bbc_converter->skip_tags(['font', 'span']); |
86
|
|
|
$bbc_converter->skip_styles(['font-family', 'font-size', 'color']); |
87
|
|
|
|
88
|
|
|
$text = $bbc_converter->get_bbc(); |
89
|
|
|
|
90
|
|
|
// Some tags often end up as just empty tags - remove those. |
91
|
|
|
$emptytags = [ |
92
|
|
|
'~\[[bisu]\]\s*\[/[bisu]\]~' => '', |
93
|
|
|
'~\[quote\]\s*\[/quote\]~' => '', |
94
|
|
|
'~\[center\]\s*\[/center\]~' => '', |
95
|
|
|
'~(\n){3,}~si' => "\n\n", |
96
|
|
|
]; |
97
|
|
|
|
98
|
|
|
return preg_replace(array_keys($emptytags), array_values($emptytags), $text); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Runs the ACP email parsers |
103
|
|
|
* - returns cut email or original if the cut would result in a blank message |
104
|
|
|
* |
105
|
|
|
* @param string $text |
106
|
|
|
* @return string |
107
|
|
|
*/ |
108
|
|
|
function pbe_run_parsers($text) |
109
|
|
|
{ |
110
|
|
|
if (empty($text)) |
111
|
|
|
{ |
112
|
|
|
return ''; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Run our parsers, as defined in the ACP, to remove the original "replied to" message |
116
|
|
|
$text_save = $text; |
117
|
|
|
$result = pbe_parse_email_message($text); |
118
|
|
|
|
119
|
|
|
// If we have no message left after running the parser, then they may have replied |
120
|
|
|
// below and/or inside the original message. People like this should not be allowed |
121
|
|
|
// to use the net, or be forced to read their own messed up emails |
122
|
|
|
if (empty($result) || (trim(strip_tags(pbe_filter_email_message($text))) === '')) |
123
|
|
|
{ |
124
|
|
|
return $text_save; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return $text; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Prepares the email body so that it looks like a forum post |
132
|
|
|
* |
133
|
|
|
* What it does: |
134
|
|
|
* |
135
|
|
|
* - Removes extra content as defined in the ACP filters |
136
|
|
|
* - Fixes quotes and quote levels |
137
|
|
|
* - Re-flows (unfolds) an email using the EmailFormat.class |
138
|
|
|
* - Attempts to remove any exposed email address |
139
|
|
|
* |
140
|
|
|
* @param string $body |
141
|
|
|
* @param string $real_name |
142
|
|
|
* @param string $charset character set of the text |
143
|
|
|
* |
144
|
|
|
* @return null|string |
145
|
|
|
* @package Maillist |
146
|
|
|
* |
147
|
|
|
* @uses EmailFormat.class.php |
148
|
|
|
*/ |
149
|
|
|
function pbe_fix_email_body($body, $real_name = '', $charset = 'UTF-8') |
150
|
|
|
{ |
151
|
|
|
global $txt; |
152
|
|
|
|
153
|
|
|
// Remove the \r's now so its done |
154
|
|
|
$body = trim(str_replace("\r", '', $body)); |
155
|
|
|
|
156
|
|
|
// Remove any control characters |
157
|
|
|
$body = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $body); |
158
|
|
|
|
159
|
|
|
// Remove the riff-raff as defined by the ACP filters |
160
|
|
|
$body = pbe_filter_email_message($body); |
161
|
|
|
|
162
|
|
|
// Any old school email john smith wrote: etc. style quotes that we need to update |
163
|
|
|
$body = pbe_fix_client_quotes($body); |
164
|
|
|
|
165
|
|
|
// Attempt to remove any exposed email addresses that are in the reply |
166
|
|
|
$body = preg_replace('~>' . $txt['to'] . '(.*)@(.*?)(?:\n|\[br])~i', '', $body); |
167
|
|
|
$body = preg_replace('~\b\s?[a-z0-9._%+-]+@[a-zZ0-9.-]+\.[a-z]{2,4}\b.?' . $txt['email_wrote'] . ':\s?~i', '', $body); |
168
|
|
|
$body = preg_replace('~<(.*?)>(.*@.*?)(?:\n|\[br])~', '$1' . "\n", $body); |
169
|
|
|
$body = preg_replace('~' . $txt['email_quoting'] . ' (.*) (?:<|<|\[email]).*?@.*?(?:>|>|\[\/email]):~i', '', $body); |
170
|
|
|
|
171
|
|
|
// Remove multiple sequential blank lines, again |
172
|
|
|
$body = preg_replace('~(\n\s?){3,}~i', "\n\n", $body); |
173
|
|
|
|
174
|
|
|
// Check and remove any blank quotes |
175
|
|
|
$body = preg_replace('~(\[quote\s?([a-zA-Z0-9"=]*)?]\s*(\[br]s*)?\[\/quote])~', '', $body); |
176
|
|
|
|
177
|
|
|
// Reflow and Cleanup this message to something that looks normal-er |
178
|
|
|
return (new EmailFormat())->reflow($body, $real_name, $charset); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Replaces a messages >'s with BBC [quote] [/quote] blocks |
183
|
|
|
* |
184
|
|
|
* - Uses quote depth function |
185
|
|
|
* - Works with nested quotes of many forms >, > >, >>, >asd |
186
|
|
|
* - Bypassed for gmail as it only block quotes the outer layer and then plain |
187
|
|
|
* text > quotes the inner which is confusing to all |
188
|
|
|
* |
189
|
|
|
* @param string $body |
190
|
|
|
* @param bool $html |
191
|
|
|
* |
192
|
|
|
* @return string |
193
|
|
|
* @package Maillist |
194
|
|
|
*/ |
195
|
|
|
function pbe_fix_email_quotes($body, $html) |
196
|
|
|
{ |
197
|
|
|
// Coming from HTML then remove lines that start with > and are inside [quote] ... [/quote] blocks |
198
|
|
|
if ($html) |
199
|
|
|
{ |
200
|
|
|
$quotes = []; |
201
|
|
|
if (preg_match_all('~\[quote](.*)\[/quote]~sU', $body, $quotes, PREG_SET_ORDER)) |
202
|
|
|
{ |
203
|
|
|
foreach ($quotes as $quote) |
204
|
|
|
{ |
205
|
|
|
$quotenew = preg_replace('~^\s?> (.*)$~m', '$1', $quote[1] . "\n"); |
206
|
|
|
$body = str_replace($quote[0], '[quote]' . $quotenew . '[/quote]', $body); |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
// Create a line by line array broken on the newlines |
212
|
|
|
$body_array = explode("\n", $body); |
213
|
|
|
$original = $body_array; |
214
|
|
|
|
215
|
|
|
// Init |
216
|
|
|
$current_quote = 0; |
217
|
|
|
$quote_done = ''; |
218
|
|
|
|
219
|
|
|
// Go line by line and add the quote blocks where needed, fixing where needed |
220
|
|
|
for ($i = 0, $num = count($body_array); $i < $num; $i++) |
221
|
|
|
{ |
222
|
|
|
$body_array[$i] = trim($body_array[$i]); |
223
|
|
|
|
224
|
|
|
// Get the quote "depth" level for this line |
225
|
|
|
$level = pbe_email_quote_depth($body_array[$i]); |
226
|
|
|
|
227
|
|
|
// No quote marker on this line, but we are in a quote |
228
|
|
|
if ($level === 0 && $current_quote > 0) |
229
|
|
|
{ |
230
|
|
|
// Make sure we don't have an email wrap issue |
231
|
|
|
$level_prev = pbe_email_quote_depth($original[$i - 1], false); |
232
|
|
|
$level_next = pbe_email_quote_depth($original[$i + 1], false); |
233
|
|
|
|
234
|
|
|
// A line between two = quote or descending quote levels, |
235
|
|
|
// probably an email break so join (wrap) it back up and continue |
236
|
|
|
if (($level_prev !== 0) && ($level_prev >= $level_next && $level_next !== 0)) |
237
|
|
|
{ |
238
|
|
|
$body_array[$i - 1] .= ' ' . $body_array[$i]; |
239
|
|
|
unset($body_array[$i]); |
240
|
|
|
continue; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// No quote or in the same quote just continue |
245
|
|
|
if ($level === $current_quote) |
246
|
|
|
{ |
247
|
|
|
continue; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// Deeper than we were so add a quote |
251
|
|
|
if ($level > $current_quote) |
252
|
|
|
{ |
253
|
|
|
$qin_temp = ''; |
254
|
|
|
while ($level > $current_quote) |
255
|
|
|
{ |
256
|
|
|
$qin_temp .= '[quote]' . "\n"; |
257
|
|
|
$current_quote++; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
$body_array[$i] = $qin_temp . $body_array[$i]; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
// Less deep so back out |
264
|
|
|
if ($level < $current_quote) |
265
|
|
|
{ |
266
|
|
|
$qout_temp = ''; |
267
|
|
|
while ($level < $current_quote) |
268
|
|
|
{ |
269
|
|
|
$qout_temp .= '[/quote]' . "\n"; |
270
|
|
|
$current_quote--; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
$body_array[$i] = $qout_temp . $body_array[$i]; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// That's all I have to say about that |
277
|
|
|
if ($level === 0 && $current_quote !== 0) |
278
|
|
|
{ |
279
|
|
|
$quote_done = ''; |
280
|
|
|
while ($current_quote) |
281
|
|
|
{ |
282
|
|
|
$quote_done .= '[/quote]' . "\n"; |
283
|
|
|
$current_quote--; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
$body_array[$i] = $quote_done . $body_array[$i]; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
// No more lines, lets just make sure we did not leave ourselves any open quotes |
291
|
|
|
while (!empty($current_quote)) |
292
|
|
|
{ |
293
|
|
|
$quote_done .= '[/quote]' . "\n"; |
294
|
|
|
$current_quote--; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
$body_array[$i] = $quote_done; |
298
|
|
|
|
299
|
|
|
// Join the array back together while dropping null index's |
300
|
|
|
return implode("\n", array_values($body_array)); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Looks for text quotes in the form of > and returns the current level for the line |
305
|
|
|
* |
306
|
|
|
* - If update is true (default), will strip the >'s and return the numeric level found |
307
|
|
|
* - Called by pbe_fix_email_quotes |
308
|
|
|
* |
309
|
|
|
* @param string $string |
310
|
|
|
* @param bool $update |
311
|
|
|
* |
312
|
|
|
* @return int |
313
|
|
|
* @package Maillist |
314
|
|
|
* |
315
|
|
|
*/ |
316
|
|
|
function pbe_email_quote_depth(&$string, $update = true) |
317
|
|
|
{ |
318
|
|
|
// Get the quote "depth" level for this line |
319
|
|
|
$level = 0; |
320
|
|
|
$check = true; |
321
|
|
|
$string_save = $string; |
322
|
|
|
|
323
|
|
|
while ($check) |
324
|
|
|
{ |
325
|
|
|
// We have a quote marker, increase our depth and strip the line of that quote marker |
326
|
|
|
if ($string === '>' || strpos($string, '> ') === 0) |
327
|
|
|
{ |
328
|
|
|
$level++; |
329
|
|
|
$string = substr($string, 2); |
330
|
|
|
} |
331
|
|
|
// Maybe a poorly nested quote, with no spaces between the >'s or the > and the data with no space |
332
|
|
|
elseif ((strpos($string, '>>') === 0) || (preg_match('~^>[a-z0-9<-]+~Ui', $string) === 1)) |
333
|
|
|
{ |
334
|
|
|
$level++; |
335
|
|
|
$string = substr($string, 1); |
336
|
|
|
} |
337
|
|
|
// All done getting the depth |
338
|
|
|
else |
339
|
|
|
{ |
340
|
|
|
$check = false; |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
if (!$update) |
345
|
|
|
{ |
346
|
|
|
$string = $string_save; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
return $level; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Splits a message at a given string, returning only the upper portion |
354
|
|
|
* |
355
|
|
|
* - Intended to split off the 'replied to' portion that often follows the reply |
356
|
|
|
* - Uses parsers as defined in the ACP to do its searching |
357
|
|
|
* - Stops after the first successful hit occurs |
358
|
|
|
* - Goes in the order defined in the table |
359
|
|
|
* |
360
|
|
|
* @param string $body |
361
|
|
|
* @return bool on find |
362
|
|
|
* @package Maillist |
363
|
|
|
*/ |
364
|
|
|
function pbe_parse_email_message(&$body) |
365
|
|
|
{ |
366
|
|
|
$db = database(); |
367
|
|
|
|
368
|
|
|
// Load up the parsers from the database |
369
|
|
|
$expressions = []; |
370
|
|
|
$db->fetchQuery(' |
371
|
|
|
SELECT |
372
|
|
|
filter_from, filter_type |
373
|
|
|
FROM {db_prefix}postby_emails_filters |
374
|
|
|
WHERE filter_style = {string:filter_style} |
375
|
|
|
ORDER BY filter_order ASC', |
376
|
|
|
[ |
377
|
|
|
'filter_style' => 'parser' |
378
|
|
|
] |
379
|
|
|
)->fetch_callback( |
380
|
|
|
static function ($row) use (&$expressions) { |
381
|
|
|
// Build an array of valid expressions |
382
|
|
|
$expressions[] = [ |
383
|
|
|
'type' => $row['filter_type'] === 'regex' ? 'regex' : 'string', |
384
|
|
|
'parser' => $row['filter_from']]; |
385
|
|
|
} |
386
|
|
|
); |
387
|
|
|
|
388
|
|
|
// Look for the markers, **stop** after the first successful one, good hunting! |
389
|
|
|
foreach ($expressions as $expression) |
390
|
|
|
{ |
391
|
|
|
$split = $expression['type'] === 'regex' |
392
|
|
|
? preg_split($expression['parser'], $body) |
393
|
|
|
: explode($expression['parser'], $body, 2); |
394
|
|
|
|
395
|
|
|
// If an expression was matched our fine work is done |
396
|
|
|
if (!empty($split[1])) |
397
|
|
|
{ |
398
|
|
|
// If we had a hit then we clip off the mail and return above the split text |
399
|
|
|
$body = $split[0]; |
400
|
|
|
return true; |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
return false; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Searches for extraneous text and removes/replaces it |
409
|
|
|
* |
410
|
|
|
* - Uses filters as defined in the ACP to do the search / replace |
411
|
|
|
* - Will apply regex filters first, then string match filters |
412
|
|
|
* - Apply all filters to a message |
413
|
|
|
* |
414
|
|
|
* @param string $text |
415
|
|
|
* |
416
|
|
|
* @return string |
417
|
|
|
* @package Maillist |
418
|
|
|
* |
419
|
|
|
*/ |
420
|
|
|
function pbe_filter_email_message($text) |
421
|
|
|
{ |
422
|
|
|
if (empty($text)) |
423
|
|
|
{ |
424
|
|
|
return ''; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
$db = database(); |
428
|
|
|
|
429
|
|
|
// load up the text filters from the database, regex first and ordered by the filter order ... |
430
|
|
|
$db->fetchQuery(' |
431
|
|
|
SELECT |
432
|
|
|
filter_from, filter_to, filter_type |
433
|
|
|
FROM {db_prefix}postby_emails_filters |
434
|
|
|
WHERE filter_style = {string:filter_style} |
435
|
|
|
ORDER BY filter_type ASC, filter_order ASC', |
436
|
|
|
[ |
437
|
|
|
'filter_style' => 'filter' |
438
|
|
|
] |
439
|
|
|
)->fetch_callback( |
440
|
|
|
static function ($row) use (&$text) { |
441
|
|
|
if ($row['filter_type'] === 'regex') |
442
|
|
|
{ |
443
|
|
|
// Newline madness |
444
|
|
|
if (!empty($row['filter_to'])) |
445
|
|
|
{ |
446
|
|
|
$row['filter_to'] = str_replace('\n', "\n", $row['filter_to']); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
// Test the regex and if good use, else skip, don't want a bad regex to empty the message! |
450
|
|
|
$temp = preg_replace($row['filter_from'], $row['filter_to'], $text); |
451
|
|
|
if ($temp !== null) |
452
|
|
|
{ |
453
|
|
|
$text = $temp; |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
else |
457
|
|
|
{ |
458
|
|
|
$text = str_replace($row['filter_from'], $row['filter_to'], $text); |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
); |
462
|
|
|
|
463
|
|
|
return $text; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Finds Re: Subject: FW: FWD or [$sitename] in the subject and strips it |
468
|
|
|
* |
469
|
|
|
* - Recursively calls itself till no more tags are found |
470
|
|
|
* |
471
|
|
|
* @param string $text |
472
|
|
|
* @param bool $check if true will return whether tags were found |
473
|
|
|
* |
474
|
|
|
* @return bool|string |
475
|
|
|
* @package Maillist |
476
|
|
|
* |
477
|
|
|
*/ |
478
|
|
|
function pbe_clean_email_subject($text, $check = false) |
479
|
|
|
{ |
480
|
|
|
global $txt, $modSettings, $mbname; |
481
|
|
|
|
482
|
|
|
$sitename = empty($modSettings['maillist_sitename']) ? $mbname : $modSettings['maillist_sitename']; |
483
|
|
|
|
484
|
|
|
// Find Re: Subject: FW: FWD or [$sitename] in the subject and strip it |
485
|
|
|
$re = stripos($text, $txt['RE:']); |
486
|
|
|
if ($re !== false) |
487
|
|
|
{ |
488
|
|
|
$text = substr($text, 0, $re) . substr($text, $re + strlen($txt['RE:']), strlen($text)); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
$su = stripos($text, $txt['SUBJECT:']); |
492
|
|
|
if ($su !== false) |
493
|
|
|
{ |
494
|
|
|
$text = substr($text, 0, $su) . substr($text, $su + strlen($txt['SUBJECT:']), strlen($text)); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
$fw = stripos($text, $txt['FW:']); |
498
|
|
|
if ($fw !== false) |
499
|
|
|
{ |
500
|
|
|
$text = substr($text, 0, $fw) . substr($text, $fw + strlen($txt['FW:']), strlen($text)); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
$gr = strpos($text, '[' . $sitename . ']'); |
504
|
|
|
if ($gr !== false) |
505
|
|
|
{ |
506
|
|
|
$text = substr($text, 0, $gr) . substr($text, $gr + strlen($sitename) + 2, strlen($text)); |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
$fwd = stripos($text, $txt['FWD:']); |
510
|
|
|
if ($fwd !== false) |
511
|
|
|
{ |
512
|
|
|
$text = substr($text, 0, $fwd) . substr($text, $fwd + strlen($txt['FWD:']), strlen($text)); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
// if not done then call ourselves again, we like the sound of our name |
516
|
|
|
if (stripos($text, (string) $txt['RE:']) || stripos($text, $txt['FW:']) || stripos($text, $txt['FWD:']) || strpos($text, '[' . $sitename . ']')) |
517
|
|
|
{ |
518
|
|
|
$text = pbe_clean_email_subject($text); |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
// clean or not? |
522
|
|
|
if ($check) |
523
|
|
|
{ |
524
|
|
|
return ($re === false && $su === false && $gr === false && $fw === false && $fwd === false); |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
return trim($text); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Used if the original email could not be removed from the message (top of post) |
532
|
|
|
* |
533
|
|
|
* - Tries to quote the original message instead by using a loose original message search |
534
|
|
|
* - Looks for email client original message tags and converts them to bbc quotes |
535
|
|
|
* |
536
|
|
|
* @param string $body |
537
|
|
|
* |
538
|
|
|
* @return null|string |
539
|
|
|
* @package Maillist |
540
|
|
|
* |
541
|
|
|
*/ |
542
|
|
|
function pbe_fix_client_quotes($body) |
543
|
|
|
{ |
544
|
|
|
global $txt; |
545
|
|
|
|
546
|
|
|
// Define some common quote markers (from the original messages) |
547
|
|
|
// @todo ACP for this? ... not sure |
548
|
|
|
$regex = []; |
549
|
|
|
|
550
|
|
|
// On mon, jan 12, 2020 at 10:10 AM, John Smith wrote: [quote] |
551
|
|
|
$regex[] = '~(?:' . $txt['email_on'] . ')?\w{3}, \w{3} \d{1,2},\s?\d{4} ' . $txt['email_at'] . ' \d{1,2}:\d{1,2} [AP]M,(.*)?' . $txt['email_wrote'] . ':\s?\s{1,4}\[quote\]~i'; |
552
|
|
|
// [quote] on: mon jan 12, 2004 John Smith wrote: |
553
|
|
|
$regex[] = '~\[quote\]\s?' . $txt['email_on'] . ': \w{3} \w{3} \d{1,2}, \d{4} (.*)?' . $txt['email_wrote'] . ':\s~i'; |
554
|
|
|
// on jan 12, 2020 at 10:10 PM, John Smith wrote: [quote] |
555
|
|
|
$regex[] = '~' . $txt['email_on'] . ' \w{3} \d{1,2}, \d{4}, ' . $txt['email_at'] . ' \d{1,2}:\d{1,2} [AP]M,(.*)?' . $txt['email_wrote'] . ':\s{1,4}\[quote\]~i'; |
556
|
|
|
// on jan 12, 2020 at 10:10, John Smith wrote [quote] |
557
|
|
|
$regex[] = '~' . $txt['email_on'] . ' \w{3} \d{1,2}, \d{4}, ' . $txt['email_at'] . ' \d{1,2}:\d{1,2}, (.*)?' . $txt['email_wrote'] . ':\s{1,4}\[quote\]~i'; |
558
|
|
|
// quoting: John Smith on stuffz at 10:10:23 AM |
559
|
|
|
$regex[] = '~' . $txt['email_quotefrom'] . ': (.*) ' . $txt['email_on'] . ' .* ' . $txt['email_at'] . ' \d{1,2}:\d{1,2}:\d{1,2} [AP]M~'; |
560
|
|
|
// quoting John Smith <[email protected]> |
561
|
|
|
$regex[] = '~' . $txt['email_quoting'] . ' (.*) (?:<|<|\[email\]).*?@.*?(?:>|>|\[/email\]):~i'; |
562
|
|
|
// --- in some group name "John Smith" <[email protected]> wrote: |
563
|
|
|
$regex[] = '~---\s.*?"(.*)"\s+' . $txt['email_wrote'] . ':\s(\[quote\])?~i'; |
564
|
|
|
// --- in [email protected] John Smith wrote |
565
|
|
|
$regex[] = '~---\s.*?\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}\b,\s(.*?)\s' . $txt['email_wrote'] . ':?~iu'; |
566
|
|
|
// --- In some@g..., "someone" wrote: |
567
|
|
|
$regex[] = '~---\s.*?\b[A-Z0-9._%+-]+@[A-Z0-9][.]{3}, [A-Z0-9._%+\-"]+\b(.*?)\s' . $txt['email_wrote'] . ':?~iu'; |
568
|
|
|
// --- In [email]something[/email] "someone" wrote: |
569
|
|
|
$regex[] = '~---\s.*?\[email=.*?/email\],?\s"?(.*?)"?\s' . $txt['email_wrote'] . ':?~iu'; |
570
|
|
|
|
571
|
|
|
// For each one see if we can do a nice [quote author=john smith] |
572
|
|
|
foreach ($regex as $reg) |
573
|
|
|
{ |
574
|
|
|
if (preg_match_all($reg, $body, $matches, PREG_SET_ORDER)) |
575
|
|
|
{ |
576
|
|
|
foreach ($matches as $quote) |
577
|
|
|
{ |
578
|
|
|
$quote[1] = preg_replace('~\[email].*\[/email]~', '', $quote[1]); |
579
|
|
|
$body = pbe_str_replace_once($quote[0], "\n" . '[quote author=' . trim($quote[1]) . "]\n", $body); |
580
|
|
|
|
581
|
|
|
$quote[1] = preg_quote($quote[1], '~'); |
582
|
|
|
|
583
|
|
|
// Look for [quote author=][/quote][quote] issues |
584
|
|
|
$body = preg_replace('~\[quote author=' . trim($quote[1]) . '] ?(?:\n|\[br]?){2,4} ?\[/ote]?\[quote]~u', '[quote author=' . trim($quote[1]) . "]\n", $body, 1); |
585
|
|
|
|
586
|
|
|
// And [quote author=][quote] newlines [/quote] issues |
587
|
|
|
$body = preg_replace('~\[quote author=' . trim($quote[1]) . '] ?(?:\n|\[br]?){2,4}\[quote]~u', '[quote author=' . trim($quote[1]) . "]\n", $body); |
588
|
|
|
} |
589
|
|
|
} |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
return $body; |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* Does a single replacement of the first found string in the haystack |
597
|
|
|
* |
598
|
|
|
* @param string $needle |
599
|
|
|
* @param string $replace |
600
|
|
|
* @param string $haystack |
601
|
|
|
* @return string |
602
|
|
|
* @package Maillist |
603
|
|
|
*/ |
604
|
|
|
function pbe_str_replace_once($needle, $replace, $haystack) |
605
|
|
|
{ |
606
|
|
|
// Looks for the first occurrence of $needle in $haystack and replaces it with $replace |
607
|
|
|
$pos = strpos($haystack, $needle); |
608
|
|
|
if ($pos === false) |
609
|
|
|
{ |
610
|
|
|
return $haystack; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
return substr_replace($haystack, $replace, $pos, strlen($needle)); |
|
|
|
|
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Does a moderation check on a given user (global) |
618
|
|
|
* |
619
|
|
|
* - Removes permissions of PBE concern that a given moderated level denies |
620
|
|
|
* |
621
|
|
|
* @param array $pbe array of user values |
622
|
|
|
* @package Maillist |
623
|
|
|
*/ |
624
|
|
|
function pbe_check_moderation(&$pbe) |
625
|
|
|
{ |
626
|
|
|
global $modSettings; |
627
|
|
|
|
628
|
|
|
if (empty($modSettings['postmod_active'])) |
629
|
|
|
{ |
630
|
|
|
return; |
631
|
|
|
} |
632
|
|
|
|
633
|
|
|
// Have they been muted for being naughty? |
634
|
|
|
if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $pbe['user_info']['warning']) |
635
|
|
|
{ |
636
|
|
|
// Remove anything that would allow them to do anything via PBE |
637
|
|
|
$denied_permissions = [ |
638
|
|
|
'pm_send', 'postby_email', |
639
|
|
|
'admin_forum', 'moderate_forum', |
640
|
|
|
'post_new', 'post_reply_own', 'post_reply_any', |
641
|
|
|
'post_attachment', 'post_unapproved_attachments', |
642
|
|
|
'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any', |
643
|
|
|
]; |
644
|
|
|
$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], $denied_permissions); |
645
|
|
|
} |
646
|
|
|
elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $pbe['user_info']['warning']) |
647
|
|
|
{ |
648
|
|
|
// Work out what permissions should change if they are just being moderated |
649
|
|
|
$permission_change = [ |
650
|
|
|
'post_new' => 'post_unapproved_topics', |
651
|
|
|
'post_reply_own' => 'post_unapproved_replies_own', |
652
|
|
|
'post_reply_any' => 'post_unapproved_replies_any', |
653
|
|
|
'post_attachment' => 'post_unapproved_attachments', |
654
|
|
|
]; |
655
|
|
|
|
656
|
|
|
foreach ($permission_change as $old => $new) |
657
|
|
|
{ |
658
|
|
|
if (!in_array($old, $pbe['user_info']['permissions'])) |
659
|
|
|
{ |
660
|
|
|
unset($permission_change[$old]); |
661
|
|
|
} |
662
|
|
|
else |
663
|
|
|
{ |
664
|
|
|
$pbe['user_info']['permissions'][] = $new; |
665
|
|
|
} |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], array_keys($permission_change)); |
669
|
|
|
} |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
/** |
673
|
|
|
* Creates a failed email entry in the postby_emails_error table |
674
|
|
|
* |
675
|
|
|
* - Attempts an auto-correct for common errors so the admin / moderator |
676
|
|
|
* - can choose to approve the email with the corrections |
677
|
|
|
* |
678
|
|
|
* @param string $error |
679
|
|
|
* @param EmailParse $email_message |
680
|
|
|
* |
681
|
|
|
* @return bool |
682
|
|
|
* @package Maillist |
683
|
|
|
* |
684
|
|
|
*/ |
685
|
|
|
function pbe_emailError($error, $email_message) |
686
|
|
|
{ |
687
|
|
|
global $txt; |
688
|
|
|
|
689
|
|
|
$db = database(); |
690
|
|
|
|
691
|
|
|
Txt::load('EmailTemplates'); |
692
|
|
|
|
693
|
|
|
// Some extra items we will need to remove from the message subject |
694
|
|
|
$pm_subject_leader = str_replace('{SUBJECT}', '', $txt['new_pm_subject']); |
695
|
|
|
|
696
|
|
|
// Clean the subject like we don't know where it has been |
697
|
|
|
$subject = trim(str_replace($pm_subject_leader, '', $email_message->subject)); |
698
|
|
|
$subject = pbe_clean_email_subject($subject); |
699
|
|
|
$subject = ($subject === '' ? $txt['no_subject'] : $subject); |
700
|
|
|
|
701
|
|
|
// Start off with what we know about the security key, even if it's nothing |
702
|
|
|
$message_key = $email_message->message_key; |
703
|
|
|
$message_type = $email_message->message_type; |
704
|
|
|
$message_id = $email_message->message_id; |
705
|
|
|
$board_id = -1; |
706
|
|
|
|
707
|
|
|
// First up is the old, wrong email address, lets see who this should have come from if |
708
|
|
|
// it is not a new topic request |
709
|
|
|
if ($error === 'error_not_find_member' && $email_message->message_type !== 'x') |
710
|
|
|
{ |
711
|
|
|
$key_owner = query_key_owner($email_message); |
712
|
|
|
if (!empty($key_owner)) |
713
|
|
|
{ |
714
|
|
|
// Valid key so show who should have sent this key in? email aggravaters :P often mess this up |
715
|
|
|
$email_message->email['from'] .= ' => ' . $key_owner; |
716
|
|
|
} |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
// A valid key but it was not sent to this user ... but we did get the email from a valid site user |
720
|
|
|
if ($error === 'error_key_sender_match') |
721
|
|
|
{ |
722
|
|
|
$key_owner = query_key_owner($email_message); |
723
|
|
|
if (!empty($key_owner)) |
724
|
|
|
{ |
725
|
|
|
// Valid key so show who should have sent this key in |
726
|
|
|
$email_message->email['from'] = $key_owner . ' => ' . $email_message->email['from']; |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
// No key? We should at a minimum have who its from and a subject, so use that |
731
|
|
|
if ($email_message->message_type !== 'x' && (empty($message_key) || $error === 'error_pm_not_found')) |
732
|
|
|
{ |
733
|
|
|
// We don't have the message type (since we don't have a key) |
734
|
|
|
// Attempt to see if it might be a PM so we handle it correctly |
735
|
|
|
if (empty($message_type) && (strpos($email_message->subject, (string) $pm_subject_leader) !== false)) |
736
|
|
|
{ |
737
|
|
|
$message_type = 'p'; |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
// Find all keys sent to this user, sorted by date |
741
|
|
|
$user_keys = query_user_keys($email_message->email['from']); |
742
|
|
|
|
743
|
|
|
// While we have keys to look at, see if we can match up this lost message on subjects |
744
|
|
|
foreach ($user_keys as $user_key) |
745
|
|
|
{ |
746
|
|
|
$key = $user_key['message_key']; |
747
|
|
|
$type = $user_key['message_type']; |
748
|
|
|
$message = $user_key['message_id']; |
749
|
|
|
|
750
|
|
|
// If we know/suspect its a "m,t or p" then use that to avoid a match on a wrong type, that would be bad ;) |
751
|
|
|
// Look up this message/topic/pm and see if the subjects match ... if they do then tada! |
752
|
|
|
if (((!empty($message_type) && $message_type === $type) || empty($message_type) && $type !== 'p') |
|
|
|
|
753
|
|
|
&& query_load_subject($message, $type, $email_message->email['from']) === $subject) |
754
|
|
|
{ |
755
|
|
|
// This email has a subject that matches the subject of a message that was sent to them |
756
|
|
|
$message_key = $key; |
757
|
|
|
$message_id = $message; |
758
|
|
|
$message_type = $type; |
759
|
|
|
break; |
760
|
|
|
} |
761
|
|
|
} |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
// Maybe we have enough to find the board id where this was going |
765
|
|
|
if (!empty($message_id) && $message_type !== 'p') |
766
|
|
|
{ |
767
|
|
|
$board_id = query_load_board($message_id); |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
// Log the error so the moderators can take a look, helps keep them sharp |
771
|
|
|
$id = isset($_REQUEST['item']) ? (int) $_REQUEST['item'] : 0; |
772
|
|
|
$db->insert(empty($id) ? 'ignore' : 'replace', |
773
|
|
|
'{db_prefix}postby_emails_error', |
774
|
|
|
[ |
775
|
|
|
'id_email' => 'int', 'error' => 'string', 'message_key' => 'string', |
776
|
|
|
'subject' => 'string', 'message_id' => 'int', 'id_board' => 'int', |
777
|
|
|
'email_from' => 'string', 'message_type' => 'string', 'message' => 'string'], |
778
|
|
|
[ |
779
|
|
|
$id, $error, $message_key, |
780
|
|
|
$subject, $message_id, $board_id, |
781
|
|
|
$email_message->email['from'], $message_type, $email_message->raw_message], |
782
|
|
|
['id_email'] |
783
|
|
|
); |
784
|
|
|
|
785
|
|
|
// Flush the moderator error number cache, if we are here it likely just changed. |
786
|
|
|
Cache::instance()->remove('num_menu_errors'); |
787
|
|
|
|
788
|
|
|
// If not running from the cli, then go back to the form |
789
|
|
|
if (isset($_POST['item'])) |
790
|
|
|
{ |
791
|
|
|
// Back to the form we go |
792
|
|
|
$_SESSION['email_error'] = $txt[$error]; |
793
|
|
|
redirectexit('action=admin;area=maillist'); |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
return false; |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
/** |
800
|
|
|
* Writes email attachments as temp names in the proper attachment directory |
801
|
|
|
* |
802
|
|
|
* What it does: |
803
|
|
|
* |
804
|
|
|
* - populates TemporaryAttachmentsList with the email attachments |
805
|
|
|
* - does all the checks to validate them |
806
|
|
|
* - skips ones flagged with errors |
807
|
|
|
* - adds valid ones to attachmentOptions |
808
|
|
|
* - calls createAttachment to store them |
809
|
|
|
* |
810
|
|
|
* @param array $pbe |
811
|
|
|
* @param EmailParse $email_message |
812
|
|
|
* |
813
|
|
|
* @return array |
814
|
|
|
* @package Maillist |
815
|
|
|
* |
816
|
|
|
*/ |
817
|
|
|
function pbe_email_attachments($pbe, $email_message) |
818
|
|
|
{ |
819
|
|
|
// Trying to attach a file with this post .... |
820
|
|
|
global $modSettings, $context, $txt; |
821
|
|
|
|
822
|
|
|
// Init |
823
|
|
|
$attachIDs = []; |
824
|
|
|
$tmp_attachments = new TemporaryAttachmentsList(); |
|
|
|
|
825
|
|
|
|
826
|
|
|
// Make sure we know where to upload |
827
|
|
|
$attachmentDirectory = new AttachmentsDirectory($modSettings, database()); |
|
|
|
|
828
|
|
|
try |
829
|
|
|
{ |
830
|
|
|
$attachmentDirectory->automanageCheckDirectory(isset($_REQUEST['action']) && $_REQUEST['action'] === 'admin'); |
831
|
|
|
|
832
|
|
|
$attach_current_dir = $attachmentDirectory->getCurrent(); |
833
|
|
|
|
834
|
|
|
if (!is_dir($attach_current_dir)) |
835
|
|
|
{ |
836
|
|
|
$tmp_attachments->setSystemError('attach_folder_warning'); |
837
|
|
|
\ElkArte\Errors\Errors::instance()->log_error(sprintf($txt['attach_folder_admin_warning'], $attach_current_dir), 'critical'); |
|
|
|
|
838
|
|
|
} |
839
|
|
|
} |
840
|
|
|
catch (\Exception $exception) |
841
|
|
|
{ |
842
|
|
|
// If the attachment folder is not there: error. |
843
|
|
|
$tmp_attachments->setSystemError($exception->getMessage()); |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
// For attachmentChecks function |
847
|
|
|
require_once(SUBSDIR . '/Attachments.subs.php'); |
848
|
|
|
$context['attachments'] = ['quantity' => 0, 'total_size' => 0]; |
849
|
|
|
|
850
|
|
|
// Create the file(s) with a temp name, so we can validate its contents/type |
851
|
|
|
foreach ($email_message->attachments as $name => $attachment) |
852
|
|
|
{ |
853
|
|
|
if ($tmp_attachments->hasSystemError()) |
854
|
|
|
{ |
855
|
|
|
continue; |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
$attachID = $tmp_attachments->getTplName($pbe['profile']['id_member'], bin2hex(random_bytes(16))); |
859
|
|
|
|
860
|
|
|
// Write the contents to an actual file |
861
|
|
|
$destName = $attach_current_dir . '/' . $attachID; |
|
|
|
|
862
|
|
|
if (file_put_contents($destName, $attachment) !== false) |
863
|
|
|
{ |
864
|
|
|
@chmod($destName, 0644); |
|
|
|
|
865
|
|
|
|
866
|
|
|
$temp_file = new TemporaryAttachment([ |
|
|
|
|
867
|
|
|
'name' => basename($name), |
868
|
|
|
'tmp_name' => $destName, |
869
|
|
|
'attachid' => $attachID, |
870
|
|
|
'user_id' => $pbe['profile']['id_member'], |
871
|
|
|
'size' => strlen($attachment), |
872
|
|
|
'type' => null, |
873
|
|
|
'id_folder' => $attachmentDirectory->currentDirectoryId(), |
874
|
|
|
]); |
875
|
|
|
|
876
|
|
|
// Make sure its valid |
877
|
|
|
$temp_file->doChecks($attachmentDirectory); |
878
|
|
|
$tmp_attachments->addAttachment($temp_file); |
879
|
|
|
} |
880
|
|
|
} |
881
|
|
|
|
882
|
|
|
$prefix = $tmp_attachments->getTplName($pbe['profile']['id_member']); |
883
|
|
|
|
884
|
|
|
// Space for improvement: move the removeAll to the end before ->unset |
885
|
|
|
if ($tmp_attachments->hasSystemError()) |
886
|
|
|
{ |
887
|
|
|
$tmp_attachments->removeAll(); |
888
|
|
|
} |
889
|
|
|
else |
890
|
|
|
{ |
891
|
|
|
// Get the results from attachmentChecks and see if it is suitable for posting |
892
|
|
|
foreach ($tmp_attachments as $attachID => $attachment) |
893
|
|
|
{ |
894
|
|
|
// If there were any errors we just skip that file |
895
|
|
|
if (strpos($attachID, (string) $prefix) === false || $attachment->hasErrors()) |
896
|
|
|
{ |
897
|
|
|
$attachment->remove(false); |
898
|
|
|
continue; |
899
|
|
|
} |
900
|
|
|
|
901
|
|
|
// Load the attachmentOptions array with the data needed to create an attachment |
902
|
|
|
$attachmentOptions = [ |
903
|
|
|
'post' => 0, |
904
|
|
|
'poster' => $pbe['profile']['id_member'], |
905
|
|
|
'name' => $attachment['name'], |
906
|
|
|
'tmp_name' => $attachment['tmp_name'], |
907
|
|
|
'size' => (int) $attachment['size'], |
908
|
|
|
'mime_type' => (string) $attachment['type'], |
909
|
|
|
'id_folder' => (int) $attachment['id_folder'], |
910
|
|
|
'approved' => !$modSettings['postmod_active'] || in_array('post_unapproved_attachments', $pbe['user_info']['permissions']), |
911
|
|
|
'errors' => [], |
912
|
|
|
]; |
913
|
|
|
|
914
|
|
|
// Make it available to the forum/post |
915
|
|
|
if (createAttachment($attachmentOptions)) |
916
|
|
|
{ |
917
|
|
|
$attachIDs[] = $attachmentOptions['id']; |
918
|
|
|
if (!empty($attachmentOptions['thumb'])) |
919
|
|
|
{ |
920
|
|
|
$attachIDs[] = $attachmentOptions['thumb']; |
921
|
|
|
} |
922
|
|
|
} |
923
|
|
|
// We had a problem so simply remove it |
924
|
|
|
else |
925
|
|
|
{ |
926
|
|
|
$tmp_attachments->removeById($attachID, false); |
927
|
|
|
} |
928
|
|
|
} |
929
|
|
|
} |
930
|
|
|
|
931
|
|
|
$tmp_attachments->unset(); |
932
|
|
|
|
933
|
|
|
return $attachIDs; |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* Used when an email attempts to start a new topic |
938
|
|
|
* |
939
|
|
|
* - Load the board id that a given email address is assigned to in the ACP |
940
|
|
|
* - Returns the board number in which the new topic must go |
941
|
|
|
* |
942
|
|
|
* @param EmailParse $email_address |
943
|
|
|
* |
944
|
|
|
* @return int |
945
|
|
|
* @package Maillist |
946
|
|
|
* |
947
|
|
|
*/ |
948
|
|
|
function pbe_find_board_number($email_address) |
949
|
|
|
{ |
950
|
|
|
global $modSettings; |
951
|
|
|
|
952
|
|
|
$valid_address = []; |
953
|
|
|
$board_number = 0; |
954
|
|
|
|
955
|
|
|
// Load our valid email ids and the corresponding board ids |
956
|
|
|
$data = (empty($modSettings['maillist_receiving_address'])) ? [] : Util::unserialize($modSettings['maillist_receiving_address']); |
957
|
|
|
foreach ($data as $addr) |
958
|
|
|
{ |
959
|
|
|
$valid_address[$addr[0]] = $addr[1]; |
960
|
|
|
} |
961
|
|
|
|
962
|
|
|
// Who was this message sent to, may have been sent to multiple addresses, |
963
|
|
|
// so we must check each one to see if we have a valid entry |
964
|
|
|
foreach ($email_address->email['to'] as $to_email) |
965
|
|
|
{ |
966
|
|
|
if (isset($valid_address[$to_email])) |
967
|
|
|
{ |
968
|
|
|
$board_number = (int) $valid_address[$to_email]; |
969
|
|
|
break; |
970
|
|
|
} |
971
|
|
|
} |
972
|
|
|
|
973
|
|
|
return $board_number; |
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
/** |
977
|
|
|
* Converts a post/pm to text (markdown) for sending in an email |
978
|
|
|
* |
979
|
|
|
* - censors everything it will send |
980
|
|
|
* - pre-converts select bbc tags to html, so they can be markdowned properly |
981
|
|
|
* - uses parse-bbc to convert remaining bbc to html |
982
|
|
|
* - uses html2markdown to convert html into Markdown text suitable for email |
983
|
|
|
* - if someone wants to write a direct bbc->markdown conversion tool, I'm listening! |
984
|
|
|
* |
985
|
|
|
* @param string $message |
986
|
|
|
* @param string $subject |
987
|
|
|
* @param string $signature |
988
|
|
|
* @package Maillist |
989
|
|
|
*/ |
990
|
|
|
function pbe_prepare_text(&$message, &$subject = '', &$signature = '') |
991
|
|
|
{ |
992
|
|
|
$mailPreparse = new PreparseMail(); |
993
|
|
|
$message = $mailPreparse->preparseHtml($message); |
994
|
|
|
$subject = $mailPreparse->preparseSubject($subject); |
995
|
|
|
$signature = $mailPreparse->preparseSignature($signature); |
996
|
|
|
|
997
|
|
|
// Convert this to text (markdown) |
998
|
|
|
$mark_down = new Html2Md($message); |
999
|
|
|
$message = $mark_down->get_markdown(); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
/** |
1003
|
|
|
* When a DSN (bounce) is received and the feature is enabled, update the settings |
1004
|
|
|
* For the user in question to disable Board and Post notifications. Do not clear |
1005
|
|
|
* Notification subscriptions. |
1006
|
|
|
* |
1007
|
|
|
* When finished, fire off a site notification informing the user of the action and reason |
1008
|
|
|
* |
1009
|
|
|
* @param EmailParse $email_message |
1010
|
|
|
* @package Maillist |
1011
|
|
|
*/ |
1012
|
|
|
function pbe_disable_user_notify($email_message) |
1013
|
|
|
{ |
1014
|
|
|
global $modSettings; |
1015
|
|
|
$db = database(); |
1016
|
|
|
|
1017
|
|
|
$email = $email_message->get_failed_dest(); |
1018
|
|
|
|
1019
|
|
|
$request = $db->query('', ' |
1020
|
|
|
SELECT |
1021
|
|
|
id_member |
1022
|
|
|
FROM {db_prefix}members |
1023
|
|
|
WHERE email_address = {string:email} |
1024
|
|
|
LIMIT 1', |
1025
|
|
|
[ |
1026
|
|
|
'email' => $email |
1027
|
|
|
] |
1028
|
|
|
); |
1029
|
|
|
|
1030
|
|
|
if ($request->num_rows() !== 0) |
1031
|
|
|
{ |
1032
|
|
|
[$id_member] = $request->fetch_row(); |
1033
|
|
|
$request->free_result(); |
1034
|
|
|
|
1035
|
|
|
// Once we have the member's ID, we can turn off board/topic notifications |
1036
|
|
|
// by setting notify_regularity->99 ("Never") |
1037
|
|
|
$db->query('', ' |
1038
|
|
|
UPDATE {db_prefix}members |
1039
|
|
|
SET |
1040
|
|
|
notify_regularity = 99 |
1041
|
|
|
WHERE id_member = {int:id_member}', |
1042
|
|
|
[ |
1043
|
|
|
'id_member' => $id_member |
1044
|
|
|
] |
1045
|
|
|
); |
1046
|
|
|
|
1047
|
|
|
// Now that other notifications have been added, we need to turn off email for those, too. |
1048
|
|
|
$db->query('', ' |
1049
|
|
|
DELETE FROM {db_prefix}notifications_pref |
1050
|
|
|
WHERE id_member = {int:id_member} |
1051
|
|
|
AND notification_type = {string:email}', |
1052
|
|
|
[ |
1053
|
|
|
'id_member' => $id_member, |
1054
|
|
|
'email' => 'email' |
1055
|
|
|
] |
1056
|
|
|
); |
1057
|
|
|
|
1058
|
|
|
// Add a "mention" of email notification being disabled |
1059
|
|
|
if (!empty($modSettings['mentions_enabled'])) |
1060
|
|
|
{ |
1061
|
|
|
$notifier = Notifications::instance(); |
1062
|
|
|
$notifier->add(new NotificationsTask( |
1063
|
|
|
'mailfail', |
1064
|
|
|
0, |
1065
|
|
|
$id_member, |
1066
|
|
|
['id_members' => [$id_member]] |
1067
|
|
|
)); |
1068
|
|
|
$notifier->send(); |
1069
|
|
|
} |
1070
|
|
|
} |
1071
|
|
|
} |
1072
|
|
|
|
1073
|
|
|
/** |
1074
|
|
|
* Replace full bbc quote tags with html blockquote version where the cite line |
1075
|
|
|
* is used as the first line of the quote. |
1076
|
|
|
* |
1077
|
|
|
* - Callback for pbe_prepare_text |
1078
|
|
|
* - Only changes the leading [quote], the closing /quote is not changed but |
1079
|
|
|
* handled back in the main function |
1080
|
|
|
* |
1081
|
|
|
* @param string[] $matches array of matches from the regex in the preg_replace |
1082
|
|
|
* |
1083
|
|
|
* @return string |
1084
|
|
|
*/ |
1085
|
|
|
function quote_callback($matches) |
1086
|
|
|
{ |
1087
|
|
|
global $txt; |
1088
|
|
|
|
1089
|
|
|
$date = ''; |
1090
|
|
|
$author = ''; |
1091
|
|
|
|
1092
|
|
|
if (preg_match('~date=(\d{8,10})~ui', $matches[0], $match) === 1) |
1093
|
|
|
{ |
1094
|
|
|
$date = $txt['email_on'] . ': ' . date('D M j, Y', $match[1]); |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
if (preg_match('~author=([^<>\n]+?)(?=(?:link=|date=|]))~ui', $matches[0], $match) === 1) |
1098
|
|
|
{ |
1099
|
|
|
$author = $match[1] . $txt['email_wrote'] . ': '; |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
return "\n" . '<blockquote>' . $date . ' ' . $author; |
1103
|
|
|
} |
1104
|
|
|
|
1105
|
|
|
/** |
1106
|
|
|
* Loads up the vital user information given an email address |
1107
|
|
|
* |
1108
|
|
|
* - Similar to \ElkArte\MembersList::load, loadPermissions, loadUserSettings, but only loads a |
1109
|
|
|
* subset of that data, enough to validate that a user can make a post to a given board. |
1110
|
|
|
* - Done this way to avoid over-writing user_info etc for those who are running |
1111
|
|
|
* this function (on behalf of the email owner, similar to profile views etc) |
1112
|
|
|
* |
1113
|
|
|
* Sets: |
1114
|
|
|
* - pbe['profile'] |
1115
|
|
|
* - pbe['profile']['options'] |
1116
|
|
|
* - pbe['user_info'] |
1117
|
|
|
* - pbe['user_info']['permissions'] |
1118
|
|
|
* - pbe['user_info']['groups'] |
1119
|
|
|
* |
1120
|
|
|
* @param string $email |
1121
|
|
|
* |
1122
|
|
|
* @return array|bool |
1123
|
|
|
* @package Maillist |
1124
|
|
|
* |
1125
|
|
|
*/ |
1126
|
|
|
function query_load_user_info($email) |
1127
|
|
|
{ |
1128
|
|
|
global $modSettings, $language; |
1129
|
|
|
|
1130
|
|
|
$db = database(); |
1131
|
|
|
|
1132
|
|
|
if (empty($email)) |
1133
|
|
|
{ |
1134
|
|
|
return false; |
1135
|
|
|
} |
1136
|
|
|
|
1137
|
|
|
// Find the user who owns this email address |
1138
|
|
|
$request = $db->query('', ' |
1139
|
|
|
SELECT |
1140
|
|
|
id_member |
1141
|
|
|
FROM {db_prefix}members |
1142
|
|
|
WHERE email_address = {string:email} |
1143
|
|
|
AND is_activated = {int:act} |
1144
|
|
|
LIMIT 1', |
1145
|
|
|
[ |
1146
|
|
|
'email' => $email, |
1147
|
|
|
'act' => 1, |
1148
|
|
|
] |
1149
|
|
|
); |
1150
|
|
|
[$id_member] = $request->fetch_row(); |
1151
|
|
|
$request->free_result(); |
1152
|
|
|
|
1153
|
|
|
// No user found ... back we go |
1154
|
|
|
if (empty($id_member)) |
1155
|
|
|
{ |
1156
|
|
|
return false; |
1157
|
|
|
} |
1158
|
|
|
|
1159
|
|
|
// Load the users profile information |
1160
|
|
|
$pbe = []; |
1161
|
|
|
if (MembersList::load($id_member, false, 'profile')) |
1162
|
|
|
{ |
1163
|
|
|
$pbe['profile'] = MembersList::get($id_member); |
1164
|
|
|
|
1165
|
|
|
// Load in *some* user_info data just like loadUserSettings would do |
1166
|
|
|
if (empty($pbe['profile']['additional_groups'])) |
1167
|
|
|
{ |
1168
|
|
|
$pbe['user_info']['groups'] = [ |
1169
|
|
|
$pbe['profile']['id_group'], $pbe['profile']['id_post_group']]; |
1170
|
|
|
} |
1171
|
|
|
else |
1172
|
|
|
{ |
1173
|
|
|
$pbe['user_info']['groups'] = array_merge( |
1174
|
|
|
[$pbe['profile']['id_group'], $pbe['profile']['id_post_group']], |
1175
|
|
|
explode(',', $pbe['profile']['additional_groups']) |
1176
|
|
|
); |
1177
|
|
|
} |
1178
|
|
|
|
1179
|
|
|
// Clean up the groups |
1180
|
|
|
$pbe['user_info']['groups'] = array_map(static function ($v) { |
1181
|
|
|
return (int) $v; |
1182
|
|
|
}, $pbe['user_info']['groups']); |
1183
|
|
|
|
1184
|
|
|
$pbe['user_info']['groups'] = array_unique($pbe['user_info']['groups']); |
1185
|
|
|
|
1186
|
|
|
// Load the user's general permissions.... |
1187
|
|
|
query_load_permissions('general', $pbe); |
1188
|
|
|
|
1189
|
|
|
// Set the moderation warning level |
1190
|
|
|
$pbe['user_info']['warning'] = $pbe['profile']['warning'] ?? 0; |
1191
|
|
|
|
1192
|
|
|
// Work out our query_see_board string for security |
1193
|
|
|
if (in_array(1, $pbe['user_info']['groups'], true)) |
1194
|
|
|
{ |
1195
|
|
|
$pbe['user_info']['query_see_board'] = '1=1'; |
1196
|
|
|
} |
1197
|
|
|
else |
1198
|
|
|
{ |
1199
|
|
|
$pbe['user_info']['query_see_board'] = '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $pbe['user_info']['groups']) . ', b.member_groups) != 0)' . (empty($modSettings['deny_boards_access']) ? '' : ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $pbe['user_info']['groups']) . ', b.deny_member_groups) = 0)') . ')'; |
1200
|
|
|
} |
1201
|
|
|
|
1202
|
|
|
// Set some convenience items |
1203
|
|
|
$pbe['user_info']['is_admin'] = in_array(1, $pbe['user_info']['groups'], true) ? 1 : 0; |
1204
|
|
|
$pbe['user_info']['id'] = $id_member; |
1205
|
|
|
$pbe['user_info']['username'] = $pbe['profile']['member_name'] ?? ''; |
1206
|
|
|
$pbe['user_info']['name'] = $pbe['profile']['real_name'] ?? ''; |
1207
|
|
|
$pbe['user_info']['email'] = $pbe['profile']['email_address'] ?? ''; |
1208
|
|
|
$pbe['user_info']['language'] = empty($pbe['profile']['lngfile']) || empty($modSettings['userLanguage']) ? $language : $pbe['profile']['lngfile']; |
1209
|
|
|
} |
1210
|
|
|
|
1211
|
|
|
return empty($pbe) ? false : $pbe; |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
/** |
1215
|
|
|
* Load the users permissions either general or board specific |
1216
|
|
|
* |
1217
|
|
|
* - Similar to the functions in loadPermissions() |
1218
|
|
|
* |
1219
|
|
|
* @param string $type board to load board permissions, otherwise general permissions |
1220
|
|
|
* @param array $pbe |
1221
|
|
|
* @param array $topic_info |
1222
|
|
|
* @package Maillist |
1223
|
|
|
*/ |
1224
|
|
|
function query_load_permissions($type, &$pbe, $topic_info = []) |
1225
|
|
|
{ |
1226
|
|
|
global $modSettings; |
1227
|
|
|
|
1228
|
|
|
$db = database(); |
1229
|
|
|
|
1230
|
|
|
$where_query = ($type === 'board' ? '({array_int:member_groups}) AND id_profile = {int:id_profile}' : '({array_int:member_groups})'); |
1231
|
|
|
|
1232
|
|
|
// Load up the users board or general site permissions. |
1233
|
|
|
$removals = []; |
1234
|
|
|
$pbe['user_info']['permissions'] = []; |
1235
|
|
|
$db->fetchQuery(' |
1236
|
|
|
SELECT |
1237
|
|
|
permission, add_deny |
1238
|
|
|
FROM {db_prefix}' . ($type === 'board' ? 'board_permissions' : 'permissions') . ' |
1239
|
|
|
WHERE id_group IN ' . $where_query, |
1240
|
|
|
[ |
1241
|
|
|
'member_groups' => $pbe['user_info']['groups'], |
1242
|
|
|
'id_profile' => ($type === 'board') ? $topic_info['id_profile'] : '', |
1243
|
|
|
] |
1244
|
|
|
)->fetch_callback( |
1245
|
|
|
static function ($row) use (&$removals, &$pbe) { |
1246
|
|
|
if (empty($row['add_deny'])) |
1247
|
|
|
{ |
1248
|
|
|
$removals[] = $row['permission']; |
1249
|
|
|
} |
1250
|
|
|
else |
1251
|
|
|
{ |
1252
|
|
|
$pbe['user_info']['permissions'][] = $row['permission']; |
1253
|
|
|
} |
1254
|
|
|
} |
1255
|
|
|
); |
1256
|
|
|
|
1257
|
|
|
// Remove all the permissions they shouldn't have ;) |
1258
|
|
|
if (!empty($modSettings['permission_enable_deny'])) |
1259
|
|
|
{ |
1260
|
|
|
$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], $removals); |
1261
|
|
|
} |
1262
|
|
|
} |
1263
|
|
|
|
1264
|
|
|
/** |
1265
|
|
|
* Reads all the keys that have been sent to a given email id |
1266
|
|
|
* |
1267
|
|
|
* - Returns all keys sent to a user in date order |
1268
|
|
|
* |
1269
|
|
|
* @param string $email email address to lookup |
1270
|
|
|
* |
1271
|
|
|
* @return array |
1272
|
|
|
* @package Maillist |
1273
|
|
|
* |
1274
|
|
|
*/ |
1275
|
|
|
function query_user_keys($email) |
1276
|
|
|
{ |
1277
|
|
|
$db = database(); |
1278
|
|
|
|
1279
|
|
|
// Find all keys sent to this email, sorted by date |
1280
|
|
|
return $db->fetchQuery(' |
1281
|
|
|
SELECT |
1282
|
|
|
message_key, message_type, message_id |
1283
|
|
|
FROM {db_prefix}postby_emails |
1284
|
|
|
WHERE email_to = {string:email} |
1285
|
|
|
ORDER BY time_sent DESC', |
1286
|
|
|
[ |
1287
|
|
|
'email' => $email, |
1288
|
|
|
] |
1289
|
|
|
)->fetch_all(); |
1290
|
|
|
} |
1291
|
|
|
|
1292
|
|
|
/** |
1293
|
|
|
* Return the email that a given key was sent to |
1294
|
|
|
* |
1295
|
|
|
* @param EmailParse $email_message |
1296
|
|
|
* @return string email address the key was sent to |
1297
|
|
|
* @package Maillist |
1298
|
|
|
*/ |
1299
|
|
|
function query_key_owner($email_message) |
1300
|
|
|
{ |
1301
|
|
|
$db = database(); |
1302
|
|
|
|
1303
|
|
|
if ($email_message->message_key === null && $email_message->message_type === null && $email_message->message_id === null) |
1304
|
|
|
{ |
1305
|
|
|
return false; |
|
|
|
|
1306
|
|
|
} |
1307
|
|
|
|
1308
|
|
|
// Check that this is a reply to an "actual" message by finding the key in the sent email table |
1309
|
|
|
$request = $db->query('', ' |
1310
|
|
|
SELECT |
1311
|
|
|
email_to |
1312
|
|
|
FROM {db_prefix}postby_emails |
1313
|
|
|
WHERE message_key = {string:key} |
1314
|
|
|
AND message_type = {string:type} |
1315
|
|
|
AND message_id = {string:message} |
1316
|
|
|
LIMIT 1', |
1317
|
|
|
[ |
1318
|
|
|
'key' => $email_message->message_key, |
1319
|
|
|
'type' => $email_message->message_type, |
1320
|
|
|
'message' => $email_message->message_id, |
1321
|
|
|
] |
1322
|
|
|
); |
1323
|
|
|
[$email_to] = $request->fetch_row(); |
1324
|
|
|
$request->free_result(); |
1325
|
|
|
|
1326
|
|
|
return $email_to; |
1327
|
|
|
} |
1328
|
|
|
|
1329
|
|
|
/** |
1330
|
|
|
* For a given type, t m or p, query the appropriate table for a given message id |
1331
|
|
|
* |
1332
|
|
|
* - If found returns the message subject |
1333
|
|
|
* |
1334
|
|
|
* @param int $message_id |
1335
|
|
|
* @param string $message_type |
1336
|
|
|
* @param string $email |
1337
|
|
|
* |
1338
|
|
|
* @return bool|string |
1339
|
|
|
* @package Maillist |
1340
|
|
|
* |
1341
|
|
|
*/ |
1342
|
|
|
function query_load_subject($message_id, $message_type, $email) |
1343
|
|
|
{ |
1344
|
|
|
$db = database(); |
1345
|
|
|
|
1346
|
|
|
$subject = ''; |
1347
|
|
|
|
1348
|
|
|
// Load up the core topic details, |
1349
|
|
|
if ($message_type === 't') |
1350
|
|
|
{ |
1351
|
|
|
$request = $db->query('', ' |
1352
|
|
|
SELECT |
1353
|
|
|
t.id_topic, m.subject |
1354
|
|
|
FROM {db_prefix}topics AS t |
1355
|
|
|
INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) |
1356
|
|
|
WHERE t.id_topic = {int:id_topic}', |
1357
|
|
|
[ |
1358
|
|
|
'id_topic' => $message_id |
1359
|
|
|
] |
1360
|
|
|
); |
1361
|
|
|
} |
1362
|
|
|
elseif ($message_type === 'm') |
1363
|
|
|
{ |
1364
|
|
|
$request = $db->query('', ' |
1365
|
|
|
SELECT |
1366
|
|
|
m.id_topic, m.subject |
1367
|
|
|
FROM {db_prefix}messages AS m |
1368
|
|
|
INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) |
1369
|
|
|
WHERE m.id_msg = {int:message_id}', |
1370
|
|
|
[ |
1371
|
|
|
'message_id' => $message_id |
1372
|
|
|
] |
1373
|
|
|
); |
1374
|
|
|
} |
1375
|
|
|
elseif ($message_type === 'p') |
1376
|
|
|
{ |
1377
|
|
|
// With PM's ... first get the member id based on the email |
1378
|
|
|
$request = $db->query('', ' |
1379
|
|
|
SELECT |
1380
|
|
|
id_member |
1381
|
|
|
FROM {db_prefix}members |
1382
|
|
|
WHERE email_address = {string:email} |
1383
|
|
|
AND is_activated = {int:act} |
1384
|
|
|
LIMIT 1', |
1385
|
|
|
[ |
1386
|
|
|
'email' => $email, |
1387
|
|
|
'act' => 1, |
1388
|
|
|
] |
1389
|
|
|
); |
1390
|
|
|
|
1391
|
|
|
// Found them, now we find the PM to them with this ID |
1392
|
|
|
if ($request->num_rows() !== 0) |
1393
|
|
|
{ |
1394
|
|
|
[$id_member] = $request->fetch_row(); |
1395
|
|
|
$request->free_result(); |
1396
|
|
|
|
1397
|
|
|
// Now find this PM ID and make sure it was sent to this member |
1398
|
|
|
$request = $db->query('', ' |
1399
|
|
|
SELECT |
1400
|
|
|
p.subject |
1401
|
|
|
FROM {db_prefix}pm_recipients AS pmr, {db_prefix}personal_messages AS p |
1402
|
|
|
WHERE pmr.id_pm = {int:id_pm} |
1403
|
|
|
AND pmr.id_member = {int:id_member} |
1404
|
|
|
AND p.id_pm = pmr.id_pm', |
1405
|
|
|
[ |
1406
|
|
|
'id_member' => $id_member, |
1407
|
|
|
'id_pm' => $message_id, |
1408
|
|
|
] |
1409
|
|
|
); |
1410
|
|
|
} |
1411
|
|
|
} |
1412
|
|
|
else |
1413
|
|
|
{ |
1414
|
|
|
return $subject; |
1415
|
|
|
} |
1416
|
|
|
|
1417
|
|
|
// If we found the message, topic or PM, return the subject |
1418
|
|
|
if ($request->num_rows() !== 0) |
1419
|
|
|
{ |
1420
|
|
|
[$subject] = $request->fetch_row(); |
1421
|
|
|
$subject = pbe_clean_email_subject($subject); |
1422
|
|
|
} |
1423
|
|
|
|
1424
|
|
|
$request->free_result(); |
1425
|
|
|
|
1426
|
|
|
return $subject; |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
/** |
1430
|
|
|
* Loads the important information for a given topic or pm ID |
1431
|
|
|
* |
1432
|
|
|
* - Returns array with the topic or PM details |
1433
|
|
|
* |
1434
|
|
|
* @param string $message_type |
1435
|
|
|
* @param int $message_id |
1436
|
|
|
* @param array $pbe |
1437
|
|
|
* |
1438
|
|
|
* @return array|bool |
1439
|
|
|
* @package Maillist |
1440
|
|
|
* |
1441
|
|
|
*/ |
1442
|
|
|
function query_load_message($message_type, $message_id, $pbe) |
1443
|
|
|
{ |
1444
|
|
|
$db = database(); |
1445
|
|
|
|
1446
|
|
|
// Load up the topic details |
1447
|
|
|
if ($message_type === 't') |
1448
|
|
|
{ |
1449
|
|
|
$request = $db->query('', ' |
1450
|
|
|
SELECT |
1451
|
|
|
t.id_topic, t.id_board, t.locked, t.id_member_started, t.id_last_msg, |
1452
|
|
|
m.subject, |
1453
|
|
|
b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.override_theme |
1454
|
|
|
FROM {db_prefix}topics AS t |
1455
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) |
1456
|
|
|
INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) |
1457
|
|
|
WHERE {raw:query_see_board} |
1458
|
|
|
AND t.id_topic = {int:message_id}', |
1459
|
|
|
[ |
1460
|
|
|
'message_id' => $message_id, |
1461
|
|
|
'query_see_board' => $pbe['user_info']['query_see_board'], |
1462
|
|
|
] |
1463
|
|
|
); |
1464
|
|
|
} |
1465
|
|
|
elseif ($message_type === 'm') |
1466
|
|
|
{ |
1467
|
|
|
$request = $db->query('', ' |
1468
|
|
|
SELECT |
1469
|
|
|
m.id_topic, m.id_board, m.subject, |
1470
|
|
|
t.locked, t.id_member_started, t.approved, t.id_last_msg, |
1471
|
|
|
b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.override_theme |
1472
|
|
|
FROM {db_prefix}messages AS m |
1473
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) |
1474
|
|
|
INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) |
1475
|
|
|
WHERE {raw:query_see_board} |
1476
|
|
|
AND m.id_msg = {int:message_id}', |
1477
|
|
|
[ |
1478
|
|
|
'message_id' => $message_id, |
1479
|
|
|
'query_see_board' => $pbe['user_info']['query_see_board'], |
1480
|
|
|
] |
1481
|
|
|
); |
1482
|
|
|
} |
1483
|
|
|
elseif ($message_type === 'p') |
1484
|
|
|
{ |
1485
|
|
|
// Load up the personal message... |
1486
|
|
|
$request = $db->query('', ' |
1487
|
|
|
SELECT |
1488
|
|
|
p.id_pm, p.subject, p.id_member_from, p.id_pm_head |
1489
|
|
|
FROM {db_prefix}pm_recipients AS pm, {db_prefix}personal_messages AS p, {db_prefix}members AS mem |
1490
|
|
|
WHERE pm.id_pm = {int:mess_id} |
1491
|
|
|
AND pm.id_member = {int:id_mem} |
1492
|
|
|
AND p.id_pm = pm.id_pm |
1493
|
|
|
AND mem.id_member = p.id_member_from', |
1494
|
|
|
[ |
1495
|
|
|
'id_mem' => $pbe['profile']['id_member'], |
1496
|
|
|
'mess_id' => $message_id |
1497
|
|
|
] |
1498
|
|
|
); |
1499
|
|
|
} |
1500
|
|
|
|
1501
|
|
|
$topic_info = []; |
1502
|
|
|
if (isset($request)) |
1503
|
|
|
{ |
1504
|
|
|
// Found the information, load the topic_info array with the data for this topic and board |
1505
|
|
|
if ($request->num_rows() !== 0) |
1506
|
|
|
{ |
1507
|
|
|
$topic_info = $request->fetch_assoc(); |
1508
|
|
|
} |
1509
|
|
|
|
1510
|
|
|
$request->free_result(); |
1511
|
|
|
} |
1512
|
|
|
|
1513
|
|
|
// Return the results or false |
1514
|
|
|
return empty($topic_info) ? false : $topic_info; |
1515
|
|
|
} |
1516
|
|
|
|
1517
|
|
|
/** |
1518
|
|
|
* Loads the board_id for where a given message resides |
1519
|
|
|
* |
1520
|
|
|
* @param int $message_id |
1521
|
|
|
* |
1522
|
|
|
* @return int |
1523
|
|
|
* @package Maillist |
1524
|
|
|
* |
1525
|
|
|
*/ |
1526
|
|
|
function query_load_board($message_id) |
1527
|
|
|
{ |
1528
|
|
|
$db = database(); |
1529
|
|
|
|
1530
|
|
|
$request = $db->query('', ' |
1531
|
|
|
SELECT |
1532
|
|
|
id_board |
1533
|
|
|
FROM {db_prefix}messages |
1534
|
|
|
WHERE id_msg = {int:message_id}', |
1535
|
|
|
[ |
1536
|
|
|
'message_id' => $message_id, |
1537
|
|
|
] |
1538
|
|
|
); |
1539
|
|
|
[$board_id] = $request->fetch_row(); |
1540
|
|
|
$request->free_result(); |
1541
|
|
|
|
1542
|
|
|
return empty($board_id) ? 0 : $board_id; |
1543
|
|
|
} |
1544
|
|
|
|
1545
|
|
|
/** |
1546
|
|
|
* Loads the basic board information for a given board id |
1547
|
|
|
* |
1548
|
|
|
* @param int $board_id |
1549
|
|
|
* @param array $pbe |
1550
|
|
|
* @return array |
1551
|
|
|
* @package Maillist |
1552
|
|
|
*/ |
1553
|
|
|
function query_load_board_details($board_id, $pbe) |
1554
|
|
|
{ |
1555
|
|
|
$db = database(); |
1556
|
|
|
|
1557
|
|
|
// To post a NEW Topic, we need certain board details |
1558
|
|
|
$request = $db->query('', ' |
1559
|
|
|
SELECT |
1560
|
|
|
b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.id_board |
1561
|
|
|
FROM {db_prefix}boards AS b |
1562
|
|
|
WHERE {raw:query_see_board} AND id_board = {int:id_board}', |
1563
|
|
|
[ |
1564
|
|
|
'id_board' => $board_id, |
1565
|
|
|
'query_see_board' => $pbe['user_info']['query_see_board'], |
1566
|
|
|
] |
1567
|
|
|
); |
1568
|
|
|
$board_info = $request->fetch_assoc(); |
1569
|
|
|
$request->free_result(); |
1570
|
|
|
|
1571
|
|
|
return $board_info; |
1572
|
|
|
} |
1573
|
|
|
|
1574
|
|
|
/** |
1575
|
|
|
* Loads the theme settings for the theme this user is using |
1576
|
|
|
* |
1577
|
|
|
* - Mainly used to determine a users notify settings |
1578
|
|
|
* |
1579
|
|
|
* @param int $id_member |
1580
|
|
|
* @param int $id_theme |
1581
|
|
|
* @param array $board_info |
1582
|
|
|
* |
1583
|
|
|
* @return array |
1584
|
|
|
* @package Maillist |
1585
|
|
|
* |
1586
|
|
|
*/ |
1587
|
|
|
function query_get_theme($id_member, $id_theme, $board_info) |
1588
|
|
|
{ |
1589
|
|
|
global $modSettings; |
1590
|
|
|
|
1591
|
|
|
$db = database(); |
1592
|
|
|
|
1593
|
|
|
$id_theme = (int) $id_theme; |
1594
|
|
|
|
1595
|
|
|
// Verify the id_theme... |
1596
|
|
|
// Allow the board specific theme, if they are overriding. |
1597
|
|
|
if (!empty($board_info['id_theme']) && $board_info['override_theme']) |
1598
|
|
|
{ |
1599
|
|
|
$id_theme = (int) $board_info['id_theme']; |
1600
|
|
|
} |
1601
|
|
|
elseif (!empty($modSettings['knownThemes'])) |
1602
|
|
|
{ |
1603
|
|
|
$themes = array_map('intval', explode(',', $modSettings['knownThemes'])); |
1604
|
|
|
|
1605
|
|
|
$id_theme = in_array($id_theme, $themes, true) ? $id_theme : (int) $modSettings['theme_guests']; |
1606
|
|
|
} |
1607
|
|
|
|
1608
|
|
|
// With the theme and member, load the auto_notify variables |
1609
|
|
|
$theme_settings = []; |
1610
|
|
|
$db->fetchQuery(' |
1611
|
|
|
SELECT |
1612
|
|
|
variable, value |
1613
|
|
|
FROM {db_prefix}themes |
1614
|
|
|
WHERE id_member = {int:id_member} |
1615
|
|
|
AND id_theme = {int:id_theme}', |
1616
|
|
|
[ |
1617
|
|
|
'id_theme' => $id_theme, |
1618
|
|
|
'id_member' => $id_member, |
1619
|
|
|
] |
1620
|
|
|
)->fetch_callback( |
1621
|
|
|
static function ($row) use (&$theme_settings) { |
1622
|
|
|
// Put everything about this member/theme into a theme setting array |
1623
|
|
|
$theme_settings[$row['variable']] = $row['value']; |
1624
|
|
|
} |
1625
|
|
|
); |
1626
|
|
|
|
1627
|
|
|
return $theme_settings; |
1628
|
|
|
} |
1629
|
|
|
|
1630
|
|
|
/** |
1631
|
|
|
* Turn notifications on or off if the user has set auto notify 'when I reply' |
1632
|
|
|
* |
1633
|
|
|
* @param int $id_member |
1634
|
|
|
* @param int $id_board |
1635
|
|
|
* @param int $id_topic |
1636
|
|
|
* @param bool $auto_notify |
1637
|
|
|
* @param array $permissions |
1638
|
|
|
* @package Maillist |
1639
|
|
|
*/ |
1640
|
|
|
function query_notifications($id_member, $id_board, $id_topic, $auto_notify, $permissions) |
1641
|
|
|
{ |
1642
|
|
|
$db = database(); |
1643
|
|
|
|
1644
|
|
|
// First see if they have a board notification on for this board, |
1645
|
|
|
// so we don't set both board and individual topic notifications |
1646
|
|
|
$board_notify = false; |
1647
|
|
|
$request = $db->query('', ' |
1648
|
|
|
SELECT |
1649
|
|
|
id_member |
1650
|
|
|
FROM {db_prefix}log_notify |
1651
|
|
|
WHERE id_board = {int:board_list} |
1652
|
|
|
AND id_member = {int:current_member}', |
1653
|
|
|
[ |
1654
|
|
|
'current_member' => $id_member, |
1655
|
|
|
'board_list' => $id_board, |
1656
|
|
|
] |
1657
|
|
|
); |
1658
|
|
|
if ($request->fetch_row()) |
1659
|
|
|
{ |
1660
|
|
|
$board_notify = true; |
1661
|
|
|
} |
1662
|
|
|
|
1663
|
|
|
$request->free_result(); |
1664
|
|
|
|
1665
|
|
|
// If they have topic notification on and not board notification then |
1666
|
|
|
// add this post to the notification log |
1667
|
|
|
if (!empty($auto_notify) && (in_array('mark_any_notify', $permissions)) && !$board_notify) |
1668
|
|
|
{ |
1669
|
|
|
$db->insert('ignore', |
1670
|
|
|
'{db_prefix}log_notify', |
1671
|
|
|
['id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int'], |
1672
|
|
|
[$id_member, $id_topic, 0], |
1673
|
|
|
['id_member', 'id_topic', 'id_board'] |
1674
|
|
|
); |
1675
|
|
|
} |
1676
|
|
|
else |
1677
|
|
|
{ |
1678
|
|
|
// Make sure they don't get notified |
1679
|
|
|
$db->query('', ' |
1680
|
|
|
DELETE FROM {db_prefix}log_notify |
1681
|
|
|
WHERE id_member = {int:current_member} |
1682
|
|
|
AND id_topic = {int:current_topic}', |
1683
|
|
|
[ |
1684
|
|
|
'current_member' => $id_member, |
1685
|
|
|
'current_topic' => $id_topic, |
1686
|
|
|
] |
1687
|
|
|
); |
1688
|
|
|
} |
1689
|
|
|
} |
1690
|
|
|
|
1691
|
|
|
/** |
1692
|
|
|
* Called when a pm reply has been made |
1693
|
|
|
* |
1694
|
|
|
* - Marks the PM replied to as read |
1695
|
|
|
* - Marks the PM replied to as replied to |
1696
|
|
|
* - Updates the number of unread to reflect this |
1697
|
|
|
* |
1698
|
|
|
* @param EmailParse $email_message |
1699
|
|
|
* @param array $pbe |
1700
|
|
|
* @package Maillist |
1701
|
|
|
*/ |
1702
|
|
|
function query_mark_pms($email_message, $pbe) |
1703
|
|
|
{ |
1704
|
|
|
$db = database(); |
1705
|
|
|
|
1706
|
|
|
$request = $db->query('', ' |
1707
|
|
|
UPDATE {db_prefix}pm_recipients |
1708
|
|
|
SET |
1709
|
|
|
is_read = is_read | 1 |
1710
|
|
|
WHERE id_member = {int:id_member} |
1711
|
|
|
AND NOT ((is_read & 1) >= 1) |
1712
|
|
|
AND id_pm = {int:personal_messages}', |
1713
|
|
|
[ |
1714
|
|
|
'personal_messages' => $email_message->message_id, |
1715
|
|
|
'id_member' => $pbe['profile']['id_member'], |
1716
|
|
|
] |
1717
|
|
|
); |
1718
|
|
|
|
1719
|
|
|
// If something was marked as read, get the number of unread messages remaining. |
1720
|
|
|
if ($request->affected_rows() > 0) |
1721
|
|
|
{ |
1722
|
|
|
$total_unread = 0; |
1723
|
|
|
$db->fetchQuery(' |
1724
|
|
|
SELECT |
1725
|
|
|
labels, COUNT(*) AS num |
1726
|
|
|
FROM {db_prefix}pm_recipients |
1727
|
|
|
WHERE id_member = {int:id_member} |
1728
|
|
|
AND NOT ((is_read & 1) >= 1) |
1729
|
|
|
AND deleted = {int:is_not_deleted} |
1730
|
|
|
GROUP BY labels', |
1731
|
|
|
[ |
1732
|
|
|
'id_member' => $pbe['profile']['id_member'], |
1733
|
|
|
'is_not_deleted' => 0, |
1734
|
|
|
] |
1735
|
|
|
)->fetch_callback( |
1736
|
|
|
static function ($row) use (&$total_unread) { |
1737
|
|
|
$total_unread += $row['num']; |
1738
|
|
|
} |
1739
|
|
|
); |
1740
|
|
|
|
1741
|
|
|
// Update things for when they do come to the site |
1742
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1743
|
|
|
updateMemberData($pbe['profile']['id_member'], ['unread_messages' => $total_unread]); |
1744
|
|
|
} |
1745
|
|
|
|
1746
|
|
|
// Now mark the message as "replied to" since they just did |
1747
|
|
|
$db->query('', ' |
1748
|
|
|
UPDATE {db_prefix}pm_recipients |
1749
|
|
|
SET |
1750
|
|
|
is_read = is_read | 2 |
1751
|
|
|
WHERE id_pm = {int:replied_to} |
1752
|
|
|
AND id_member = {int:current_member}', |
1753
|
|
|
[ |
1754
|
|
|
'current_member' => $pbe['profile']['id_member'], |
1755
|
|
|
'replied_to' => $email_message->message_id, |
1756
|
|
|
] |
1757
|
|
|
); |
1758
|
|
|
} |
1759
|
|
|
|
1760
|
|
|
/** |
1761
|
|
|
* Once a key has been used it is removed and can not be used again |
1762
|
|
|
* |
1763
|
|
|
* - Also removes any old keys to minimize security issues |
1764
|
|
|
* |
1765
|
|
|
* @param EmailParse $email_message |
1766
|
|
|
* @package Maillist |
1767
|
|
|
*/ |
1768
|
|
|
function query_key_maintenance($email_message) |
1769
|
|
|
{ |
1770
|
|
|
global $modSettings; |
1771
|
|
|
|
1772
|
|
|
$db = database(); |
1773
|
|
|
|
1774
|
|
|
// Old keys simply expire |
1775
|
|
|
$days = (empty($modSettings['maillist_key_active'])) ? 21 : $modSettings['maillist_key_active']; |
1776
|
|
|
$delete_old = time() - ($days * 24 * 60 * 60); |
1777
|
|
|
|
1778
|
|
|
// Consume the database key that was just used .. one reply per key, |
1779
|
|
|
// but we let PM's slide, they often seem to be re re re replied to |
1780
|
|
|
if ($email_message->message_type !== 'p') |
1781
|
|
|
{ |
1782
|
|
|
$db->query('', ' |
1783
|
|
|
DELETE FROM {db_prefix}postby_emails |
1784
|
|
|
WHERE message_key = {string:key} |
1785
|
|
|
AND message_type = {string:type} |
1786
|
|
|
AND message_id = {string:message_id}', |
1787
|
|
|
[ |
1788
|
|
|
'key' => $email_message->message_key, |
1789
|
|
|
'type' => $email_message->message_type, |
1790
|
|
|
'message_id' => $email_message->message_id, |
1791
|
|
|
] |
1792
|
|
|
); |
1793
|
|
|
} |
1794
|
|
|
|
1795
|
|
|
// Since we are here lets delete any items older than delete_old days, |
1796
|
|
|
// if they have not responded in that time tuff |
1797
|
|
|
$db->query('', ' |
1798
|
|
|
DELETE FROM {db_prefix}postby_emails |
1799
|
|
|
WHERE time_sent < {int:delete_old}', |
1800
|
|
|
[ |
1801
|
|
|
'delete_old' => $delete_old |
1802
|
|
|
] |
1803
|
|
|
); |
1804
|
|
|
} |
1805
|
|
|
|
1806
|
|
|
/** |
1807
|
|
|
* After an email post has been made, this updates the user information just like |
1808
|
|
|
* they are on the site to perform the given action. |
1809
|
|
|
* |
1810
|
|
|
* - Updates time on line |
1811
|
|
|
* - Updates last active |
1812
|
|
|
* - Updates the who's online list with the member and action |
1813
|
|
|
* |
1814
|
|
|
* @param array $pbe |
1815
|
|
|
* @param EmailParse $email_message |
1816
|
|
|
* @param array $topic_info |
1817
|
|
|
* @package Maillist |
1818
|
|
|
*/ |
1819
|
|
|
function query_update_member_stats($pbe, $email_message, $topic_info = []) |
1820
|
|
|
{ |
1821
|
|
|
$db = database(); |
1822
|
|
|
|
1823
|
|
|
$last_login = time(); |
1824
|
|
|
$do_delete = false; |
1825
|
|
|
$total_time_logged_in = empty($pbe['profile']['total_time_logged_in']) ? 0 : $pbe['profile']['total_time_logged_in']; |
1826
|
|
|
|
1827
|
|
|
// If they were active in the last 15 min, we don't want to run up their time |
1828
|
|
|
if (!empty($pbe['profile']['last_login']) && $pbe['profile']['last_login'] < (time() - (60 * 15))) |
1829
|
|
|
{ |
1830
|
|
|
// not recently active so add some time to their login .... |
1831
|
|
|
$do_delete = true; |
1832
|
|
|
$total_time_logged_in += 60 * 10; |
1833
|
|
|
} |
1834
|
|
|
|
1835
|
|
|
// Update the members total time logged in data |
1836
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1837
|
|
|
updateMemberData($pbe['profile']['id_member'], ['total_time_logged_in' => $total_time_logged_in, 'last_login' => $last_login]); |
1838
|
|
|
|
1839
|
|
|
// Show they are active in the who's online list and what they have done |
1840
|
|
|
if ($email_message->message_type === 'm' || $email_message->message_type === 't') |
1841
|
|
|
{ |
1842
|
|
|
$get_temp = [ |
1843
|
|
|
'action' => 'postbyemail', |
1844
|
|
|
'topic' => $topic_info['id_topic'], |
1845
|
|
|
'last_msg' => $topic_info['id_last_msg'], |
1846
|
|
|
'board' => $topic_info['id_board'] |
1847
|
|
|
]; |
1848
|
|
|
} |
1849
|
|
|
elseif ($email_message->message_type === 'x') |
1850
|
|
|
{ |
1851
|
|
|
$get_temp = [ |
1852
|
|
|
'action' => 'topicbyemail', |
1853
|
|
|
'topic' => $topic_info['id'], |
1854
|
|
|
'board' => $topic_info['board'], |
1855
|
|
|
]; |
1856
|
|
|
} |
1857
|
|
|
else |
1858
|
|
|
{ |
1859
|
|
|
$get_temp = [ |
1860
|
|
|
'action' => 'pm', |
1861
|
|
|
'sa' => 'byemail' |
1862
|
|
|
]; |
1863
|
|
|
} |
1864
|
|
|
|
1865
|
|
|
// Place the entry in to the online log so the who's online can use it |
1866
|
|
|
$serialized = serialize($get_temp); |
1867
|
|
|
$session_id = 'ip' . $pbe['profile']['member_ip']; |
1868
|
|
|
$member_ip = empty($pbe['profile']['member_ip']) ? 0 : $pbe['profile']['member_ip']; |
1869
|
|
|
$db->insert($do_delete ? 'ignore' : 'replace', |
1870
|
|
|
'{db_prefix}log_online', |
1871
|
|
|
['session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'string', 'url' => 'string'], |
1872
|
|
|
[$session_id, $pbe['profile']['id_member'], 0, $last_login, $member_ip, $serialized], |
1873
|
|
|
['session'] |
1874
|
|
|
); |
1875
|
|
|
} |
1876
|
|
|
|
1877
|
|
|
/** |
1878
|
|
|
* Calls the necessary functions to extract and format the message for posting |
1879
|
|
|
* |
1880
|
|
|
* What it does: |
1881
|
|
|
* |
1882
|
|
|
* - Converts an email response (text or html) to a BBC equivalent via pbe_Email_to_bbc |
1883
|
|
|
* - Formats the email response such that it looks structured and not chopped up (via pbe_fix_email_body) |
1884
|
|
|
* |
1885
|
|
|
* @param EmailParse $email_message |
1886
|
|
|
* @param array $pbe |
1887
|
|
|
* |
1888
|
|
|
* @return string |
1889
|
|
|
* @package Maillist |
1890
|
|
|
*/ |
1891
|
|
|
function pbe_load_text($email_message, $pbe) |
1892
|
|
|
{ |
1893
|
|
|
$html = $email_message->html_found; |
1894
|
|
|
|
1895
|
|
|
$text = $html ? pbe_load_html($email_message, $html) : $email_message->getPlainBody(); |
1896
|
|
|
|
1897
|
|
|
// Convert to BBC and format it, so it looks like a post |
1898
|
|
|
$text = pbe_email_to_bbc($text, $html); |
1899
|
|
|
|
1900
|
|
|
$pbe_real_name = $pbe['profile']['real_name'] ?? ''; |
1901
|
|
|
$text = pbe_fix_email_body($text, $pbe_real_name, (empty($email_message->_converted_utf8) ? $email_message->headers['x-parameters']['content-type']['charset'] : 'UTF-8')); |
1902
|
|
|
|
1903
|
|
|
// Do we even have a message left to post? |
1904
|
|
|
$text = Util::htmltrim($text); |
1905
|
|
|
if (empty($text)) |
1906
|
|
|
{ |
1907
|
|
|
return ''; |
1908
|
|
|
} |
1909
|
|
|
|
1910
|
|
|
// PM's are handled by sendpm |
1911
|
|
|
if ($email_message->message_type !== 'p') |
1912
|
|
|
{ |
1913
|
|
|
// Prepare it for the database |
1914
|
|
|
$text = Util::htmlspecialchars($text, ENT_QUOTES, 'UTF-8', true); |
1915
|
|
|
require_once(SUBSDIR . '/Post.subs.php'); |
1916
|
|
|
preparsecode($text); |
1917
|
|
|
} |
1918
|
|
|
|
1919
|
|
|
return $text; |
1920
|
|
|
} |
1921
|
|
|
|
1922
|
|
|
/** |
1923
|
|
|
* Checks and removes role=presentation tables. If to many tables remain, returns the plain text |
1924
|
|
|
* version of the email as converting too many html tables to bbc simply will not look good |
1925
|
|
|
* |
1926
|
|
|
* @param EmailParse $email_message |
1927
|
|
|
* @param bool $html |
1928
|
|
|
* @return string |
1929
|
|
|
*/ |
1930
|
|
|
function pbe_load_html($email_message, &$html) |
1931
|
|
|
{ |
1932
|
|
|
// un_htmlspecialchars areas outside code blocks |
1933
|
|
|
$preparse = PreparseCode::instance(''); |
1934
|
|
|
$text = $preparse->tokenizeCodeBlocks($email_message->body, true); |
1935
|
|
|
$text = un_htmlspecialchars($text); |
1936
|
|
|
$text = $preparse->restoreCodeBlocks($text); |
1937
|
|
|
|
1938
|
|
|
// If we are dealing with tables .... |
1939
|
|
|
if (preg_match('~<table.*?>~i', $text)) |
1940
|
|
|
{ |
1941
|
|
|
// Try and strip out purely presentational ones, for example how we send html emails |
1942
|
|
|
$text = preg_replace_callback('~(<table[^>].*?role="presentation".*?>.*?</table>)~s', |
1943
|
|
|
static function ($matches) { |
1944
|
|
|
$result = preg_replace('~<table[^>].*?role="presentation".*?>~', '', $matches[0]); |
1945
|
|
|
$result = str_replace('</table>', '', $result); |
1946
|
|
|
return preg_replace('~<tr.*?>|</tr>|<td.*?>|</td>|<tbody.*?>|</tbody>~', '', $result); |
1947
|
|
|
}, |
1948
|
|
|
$text); |
1949
|
|
|
|
1950
|
|
|
// Another check is in order, still to many tables? |
1951
|
|
|
if (preg_match_all('~<table.*?>~i', $text) > 2) |
1952
|
|
|
{ |
1953
|
|
|
$text = $email_message->getPlainBody(); |
1954
|
|
|
$html = false; |
1955
|
|
|
} |
1956
|
|
|
} |
1957
|
|
|
|
1958
|
|
|
return $text; |
1959
|
|
|
} |
1960
|
|
|
|
1961
|
|
|
/** |
1962
|
|
|
* Attempts to create a reply post on the forum |
1963
|
|
|
* |
1964
|
|
|
* What it does: |
1965
|
|
|
* |
1966
|
|
|
* - Checks if the user has permissions to post/reply/postby email |
1967
|
|
|
* - Calls pbe_load_text to prepare text for the post |
1968
|
|
|
* - returns true if successful or false for any number of failures |
1969
|
|
|
* |
1970
|
|
|
* @param array $pbe array of all pbe user_info values |
1971
|
|
|
* @param EmailParse $email_message |
1972
|
|
|
* @param array $topic_info |
1973
|
|
|
* |
1974
|
|
|
* @return bool |
1975
|
|
|
* @package Maillist |
1976
|
|
|
* |
1977
|
|
|
*/ |
1978
|
|
|
function pbe_create_post($pbe, $email_message, $topic_info) |
1979
|
|
|
{ |
1980
|
|
|
global $modSettings, $txt; |
1981
|
|
|
|
1982
|
|
|
// Validate they have permission to reply |
1983
|
|
|
$becomesApproved = true; |
1984
|
|
|
if (!$pbe['user_info']['is_admin'] && !in_array('postby_email', $pbe['user_info']['permissions'], true)) |
1985
|
|
|
{ |
1986
|
|
|
return pbe_emailError('error_permission', $email_message); |
1987
|
|
|
} |
1988
|
|
|
|
1989
|
|
|
if ($topic_info['locked'] && !$pbe['user_info']['is_admin'] && !in_array('moderate_forum', $pbe['user_info']['permissions'], true)) |
1990
|
|
|
{ |
1991
|
|
|
return pbe_emailError('error_locked', $email_message); |
1992
|
|
|
} |
1993
|
|
|
|
1994
|
|
|
if ($topic_info['id_member_started'] === $pbe['profile']['id_member'] && !$pbe['user_info']['is_admin']) |
1995
|
|
|
{ |
1996
|
|
|
if ($modSettings['postmod_active'] && in_array('post_unapproved_replies_any', $pbe['user_info']['permissions'], true) && (!in_array('post_reply_any', $pbe['user_info']['permissions']))) |
1997
|
|
|
{ |
1998
|
|
|
$becomesApproved = false; |
1999
|
|
|
} |
2000
|
|
|
elseif (!in_array('post_reply_own', $pbe['user_info']['permissions'], true)) |
2001
|
|
|
{ |
2002
|
|
|
return pbe_emailError('error_cant_reply', $email_message); |
2003
|
|
|
} |
2004
|
|
|
} |
2005
|
|
|
elseif (!$pbe['user_info']['is_admin']) |
2006
|
|
|
{ |
2007
|
|
|
if ($modSettings['postmod_active'] && in_array('post_unapproved_replies_any', $pbe['user_info']['permissions'], true) && (!in_array('post_reply_any', $pbe['user_info']['permissions']))) |
2008
|
|
|
{ |
2009
|
|
|
$becomesApproved = false; |
2010
|
|
|
} |
2011
|
|
|
elseif (!in_array('post_reply_any', $pbe['user_info']['permissions'], true)) |
2012
|
|
|
{ |
2013
|
|
|
return pbe_emailError('error_cant_reply', $email_message); |
2014
|
|
|
} |
2015
|
|
|
} |
2016
|
|
|
|
2017
|
|
|
// Convert to BBC and Format the message |
2018
|
|
|
$text = pbe_load_text($email_message, $pbe); |
2019
|
|
|
if (empty($text)) |
2020
|
|
|
{ |
2021
|
|
|
return pbe_emailError('error_no_message', $email_message); |
2022
|
|
|
} |
2023
|
|
|
|
2024
|
|
|
// Seriously? Attachments? |
2025
|
|
|
if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1) |
2026
|
|
|
{ |
2027
|
|
|
if (($modSettings['postmod_active'] && in_array('post_unapproved_attachments', $pbe['user_info']['permissions'])) || in_array('post_attachment', $pbe['user_info']['permissions'])) |
2028
|
|
|
{ |
2029
|
|
|
$attachIDs = pbe_email_attachments($pbe, $email_message); |
2030
|
|
|
} |
2031
|
|
|
else |
2032
|
|
|
{ |
2033
|
|
|
$text .= "\n\n" . $txt['error_no_attach'] . "\n"; |
2034
|
|
|
} |
2035
|
|
|
} |
2036
|
|
|
|
2037
|
|
|
// Setup the post variables. |
2038
|
|
|
$msgOptions = [ |
2039
|
|
|
'id' => 0, |
2040
|
|
|
'subject' => strpos($topic_info['subject'], trim($pbe['response_prefix'])) === 0 ? $topic_info['subject'] : $pbe['response_prefix'] . $topic_info['subject'], |
2041
|
|
|
'smileys_enabled' => true, |
2042
|
|
|
'body' => $text, |
2043
|
|
|
'attachments' => empty($attachIDs) ? [] : $attachIDs, |
2044
|
|
|
'approved' => $becomesApproved |
2045
|
|
|
]; |
2046
|
|
|
|
2047
|
|
|
$topicOptions = [ |
2048
|
|
|
'id' => $topic_info['id_topic'], |
2049
|
|
|
'board' => $topic_info['id_board'], |
2050
|
|
|
'mark_as_read' => true, |
2051
|
|
|
'is_approved' => !$modSettings['postmod_active'] || empty($topic_info['id_topic']) || !empty($topic_info['approved']) |
2052
|
|
|
]; |
2053
|
|
|
|
2054
|
|
|
$posterOptions = [ |
2055
|
|
|
'id' => $pbe['profile']['id_member'], |
2056
|
|
|
'name' => $pbe['profile']['real_name'], |
2057
|
|
|
'email' => $pbe['profile']['email_address'], |
2058
|
|
|
'update_post_count' => empty($topic_info['count_posts']), |
2059
|
|
|
'ip' => $email_message->load_ip() ? $email_message->ip : $pbe['profile']['member_ip'] |
2060
|
|
|
]; |
2061
|
|
|
|
2062
|
|
|
// Make the post. |
2063
|
|
|
createPost($msgOptions, $topicOptions, $posterOptions); |
2064
|
|
|
|
2065
|
|
|
// Bind any attachments that may be included to this new message |
2066
|
|
|
if (!empty($attachIDs) && !empty($msgOptions['id'])) |
2067
|
|
|
{ |
2068
|
|
|
bindMessageAttachments($msgOptions['id'], $attachIDs); |
2069
|
|
|
} |
2070
|
|
|
|
2071
|
|
|
// We need the auto_notify setting, it may be theme based so pass the theme in use |
2072
|
|
|
$theme_settings = query_get_theme($pbe['profile']['id_member'], $pbe['profile']['id_theme'], $topic_info); |
2073
|
|
|
$auto_notify = $theme_settings['auto_notify'] ?? 0; |
2074
|
|
|
|
2075
|
|
|
// Turn notifications on or off |
2076
|
|
|
query_notifications($pbe['profile']['id_member'], $topic_info['id_board'], $topic_info['id_topic'], $auto_notify, $pbe['user_info']['permissions']); |
|
|
|
|
2077
|
|
|
|
2078
|
|
|
// Notify members who have notification turned on for this, |
2079
|
|
|
// but only if it's going to be approved |
2080
|
|
|
if ($becomesApproved) |
2081
|
|
|
{ |
2082
|
|
|
require_once(SUBSDIR . '/Notification.subs.php'); |
2083
|
|
|
sendNotifications($topic_info['id_topic'], 'reply', [], [], $pbe); |
2084
|
|
|
} |
2085
|
|
|
|
2086
|
|
|
return true; |
2087
|
|
|
} |
2088
|
|
|
|
2089
|
|
|
/** |
2090
|
|
|
* Attempts to create a PM (reply) on the forum |
2091
|
|
|
* |
2092
|
|
|
* What it does |
2093
|
|
|
* - Checks if the user has permissions |
2094
|
|
|
* - Calls pbe_load_text to prepare text for the pm |
2095
|
|
|
* - Calls query_mark_pms to mark things as read |
2096
|
|
|
* - Returns true if successful or false for any number of failures |
2097
|
|
|
* |
2098
|
|
|
* @param array $pbe array of pbe 'user_info' values |
2099
|
|
|
* @param EmailParse $email_message |
2100
|
|
|
* @param array $pm_info |
2101
|
|
|
* |
2102
|
|
|
* @return bool |
2103
|
|
|
* @package Maillist |
2104
|
|
|
* |
2105
|
|
|
* @uses sendpm to do the actual "sending" |
2106
|
|
|
*/ |
2107
|
|
|
function pbe_create_pm($pbe, $email_message, $pm_info) |
2108
|
|
|
{ |
2109
|
|
|
global $modSettings, $txt; |
2110
|
|
|
|
2111
|
|
|
// Can they send? |
2112
|
|
|
if (!$pbe['user_info']['is_admin'] && !in_array('pm_send', $pbe['user_info']['permissions'], true)) |
2113
|
|
|
{ |
2114
|
|
|
return pbe_emailError('error_pm_not_allowed', $email_message); |
2115
|
|
|
} |
2116
|
|
|
|
2117
|
|
|
// Convert the PM to BBC and Format the message |
2118
|
|
|
$text = pbe_load_text($email_message, $pbe); |
2119
|
|
|
if (empty($text)) |
2120
|
|
|
{ |
2121
|
|
|
return pbe_emailError('error_no_message', $email_message); |
2122
|
|
|
} |
2123
|
|
|
|
2124
|
|
|
// If they tried to attach a file, just say sorry |
2125
|
|
|
if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1) |
2126
|
|
|
{ |
2127
|
|
|
$text .= "\n\n" . $txt['error_no_pm_attach'] . "\n"; |
2128
|
|
|
} |
2129
|
|
|
|
2130
|
|
|
// For sending the message... |
2131
|
|
|
$from = [ |
2132
|
|
|
'id' => $pbe['profile']['id_member'], |
2133
|
|
|
'name' => $pbe['profile']['real_name'], |
2134
|
|
|
'username' => $pbe['profile']['member_name'] |
2135
|
|
|
]; |
2136
|
|
|
|
2137
|
|
|
$pm_info['subject'] = strpos($pm_info['subject'], trim($pbe['response_prefix'])) === 0 ? $pm_info['subject'] : $pbe['response_prefix'] . $pm_info['subject']; |
2138
|
|
|
|
2139
|
|
|
// send/save the actual PM. |
2140
|
|
|
require_once(SUBSDIR . '/PersonalMessage.subs.php'); |
2141
|
|
|
$pm_result = sendpm(['to' => [$pm_info['id_member_from']], 'bcc' => []], $pm_info['subject'], $text, true, $from, $pm_info['id_pm_head']); |
2142
|
|
|
|
2143
|
|
|
// Assuming all went well, mark this as read, replied to and update the unread counter |
2144
|
|
|
if (!empty($pm_result)) |
2145
|
|
|
{ |
2146
|
|
|
query_mark_pms($email_message, $pbe); |
2147
|
|
|
} |
2148
|
|
|
|
2149
|
|
|
return !empty($pm_result); |
2150
|
|
|
} |
2151
|
|
|
|
2152
|
|
|
/** |
2153
|
|
|
* Create a new topic by email |
2154
|
|
|
* |
2155
|
|
|
* What it does: |
2156
|
|
|
* |
2157
|
|
|
* - Called by pbe_topic to create a new topic or by pbe_main to create a new topic via a subject change |
2158
|
|
|
* - checks posting permissions, but requires all email validation checks are complete |
2159
|
|
|
* - Calls pbe_load_text to prepare text for the post |
2160
|
|
|
* - Calls sendNotifications to announce the new post |
2161
|
|
|
* - Calls query_update_member_stats to show they did something |
2162
|
|
|
* - Requires the pbe, email_message and board_info arrays to be populated. |
2163
|
|
|
* |
2164
|
|
|
* @param array $pbe array of pbe 'user_info' values |
2165
|
|
|
* @param EmailParse $email_message |
2166
|
|
|
* @param array $board_info |
2167
|
|
|
* |
2168
|
|
|
* @return bool |
2169
|
|
|
* @package Maillist |
2170
|
|
|
* |
2171
|
|
|
* @uses createPost to do the actual "posting" |
2172
|
|
|
*/ |
2173
|
|
|
function pbe_create_topic($pbe, $email_message, $board_info) |
2174
|
|
|
{ |
2175
|
|
|
global $txt, $modSettings; |
2176
|
|
|
|
2177
|
|
|
// It does not work like that |
2178
|
|
|
if (empty($pbe) || empty($email_message)) |
2179
|
|
|
{ |
2180
|
|
|
return false; |
2181
|
|
|
} |
2182
|
|
|
|
2183
|
|
|
// We have the board info, and their permissions - do they have a right to start a new topic? |
2184
|
|
|
$becomesApproved = true; |
2185
|
|
|
if (!$pbe['user_info']['is_admin']) |
2186
|
|
|
{ |
2187
|
|
|
if (!in_array('postby_email', $pbe['user_info']['permissions'], true)) |
2188
|
|
|
{ |
2189
|
|
|
return pbe_emailError('error_permission', $email_message); |
2190
|
|
|
} |
2191
|
|
|
|
2192
|
|
|
if ($modSettings['postmod_active'] && in_array('post_unapproved_topics', $pbe['user_info']['permissions']) && (!in_array('post_new', $pbe['user_info']['permissions']))) |
2193
|
|
|
{ |
2194
|
|
|
$becomesApproved = false; |
2195
|
|
|
} |
2196
|
|
|
elseif (!in_array('post_new', $pbe['user_info']['permissions'], true)) |
2197
|
|
|
{ |
2198
|
|
|
return pbe_emailError('error_cant_start', $email_message); |
2199
|
|
|
} |
2200
|
|
|
} |
2201
|
|
|
|
2202
|
|
|
// Approving all new topics by email anyway, smart admin this one is ;) |
2203
|
|
|
if (!empty($modSettings['maillist_newtopic_needsapproval'])) |
2204
|
|
|
{ |
2205
|
|
|
$becomesApproved = false; |
2206
|
|
|
} |
2207
|
|
|
|
2208
|
|
|
// First on the agenda the subject |
2209
|
|
|
$subject = pbe_clean_email_subject($email_message->subject); |
2210
|
|
|
$subject = strtr(Util::htmlspecialchars($subject), ["\r" => '', "\n" => '', "\t" => '']); |
2211
|
|
|
|
2212
|
|
|
// Not to long not to short |
2213
|
|
|
if (Util::strlen($subject) > 100) |
2214
|
|
|
{ |
2215
|
|
|
$subject = Util::substr($subject, 0, 100); |
2216
|
|
|
} |
2217
|
|
|
|
2218
|
|
|
if ($subject === '') |
2219
|
|
|
{ |
2220
|
|
|
return pbe_emailError('error_no_subject', $email_message); |
2221
|
|
|
} |
2222
|
|
|
|
2223
|
|
|
// The message itself will need a bit of work |
2224
|
|
|
$text = pbe_load_text($email_message, $pbe); |
2225
|
|
|
if (empty($text)) |
2226
|
|
|
{ |
2227
|
|
|
return pbe_emailError('error_no_message', $email_message); |
2228
|
|
|
} |
2229
|
|
|
|
2230
|
|
|
// Build the attachment array if needed |
2231
|
|
|
if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1) |
2232
|
|
|
{ |
2233
|
|
|
if (($modSettings['postmod_active'] && in_array('post_unapproved_attachments', $pbe['user_info']['permissions'])) || in_array('post_attachment', $pbe['user_info']['permissions'])) |
2234
|
|
|
{ |
2235
|
|
|
$attachIDs = pbe_email_attachments($pbe, $email_message); |
2236
|
|
|
} |
2237
|
|
|
else |
2238
|
|
|
{ |
2239
|
|
|
$text .= "\n\n" . $txt['error_no_attach'] . "\n"; |
2240
|
|
|
} |
2241
|
|
|
} |
2242
|
|
|
|
2243
|
|
|
// If we get to this point ... then its time to play, lets start a topic ! |
2244
|
|
|
require_once(SUBSDIR . '/Post.subs.php'); |
2245
|
|
|
|
2246
|
|
|
// Setup the topic variables. |
2247
|
|
|
$msgOptions = [ |
2248
|
|
|
'id' => 0, |
2249
|
|
|
'subject' => $subject, |
2250
|
|
|
'smileys_enabled' => true, |
2251
|
|
|
'body' => $text, |
2252
|
|
|
'attachments' => empty($attachIDs) ? [] : $attachIDs, |
2253
|
|
|
'approved' => $becomesApproved |
2254
|
|
|
]; |
2255
|
|
|
|
2256
|
|
|
$topicOptions = [ |
2257
|
|
|
'id' => 0, |
2258
|
|
|
'board' => $board_info['id_board'], |
2259
|
|
|
'mark_as_read' => false |
2260
|
|
|
]; |
2261
|
|
|
|
2262
|
|
|
$posterOptions = [ |
2263
|
|
|
'id' => $pbe['profile']['id_member'], |
2264
|
|
|
'name' => $pbe['profile']['real_name'], |
2265
|
|
|
'email' => $pbe['profile']['email_address'], |
2266
|
|
|
'update_post_count' => empty($board_info['count_posts']), |
2267
|
|
|
'ip' => $email_message->ip ?? $pbe['profile']['member_ip'] |
2268
|
|
|
]; |
2269
|
|
|
|
2270
|
|
|
// Attempt to make the new topic. |
2271
|
|
|
createPost($msgOptions, $topicOptions, $posterOptions); |
2272
|
|
|
|
2273
|
|
|
// Bind any attachments that may be included to this new topic |
2274
|
|
|
if (!empty($attachIDs) && !empty($msgOptions['id'])) |
2275
|
|
|
{ |
2276
|
|
|
bindMessageAttachments($msgOptions['id'], $attachIDs); |
2277
|
|
|
} |
2278
|
|
|
|
2279
|
|
|
// The auto_notify setting |
2280
|
|
|
$theme_settings = query_get_theme($pbe['profile']['id_member'], $pbe['profile']['id_theme'], $board_info); |
2281
|
|
|
$auto_notify = $theme_settings['auto_notify'] ?? 0; |
2282
|
|
|
|
2283
|
|
|
// Notifications on or off |
2284
|
|
|
query_notifications($pbe['profile']['id_member'], $board_info['id_board'], $topicOptions['id'], $auto_notify, $pbe['user_info']['permissions']); |
|
|
|
|
2285
|
|
|
|
2286
|
|
|
// Notify members who have notification turned on for this, (if it's approved) |
2287
|
|
|
if ($becomesApproved) |
2288
|
|
|
{ |
2289
|
|
|
require_once(SUBSDIR . '/Notification.subs.php'); |
2290
|
|
|
sendNotifications($topicOptions['id'], 'reply', [], [], $pbe); |
2291
|
|
|
} |
2292
|
|
|
|
2293
|
|
|
// Update this users info so the log shows them as active |
2294
|
|
|
query_update_member_stats($pbe, $email_message, $topicOptions); |
2295
|
|
|
|
2296
|
|
|
return true; |
2297
|
|
|
} |
2298
|
|
|
|