AddMailQueue()   B
last analyzed

Complexity

Conditions 10
Paths 44

Size

Total Lines 95
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 20.5287

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 95
rs 7.6666
c 0
b 0
f 0
cc 10
nc 44
nop 9
ccs 19
cts 36
cp 0.5278
crap 20.5287

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * This file handles tasks related to mail.
5
 * The functions in this file do NOT check permissions.
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
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
use BBC\ParserWrapper;
19
use ElkArte\Languages\Loader as LangLoader;
20
use ElkArte\Languages\Txt;
21
use ElkArte\Mail\BuildMail;
22
use ElkArte\Mail\QueueMail;
23
use ElkArte\User;
24
25
/**
26
 * This function sends an email to the specified recipient(s).
27
 *
28
 * It uses the mail_type settings and webmaster_email variable.
29
 *
30
 * @param string[]|string $to - the email(s) to send to
31
 * @param string $subject - email subject, expected to have entities, and slashes, but not be parsed
32
 * @param string $message - email body, expected to have slashes, no htmlentities
33
 * @param string|null $from = null - the address to use for replies
34
 * @param string|null $message_id = null - if specified, it will be used as local part of the Message-ID header.
35
 * @param bool $send_html = false, whether the message is HTML vs. plain text
36
 * @param int $priority = 3 Primarily used for queue priority.  0 = send now, >3 = no PBE
37
 * @param bool|null $hotmail_fix = null  ** No longer used, left only for old function calls **
38
 * @param bool $is_private - Hides to/from names when viewing the mail queue
39
 * @param string|null $from_wrapper - used to provide envelope from wrapper based on if we share users display name
40
 * @param int|null $reference - The parent topic id for use in a References header
41
 * @return bool whether the email was accepted properly.
42
 * @package Mail
43 3
 */
44
function sendmail($to, $subject, $message, $from = null, $message_id = null, $send_html = false, $priority = 3, $hotmail_fix = null, $is_private = false, $from_wrapper = null, $reference = null)
0 ignored issues
show
Unused Code introduced by
The parameter $hotmail_fix is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

