Completed
Branch development (176841)
by Elk
06:59
created

Emailpost.subs.php ➔ pbe_emailError()   F

Complexity

Conditions 24
Paths 1584

Size

Total Lines 123

Duplication

Lines 28
Ratio 22.76 %

Code Coverage

Tests 0
CRAP Score 600

Importance

Changes 0
Metric Value
cc 24
nc 1584
nop 2
dl 28
loc 123
rs 0
c 0
b 0
f 0
ccs 0
cts 55
cp 0
crap 600

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
/**
16
 * Converts text / HTML to BBC
17
 *
18
 * What it does:
19
 *
20
 * - protects certain tags from conversion
21
 * - strips original message from the reply if possible
22
 * - If the email is html based, this will convert basic html tags to bbc tags
23
 * - If the email is plain text it will convert it to html based on markdown text
24
 * conventions and then that will be converted to bbc.
25
 *
26
 * @uses Html2BBC.class.php for the html to bbc conversion
27
 * @uses markdown.php for text to html conversions
28
 * @package Maillist
29
 *
30
 * @param string $text
31
 * @param boolean $html
32
 *
33
 * @return mixed|null|string|string[]
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
34
 */
35
function pbe_email_to_bbc($text, $html)
36
{
37
	// Define some things that need to be converted/modified, outside normal html or markup
38
	$tags = array(
39 4
		'~\*\*\s?(.*?)\*\*~is' => '**$1**',
40
		'~<\*>~i' => '&lt;*&gt;',
41
		'~-{20,}~' => '<hr>',
42
		'~#([0-9a-fA-F]{4,6}\b)~' => '&#35;$1',
43
	);
44
45
	// We are starting with HTML, our goal is to convert the best parts of it to BBC,
46 4
	if ($html)
47
	{
48
		// upfront pre-process $tags, mostly for the email template strings
49 2
		$text = preg_replace(array_keys($tags), array_values($tags), $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
50
51
		// Run the parsers on the html
52 2
		$text = pbe_run_parsers($text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
53
54 2
		$bbc_converter = new \ElkArte\Html2BBC($text);
55 2
		$bbc_converter->skip_tags(array('font', 'span'));
56 2
		$bbc_converter->skip_styles(array('font-family', 'font-size', 'color'));
57 2
		$text = $bbc_converter->get_bbc();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
58
	}
59
	// Starting with plain text, possibly even markdown style ;)
60
	else
61
	{
62
		// Run the parser to try and remove common mail clients "reply to" stuff
63 2
		$text = pbe_run_parsers($text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
64
65
		// Set a gmail flag for special quote processing since its quotes are strange
66 2
		$gmail = (bool) preg_match('~<div class="gmail_quote">~i', $text);
67
68
		// Attempt to fix textual ('>') quotes so we also fix wrapping issues first!
69 2
		$text = pbe_fix_email_quotes($text, ($html && !$gmail));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
70 2
		$text = str_replace(array('[quote]', '[/quote]'), array('&gt;blockquote>', '&gt;/blockquote>'), $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
71
72
		// Convert this (markup) text to html
73 2
		$text = preg_replace(array_keys($tags), array_values($tags), $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
74 2
		require_once(EXTDIR . '/markdown/markdown.php');
75 2
		$text = Markdown($text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
76 2
		$text = str_replace(array('&gt;blockquote>', '&gt;/blockquote>'), array('<blockquote>', '</blockquote>'), $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
77
78
		// Convert any resulting HTML created by markup style text in the email to BBC
79 2
		$bbc_converter = new \ElkArte\Html2BBC($text, false);
80 2
		$text = $bbc_converter->get_bbc();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
81
	}
82
83
	// Some tags often end up as just empty tags - remove those.
84
	$emptytags = array(
85 4
		'~\[[bisu]\]\s*\[/[bisu]\]~' => '',
86
		'~\[quote\]\s*\[/quote\]~' => '',
87
		'~(\n){3,}~si' => "\n\n",
88
	);
89 4
	$text = preg_replace(array_keys($emptytags), array_values($emptytags), $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
90
91 4
	return $text;
92
}
93
94
/**
95
 * Runs the ACP email parsers
96
 *	 - returns cut email or original if the cut would result in a blank message
97
 *
98
 * @param string $text
99
 * @return string
100
 */
101
function pbe_run_parsers($text)
102
{
103
	// Run our parsers, as defined in the ACP,  to remove the original "replied to" message
104 4
	$text_save = $text;
105 4
	$result = pbe_parse_email_message($text);
106
107
	// If we have no message left after running the parser, then they may have replied
108
	// below and/or inside the original message. People like this should not be allowed
109
	// to use the net, or be forced to read their own messed up emails
110 4
	if (empty($result) || (trim(strip_tags(pbe_filter_email_message($text))) === ''))
111 4
		$text = $text_save;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
112
113 4
	return $text;
114
}
115
116
/**
117
 * Prepares the email body so that it looks like a forum post
118
 *
119
 * What it does:
120
 *
121
 * - Removes extra content as defined in the ACP filters
122
 * - Fixes quotes and quote levels
123
 * - Re-flows (unfolds) an email using the EmailFormat.class
124
 * - Attempts to remove any exposed email address
125
 *
126
 * @uses EmailFormat.class.php
127
 * @package Maillist
128
 *
129
 * @param string $body
130
 * @param string $real_name
131
 * @param string $charset character set of the text
132
 *
133
 * @return mixed|null|string|string[]
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
134
 */
135
function pbe_fix_email_body($body, $real_name = '', $charset = 'UTF-8')
136
{
137 2
	global $txt;
138
139
	// Remove the \r's now so its done
140 2
	$body = trim(str_replace("\r", '', $body));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
141
142
	// Remove the riff-raff as defined by the ACP filters
143 2
	$body = pbe_filter_email_message($body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
144
145
	// Any old school email john smith wrote: etc style quotes that we need to update
146 2
	$body = pbe_fix_client_quotes($body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
147
148
	// Attempt to remove any exposed email addresses that are in the reply
149 2
	$body = preg_replace('~>' . $txt['to'] . '(.*)@(.*?)(?:\n|\[br\])~i', '', $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
150 2
	$body = preg_replace('~\b\s?[a-z0-9._%+-]+@[a-zZ0-9.-]+\.[a-z]{2,4}\b.?' . $txt['email_wrote'] . ':\s?~i', '', $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
151 2
	$body = preg_replace('~<(.*?)>(.*@.*?)(?:\n|\[br\])~', '$1' . "\n", $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
152 2
	$body = preg_replace('~' . $txt['email_quoting'] . ' (.*) (?:<|&lt;|\[email\]).*?@.*?(?:>|&gt;|\[/email\]):~i', '', $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
153
154
	// Remove multiple sequential blank lines, again
155 2
	$body = preg_replace('~(\n\s?){3,}~si', "\n\n", $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
156
157
	// Check for blank quotes
158 2
	$body = preg_replace('~(\[quote\s?([a-zA-Z0-9"=]*)?\]\s*(\[br\]\s*)?\[/quote\])~s', '', $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
159
160
	// Reflow and Cleanup this message to something that looks normal-er
161 2
	$formatter = new \ElkArte\EmailFormat();
162 2
	$body = $formatter->reflow($body, $real_name, $charset);
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type array<integer,string>; however, ElkArte\EmailFormat::reflow() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
163
164 2
	return $body;
165
}
166
167
/**
168
 * Replaces a messages >'s with BBC [quote] [/quote] blocks
169
 *
170
 * - Uses quote depth function
171
 * - Works with nested quotes of many forms >, > >, >>, >asd
172
 * - Bypassed for gmail as it only block quotes the outer layer and then plain
173
 * text > quotes the inner which is confusing to all
174
 *
175
 * @package Maillist
176
 *
177
 * @param string $body
178
 * @param boolean $html
179
 *
180
 * @return mixed|string
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
181
 */
182
function pbe_fix_email_quotes($body, $html)
183
{
184
	// Coming from HTML then remove lines that start with > and are inside [quote] ... [/quote] blocks
185 2
	if ($html)
186
	{
187
		$quotes = array();
188
		if (preg_match_all('~\[quote\](.*)\[\/quote\]~sU', $body, $quotes, PREG_SET_ORDER))
189
		{
190
			foreach ($quotes as $quote)
191
			{
192
				$quotenew = preg_replace('~^.?> (.*)$~im', '$1', $quote[1] . "\n");
193
				$body = str_replace($quote[0], '[quote]' . $quotenew . '[/quote]', $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
194
			}
195
		}
196
	}
197
198
	// Create a line by line array broken on the newlines
199 2
	$body_array = explode("\n", $body);
200 2
	$original = $body_array;
201
202
	// Init
203 2
	$current_quote = 0;
204 2
	$quote_done = '';
205
206
	// Go line by line and add the quote blocks where needed, fixing where needed
207 2
	for ($i = 0, $num = count($body_array); $i < $num; $i++)
208
	{
209 2
		$body_array[$i] = trim($body_array[$i]);
210
211
		// Get the quote "depth" level for this line
212 2
		$level = pbe_email_quote_depth($body_array[$i]);
213
214
		// No quote marker on this line but we we are in a quote
215 2
		if ($level === 0 && $current_quote > 0)
216
		{
217
			// Make sure we don't have an email wrap issue
218
			$level_prev = pbe_email_quote_depth($original[$i - 1], false);
219
			$level_next = pbe_email_quote_depth($original[$i + 1], false);
220
221
			// A line between two = quote or descending quote levels,
222
			// probably an email break so join (wrap) it back up and continue
223
			if (($level_prev !== 0) && ($level_prev >= $level_next && $level_next !== 0))
224
			{
225
				$body_array[$i - 1] .= ' ' . $body_array[$i];
226
				unset($body_array[$i]);
227
				continue;
228
			}
229
		}
230
231
		// No quote or in the same quote just continue
232 2
		if ($level == $current_quote)
233 2
			continue;
234
235
		// Deeper than we were so add a quote
236
		if ($level > $current_quote)
237
		{
238
			$qin_temp = '';
239
			while ($level > $current_quote)
240
			{
241
				$qin_temp .= '[quote]' . "\n";
242
				$current_quote++;
243
			}
244
			$body_array[$i] = $qin_temp . $body_array[$i];
245
		}
246
247
		// Less deep so back out
248
		if ($level < $current_quote)
249
		{
250
			$qout_temp = '';
251
			while ($level < $current_quote)
252
			{
253
				$qout_temp .= '[/quote]' . "\n";
254
				$current_quote--;
255
			}
256
			$body_array[$i] = $qout_temp . $body_array[$i];
257
		}
258
259
		// That's all I have to say about that
260
		if ($level === 0 && $current_quote !== 0)
261
		{
262
			$quote_done = '';
263
			while ($current_quote)
264
			{
265
				$quote_done .= '[/quote]' . "\n";
266
				$current_quote--;
267
			}
268
			$body_array[$i] = $quote_done . $body_array[$i];
269
		}
270
	}
271
272
	// No more lines, lets just make sure we did not leave ourselves any open quotes
273 2
	while (!empty($current_quote))
274
	{
275
		$quote_done .= '[/quote]' . "\n";
276
		$current_quote--;
277
	}
278 2
	$body_array[$i] = $quote_done;
279
280
	// Join the array back together while dropping null index's
281 2
	$body = implode("\n", array_values($body_array));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
282
283 2
	return $body;
284
}
285
286
/**
287
 * Looks for text quotes in the form of > and returns the current level for the line
288
 *
289
 * - If update is true (default), will strip the >'s and return the numeric level found
290
 * - Called by pbe_fix_email_quotes
291
 *
292
 * @package Maillist
293
 *
294
 * @param string $string
295
 * @param boolean $update
296
 *
297
 * @return int
298
 */
299
function pbe_email_quote_depth(&$string, $update = true)
300
{
301
	// Get the quote "depth" level for this line
302 2
	$level = 0;
303 2
	$check = true;
304 2
	$string_save = $string;
305
306 2
	while ($check)
307
	{
308
		// We have a quote marker, increase our depth and strip the line of that quote marker
309 2
		if ((substr($string, 0, 2) === '> ') || ($string === '>'))
310
		{
311
			$level++;
312
			$string = substr($string, 2);
313
		}
314
		// Maybe a poorly nested quote, with no spaces between the >'s or the > and the data with no space
315 2
		elseif ((substr($string, 0, 2) === '>>') || (preg_match('~^>[a-z0-9<-]+~Uis', $string) == 1))
316
		{
317
			$level++;
318
			$string = substr($string, 1);
319
		}
320
		// All done getting the depth
321
		else
322 2
			$check = false;
323
	}
324
325 2
	if (!$update)
326
		$string = $string_save;
327
328 2
	return $level;
329
}
330
331
/**
332
 * Splits a message at a given string, returning only the upper portion
333
 *
334
 * - Intended to split off the 'replied to' portion that often follows the reply
335
 * - Uses parsers as defined in the ACP to do its searching
336
 * - Stops after the first successful hit occurs
337
 * - Goes in the order defined in the table
338
 *
339
 * @package Maillist
340
 * @param string $body
341
 * @return boolean on find
342
 */
343
function pbe_parse_email_message(&$body)
344
{
345 4
	$db = database();
346
347
	// Load up the parsers from the database
348 4
	$request = $db->query('', '
349
		SELECT
350
			filter_from, filter_type
351
		FROM {db_prefix}postby_emails_filters
352
		WHERE filter_style = {string:filter_style}
353
		ORDER BY filter_order ASC',
354
		array(
355 4
			'filter_style' => 'parser'
356
		)
357
	);
358
	// Build an array of valid expressions
359 4
	$expressions = array();
360 4
	while ($row = $db->fetch_assoc($request))
361
	{
362
		if ($row['filter_type'] === 'regex')
363
		{
364
			// Test the regex and if good add it to the array, else skip it
365
			// @todo these are tested at insertion now, so this test is really not necessary
366
			$temp = preg_replace($row['filter_from'], '', '$5#6#8%9456@^)098');
367
			if ($temp !== null)
368
				$expressions[] = array('type' => 'regex', 'parser' => $row['filter_from']);
369
		}
370
		else
371
			$expressions[] = array('type' => 'string', 'parser' => $row['filter_from']);
372
	}
373 4
	$db->free_result($request);
374
375
	// Look for the markers, **stop** after the first successful one, good hunting!
376 4
	$match = false;
377 4
	foreach ($expressions as $expression)
378
	{
379
		if ($expression['type'] === 'regex')
380
			$split = preg_split($expression['parser'], $body);
381
		else
382
			$split = explode($expression['parser'], $body, 2);
383
384
		// If an expression was matched our fine work is done
385
		if (!empty($split[1]))
386
		{
387
			// If we had a hit then we clip off the mail and return whats above the split
388
			$match = true;
389
			$body = $split[0];
390
			break;
391
		}
392
	}
393
394 4
	return $match;
395
}
396
397
/**
398
 * Searches for extraneous text and removes/replaces it
399
 *
400
 * - Uses filters as defined in the ACP to do the search / replace
401
 * - Will apply regex filters first, then string match filters
402
 * - Apply all filters to a message
403
 *
404
 * @package Maillist
405
 *
406
 * @param string $text
407
 *
408
 * @return mixed|null|string|string[]
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
409
 */
410
function pbe_filter_email_message($text)
411
{
412 2
	$db = database();
413
414
	// load up the text filters from the database, regex first and ordered by the filter order ...
415 2
	$request = $db->query('', '
416
		SELECT
417
			filter_from, filter_to, filter_type
418
		FROM {db_prefix}postby_emails_filters
419
		WHERE filter_style = {string:filter_style}
420
		ORDER BY filter_type ASC, filter_order ASC',
421
		array(
422 2
			'filter_style' => 'filter'
423
		)
424
	);
425
426
	// Remove all the excess things as defined, i.e. sent from my iPhone, I hate those >:D
427 2
	while ($row = $db->fetch_assoc($request))
428
	{
429
		if ($row['filter_type'] === 'regex')
430
		{
431
			// Newline madness
432
			if (!empty($row['filter_to']))
433
				$row['filter_to'] = str_replace('\n', "\n", $row['filter_to']);
434
435
			// Test the regex and if good use, else skip, don't want a bad regex to empty the message!
436
			$temp = preg_replace($row['filter_from'], $row['filter_to'], $text);
437
			if ($temp !== null)
438
				$text = $temp;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
439
		}
440
		else
441
			$text = str_replace($row['filter_from'], $row['filter_to'], $text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
442
	}
443 2
	$db->free_result($request);
444
445 2
	return $text;
446
}
447
448
/**
449
 * Finds Re: Subject: FW: FWD or [$sitename] in the subject and strips it
450
 *
451
 * - Recursively calls itself till no more tags are found
452
 *
453
 * @package Maillist
454
 *
455
 * @param string $text
456
 * @param boolean $check if true will return if there tags were found
457
 *
458
 * @return bool|string
459
 */
460
function pbe_clean_email_subject($text, $check = false)
461
{
462
	global $txt, $modSettings, $mbname;
463
464
	$sitename = !empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $mbname;
465
466
	// Find Re: Subject: FW: FWD or [$sitename] in the subject and strip it
467
	$re = strpos(strtoupper($text), $txt['RE:']);
468
	if ($re !== false)
469
		$text = substr($text, 0, $re) . substr($text, $re + strlen($txt['RE:']), strlen($text));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
470
471
	$su = strpos(strtoupper($text), $txt['SUBJECT:']);
472
	if ($su !== false)
473
		$text = substr($text, 0, $su) . substr($text, $su + strlen($txt['SUBJECT:']), strlen($text));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
474
475
	$fw = strpos(strtoupper($text), $txt['FW:']);
476
	if ($fw !== false)
477
		$text = substr($text, 0, $fw) . substr($text, $fw + strlen($txt['FW:']), strlen($text));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
478
479
	$gr = strpos($text, '[' . $sitename . ']');
480 View Code Duplication
	if ($gr !== false)
481
		$text = substr($text, 0, $gr) . substr($text, $gr + strlen($sitename) + 2, strlen($text));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
482
483
	$fwd = strpos(strtoupper($text), $txt['FWD:']);
484 View Code Duplication
	if ($fwd !== false)
485
		$text = substr($text, 0, $fwd) . substr($text, $fwd + strlen($txt['FWD:']), strlen($text));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
486
487
	// if not done then call ourselves again, we like the sound of our name
488
	if (strpos(strtoupper($text), $txt['RE:']) || strpos(strtoupper($text), $txt['FW:']) || strpos(strtoupper($text), $txt['FWD:']) || strpos($text, '[' . $sitename . ']'))
489
		$text = pbe_clean_email_subject($text);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $text. This often makes code more readable.
Loading history...
490
491
	// clean or not?
492
	if ($check)
493
		return ($re === false && $su === false && $gr === false && $fw === false && $fwd === false);
494
	else
495
		return trim($text);
496
}
497
498
/**
499
 * Used if the original email could not be removed from the message (top of post)
500
 *
501
 * - Tries to quote the original message instead by using a loose original message search
502
 * - Looks for email client original message tags and converts them to bbc quotes
503
 *
504
 * @package Maillist
505
 *
506
 * @param string $body
507
 *
508
 * @return null|string|string[]
509
 */
510
function pbe_fix_client_quotes($body)
511
{
512 2
	global $txt;
513
514
	// Define some common quote markers (from the original messages)
515
	// @todo ACP for this? ... not sure
516 2
	$regex = array();
517
518
	// On mon, jan 12, 2004 at 10:10 AM, John Smith wrote: [quote]
519 2
	$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';
520
	// [quote] on: mon jan 12, 2004 John Smith wrote:
521 2
	$regex[] = '~\[quote\]\s?' . $txt['email_on'] . ': \w{3} \w{3} \d{1,2}, \d{4} (.*)?' . $txt['email_wrote'] . ':\s~i';
522
	// on jan 12, 2004 at 10:10 PM, John Smith wrote:   [quote]
523 2
	$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';
524
	// on jan 12, 2004 at 10:10, John Smith wrote   [quote]
525 2
	$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';
526
	// quoting: John Smith on stuffz at 10:10:23 AM
527 2
	$regex[] = '~' . $txt['email_quotefrom'] . ': (.*) ' . $txt['email_on'] . ' .* ' . $txt['email_at'] . ' \d{1,2}:\d{1,2}:\d{1,2} [AP]M~';
528
	// quoting John Smith <[email protected]>
529 2
	$regex[] = '~' . $txt['email_quoting'] . ' (.*) (?:<|&lt;|\[email\]).*?@.*?(?:>|&gt;|\[/email\]):~i';
530
	// --- in some group name "John Smith" <[email protected]> wrote:
531 2
	$regex[] = '~---\s.*?"(.*)"\s+' . $txt['email_wrote'] . ':\s(\[quote\])?~i';
532
	// --- in [email protected] John Smith wrote
533 2
	$regex[] = '~---\s.*?\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6}\b,\s(.*?)\s' . $txt['email_wrote'] . ':?~iu';
534
	// --- In some@g..., "someone"  wrote:
535 2
	$regex[] = '~---\s.*?\b[A-Z0-9._%+-]+@[A-Z0-9][.]{3}, [A-Z0-9._%+\-"]+\b(.*?)\s' . $txt['email_wrote'] . ':?~iu';
536
	// --- In [email]something[/email] "someone" wrote:
537 2
	$regex[] = '~---\s.*?\[email=.*?/email\],?\s"?(.*?)"?\s' . $txt['email_wrote'] . ':?~iu';
538
539
	// For each one see if we can do a nice [quote author=john smith]
540 2
	foreach ($regex as $reg)
541
	{
542 2
		if (preg_match_all($reg, $body, $matches, PREG_SET_ORDER))
543
		{
544
			foreach ($matches as $quote)
545
			{
546
				$quote[1] = preg_replace('~\[email\].*\[\/email\]~', '', $quote[1]);
547
				$body = pbe_str_replace_once($quote[0], "\n" . '[quote author=' . trim($quote[1]) . "]\n", $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
548
549
				$quote[1] = preg_quote($quote[1], '~');
550
551
				// Look for [quote author=][/quote][quote] issues
552
				$body = preg_replace('~\[quote author=' . trim($quote[1]) . '\] ?(?:\n|\[br\] ?){2,4} ?\[\/quote\] ?\[quote\]~u', '[quote author=' . trim($quote[1]) . "]\n", $body, 1);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
553
554
				// And [quote author=][quote] newlines [/quote] issues
555 1
				$body = preg_replace('~\[quote author=' . trim($quote[1]) . '\] ?(?:\n|\[br\] ?){2,4}\[quote\]~u', '[quote author=' . trim($quote[1]) . "]\n", $body);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $body. This often makes code more readable.
Loading history...
556
			}
557
		}
558
	}
559
560 2
	return $body;
561
}
562
563
/**
564
 * Does a single replacement of the first found string in the haystack
565
 *
566
 * @package Maillist
567
 * @param string $needle
568
 * @param string $replace
569
 * @param string $haystack
570
 * @return string
571
 */
572
function pbe_str_replace_once($needle, $replace, $haystack)
573
{
574
	// Looks for the first occurrence of $needle in $haystack and replaces it with $replace
575
	$pos = strpos($haystack, $needle);
576
	if ($pos === false)
577
		return $haystack;
578
579
	return substr_replace($haystack, $replace, $pos, strlen($needle));
580
}
581
582
/**
583
 * Does a moderation check on a given user (global)
584
 *
585
 * - Removes permissions of PBE concern that a given moderated level denies
586
 *
587
 * @package Maillist
588
 * @param mixed[] $pbe array of user values
589
 */
590
function pbe_check_moderation(&$pbe)
591
{
592
	global $modSettings;
593
594
	if (empty($modSettings['postmod_active']))
595
		return;
596
597
	// Have they been muted for being naughty?
598
	if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $pbe['user_info']['warning'])
599
	{
600
		// Remove anything that would allow them to do anything via PBE
601
		$denied_permissions = array(
602
			'pm_send', 'postby_email',
603
			'admin_forum', 'moderate_forum',
604
			'post_new', 'post_reply_own', 'post_reply_any',
605
			'post_attachment', 'post_unapproved_attachments',
606
			'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any',
607
		);
608
		$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], $denied_permissions);
609
	}
610
	elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $pbe['user_info']['warning'])
611
	{
612
		// Work out what permissions should change if they are just being moderated
613
		$permission_change = array(
614
			'post_new' => 'post_unapproved_topics',
615
			'post_reply_own' => 'post_unapproved_replies_own',
616
			'post_reply_any' => 'post_unapproved_replies_any',
617
			'post_attachment' => 'post_unapproved_attachments',
618
		);
619
620
		foreach ($permission_change as $old => $new)
621
		{
622
			if (!in_array($old, $pbe['user_info']['permissions']))
623
				unset($permission_change[$old]);
624
			else
625
				$pbe['user_info']['permissions'][] = $new;
626
		}
627
628
		$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], array_keys($permission_change));
629
	}
630
631
	return;
632
}
633
634
/**
635
 * Creates a failed email entry in the postby_emails_error table
636
 *
637
 * - Attempts an auto-correct for common errors so the admin / moderator
638
 * - can choose to approve the email with the corrections
639
 *
640
 * @package Maillist
641
 *
642
 * @param string $error
643
 * @param \ElkArte\EmailParse $email_message
644
 *
645
 * @return bool
646
 * @throws \ElkArte\Exceptions\Exception
647
 */
648
function pbe_emailError($error, $email_message)
649
{
650
	global $txt;
651
652
	$db = database();
653
654
	theme()->getTemplates()->loadLanguageFile('EmailTemplates');
655
656
	// Some extra items we will need to remove from the message subject
657
	$pm_subject_leader = str_replace('{SUBJECT}', '', $txt['new_pm_subject']);
658
659
	// Clean the subject like we don't know where it has been
660
	$subject = trim(str_replace($pm_subject_leader, '', $email_message->subject));
661
	$subject = pbe_clean_email_subject($subject);
662
	$subject = ($subject === '' ? $txt['no_subject'] : $subject);
663
664
	// Start off with what we know about the security key, even if its nothing
665
	$message_key = (string) $email_message->message_key_id;
666
	$message_type = (string) $email_message->message_type;
667
	$message_id = (int) $email_message->message_id;
668
	$board_id = -1;
669
670
	// First up is the old, wrong email address, lets see who this should have come from if its not a new topic request
671 View Code Duplication
	if ($error === 'error_not_find_member' && $email_message->message_type !== 'x')
672
	{
673
		$key_owner = query_key_owner($email_message);
674
		if (!empty($key_owner))
675
		{
676
			// Valid key so show who should have sent this key in? email aggravaters :P often mess this up
677
			$email_message->email['from'] = $email_message->email['from'] . ' => ' . $key_owner;
678
679
			// Since we have a valid key set those details as well
680
			$message_key = $email_message->message_key_id;
681
			$message_type = $email_message->message_type;
682
			$message_id = $email_message->message_id;
683
		}
684
	}
685
686
	// A valid key but it was not sent to this user ... but we did get the email from a valid site user
687 View Code Duplication
	if ($error === 'error_key_sender_match')
688
	{
689
		$key_owner = query_key_owner($email_message);
690
		if (!empty($key_owner))
691
		{
692
			// Valid key so show who should have sent this key in
693
			$email_message->email['from'] = $key_owner . ' => ' . $email_message->email['from'];
694
695
			// Since we have a valid key set those details as well
696
			$message_key = $email_message->message_key_id;
697
			$message_type = $email_message->message_type;
698
			$message_id = $email_message->message_id;
699
		}
700
	}
701
702
	// No key? We should at a minimum have who its from and a subject, so use that
703
	if ($email_message->message_type !== 'x' && (empty($message_key) || $error === 'error_pm_not_found'))
704
	{
705
		// We don't have the message type (since we don't have a key)
706
		// Attempt to see if it might be a PM so we handle it correctly
707
		if (empty($message_type) && (strpos($email_message->subject, $pm_subject_leader) !== false))
708
			$message_type = 'p';
709
710
		// Find all keys sent to this user, sorted by date
711
		$user_keys = query_user_keys($email_message->email['from']);
712
713
		// While we have keys to look at see if we can match up this lost message on subjects
714
		foreach ($user_keys as $user_key)
715
		{
716
			if (preg_match('~([a-z0-9]{32})\-(p|t|m)(\d+)~', $user_key['id_email'], $match))
717
			{
718
				$key = $match[0];
719
				$type = $match[2];
720
				$message = $match[3];
721
722
				// 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 ;)
723
				if ((!empty($message_type) && $message_type === $type) || (empty($message_type) && $type !== 'p'))
724
				{
725
					// lets look up this message/topic/pm and see if the subjects match ... if they do then tada!
726
					if (query_load_subject($message, $type, $email_message->email['from']) === $subject)
727
					{
728
						// This email has a subject that matches the subject of a message that was sent to them
729
						$message_key = $key;
730
						$message_id = $message;
731
						$message_type = $type;
732
						break;
733
					}
734
				}
735
			}
736
		}
737
	}
738
739
	// Maybe we have enough to find the board id where this was going
740
	if (!empty($message_id) && $message_type !== 'p')
741
		$board_id = query_load_board($message_id);
742
743
	// Log the error so the moderators can take a look, helps keep them sharp
744
	$id = isset($_REQUEST['item']) ? (int) $_REQUEST['item'] : 0;
745
	$db->insert(!empty($id) ? 'replace' : 'ignore',
746
		'{db_prefix}postby_emails_error',
747
		array(
748
			'id_email' => 'int', 'error' => 'string', 'message_key' => 'string',
749
			'subject' => 'string', 'message_id' => 'int', 'id_board' => 'int',
750
			'email_from' => 'string', 'message_type' => 'string', 'message' => 'string'),
751
		array(
752
			$id, $error, $message_key,
753
			$subject, $message_id, $board_id,
754
			$email_message->email['from'], $message_type, $email_message->raw_message),
755
		array('id_email')
756
	);
757
758
	// Flush the moderator error number cache, if we are here it likely just changed.
759
	\ElkArte\Cache\Cache::instance()->remove('num_menu_errors');
760
761
	// If not running from the cli, then go back to the form
762
	if (isset($_POST['item']))
763
	{
764
		// Back to the form we go
765
		$_SESSION['email_error'] = $txt[$error];
766
		redirectexit('action=admin;area=maillist');
767
	}
768
769
	return false;
770
}
771
772
/**
773
 * Writes email attachments as temp names in the proper attachment directory
774
 *
775
 * What it does:
776
 *
777
 * - populates $_SESSION['temp_attachments'] with the email attachments
778
 * - calls attachmentChecks to validate them
779
 * - skips ones flagged with errors
780
 * - adds valid ones to attachmentOptions
781
 * - calls createAttachment to store them
782
 *
783
 * @package Maillist
784
 *
785
 * @param mixed[] $pbe
786
 * @param \ElkArte\EmailParse $email_message
787
 *
788
 * @return array
789
 * @throws \ElkArte\Exceptions\Exception
790
 */
791
function pbe_email_attachments($pbe, $email_message)
792
{
793
	// Trying to attach a file with this post ....
794
	global $modSettings, $context;
795
796
	// Init
797
	$attachment_count = 0;
798
	$attachIDs = array();
799
800
	// Make sure we're uploading the files to the right place.
801 View Code Duplication
	if (!empty($modSettings['currentAttachmentUploadDir']))
802
	{
803
		if (!is_array($modSettings['attachmentUploadDir']))
804
			$modSettings['attachmentUploadDir'] = \ElkArte\Util::unserialize($modSettings['attachmentUploadDir']);
805
806
		// The current directory, of course!
807
		$current_attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
808
	}
809
	else
810
		$current_attach_dir = $modSettings['attachmentUploadDir'];
811
812
	// For attachmentChecks function
813
	require_once(SUBSDIR . '/Attachments.subs.php');
814
	$context['attachments'] = array('quantity' => 0, 'total_size' => 0);
815
	$context['attach_dir'] = $current_attach_dir;
816
817
	// Create the file(s) with a temp name so we can validate its contents/type
818
	foreach ($email_message->attachments as $name => $attachment)
819
	{
820
		// Write the contents to an actual file
821
		$attachID = 'post_tmp_' . $pbe['profile']['id_member'] . '_' . md5(mt_rand()) . $attachment_count;
822
		$destName = $current_attach_dir . '/' . $attachID;
823
824
		if (file_put_contents($destName, $attachment) !== false)
825
		{
826
			@chmod($destName, 0644);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
827
828
			// Place them in session since that's where attachmentChecks looks
829
			$_SESSION['temp_attachments'][$attachID] = array(
830
				'name' => htmlspecialchars(basename($name), ENT_COMPAT, 'UTF-8'),
831
				'tmp_name' => $destName,
832
				'size' => strlen($attachment),
833
				'id_folder' => $modSettings['currentAttachmentUploadDir'],
834
				'errors' => array(),
835
				'approved' => !$modSettings['postmod_active'] || in_array('post_unapproved_attachments', $pbe['user_info']['permissions'])
836
			);
837
838
			// Make sure its valid
839
			attachmentChecks($attachID);
840
			$attachment_count++;
841
		}
842
	}
843
844
	// Get the results from attachmentChecks and see if its suitable for posting
845
	$attachments = $_SESSION['temp_attachments'];
846
	unset($_SESSION['temp_attachments']);
847
	foreach ($attachments as $attachID => $attachment)
848
	{
849
		// If there were any errors we just skip that file
850
		if (($attachID != 'initial_error' && strpos($attachID, 'post_tmp_' . $pbe['profile']['id_member'] . '_') === false) || ($attachID == 'initial_error' || !empty($attachment['errors'])))
851
		{
852
			@unlink($attachment['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
853
			continue;
854
		}
855
856
		// Load the attachmentOptions array with the data needed to create an attachment
857
		$attachmentOptions = array(
858
			'post' => !empty($email_message->message_id) ? $email_message->message_id : 0,
859
			'poster' => $pbe['profile']['id_member'],
860
			'name' => $attachment['name'],
861
			'tmp_name' => $attachment['tmp_name'],
862
			'size' => isset($attachment['size']) ? $attachment['size'] : 0,
863
			'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
864
			'id_folder' => isset($attachment['id_folder']) ? $attachment['id_folder'] : 0,
865
			'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
866
			'errors' => array(),
867
		);
868
869
		// Make it available to the forum/post
870
		if (createAttachment($attachmentOptions))
871
		{
872
			$attachIDs[] = $attachmentOptions['id'];
873
			if (!empty($attachmentOptions['thumb']))
874
				$attachIDs[] = $attachmentOptions['thumb'];
875
		}
876
		// We had a problem so simply remove it
877
		elseif (file_exists($attachment['tmp_name']))
878
			@unlink($attachment['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
879
	}
880
881
	return $attachIDs;
882
}
883
884
/**
885
 * Used when a email attempts to start a new topic
886
 *
887
 * - Load the board id that a given email address is assigned to in the ACP
888
 * - Returns the board number in which the new topic must go
889
 *
890
 * @package Maillist
891
 *
892
 * @param \ElkArte\EmailParse $email_address
893
 *
894
 * @return int
895
 */
896
function pbe_find_board_number($email_address)
897
{
898
	global $modSettings;
899
900
	$valid_address = array();
901
	$board_number = 0;
902
903
	// Load our valid email ids and the corresponding board ids
904
	$data = (!empty($modSettings['maillist_receiving_address'])) ? \ElkArte\Util::unserialize($modSettings['maillist_receiving_address']) : array();
905
	foreach ($data as $key => $addr)
906
		$valid_address[$addr[0]] = $addr[1];
907
908
	// Who was this message sent to, may have been sent to multiple addresses
909
	// so we must check each one to see if we have a valid entry
910
	foreach ($email_address->email['to'] as $to_email)
911
	{
912
		if (isset($valid_address[$to_email]))
913
		{
914
			$board_number = (int) $valid_address[$to_email];
915
			continue;
916
		}
917
	}
918
919
	return $board_number;
920
}
921
922
/**
923
 * Converts a post/pm to text (markdown) for sending in an email
924
 *
925
 * - censors everything it will send
926
 * - pre-converts select bbc tags to html so they can be markdowned properly
927
 * - uses parse-bbc to convert remaining bbc to html
928
 * - uses html2markdown to convert html to markdown text suitable for email
929
 * - if someone wants to write a direct bbc->markdown conversion tool, I'm listening!
930
 *
931
 * @package Maillist
932
 * @param string $message
933
 * @param string $subject
934
 * @param string $signature
935
 */
936
function pbe_prepare_text(&$message, &$subject = '', &$signature = '')
937
{
938
	global $context;
939
940
	theme()->getTemplates()->loadLanguageFile('Maillist');
941
942
	// Check on some things needed by parse_bbc as an autotask does not load them
943
	if (!isset($context['browser']))
944
	{
945
		detectBrowser();
946
	}
947
948
	// Server?
949
	detectServer();
950
951
	// Clean it up.
952
	$message = censor($message);
953
	$signature = censor($signature);
954
	$subject = censor(un_htmlspecialchars($subject));
955
956
	// Convert bbc [quotes] before we go to parsebbc so they are easier to plain-textify later
957
	$message = preg_replace_callback('~(\[quote)\s?author=(.*)\s?link=(.*)\s?date=([0-9]{10})(\])~sU', 'quote_callback', $message);
958
	$message = preg_replace_callback('~(\[quote)\s?author=(.*)\s?date=([0-9]{10})\s?link=(.*)(\])~sU', 'quote_callback_2', $message);
959
	$message = preg_replace('~(\[quote\s?\])~sU', "\n" . '<blockquote>', $message);
960
	$message = str_replace('[/quote]', "</blockquote>\n\n", $message);
961
962
	// Prevent img tags from getting linked
963
	$message = preg_replace('~\[img\](.*?)\[/img\]~is', '`&lt;img src="\\1">', $message);
964
965
	// Leave code tags as code tags for the conversion
966
	$message = preg_replace('~\[code(.*?)\](.*?)\[/code\]~is', '`&lt;code\\1>\\2`&lt;/code>', $message);
967
968
	// Allow addons to account for their own unique bbc additions e.g. gallery's etc.
969
	call_integration_hook('integrate_mailist_pre_parsebbc', array(&$message));
970
971
	// Convert the remaining bbc to html
972
	$bbc_wrapper = \BBC\ParserWrapper::instance();
973
	$message = $bbc_wrapper->parseMessage($message, false);
974
975
	// Change list style to something standard to make text conversion easier
976
	$message = preg_replace('~<ul class=\"bbc_list\" style=\"list-style-type: decimal;\">(.*?)</ul>~si', '<ol>\\1</ol>', $message);
977
978
	// Do we have any tables? if so we add in th's based on the number of cols.
979
	$table_content = array();
980
	if (preg_match_all('~<table class="bbc_table">(.*?)</tr>.*?</table>~si', $message, $table_content, PREG_SET_ORDER))
981
	{
982
		// The answer is yes ... work on each one
983
		foreach ($table_content as $table_temp)
984
		{
985
			$cols = substr_count($table_temp[1], '<td>');
986
			$table_header = '';
987
988
			// Build the th line for this table
989
			for ($i = 1; $i <= $cols; $i++)
990
				$table_header .= '<th>- ' . $i . ' -</th>';
991
992
			// Insert it in to the table tag
993
			$table_header = '<tr>' . $table_header . '</tr>';
994
			$new_table = str_replace('<table class="bbc_table">', '<br /><table>' . $table_header, $table_temp[0]);
995
996
			// Replace the old table with the new th enabled one
997
			$message = str_replace($table_temp[0], $new_table, $message);
998
		}
999
	}
1000
1001
	// Allow addons to account for their own unique bbc additions e.g. gallery's etc.
1002
	call_integration_hook('integrate_mailist_pre_markdown', array(&$message));
1003
1004
	// Convert the protected (hidden) entities back for the final conversion
1005
	$message = strtr($message, array(
1006
		'&#91;' => '[',
1007
		'&#93;' => ']',
1008
		'`&lt;' => '<',
1009
		)
1010
	);
1011
1012
	// Convert this to text (markdown)
1013
	$mark_down = new \ElkArte\Html2Md($message);
1014
	$message = $mark_down->get_markdown();
1015
1016
	// Finally the sig, its goes as just plain text
1017
	if ($signature !== '')
1018
	{
1019
		call_integration_hook('integrate_mailist_pre_sig_parsebbc', array(&$signature));
1020
1021
		$signature = $bbc_wrapper->parseSignature($signature, false);
1022
		$signature = trim(un_htmlspecialchars(strip_tags(strtr($signature, array('</tr>' => "   \n", '<br />' => "   \n", '</div>' => "\n", '</li>' => "   \n", '&#91;' => '[', '&#93;' => ']')))));
1023
	}
1024
1025
	return;
1026
}
1027
1028
/**
1029
 * When a DSN (bounce) is received and the feature is enabled, update the settings
1030
 * For the user in question to disable Board and Post notifications. Do not clear
1031
 * Notification subscriptions.
1032
 *
1033
 * When finished, fire off a site notification informing the user of the action and reason
1034
 *
1035
 * @package Maillist
1036
 * @param \ElkArte\EmailParse $email_message
1037
 */
1038
function pbe_disable_user_notify($email_message)
1039
{
1040
	global $modSettings;
1041
	$db = database();
1042
1043
	$email = $email_message->get_failed_dest();
1044
1045
	$request = $db->query('', '
1046
		SELECT
1047
			id_member
1048
		FROM {db_prefix}members
1049
		WHERE email_address = {string:email}
1050
		LIMIT 1',
1051
		array(
1052
			'email' => $email
1053
		)
1054
	);
1055
1056
	if ($db->num_rows($request) !== 0)
1057
	{
1058
		list ($id_member) = $db->fetch_row($request);
1059
		$db->free_result($request);
1060
1061
		// Once we have the member's ID, we can turn off board/topic notifications
1062
		// by setting notify_regularity->99 ("Never")
1063
		$db->query('', '
1064
			UPDATE {db_prefix}members
1065
			SET
1066
				notify_regularity = 99
1067
			WHERE id_member = {int:id_member}',
1068
			array(
1069
				'id_member' => $id_member
1070
			)
1071
		);
1072
1073
		//Now that other notifications have been added, we need to turn off email for those, too.
1074
		//notification_level "1" = "Only mention/alert"
1075
		$db->query('', '
1076
			UPDATE {db_prefix}notifications_pref
1077
			SET
1078
				notification_level = 1
1079
			WHERE id_member = {int:id_member}',
1080
			array(
1081
				'id_member' => $id_member
1082
			)
1083
		);
1084
1085
		//Add a "mention" of email notification being disabled
1086 View Code Duplication
		if (!empty($modSettings['mentions_enabled']))
1087
		{
1088
			$notifier = \ElkArte\Notifications::instance();
1089
			$notifier->add(new \ElkArte\NotificationsTask(
1090
				'mailfail',
1091
				0,
1092
				$id_member,
1093
				array('id_members'=>array($id_member))
1094
			));
1095
			$notifier->send();
1096
		}
1097
	}
1098
}
1099
1100
/**
1101
 * Replace full bbc quote tags with an html blockquote version
1102
 *
1103
 * - Callback for pbe_prepare_text
1104
 * - Only changes the leading [quote], the closing /quote is not changed but
1105
 * handled back in the main function
1106
 *
1107
 * @param string[] $matches array of matches from the regex in the preg_replace
1108
 *
1109
 * @return string
1110
 */
1111
function quote_callback($matches)
1112
{
1113
	global $txt;
1114
1115
	return "\n" . '<blockquote>' . $txt['email_on'] . ': ' . date('D M j, Y', $matches[4]) . ' ' . $matches[2] . ' ' . $txt['email_wrote'] . ': ';
1116
}
1117
1118
/**
1119
 * Replace full bbc quote tags with an html blockquote version
1120
 *
1121
 * - Callback for pbe_prepare_text
1122
 * - Only changes the leading [quote], the closing /quote is not changed but
1123
 * handled back in the main function
1124
 *
1125
 * @param string[] $matches array of matches from the regex in the preg_replace
1126
 *
1127
 * @return string
1128
 */
1129
function quote_callback_2($matches)
1130
{
1131
	global $txt;
1132
1133
	return "\n" . '<blockquote>' . $txt['email_on'] . ': ' . date('D M j, Y', $matches[3]) . ' ' . $matches[2] . ' ' . $txt['email_wrote'] . ': ';
1134
}
1135
1136
/**
1137
 * Loads up the vital user information given an email address
1138
 *
1139
 * - Similar to \ElkArte\MembersList::load, loadPermissions, loadUserSettings, but only loads a
1140
 * subset of that data, enough to validate that a user can make a post to a given board.
1141
 * - Done this way to avoid over-writing user_info etc for those who are running
1142
 * this function (on behalf of the email owner, similar to profile views etc)
1143
 *
1144
 * Sets:
1145
 * - pbe['profile']
1146
 * - pbe['profile']['options']
1147
 * - pbe['user_info']
1148
 * - pbe['user_info']['permissions']
1149
 * - pbe['user_info']['groups']
1150
 *
1151
 * @package Maillist
1152
 *
1153
 * @param string $email
1154
 *
1155
 * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1156
 */
1157
function query_load_user_info($email)
1158
{
1159
	global $modSettings, $language;
1160
1161
	$db = database();
1162
1163
	if (empty($email))
1164
		return false;
1165
1166
	// Find the user who owns this email address
1167
	$request = $db->query('', '
1168
		SELECT
1169
			id_member
1170
		FROM {db_prefix}members
1171
		WHERE email_address = {string:email}
1172
		AND is_activated = {int:act}
1173
		LIMIT 1',
1174
		array(
1175
			'email' => $email,
1176
			'act' => 1,
1177
		)
1178
	);
1179
	list ($id_member) = $db->fetch_row($request);
1180
	$db->free_result($request);
1181
1182
	// No user found ... back we go
1183
	if (empty($id_member))
1184
	{
1185
		return false;
1186
	}
1187
1188
	// Load the users profile information
1189
	$pbe = array();
1190
	if (\ElkArte\MembersList::load($id_member, false, 'profile'))
1191
	{
1192
		$pbe['profile'] = \ElkArte\MembersList::get($id_member);
1193
1194
		// Load in *some* user_info data just like loadUserSettings would do
1195
		if (empty($pbe['profile']['additional_groups']))
1196
		{
1197
			$pbe['user_info']['groups'] = array(
1198
				$pbe['profile']['id_group'], $pbe['profile']['id_post_group']);
1199
		}
1200
		else
1201
		{
1202
			$pbe['user_info']['groups'] = array_merge(
1203
				array($pbe['profile']['id_group'], $pbe['profile']['id_post_group']),
1204
				explode(',', $pbe['profile']['additional_groups'])
1205
			);
1206
		}
1207
1208
		// Clean up the groups
1209
		foreach ($pbe['user_info']['groups'] as $k => $v)
1210
		{
1211
			$pbe['user_info']['groups'][$k] = (int) $v;
1212
		}
1213
		$pbe['user_info']['groups'] = array_unique($pbe['user_info']['groups']);
1214
1215
		// Load the user's general permissions....
1216
		query_load_permissions('general', $pbe);
1217
1218
		// Set the moderation warning level
1219
		$pbe['user_info']['warning'] = isset($pbe['profile']['warning']) ? $pbe['profile']['warning'] : 0;
1220
1221
		// Work out our query_see_board string for security
1222
		if (in_array(1, $pbe['user_info']['groups']))
1223
		{
1224
			$pbe['user_info']['query_see_board'] = '1=1';
1225
		}
1226
		else
1227
		{
1228
			$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)' : '') . ')';
1229
		}
1230
1231
		// Set some convenience items
1232
		$pbe['user_info']['is_admin'] = in_array(1, $pbe['user_info']['groups']) ? 1 : 0;
1233
		$pbe['user_info']['id'] = $id_member;
1234
		$pbe['user_info']['username'] = isset($pbe['profile']['member_name']) ? $pbe['profile']['member_name'] : '';
1235
		$pbe['user_info']['name'] = isset($pbe['profile']['real_name']) ? $pbe['profile']['real_name'] : '';
1236
		$pbe['user_info']['email'] = isset($pbe['profile']['email_address']) ? $pbe['profile']['email_address'] : '';
1237
		$pbe['user_info']['language'] = empty($pbe['profile']['lngfile']) || empty($modSettings['userLanguage']) ? $language : $pbe['profile']['lngfile'];
1238
	}
1239
1240
	return !empty($pbe) ? $pbe : false;
1241
}
1242
1243
/**
1244
 * Load the users permissions either general or board specific
1245
 *
1246
 * - Similar to the functions in loadPermissions()
1247
 *
1248
 * @package Maillist
1249
 * @param string $type board to load board permissions, otherwise general permissions
1250
 * @param mixed[] $pbe
1251
 * @param mixed[] $topic_info
1252
 */
1253
function query_load_permissions($type, &$pbe, $topic_info = array())
1254
{
1255
	global $modSettings;
1256
1257
	$db = database();
1258
1259
	$where_query = ($type === 'board' ? '({array_int:member_groups}) AND id_profile = {int:id_profile}' : '({array_int:member_groups})');
1260
1261
	// Load up the users board or general site permissions.
1262
	$request = $db->query('', '
1263
		SELECT
1264
			permission, add_deny
1265
		FROM {db_prefix}' . ($type === 'board' ? 'board_permissions' : 'permissions') . '
1266
		WHERE id_group IN ' . $where_query,
1267
		array(
1268
			'member_groups' => $pbe['user_info']['groups'],
1269
			'id_profile' => ($type === 'board') ? $topic_info['id_profile'] : '',
1270
		)
1271
	);
1272
	$removals = array();
1273
	$pbe['user_info']['permissions'] = array();
1274
	// While we have results, put them in our yeah or nay arrays
1275 View Code Duplication
	while ($row = $db->fetch_assoc($request))
1276
	{
1277
		if (empty($row['add_deny']))
1278
			$removals[] = $row['permission'];
1279
		else
1280
			$pbe['user_info']['permissions'][] = $row['permission'];
1281
	}
1282
	$db->free_result($request);
1283
1284
	// Remove all the permissions they shouldn't have ;)
1285
	if (!empty($modSettings['permission_enable_deny']))
1286
		$pbe['user_info']['permissions'] = array_diff($pbe['user_info']['permissions'], $removals);
1287
}
1288
1289
/**
1290
 * Fetches the senders email wrapper details
1291
 *
1292
 * - Gets the senders signature for inclusion in the email
1293
 * - Gets the senders email address and visibility flag
1294
 *
1295
 * @package Maillist
1296
 * @param string $from
1297
 * @return mixed[]
1298
 */
1299
function query_sender_wrapper($from)
1300
{
1301
	$db = database();
1302
1303
	// The signature and email visibility details
1304
	$request = $db->query('', '
1305
		SELECT
1306
			hide_email, email_address, signature
1307
		FROM {db_prefix}members
1308
		WHERE id_member  = {int:uid}
1309
			AND is_activated = {int:act}
1310
		LIMIT 1',
1311
		array(
1312
			'uid' => $from,
1313
			'act' => 1,
1314
		)
1315
	);
1316
	$result = $db->fetch_assoc($request);
1317
1318
	// Clean up the signature line
1319
	if (!empty($result['signature']))
1320
	{
1321
		$bbc_wrapper = \BBC\ParserWrapper::instance();
1322
		$result['signature'] = trim(un_htmlspecialchars(strip_tags(strtr($bbc_wrapper->parseSignature($result['signature'], false), array('</tr>' => "   \n", '<br />' => "   \n", '</div>' => "\n", '</li>' => "   \n", '&#91;' => '[', '&#93;' => ']')))));
1323
	}
1324
1325
	$db->free_result($request);
1326
1327
	return $result;
1328
}
1329
1330
/**
1331
 * Reads all the keys that have been sent to a given email id
1332
 *
1333
 * - Returns all keys sent to a user in date order
1334
 *
1335
 * @package Maillist
1336
 *
1337
 * @param string $email email address to lookup
1338
 *
1339
 * @return array
1340
 */
1341
function query_user_keys($email)
1342
{
1343
	$db = database();
1344
1345
	// Find all keys sent to this email, sorted by date
1346
	return $db->fetchQuery('
0 ignored issues
show
Bug introduced by
The method fetch_all cannot be called on $db->fetchQuery(' SELE...ray('email' => $email)) (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1347
		SELECT
1348
			message_key, message_type, message_id
1349
		FROM {db_prefix}postby_emails
1350
		WHERE email_to = {string:email}
1351
		ORDER BY time_sent DESC',
1352
		array(
1353
			'email' => $email,
1354
		)
1355
	)->fetch_all();
1356
}
1357
1358
/**
1359
 * Return the email that a given key was sent to
1360
 *
1361
 * @package Maillist
1362
 * @param \ElkArte\EmailParse $email_message
1363
 * @return string email address the key was sent to
1364
 */
1365
function query_key_owner($email_message)
1366
{
1367
	$db = database();
1368
1369
	if (!isset($email_message->message_key, $email_message->message_type, $email_message->message_id))
1370
	{
1371
		return false;
1372
	}
1373
1374
	// Check that this is a reply to an "actual" message by finding the key in the sent email table
1375
	$request = $db->query('', '
1376
		SELECT
1377
			email_to
1378
		FROM {db_prefix}postby_emails
1379
		WHERE message_key = {string:key}
1380
			AND message_type = {string:type}
1381
			AND message_id = {string:message}
1382
		LIMIT 1',
1383
		array(
1384
			'key' => $email_message->message_key,
1385
			'type' => $email_message->message_type,
1386
			'message' => $email_message->message_id,
1387
		)
1388
	);
1389
	list ($email_to) = $db->fetch_row($request);
1390
	$db->free_result($request);
1391
1392
	return $email_to;
1393
}
1394
1395
/**
1396
 * For a given type, t m or p, query the appropriate table for a given message id
1397
 *
1398
 * - If found returns the message subject
1399
 *
1400
 * @package Maillist
1401
 *
1402
 * @param int $message_id
1403
 * @param string $message_type
1404
 * @param string $email
1405
 *
1406
 * @return bool|string
1407
 */
1408
function query_load_subject($message_id, $message_type, $email)
1409
{
1410
	$db = database();
1411
1412
	$subject = '';
1413
1414
	// Load up the core topic details,
1415
	if ($message_type === 't')
1416
	{
1417
		$request = $db->query('', '
1418
			SELECT
1419
				t.id_topic, m.subject
1420
			FROM {db_prefix}topics AS t
1421
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1422
			WHERE t.id_topic = {int:id_topic}',
1423
			array(
1424
				'id_topic' => $message_id
1425
			)
1426
		);
1427
	}
1428
	elseif ($message_type === 'm')
1429
	{
1430
		$request = $db->query('', '
1431
			SELECT
1432
				m.id_topic, m.subject
1433
			FROM {db_prefix}messages AS m
1434
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1435
			WHERE m.id_msg = {int:message_id}',
1436
			array(
1437
				'message_id' => $message_id
1438
			)
1439
		);
1440
	}
1441
	elseif ($message_type === 'p')
1442
	{
1443
		// With PM's ... first get the member id based on the email
1444
		$request = $db->query('', '
1445
			SELECT
1446
				id_member
1447
			FROM {db_prefix}members
1448
			WHERE email_address = {string:email}
1449
				AND is_activated = {int:act}
1450
			LIMIT 1',
1451
			array(
1452
				'email' => $email,
1453
				'act' => 1,
1454
			)
1455
		);
1456
1457
		// Found them, now we find the PM to them with this ID
1458
		if ($db->num_rows($request) !== 0)
1459
		{
1460
			list ($id_member) = $db->fetch_row($request);
1461
			$db->free_result($request);
1462
1463
			// Now find this PM ID and make sure it was sent to this member
1464
			$request = $db->query('', '
1465
				SELECT
1466
					p.subject
1467
				FROM {db_prefix}pm_recipients AS pmr, {db_prefix}personal_messages AS p
1468
				WHERE pmr.id_pm = {int:id_pm}
1469
					AND pmr.id_member = {int:id_member}
1470
					AND p.id_pm = pmr.id_pm',
1471
				array(
1472
					'id_member' => $id_member,
1473
					'id_pm' => $message_id,
1474
				)
1475
			);
1476
		}
1477
	}
1478
	else
1479
	{
1480
		return $subject;
1481
	}
1482
1483
	// If we found the message, topic or PM, return the subject
1484
	if ($db->num_rows($request) != 0)
1485
	{
1486
		list ($subject) = $db->fetch_row($request);
1487
		$subject = pbe_clean_email_subject($subject);
1488
	}
1489
	$db->free_result($request);
1490
1491
	return $subject;
1492
}
1493
1494
/**
1495
 * Loads the important information for a given topic or pm ID
1496
 *
1497
 * - Returns array with the topic or PM details
1498
 *
1499
 * @package Maillist
1500
 *
1501
 * @param string $message_type
1502
 * @param int $message_id
1503
 * @param mixed[] $pbe
1504
 *
1505
 * @return array|bool
1506
 */
1507
function query_load_message($message_type, $message_id, $pbe)
1508
{
1509
	$db = database();
1510
1511
	// Load up the topic details
1512
	if ($message_type === 't')
1513
	{
1514
		$request = $db->query('', '
1515
			SELECT
1516
				t.id_topic, t.id_board, t.locked, t.id_member_started, t.id_last_msg,
1517
				m.subject,
1518
				b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.override_theme
1519
			FROM {db_prefix}topics AS t
1520
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1521
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
1522
			WHERE {raw:query_see_board}
1523
				AND t.id_topic = {int:message_id}',
1524
			array(
1525
				'message_id' => $message_id,
1526
				'query_see_board' => $pbe['user_info']['query_see_board'],
1527
			)
1528
		);
1529
	}
1530
	elseif ($message_type === 'm')
1531
	{
1532
		$request = $db->query('', '
1533
			SELECT
1534
				m.id_topic, m.id_board, m.subject,
1535
				t.locked, t.id_member_started, t.approved, t.id_last_msg,
1536
				b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.override_theme
1537
			FROM {db_prefix}messages AS m
1538
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
1539
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1540
			WHERE  {raw:query_see_board}
1541
				AND m.id_msg = {int:message_id}',
1542
			array(
1543
				'message_id' => $message_id,
1544
				'query_see_board' => $pbe['user_info']['query_see_board'],
1545
			)
1546
		);
1547
	}
1548
	elseif ($message_type === 'p')
1549
	{
1550
		// Load up the personal message...
1551
		$request = $db->query('', '
1552
			SELECT
1553
				p.id_pm, p.subject, p.id_member_from, p.id_pm_head
1554
			FROM {db_prefix}pm_recipients AS pm, {db_prefix}personal_messages AS p, {db_prefix}members AS mem
1555
			WHERE pm.id_pm = {int:mess_id}
1556
				AND pm.id_member = {int:id_mem}
1557
				AND p.id_pm = pm.id_pm
1558
				AND mem.id_member = p.id_member_from',
1559
			array(
1560
				'id_mem' => $pbe['profile']['id_member'],
1561
				'mess_id' => $message_id
1562
			)
1563
		);
1564
	}
1565
	$topic_info = array();
1566
	if (isset($request))
1567
	{
1568
		// Found the information, load the topic_info array with the data for this topic and board
1569
		if ($db->num_rows($request) !== 0)
1570
		{
1571
			$topic_info = $db->fetch_assoc($request);
1572
		}
1573
		$db->free_result($request);
1574
	}
1575
1576
	// Return the results or false
1577
	return !empty($topic_info) ? $topic_info : false;
1578
}
1579
1580
/**
1581
 * Loads the board_id for where a given message resides
1582
 *
1583
 * @package Maillist
1584
 *
1585
 * @param int $message_id
1586
 *
1587
 * @return int
1588
 */
1589 View Code Duplication
function query_load_board($message_id)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1590
{
1591
	$db = database();
1592
1593
	$request = $db->query('', '
1594
		SELECT
1595
			id_board
1596
		FROM {db_prefix}messages
1597
		WHERE id_msg = {int:message_id}',
1598
		array(
1599
			'message_id' => $message_id,
1600
		)
1601
	);
1602
1603
	list ($board_id) = $db->fetch_row($request);
1604
	$db->free_result($request);
1605
1606
	return $board_id === '' ? 0 : $board_id;
1607
}
1608
1609
/**
1610
 * Loads the basic board information for a given board id
1611
 *
1612
 * @package Maillist
1613
 * @param int $board_id
1614
 * @param mixed[] $pbe
1615
 * @return mixed[]
1616
 */
1617
function query_load_board_details($board_id, $pbe)
1618
{
1619
	$db = database();
1620
1621
	// To post a NEW Topic, we need certain board details
1622
	$request = $db->query('', '
1623
		SELECT
1624
			b.count_posts, b.id_profile, b.member_groups, b.id_theme, b.id_board
1625
		FROM {db_prefix}boards AS b
1626
		WHERE {raw:query_see_board} AND id_board = {int:id_board}',
1627
		array(
1628
			'id_board' => $board_id,
1629
			'query_see_board' => $pbe['user_info']['query_see_board'],
1630
		)
1631
	);
1632
	$board_info = $db->fetch_assoc($request);
1633
	$db->free_result($request);
1634
1635
	return $board_info;
1636
}
1637
1638
/**
1639
 * Loads the theme settings for the theme this user is using
1640
 *
1641
 * - Mainly used to determine a users notify settings
1642
 *
1643
 * @package Maillist
1644
 *
1645
 * @param int $id_member
1646
 * @param int $id_theme
1647
 * @param mixed[] $board_info
1648
 *
1649
 * @return array
1650
 */
1651
function query_get_theme($id_member, $id_theme, $board_info)
1652
{
1653
	global $modSettings;
1654
1655
	$db = database();
1656
1657
	// Verify the id_theme...
1658
	// Allow the board specific theme, if they are overriding.
1659
	if (!empty($board_info['id_theme']) && $board_info['override_theme'])
1660
		$id_theme = (int) $board_info['id_theme'];
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $id_theme. This often makes code more readable.
Loading history...
1661
	elseif (!empty($modSettings['knownThemes']))
1662
	{
1663
		$themes = explode(',', $modSettings['knownThemes']);
1664
1665
		if (!in_array($id_theme, $themes))
1666
			$id_theme = $modSettings['theme_guests'];
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $id_theme. This often makes code more readable.
Loading history...
1667
		else
1668
			$id_theme = (int) $id_theme;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $id_theme. This often makes code more readable.
Loading history...
1669
	}
1670
	else
1671
		$id_theme = (int) $id_theme;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $id_theme. This often makes code more readable.
Loading history...
1672
1673
	// With the theme and member, load the auto_notify variables
1674
	$result = $db->query('', '
1675
		SELECT
1676
			variable, value
1677
		FROM {db_prefix}themes
1678
		WHERE id_member = {int:id_member}
1679
			AND id_theme = {int:id_theme}',
1680
		array(
1681
			'id_theme' => $id_theme,
1682
			'id_member' => $id_member,
1683
		)
1684
	);
1685
1686
	// Put everything about this member/theme into a theme setting array
1687
	$theme_settings = array();
1688
	while ($row = $db->fetch_assoc($result))
1689
		$theme_settings[$row['variable']] = $row['value'];
1690
1691
	$db->free_result($result);
1692
1693
	return $theme_settings;
1694
}
1695
1696
/**
1697
 * Turn notifications on or off if the user has set auto notify 'when I reply'
1698
 *
1699
 * @package Maillist
1700
 * @param int $id_member
1701
 * @param int $id_board
1702
 * @param int $id_topic
1703
 * @param boolean $auto_notify
1704
 * @param mixed[] $permissions
1705
 */
1706
function query_notifications($id_member, $id_board, $id_topic, $auto_notify, $permissions)
1707
{
1708
	$db = database();
1709
1710
	// First see if they have a board notification on for this board
1711
	// so we don't set both board and individual topic notifications
1712
	$board_notify = false;
1713
	$request = $db->query('', '
1714
		SELECT
1715
			id_member
1716
		FROM {db_prefix}log_notify
1717
		WHERE id_board = {int:board_list}
1718
			AND id_member = {int:current_member}',
1719
		array(
1720
			'current_member' => $id_member,
1721
			'board_list' => $id_board,
1722
		)
1723
	);
1724
	if ($db->fetch_row($request))
1725
		$board_notify = true;
1726
	$db->free_result($request);
1727
1728
	// If they have topic notification on and not board notification then
1729
	// add this post to the notification log
1730
	if (!empty($auto_notify) && (in_array('mark_any_notify', $permissions)) && !$board_notify)
1731
	{
1732
		$db->insert('ignore',
1733
			'{db_prefix}log_notify',
1734
			array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int'),
1735
			array($id_member, $id_topic, 0),
1736
			array('id_member', 'id_topic', 'id_board')
1737
		);
1738
	}
1739
	else
1740
	{
1741
		// Make sure they don't get notified
1742
		$db->query('', '
1743
			DELETE FROM {db_prefix}log_notify
1744
			WHERE id_member = {int:current_member}
1745
				AND id_topic = {int:current_topic}',
1746
			array(
1747
				'current_member' => $id_member,
1748
				'current_topic' => $id_topic,
1749
			)
1750
		);
1751
	}
1752
}
1753
1754
/**
1755
 * Called when a pm reply has been made
1756
 *
1757
 * - Marks the PM replied to as read
1758
 * - Marks the PM replied to as replied to
1759
 * - Updates the number of unread to reflect this
1760
 *
1761
 * @package Maillist
1762
 * @param \ElkArte\EmailParse $email_message
1763
 * @param mixed[] $pbe
1764
 */
1765
function query_mark_pms($email_message, $pbe)
1766
{
1767
	$db = database();
1768
1769
	$db->query('', '
1770
		UPDATE {db_prefix}pm_recipients
1771
		SET is_read = is_read | 1
1772
		WHERE id_member = {int:id_member}
1773
			AND NOT ((is_read & 1) >= 1)
1774
			AND id_pm = {int:personal_messages}',
1775
		array(
1776
			'personal_messages' => $email_message->message_id,
1777
			'id_member' => $pbe['profile']['id_member'],
1778
		)
1779
	);
1780
1781
	// If something was marked as read, get the number of unread messages remaining.
1782
	if ($db->affected_rows() > 0)
1783
	{
1784
		$result = $db->query('', '
1785
			SELECT
1786
				labels, COUNT(*) AS num
1787
			FROM {db_prefix}pm_recipients
1788
			WHERE id_member = {int:id_member}
1789
				AND NOT ((is_read & 1) >= 1)
1790
				AND deleted = {int:is_not_deleted}
1791
			GROUP BY labels',
1792
			array(
1793
				'id_member' => $pbe['profile']['id_member'],
1794
				'is_not_deleted' => 0,
1795
			)
1796
		);
1797
		$total_unread = 0;
1798
		while ($row = $db->fetch_assoc($result))
1799
			$total_unread += $row['num'];
1800
		$db->free_result($result);
1801
1802
		// Update things for when they do come to the site
1803
		require_once(SUBSDIR . '/Members.subs.php');
1804
		updateMemberData($pbe['profile']['id_member'], array('unread_messages' => $total_unread));
1805
	}
1806
1807
	// Now mark the message as "replied to" since they just did
1808
	$db->query('', '
1809
		UPDATE {db_prefix}pm_recipients
1810
		SET is_read = is_read | 2
1811
		WHERE id_pm = {int:replied_to}
1812
			AND id_member = {int:current_member}',
1813
		array(
1814
			'current_member' => $pbe['profile']['id_member'],
1815
			'replied_to' => $email_message->message_id,
1816
		)
1817
	);
1818
}
1819
1820
/**
1821
 * Once a key has been used it is removed and can not be used again
1822
 *
1823
 * - Also removes any old keys to minimize security issues
1824
 *
1825
 * @package Maillist
1826
 * @param \ElkArte\EmailParse $email_message
1827
 */
1828
function query_key_maintenance($email_message)
1829
{
1830
	global $modSettings;
1831
1832
	$db = database();
1833
1834
	// Old keys simply expire
1835
	$days = (!empty($modSettings['maillist_key_active'])) ? $modSettings['maillist_key_active'] : 21;
1836
	$delete_old = time() - ($days * 24 * 60 * 60);
1837
1838
	// Consume the database key that was just used .. one reply per key
1839
	// but we let PM's slide, they often seem to be re re re replied to
1840
	if ($email_message->message_type !== 'p')
1841
	{
1842
		$db->query('', '
1843
			DELETE FROM {db_prefix}postby_emails
1844
			WHERE message_key = {string:key}
1845
				AND message_type = {string:type}
1846
				AND message_id = {string:message_id}',
1847
			array(
1848
				'key' => $email_message->message_key,
1849
				'type' => $email_message->message_type,
1850
				'message_id' => $email_message->message_id,
1851
			)
1852
		);
1853
	}
1854
1855
	// Since we are here lets delete any items older than delete_old days,
1856
	// if they have not responded in that time tuff
1857
	$db->query('', '
1858
		DELETE FROM {db_prefix}postby_emails
1859
		WHERE time_sent < {int:delete_old}',
1860
		array(
1861
			'delete_old' => $delete_old
1862
		)
1863
	);
1864
}
1865
1866
/**
1867
 * After a email post has been made, this updates the users information just like
1868
 * they are on the site to perform the given action.
1869
 *
1870
 * - Updates time on line
1871
 * - Updates last active
1872
 * - Updates the who's online list with the member and action
1873
 *
1874
 * @package Maillist
1875
 * @param mixed[] $pbe
1876
 * @param \ElkArte\EmailParse $email_message
1877
 * @param mixed[] $topic_info
1878
 */
1879
function query_update_member_stats($pbe, $email_message, $topic_info = array())
1880
{
1881
	$db = database();
1882
1883
	$last_login = time();
1884
	$do_delete = false;
1885
	$total_time_logged_in = empty($pbe['profile']['total_time_logged_in']) ? 0 : $pbe['profile']['total_time_logged_in'];
1886
1887
	// If they were active in the last 15 min, we don't want to run up their time
1888
	if (!empty($pbe['profile']['last_login']) && $pbe['profile']['last_login'] < (time() - (60 * 15)))
1889
	{
1890
		// not recently active so add some time to their login ....
1891
		$do_delete = true;
1892
		$total_time_logged_in = $total_time_logged_in + (60 * 10);
1893
	}
1894
1895
	// Update the members total time logged in data
1896
	require_once(SUBSDIR . '/Members.subs.php');
1897
	updateMemberData($pbe['profile']['id_member'], array('total_time_logged_in' => $total_time_logged_in, 'last_login' => $last_login));
1898
1899
	// Show they are active in the who's online list and what they have done
1900
	if ($email_message->message_type === 'm' || $email_message->message_type === 't')
1901
		$get_temp = array(
1902
			'action' => 'postbyemail',
1903
			'topic' => $topic_info['id_topic'],
1904
			'last_msg' => $topic_info['id_last_msg'],
1905
			'board' => $topic_info['id_board']
1906
		);
1907
	elseif ($email_message->message_type === 'x')
1908
		$get_temp = array(
1909
			'action' => 'topicbyemail',
1910
			'topic' => $topic_info['id'],
1911
			'board' => $topic_info['board'],
1912
		);
1913
	else
1914
		$get_temp = array(
1915
			'action' => 'pm',
1916
			'sa' => 'byemail'
1917
		);
1918
1919
	// Place the entry in to the online log so the who's online can use it
1920
	$serialized = serialize($get_temp);
1921
	$session_id = 'ip' . $pbe['profile']['member_ip'];
1922
	$member_ip = empty($pbe['profile']['member_ip']) ? 0 : $pbe['profile']['member_ip'];
1923
	$db->insert($do_delete ? 'ignore' : 'replace',
1924
		'{db_prefix}log_online',
1925
		array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'string', 'url' => 'string'),
1926
		array($session_id, $pbe['profile']['id_member'], 0, $last_login, $member_ip, $serialized),
1927
		array('session')
1928
	);
1929
}
1930
1931
/**
1932
 * Calls the necessary functions to extract and format the message so its ready for posting
1933
 *
1934
 * What it does:
1935
 *
1936
 * - Converts an email response (text or html) to a BBC equivalent via pbe_Email_to_bbc
1937
 * - Formats the email response so it looks structured and not chopped up (via pbe_fix_email_body)
1938
 *
1939
 * @package Maillist
1940
 *
1941
 * @param boolean $html
1942
 * @param \ElkArte\EmailParse $email_message
1943
 * @param mixed[] $pbe
1944
 *
1945
 * @return mixed|null|string|string[]
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1946
 */
1947
function pbe_load_text(&$html, $email_message, $pbe)
1948
{
1949 2
	if (!$html || ($html && preg_match_all('~<table.*?>~i', $email_message->body, $match) >= 2))
1950
	{
1951
		// Some mobile responses wrap everything in a table structure so use plain text
1952
		$text = $email_message->plain_body;
1953
		$html = false;
1954
	}
1955
	else
1956 2
		$text = un_htmlspecialchars($email_message->body);
1957
1958
	// Run filters now, before the data is manipulated
1959 2
	$text = pbe_filter_email_message($text);
1960
1961
	// Convert to BBC and format it so it looks like a post
1962 2
	$text = pbe_email_to_bbc($text, $html);
1963
1964 2
	$pbe['profile']['real_name'] = isset($pbe['profile']['real_name']) ? $pbe['profile']['real_name'] : '';
1965 2
	$text = pbe_fix_email_body($text, $pbe['profile']['real_name'], (empty($email_message->_converted_utf8) ? $email_message->headers['x-parameters']['content-type']['charset'] : 'UTF-8'));
1966
1967
	// Do we even have a message left to post?
1968 2
	$text = \ElkArte\Util::htmltrim($text);
1969 2
	if (empty($text))
1970
		return '';
1971
1972 2
	if ($email_message->message_type !== 'p')
1973
	{
1974
		// Prepare it for the database
1975 2
		require_once(SUBSDIR . '/Post.subs.php');
1976 2
		preparsecode($text);
1977
	}
1978
1979 2
	return $text;
1980
}
1981
1982
/**
1983
 * Attempts to create a reply post on the forum
1984
 *
1985
 * What it does:
1986
 *
1987
 * - Checks if the user has permissions to post/reply/postby email
1988
 * - Calls pbe_load_text to prepare text for the post
1989
 * - returns true if successful or false for any number of failures
1990
 *
1991
 * @package Maillist
1992
 *
1993
 * @param mixed[] $pbe array of all pbe user_info values
1994
 * @param \ElkArte\EmailParse $email_message
1995
 * @param mixed[] $topic_info
1996
 *
1997
 * @return bool
1998
 * @throws \ElkArte\Exceptions\Exception
1999
 */
2000
function pbe_create_post($pbe, $email_message, $topic_info)
2001
{
2002
	global $modSettings, $txt;
2003
2004
	// Validate they have permission to reply
2005
	$becomesApproved = true;
2006
	if (!in_array('postby_email', $pbe['user_info']['permissions']) && !$pbe['user_info']['is_admin'])
2007
		return pbe_emailError('error_permission', $email_message);
2008 View Code Duplication
	elseif ($topic_info['locked'] && !$pbe['user_info']['is_admin'] && !in_array('moderate_forum', $pbe['user_info']['permissions']))
2009
		return pbe_emailError('error_locked', $email_message);
2010
	elseif ($topic_info['id_member_started'] === $pbe['profile']['id_member'] && !$pbe['user_info']['is_admin'])
2011
	{
2012 View Code Duplication
		if ($modSettings['postmod_active'] && in_array('post_unapproved_replies_any', $pbe['user_info']['permissions']) && (!in_array('post_reply_any', $pbe['user_info']['permissions'])))
2013
			$becomesApproved = false;
2014
		elseif (!in_array('post_reply_own', $pbe['user_info']['permissions']))
2015
			return pbe_emailError('error_cant_reply', $email_message);
2016
	}
2017
	elseif (!$pbe['user_info']['is_admin'])
2018
	{
2019 View Code Duplication
		if ($modSettings['postmod_active'] && in_array('post_unapproved_replies_any', $pbe['user_info']['permissions']) && (!in_array('post_reply_any', $pbe['user_info']['permissions'])))
2020
			$becomesApproved = false;
2021
		elseif (!in_array('post_reply_any', $pbe['user_info']['permissions']))
2022
			return pbe_emailError('error_cant_reply', $email_message);
2023
	}
2024
2025
	// Convert to BBC and Format the message
2026
	$html = $email_message->html_found;
2027
	$text = pbe_load_text($html, $email_message, $pbe);
2028
	if (empty($text))
2029
		return pbe_emailError('error_no_message', $email_message);
2030
2031
	// Seriously? Attachments?
2032 View Code Duplication
	if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1)
2033
	{
2034
		if (($modSettings['postmod_active'] && in_array('post_unapproved_attachments', $pbe['user_info']['permissions'])) || in_array('post_attachment', $pbe['user_info']['permissions']))
2035
			$attachIDs = pbe_email_attachments($pbe, $email_message);
2036
		else
2037
			$text .= "\n\n" . $txt['error_no_attach'] . "\n";
2038
	}
2039
2040
	// Setup the post variables.
2041
	$msgOptions = array(
2042
		'id' => 0,
2043
		'subject' => strpos($topic_info['subject'], trim($pbe['response_prefix'])) === 0 ? $topic_info['subject'] : $pbe['response_prefix'] . $topic_info['subject'],
2044
		'smileys_enabled' => true,
2045
		'body' => $text,
2046
		'attachments' => empty($attachIDs) ? array() : $attachIDs,
2047
		'approved' => $becomesApproved
2048
	);
2049
2050
	$topicOptions = array(
2051
		'id' => $topic_info['id_topic'],
2052
		'board' => $topic_info['id_board'],
2053
		'mark_as_read' => true,
2054
		'is_approved' => !$modSettings['postmod_active'] || empty($topic_info['id_topic']) || !empty($topic_info['approved'])
2055
	);
2056
2057
	$posterOptions = array(
2058
		'id' => $pbe['profile']['id_member'],
2059
		'name' => $pbe['profile']['real_name'],
2060
		'email' => $pbe['profile']['email_address'],
2061
		'update_post_count' => empty($topic_info['count_posts']),
2062
		'ip' => $email_message->load_ip() ? $email_message->ip : $pbe['profile']['member_ip']
2063
	);
2064
2065
	// Make the post.
2066
	createPost($msgOptions, $topicOptions, $posterOptions);
2067
2068
	// We need the auto_notify setting, it may be theme based so pass the theme in use
2069
	$theme_settings = query_get_theme($pbe['profile']['id_member'], $pbe['profile']['id_theme'], $topic_info);
2070
	$auto_notify = isset($theme_settings['auto_notify']) ? $theme_settings['auto_notify'] : 0;
2071
2072
	// Turn notifications on or off
2073
	query_notifications($pbe['profile']['id_member'], $topic_info['id_board'], $topic_info['id_topic'], $auto_notify, $pbe['user_info']['permissions']);
2074
2075
	// Notify members who have notification turned on for this,
2076
	// but only if it's going to be approved
2077
	if ($becomesApproved)
2078
	{
2079
		require_once(SUBSDIR . '/Notification.subs.php');
2080
		sendNotifications($topic_info['id_topic'], 'reply', array(), array(), $pbe);
2081
	}
2082
2083
	return true;
2084
}
2085
2086
/**
2087
 * Attempts to create a PM (reply) on the forum
2088
 *
2089
 * What it does
2090
 * - Checks if the user has permissions
2091
 * - Calls pbe_load_text to prepare text for the pm
2092
 * - Calls query_mark_pms to mark things as read
2093
 * - Returns true if successful or false for any number of failures
2094
 *
2095
 * @uses sendpm to do the actual "sending"
2096
 * @package Maillist
2097
 *
2098
 * @param mixed[] $pbe array of pbe 'user_info' values
2099
 * @param \ElkArte\EmailParse $email_message
2100
 * @param mixed[] $pm_info
2101
 *
2102
 * @return bool
2103
 * @throws \ElkArte\Exceptions\Exception
2104
 */
2105
function pbe_create_pm($pbe, $email_message, $pm_info)
2106
{
2107
	global $modSettings, $txt;
2108
2109
	// Can they send?
2110 View Code Duplication
	if (!$pbe['user_info']['is_admin'] && !in_array('pm_send', $pbe['user_info']['permissions']))
2111
		return pbe_emailError('error_pm_not_allowed', $email_message);
2112
2113
	// Convert the PM to BBC and Format the message
2114
	$html = $email_message->html_found;
2115
	$text = pbe_load_text($html, $email_message, $pbe);
2116
	if (empty($text))
2117
		return pbe_emailError('error_no_message', $email_message);
2118
2119
	// If they tried to attach a file, just say sorry
2120
	if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1)
2121
		$text .= "\n\n" . $txt['error_no_pm_attach'] . "\n";
2122
2123
	// For sending the message...
2124
	$from = array(
2125
		'id' => $pbe['profile']['id_member'],
2126
		'name' => $pbe['profile']['real_name'],
2127
		'username' => $pbe['profile']['member_name']
2128
	);
2129
2130
	$pm_info['subject'] = strpos($pm_info['subject'], trim($pbe['response_prefix'])) === 0 ? $pm_info['subject'] : $pbe['response_prefix'] . $pm_info['subject'];
2131
2132
	// send/save the actual PM.
2133
	require_once(SUBSDIR . '/PersonalMessage.subs.php');
2134
	$pm_result = sendpm(array('to' => array($pm_info['id_member_from']), 'bcc' => array()), $pm_info['subject'], $text, true, $from, $pm_info['id_pm_head']);
2135
2136
	// Assuming all went well, mark this as read, replied to and update the unread counter
2137
	if (!empty($pm_result))
2138
		query_mark_pms($email_message, $pbe);
2139
2140
	return !empty($pm_result);
2141
}
2142
2143
/**
2144
 * Create a new topic by email
2145
 *
2146
 * What it does:
2147
 *
2148
 * - Called by pbe_topic to create a new topic or by pbe_main to create a new topic via a subject change
2149
 * - checks posting permissions, but requires all email validation checks are complete
2150
 * - Calls pbe_load_text to prepare text for the post
2151
 * - Calls sendNotifications to announce the new post
2152
 * - Calls query_update_member_stats to show they did something
2153
 * - Requires the pbe, email_message and board_info arrays to be populated.
2154
 *
2155
 * @uses createPost to do the actual "posting"
2156
 * @package Maillist
2157
 *
2158
 * @param mixed[] $pbe array of pbe 'user_info' values
2159
 * @param \ElkArte\EmailParse $email_message
2160
 * @param mixed[] $board_info
2161
 *
2162
 * @return bool
2163
 * @throws \ElkArte\Exceptions\Exception
2164
 */
2165
function pbe_create_topic($pbe, $email_message, $board_info)
2166
{
2167
	global $txt, $modSettings;
2168
2169
	// It does not work like that
2170
	if (empty($pbe) || empty($email_message))
2171
		return false;
2172
2173
	// We have the board info, and their permissions - do they have a right to start a new topic?
2174
	$becomesApproved = true;
2175
	if (!$pbe['user_info']['is_admin'])
2176
	{
2177
		if (!in_array('postby_email', $pbe['user_info']['permissions']))
2178
			return pbe_emailError('error_permission', $email_message);
2179
		elseif ($modSettings['postmod_active'] && in_array('post_unapproved_topics', $pbe['user_info']['permissions']) && (!in_array('post_new', $pbe['user_info']['permissions'])))
2180
			$becomesApproved = false;
2181
		elseif (!in_array('post_new', $pbe['user_info']['permissions']))
2182
			return pbe_emailError('error_cant_start', $email_message);
2183
	}
2184
2185
	// Approving all new topics by email anyway, smart admin this one is ;)
2186
	if (!empty($modSettings['maillist_newtopic_needsapproval']))
2187
		$becomesApproved = false;
2188
2189
	// First on the agenda the subject
2190
	$subject = pbe_clean_email_subject($email_message->subject);
2191
	$subject = strtr(\ElkArte\Util::htmlspecialchars($subject), array("\r" => '', "\n" => '', "\t" => ''));
0 ignored issues
show
Bug introduced by
It seems like $subject can also be of type boolean; however, ElkArte\Util::htmlspecialchars() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2192
2193
	// Not to long not to short
2194
	if (\ElkArte\Util::strlen($subject) > 100)
2195
		$subject = \ElkArte\Util::substr($subject, 0, 100);
2196
	elseif ($subject == '')
2197
		return pbe_emailError('error_no_subject', $email_message);
2198
2199
	// The message itself will need a bit of work
2200
	$html = $email_message->html_found;
2201
	$text = pbe_load_text($html, $email_message, $pbe);
2202
	if (empty($text))
2203
		return pbe_emailError('error_no_message', $email_message);
2204
2205
	// Build the attachment array if needed
2206 View Code Duplication
	if (!empty($email_message->attachments) && !empty($modSettings['maillist_allow_attachments']) && !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1)
2207
	{
2208
		if (($modSettings['postmod_active'] && in_array('post_unapproved_attachments', $pbe['user_info']['permissions'])) || in_array('post_attachment', $pbe['user_info']['permissions']))
2209
			$attachIDs = pbe_email_attachments($pbe, $email_message);
2210
		else
2211
			$text .= "\n\n" . $txt['error_no_attach'] . "\n";
2212
	}
2213
2214
	// If we get to this point ... then its time to play, lets start a topic !
2215
	require_once(SUBSDIR . '/Post.subs.php');
2216
2217
	// Setup the topic variables.
2218
	$msgOptions = array(
2219
		'id' => 0,
2220
		'subject' => $subject,
2221
		'smileys_enabled' => true,
2222
		'body' => $text,
2223
		'attachments' => empty($attachIDs) ? array() : $attachIDs,
2224
		'approved' => $becomesApproved
2225
	);
2226
2227
	$topicOptions = array(
2228
		'id' => 0,
2229
		'board' => $board_info['id_board'],
2230
		'mark_as_read' => false
2231
	);
2232
2233
	$posterOptions = array(
2234
		'id' => $pbe['profile']['id_member'],
2235
		'name' => $pbe['profile']['real_name'],
2236
		'email' => $pbe['profile']['email_address'],
2237
		'update_post_count' => empty($board_info['count_posts']),
2238
		'ip' => (isset($email_message->ip)) ? $email_message->ip : $pbe['profile']['member_ip']
2239
	);
2240
2241
	// Attempt to make the new topic.
2242
	createPost($msgOptions, $topicOptions, $posterOptions);
2243
2244
	// The auto_notify setting
2245
	$theme_settings = query_get_theme($pbe['profile']['id_member'], $pbe['profile']['id_theme'], $board_info);
2246
	$auto_notify = isset($theme_settings['auto_notify']) ? $theme_settings['auto_notify'] : 0;
2247
2248
	// Notifications on or off
2249
	query_notifications($pbe['profile']['id_member'], $board_info['id_board'], $topicOptions['id'], $auto_notify, $pbe['user_info']['permissions']);
2250
2251
	// Notify members who have notification turned on for this, (if it's approved)
2252
	if ($becomesApproved)
2253
	{
2254
		require_once(SUBSDIR . '/Notification.subs.php');
2255
		sendNotifications($topicOptions['id'], 'reply', array(), array(), $pbe);
2256
	}
2257
2258
	// Update this users info so the log shows them as active
2259
	query_update_member_stats($pbe, $email_message, $topicOptions);
2260
2261
	return true;
2262
}
2263