44
function sendmail($to, $subject, $message, $from = null, $message_id = null, $send_html = false, $priority = 3, /** @scrutinizer ignore-unused */ $hotmail_fix = null, $is_private = false, $from_wrapper = null, $reference = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
45
{
46 3
	// Pass this on to the buildEmail and sendMail functions
47
	return (new BuildMail())->buildEmail($to, $subject, $message, $from, $message_id, $send_html, $priority, $is_private, $from_wrapper, $reference);
48
}
49 3
50
/**
51
 * Add an email to the mail queue.
52 3
 *
53
 * @param bool $flush = false
54 3
 * @param string[] $to_array = array()
55
 * @param string $subject = ''
56
 * @param string $message = ''
57
 * @param string $headers = ''
58
 * @param bool $send_html = false
59
 * @param int $priority = 3
60
 * @param bool $is_private
61 3
 * @param string|null $message_id
62
 * @return bool
63
 * @package Mail
64
 */
65 3
function AddMailQueue($flush = false, $to_array = [], $subject = '', $message = '', $headers = '', $send_html = false, $priority = 3, $is_private = false, $message_id = '')
66
{
67
	global $context;
68 3
69
	$db = database();
70
71
	static $cur_insert = [];
72 3
	static $cur_insert_len = 0;
73
74 3
	if ($cur_insert_len === 0)
75 3
	{
76
		$cur_insert = [];
77 3
	}
78
79
	// If we're flushing, make the final inserts - also if we're near the MySQL length limit!
80 1
	if (($flush || $cur_insert_len > 800000) && !empty($cur_insert))
81
	{
82
		// Only do these once.
83
		$cur_insert_len = 0;
84
85 3
		// Dump the data...
86
		$db->insert('',
87
			'{db_prefix}mail_queue',
88
			[
89
				'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string', 'subject' => 'string-255',
90
				'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255',
91 3
			],
92
			$cur_insert,
93
			['id_mail']
94 3
		);
95
96
		$cur_insert = [];
97
		$context['flush_mail'] = false;
98
	}
99
100
	// If we're flushing we're done.
101 3
	if ($flush)
102
	{
103
		$nextSendTime = time() + 10;
104 3
105
		$db->query('', '
106
			UPDATE {db_prefix}settings
107 3
			SET 
108
				value = {string:nextSendTime}
109
			WHERE variable = {string:mail_next_send}
110
				AND value = {string:no_outstanding}',
111
			[
112
				'nextSendTime' => $nextSendTime,
113
				'mail_next_send' => 'mail_next_send',
114
				'no_outstanding' => '0',
115
			]
116 3
		);
117
118
		return true;
119
	}
120
121
	// Ensure we tell obExit to flush.
122
	$context['flush_mail'] = true;
123
124
	foreach ($to_array as $to)
125
	{
126
		// Will this insert go over MySQL's limit?
127
		$this_insert_len = strlen($to) + strlen($message) + strlen($headers) + 700;
128
129
		// Insert limit of 1M (just under the safety) is reached?
130
		if ($this_insert_len + $cur_insert_len > 1000000)
131
		{
132
			// Flush out what we have so far.
133
			$db->insert('',
134
				'{db_prefix}mail_queue',
135
				[
136
					'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string', 'subject' => 'string-255',
137
					'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255',
138
				],
139
				$cur_insert,
140 3
				['id_mail']
141 3
			);
142 3
143
			// Clear this out.
144 3
			$cur_insert = [];
145
			$cur_insert_len = 0;
146
		}
147
148 3
		// Now add the current insert to the array...
149
		$cur_insert[] = [time(), (string) $to, (string) $message, (string) $subject, (string) $headers, ($send_html ? 1 : 0), $priority, (int) $is_private, (string) $message_id];
150
		$cur_insert_len += $this_insert_len;
151
	}
152
153
	// If they are using SSI there is a good chance obExit will never be called.  So lets be nice and flush it for them.
154
	if (ELK === 'SSI')
0 ignored issues
show
introduced by
The condition ELK === 'SSI' is always true.
Loading history...
155
	{
156
		return AddMailQueue(true);
157
	}
158
159
	return true;
160
}
161
162 3
/**
163 3
 * Converts out of ascii range utf-8 characters in to HTML entities.  Primarily used
164
 * to maintain 7bit compliance for plain emails
165
 *
166
 * - Character codes <= 128 are left as is
167 3
 * - Character codes U+0080 <> U+00A0 range (control) are dropped
168
 * - Callback function of preg_replace_callback
169
 *
170 3
 * @param array $match
171 3
 *
172 3
 * @return string
173
 * @package Mail
174
 *
175 3
 */
176
function entityConvert($match)
177
{
178
	$c = $match[1];
179
	$c_strlen = strlen($c);
180
	$c_ord = ord($c[0]);
181
182
	// <= 127 are standard ASCII characters
183
	if ($c_strlen === 1 && $c_ord <= 0x7F)
184
	{
185
		return $c;
186
	}
187
188
	// Drop 2 byte control characters in the  U+0080 <> U+00A0 range
189 3
	if ($c_strlen === 2 && $c_ord === 0xC2 && ord($c[1]) <= 0xA0)
190
	{
191
		return '';
192
	}
193
194
	if ($c_strlen === 2 && $c_ord >= 0xC0 && $c_ord <= 0xDF)
195 3
	{
196
		return '&#' . ((($c_ord ^ 0xC0) << 6) + (ord($c[1]) ^ 0x80)) . ';';
197
	}
198 3
199
	if ($c_strlen === 3 && $c_ord >= 0xE0 && $c_ord <= 0xEF)
200
	{
201 3
		return '&#' . ((($c_ord ^ 0xE0) << 12) + ((ord($c[1]) ^ 0x80) << 6) + (ord($c[2]) ^ 0x80)) . ';';
202 3
	}
203 3
204
	if ($c_strlen === 4 && $c_ord >= 0xF0 && $c_ord <= 0xF7)
205
	{
206 3
		return '&#' . ((($c_ord ^ 0xF0) << 18) + ((ord($c[1]) ^ 0x80) << 12) + ((ord($c[2]) ^ 0x80) << 6) + (ord($c[3]) ^ 0x80)) . ';';
207
	}
208
209
	return '';
210
}
211
212
/**
213
 * Adds the unique security key in to an email
214
 *
215
 * - adds the key in to (each) message body section
216
 * - safety net for clients that strip out the message-id and in-reply-to headers
217
 *
218
 * @param string $message
219
 * @param string $unq_head
220
 * @param string $line_break
221
 *
222
 * @return string
223
 * @package Mail
224
 *
225
 */
226
function mail_insert_key($message, $unq_head, $line_break)
227
{
228
	$regex = [];
229
	$regex['plain'] = '~^(.*?)(' . $line_break . '--ELK-[a-z0-9]{28})~s';
230 3
	$regex['qp'] = '~(Content-Transfer-Encoding: Quoted-Printable' . $line_break . $line_break . ')(.*?)(' . $line_break . '--ELK-[a-z0-9]{28})~s';
231 3
	$regex['base64'] = '~(Content-Transfer-Encoding: base64' . $line_break . $line_break . ')(.*?)(' . $line_break . '--ELK-[a-z0-9]{28})~s';
232
233
	// Append the key to the bottom of the plain section, it is always the first one
234 3
	$message = preg_replace($regex['plain'], "$1{$line_break}{$line_break}[{$unq_head}]{$line_break}$2", $message);
235 3
236 3
	// Quoted Printable section, add the key in background color so the html message looks good
237 3
	if (preg_match($regex['qp'], $message, $match))
238
	{
239
		$qp_message = quoted_printable_decode($match[2]);
240
		$qp_message = str_replace('<span class="key-holder">[]</span>', '<span style="color: #F6F6F6">[' . $unq_head . ']</span>', $qp_message);
241 3
		$qp_message = quoted_printable_encode($qp_message);
242
		$message = str_replace($match[2], $qp_message, $message);
243
	}
244
245
	// base64 the harder one as it must match RFC 2045 semantics
246 3
	// Find the sections, decode, add in the new key, and encode the new message
247
	if (preg_match($regex['base64'], $message, $match))
248
	{
249
		// un-chunk, add in our encoded key header, and re chunk.  Done so we match RFC 2045 semantics.
250
		$encoded_message = base64_decode(str_replace($line_break, '', $match[2]));
251
		$encoded_message .= $line_break . $line_break . '[' . $unq_head . ']' . $line_break;
252
		$encoded_message = base64_encode($encoded_message);
253
		$encoded_message = chunk_split($encoded_message, 76, $line_break);
254
		$message = str_replace($match[2], $encoded_message, $message);
255
	}
256
257
	return $message;
258
}
259
260
/**
261
 * Load a template from EmailTemplates language file.
262 3
 *
263
 * @param string $template
264 3
 * @param array $replacements
265 3
 * @param string $lang = ''
266
 * @param bool $html = false - If to prepare the template for HTML output (newlines to BR, <a></a> links)
267
 * @param bool $loadLang = true
268
 * @param string[] $suffixes - Additional suffixes to find and return
269
 * @param string[] $additional_files - Additional language files to load
270 3
 *
271 3
 * @return array
272
 * @throws \ElkArte\Exceptions\Exception email_no_template
273 3
 * @package Mail
274
 */
275 3
function loadEmailTemplate($template, $replacements = [], $lang = '', $html = false, $loadLang = true, $suffixes = [], $additional_files = [])
276 3
{
277 3
	global $txt, $mbname, $scripturl, $settings, $boardurl, $modSettings;
278
279
	// First things first, load up the email templates language file, if we need to.
280 3
	if ($loadLang)
281
	{
282
		$lang_loader = new LangLoader($lang, $txt, database());
283
		$lang_loader->load('EmailTemplates+MaillistTemplates');
284
285
		if (!empty($additional_files))
286
		{
287
			foreach ($additional_files as $file)
288
			{
289
				$lang_loader->load($file);
290 3
			}
291
		}
292 3
	}
293
294
	$templateSubject = $template . '_subject';
295
	$templateBody = $template . '_body';
296
	if (!isset($txt[$templateSubject]))
297 3
	{
298 3
		throw new \ElkArte\Exceptions\Exception('email_no_template', 'template', [$templateSubject]);
299
	}
300 3
301 3
	if (!isset($txt[$templateBody]))
302
	{
303
		throw new \ElkArte\Exceptions\Exception('email_no_template', 'template', [$templateBody]);
304
	}
305
306
	$ret = [
307
		'subject' => $txt[$templateSubject],
308
		'body' => $txt[$templateBody],
309
	];
310
311
	if (!empty($suffixes))
312
	{
313
		foreach ($suffixes as $key)
314
		{
315
			$ret[$key] = $txt[$template . '_' . $key];
316
		}
317
	}
318
319
	// Add in the default replacements.
320
	$replacements += [
321 3
		'FORUMNAME' => $mbname,
322
		'FORUMNAMESHORT' => (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $mbname),
323
		'EMAILREGARDS' => (!empty($modSettings['maillist_sitename_regards']) ? $modSettings['maillist_sitename_regards'] : ''),
324 3
		'FORUMURL' => $boardurl,
325
		'SCRIPTURL' => $scripturl,
326
		'THEMEURL' => $settings['theme_url'],
327
		'IMAGESURL' => $settings['images_url'],
328 3
		'DEFAULT_THEMEURL' => $settings['default_theme_url'],
329
		'REGARDS' => replaceBasicActionUrl($txt['regards_team']),
330
	];
331 3
332
	// Split the replacements up into two arrays, for use with str_replace
333
	$find = [];
334
	$replace = [];
335
336
	foreach ($replacements as $f => $r)
337
	{
338
		$find[] = '{' . $f . '}';
339
		$replace[] = $html && strpos($r, 'http') === 0 ? '<a href="' . $r . '">' . $r . '</a>' : $r;
340
	}
341 3
342
	// Do the variable replacements.
343
	foreach ($ret as $key => $val)
344 3
	{
345
		$val = str_replace($find, $replace, $val);
346
347
		// Now deal with the {USER.variable} items.
348
		$ret[$key] = preg_replace_callback('~{USER.([^}]+)}~', 'user_info_callback', $val);
349
	}
350
351
	// If we want this template to be used as HTML,
352
	$ret['body'] = $html ? templateToHtml($ret['body']) : $ret['body'];
353
354
	// Finally return the email to the caller, so they can send it out.
355
	return $ret;
356
}
357
358
/**
359
 * Used to preserve the Pre formatted look of txt template's when sending HTML
360
 *
361
 * @param $string
362
 * @return string
363
 */
364
function templateToHtml($string)
365
{
366
	$newString = preg_replace('~^-{3,40}$~m', '<hr />', $string);
367
368
	$newString = str_replace("\n", '<br />', $newString);
369
370
	return $newString ?? $string;
371
}
372
373
/**
374
 * Prepare subject and message of an email for the preview box
375
 *
376
 * Used in action_mailingcompose and RetrievePreview (Xml.controller.php)
377
 *
378
 * @package Mail
379
 */
380
function prepareMailingForPreview()
381
{
382
	global $context, $modSettings, $scripturl, $txt;
383
384
	Txt::load('Errors');
385
	require_once(SUBSDIR . '/Post.subs.php');
386
387
	$processing = [
388
		'preview_subject' => 'subject',
389
		'preview_message' => 'message'
390
	];
391
392
	// Use the default time format.
393
	User::$info->time_format = $modSettings['time_format'];
0 ignored issues
show
Bug Best Practice introduced by
The property time_format does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __set, consider adding a @property annotation.
Loading history...
394
395
	$variables = [
396
		'{$board_url}',
397
		'{$current_time}',
398
		'{$latest_member.link}',
399
		'{$latest_member.id}',
400
		'{$latest_member.name}'
401
	];
402
403
	$html = $context['send_html'];
404
405
	// We might need this in a bit
406
	$cleanLatestMember = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
407
408
	$bbc_parser = ParserWrapper::instance();
409
410
	foreach ($processing as $key => $post)
411
	{
412
		$context[$key] = !empty($_REQUEST[$post]) ? $_REQUEST[$post] : '';
413
414
		if (empty($context[$key]) && empty($_REQUEST['xml']))
415
		{
416
			$context['post_error']['messages'][] = $txt['error_no_' . $post];
417
		}
418
		elseif (!empty($_REQUEST['xml']))
419
		{
420
			continue;
421
		}
422
423
		preparsecode($context[$key]);
424
425
		// Sending as html then we convert any bbc
426
		if ($html)
427
		{
428
			$enablePostHTML = $modSettings['enablePostHTML'];
429
			$modSettings['enablePostHTML'] = $context['send_html'];
430
			$context[$key] = $bbc_parser->parseEmail($context[$key]);
431
			$modSettings['enablePostHTML'] = $enablePostHTML;
432
		}
433
434
		// Replace in all the standard things.
435
		$context[$key] = str_replace($variables,
436
			[
437
				!empty($context['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
438
				standardTime(forum_time(), false),
439
				!empty($context['send_html']) ? '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $cleanLatestMember . '</a>' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . ']' . $cleanLatestMember . '[/url]' : $cleanLatestMember),
440
				$modSettings['latestMember'],
441
				$cleanLatestMember
442
			], $context[$key]);
443
	}
444
}
445
446
/**
447
 * Callback function for load email template on subject and body
448
 * Uses capture group 1 in array
449
 *
450
 * @param array $matches
451
 * @return string
452
 * @package Mail
453
 */
454
function user_info_callback($matches)
455
{
456
	if (empty($matches[1]))
457
	{
458
		return '';
459
	}
460
461
	$use_ref = true;
462
	$ref = User::$info->toArray();
463
464
	foreach (explode('.', $matches[1]) as $index)
465
	{
466
		if ($use_ref && isset($ref[$index]))
467
		{
468
			$ref = &$ref[$index];
469
		}
470
		else
471
		{
472
			$use_ref = false;
473
			break;
474
		}
475
	}
476
477
	return $use_ref ? $ref : $matches[0];
478
}
479
480 3
/**
481
 * This function grabs the mail queue items from the database, according to the params given.
482
 *
483 3
 * @param int $start The item to start with (for pagination purposes)
484
 * @param int $items_per_page The number of items to show per page
485
 * @param string $sort A string indicating how to sort the results
486
 * @return array
487
 * @package Mail
488
 */
489
function list_getMailQueue($start, $items_per_page, $sort)
490
{
491
	global $txt;
492
493
	$db = database();
494
495
	return $db->fetchQuery('
496
		SELECT
497
			id_mail, time_sent, recipient, priority, private, subject
498
		FROM {db_prefix}mail_queue
499
		ORDER BY {raw:sort}
500
		LIMIT {int:start}, {int:items_per_page}',
501
		[
502
			'start' => $start,
503
			'sort' => $sort,
504
			'items_per_page' => $items_per_page,
505
		]
506
	)->fetch_callback(
507
		function ($row) use ($txt) {
508
			// Private PM/email subjects and similar shouldn't be shown in the mailbox area.
509
			if (!empty($row['private']))
510
			{
511 3
				$row['subject'] = $txt['personal_message'];
512
			}
513
514 3
			return $row;
515
		}
516
	);
517 3
}
518
519
/**
520
 * Returns the total count of items in the mail queue.
521
 *
522
 * @return int
523
 * @package Mail
524
 */
525
function list_getMailQueueSize()
526
{
527
	$db = database();
528
529
	// How many items do we have?
530 3
	$request = $db->query('', '
531
		SELECT 
532
			COUNT(*) AS queue_size
533
		FROM {db_prefix}mail_queue',
534
		[]
535
	);
536
	list ($mailQueueSize) = $request->fetch_row();
537
	$request->free_result();
538
539
	return $mailQueueSize;
540
}
541
542
/**
543
 * Deletes items from the mail queue
544
 *
545
 * @param int[] $items
546
 * @package Mail
547
 */
548
function deleteMailQueueItems($items)
549
{
550
	$db = database();
551
552
	$db->query('', '
553
		DELETE FROM {db_prefix}mail_queue
554
		WHERE id_mail IN ({array_int:mail_ids})',
555
		[
556
			'mail_ids' => $items,
557
		]
558
	);
559
}
560
561
/**
562
 * Get the current mail queue status
563
 *
564
 * @package Mail
565
 */
566
function list_MailQueueStatus()
567
{
568
	$db = database();
569
570
	$items = [];
571
572
	// How many items do we have?
573
	$request = $db->query('', '
574
		SELECT 
575
		    COUNT(*) AS queue_size, MIN(time_sent) AS oldest
576
		FROM {db_prefix}mail_queue',
577
		[]
578
	);
579
	list ($items['mailQueueSize'], $items['mailOldest']) = $request->fetch_row();
580
	$request->free_result();
581
582
	return $items;
583
}
584
585
/**
586
 * This function handles updates to account for failed emails.
587
 *
588
 * - It is used to keep track of failed emails attempts and next try.
589
 *
590
 * @param array $failed_emails
591
 * @package Mail
592
 */
593
function updateFailedQueue($failed_emails)
594
{
595
	global $modSettings;
596
597
	$db = database();
598
599
	// Update the failed attempts check.
600
	$db->replace(
601
		'{db_prefix}settings',
602
		['variable' => 'string', 'value' => 'string'],
603
		['mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']],
604
		['variable']
605
	);
606
607
	// If we have failed to many times, tell mail to wait a bit and try again.
608
	if ($modSettings['mail_failed_attempts'] > 5)
609
	{
610
		$db->query('', '
611
			UPDATE {db_prefix}settings
612
			SET value = {string:next_mail_send}
613
			WHERE variable = {string:mail_next_send}
614
				AND value = {string:last_send}',
615
			[
616
				'next_mail_send' => time() + 60,
617
				'mail_next_send' => 'mail_next_send',
618
				'last_send' => $modSettings['mail_next_send'],
619
			]
620
		);
621
	}
622
623
	// Add our email back to the queue, manually.
624
	$db->insert('insert',
625
		'{db_prefix}mail_queue',
626
		['time_sent' => 'int', 'recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255'],
627
		$failed_emails,
628
		['id_mail']
629
	);
630
}
631
632
/**
633
 * Updates the failed attempts to email in the database.
634
 *
635
 * - It sets mail failed attempts value to 0.
636
 *
637
 * @package Mail
638
 */
639
function updateSuccessQueue()
640
{
641
	$db = database();
642
643
	$db->query('', '
644
		UPDATE {db_prefix}settings
645
		SET value = {string:zero}
646
		WHERE variable = {string:mail_failed_attempts}',
647
		[
648
			'zero' => '0',
649
			'mail_failed_attempts' => 'mail_failed_attempts',
650
		]
651
	);
652
}
653
654
/**
655
 * Reset to 0 the next send time for emails queue.
656
 */
657
function resetNextSendTime()
658
{
659
	global $modSettings;
660
661
	$db = database();
662
663
	// Update the setting to zero, yay
664
	// ...unless someone else did.
665
	$db->query('', '
666
		UPDATE {db_prefix}settings
667
		SET value = {string:no_send}
668
		WHERE variable = {string:mail_next_send}
669
			AND value = {string:last_mail_send}',
670
		[
671
			'no_send' => '0',
672
			'mail_next_send' => 'mail_next_send',
673
			'last_mail_send' => $modSettings['mail_next_send'],
674
		]
675
	);
676
}
677
678
/**
679
 * Update the next sending time for mail queue.
680
 *
681
 * - By default, move it 10 seconds for lower per mail_period_limits
682
 * and 5 seconds for larger mail_period_limits
683
 * - Requires an affected row
684
 *
685
 * @return int|bool
686
 * @package Mail
687
 */
688
function updateNextSendTime()
689
{
690
	global $modSettings;
691
692
	$db = database();
693
694
	// Set a delay based on the per minute limit (mail_period_limit)
695
	$delay = !empty($modSettings['mail_queue_delay'])
696
		? $modSettings['mail_queue_delay']
697
		: (!empty($modSettings['mail_period_limit']) && $modSettings['mail_period_limit'] <= 5 ? 10 : 5);
698
699
	$request = $db->query('', '
700
		UPDATE {db_prefix}settings
701
		SET value = {string:next_mail_send}
702
		WHERE variable = {string:mail_next_send}
703
			AND value = {string:last_send}',
704
		[
705
			'next_mail_send' => time() + $delay,
706
			'mail_next_send' => 'mail_next_send',
707
			'last_send' => $modSettings['mail_next_send'],
708
		]
709
	);
710
	if ($request->affected_rows() === 0)
711
	{
712
		return false;
713
	}
714
715
	return (int) $delay;
716
}
717
718
/**
719
 * Retrieve all details from the database on the next emails in the queue
720
 *
721
 * - Will fetch the next batch number of queued emails, sorted by priority
722
 *
723
 * @param int $number
724
 * @return array
725
 * @package Mail
726
 */
727
function emailsInfo($number)
728
{
729
	$db = database();
730
	$ids = [];
731
	$emails = [];
732
733
	// Get the next $number emails, with all that's to know about them and one more.
734
	$db->fetchQuery('
735
		SELECT /*!40001 SQL_NO_CACHE */ 
736
			id_mail, recipient, body, subject, headers, send_html, time_sent, priority, private, message_id
737
		FROM {db_prefix}mail_queue
738
		ORDER BY priority ASC, id_mail ASC
739
		LIMIT ' . $number,
740
		[]
741
	)->fetch_callback(
742
		function ($row) use (&$ids, &$emails) {
743
			// Just get the data and go.
744
			$ids[] = $row['id_mail'];
745
			$emails[] = [
746
				'to' => $row['recipient'],
747
				'body' => $row['body'],
748
				'subject' => $row['subject'],
749
				'headers' => $row['headers'],
750
				'send_html' => $row['send_html'],
751
				'time_sent' => $row['time_sent'],
752
				'priority' => $row['priority'],
753
				'private' => $row['private'],
754
				'message_id' => $row['message_id'],
755
			];
756
		}
757
	);
758
759
	return [$ids, $emails];
760
}
761
762
/**
763
 * Sends a group of emails from the mail queue.
764
 *
765
 * - Allows a batch of emails to be released every 5 to 10 seconds (based on per period limits)
766
 * - If batch size is not set, will determine a size such that it sends in 1/2 the period (buffer)
767
 *
768
 * @param int|bool $batch_size = false the number to send each loop
769
 * @param bool $override_limit = false bypassing our limit flaf
770
 * @param bool $force_send = false
771
 * @return bool
772
 * @package Mail
773
 */
774
function reduceMailQueue($batch_size = false, $override_limit = false, $force_send = false)
775
{
776
	return (new QueueMail())->reduceMailQueue($batch_size, $override_limit, $force_send);
777
}
778
779
/**
780
 * This function finds email address and few other details of the
781
 * poster of a certain message.
782
 *
783
 * @param int $id_msg the id of a message
784
 * @param int $topic_id the topic the message belongs to
785
 * @return array the poster's details
786
 * @todo very similar to mailFromMessage
787
 * @package Mail
788
 */
789
function posterDetails($id_msg, $topic_id)
790
{
791
	$db = database();
792
793
	$request = $db->query('', '
794
		SELECT 
795
			m.id_msg, m.id_topic, m.id_board, m.subject, m.body, m.id_member AS id_poster, m.poster_name, mem.real_name
796
		FROM {db_prefix}messages AS m
797
			LEFT JOIN {db_prefix}members AS mem ON (m.id_member = mem.id_member)
798
		WHERE m.id_msg = {int:id_msg}
799
			AND m.id_topic = {int:current_topic}
800
		LIMIT 1',
801
		[
802
			'current_topic' => $topic_id,
803
			'id_msg' => $id_msg,
804
		]
805
	);
806
	$message = $request->fetch_assoc();
807
	$request->free_result();
808
809
	return $message;
810
}
811
812
/**
813
 * Little utility function to calculate how long ago a time was.
814
 *
815
 * @param int|double $time_diff
816
 * @return string
817
 * @package Mail
818
 */
819
function time_since($time_diff)
820
{
821
	global $txt;
822
823
	if ($time_diff < 0)
824
	{
825
		$time_diff = 0;
826
	}
827
828
	// Just do a bit of an if fest...
829
	if ($time_diff > 86400)
830
	{
831
		$days = round($time_diff / 86400, 1);
832
833
		return sprintf($days === 1.0 ? $txt['mq_day'] : $txt['mq_days'], $time_diff / 86400);
834
	}
835
836
	// Hours?
837
	if ($time_diff > 3600)
838
	{
839
		$hours = round($time_diff / 3600, 1);
840
841
		return sprintf($hours === 1.0 ? $txt['mq_hour'] : $txt['mq_hours'], $hours);
842
	}
843
844
	// Minutes?
845
	if ($time_diff > 60)
846
	{
847
		$minutes = (int) ($time_diff / 60);
848
849
		return sprintf($minutes === 1 ? $txt['mq_minute'] : $txt['mq_minutes'], $minutes);
850
	}
851
852
	// Otherwise must be second
853
	return sprintf($time_diff === 1 ? $txt['mq_second'] : $txt['mq_seconds'], $time_diff);
854
}
855