|
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\Themes\ThemeLoader; |
|
20
|
|
|
use ElkArte\User; |
|
21
|
|
|
use ElkArte\Languages\Loader and LangLoader; |
|
|
|
|
|
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* This function sends an email to the specified recipient(s). |
|
25
|
|
|
* |
|
26
|
|
|
* It uses the mail_type settings and webmaster_email variable. |
|
27
|
|
|
* |
|
28
|
|
|
* @param string[]|string $to - the email(s) to send to |
|
29
|
|
|
* @param string $subject - email subject, expected to have entities, and slashes, but not be parsed |
|
30
|
|
|
* @param string $message - email body, expected to have slashes, no htmlentities |
|
31
|
|
|
* @param string|null $from = null - the address to use for replies |
|
32
|
|
|
* @param string|null $message_id = null - if specified, it will be used as local part of the Message-ID header. |
|
33
|
|
|
* @param bool $send_html = false, whether or not the message is HTML vs. plain text |
|
34
|
|
|
* @param int $priority = 3 |
|
35
|
|
|
* @param bool|null $hotmail_fix = null |
|
36
|
|
|
* @param bool $is_private |
|
37
|
|
|
* @param string|null $from_wrapper - used to provide envelope from wrapper based on if we sharing a users display name |
|
38
|
|
|
* @param int|null $reference - The parent topic id for use in a References header |
|
39
|
|
|
* @return bool whether or not the email was accepted properly. |
|
40
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
41
|
|
|
* @package Mail |
|
42
|
|
|
*/ |
|
43
|
3 |
|
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) |
|
44
|
|
|
{ |
|
45
|
|
|
global $webmaster_email, $context, $modSettings, $txt, $scripturl, $boardurl; |
|
46
|
3 |
|
|
|
47
|
|
|
// Use sendmail if it's set or if no SMTP server is set. |
|
48
|
|
|
$use_sendmail = empty($modSettings['mail_type']) || $modSettings['smtp_host'] == ''; |
|
49
|
3 |
|
|
|
50
|
|
|
// Using maillist styles and this message qualifies (priority 3 and below only (4 = digest, 5 = newsletter)) |
|
51
|
|
|
$maillist = !empty($modSettings['maillist_enabled']) && $from_wrapper !== null && $message_id !== null && $priority < 4 && empty($modSettings['mail_no_message_id']); |
|
52
|
3 |
|
|
|
53
|
|
|
// Line breaks need to be \r\n only in windows or for SMTP. |
|
54
|
3 |
|
$line_break = detectServer()->is('windows') || !$use_sendmail ? "\r\n" : "\n"; |
|
55
|
|
|
|
|
56
|
|
|
if ($message_id !== null && isset($message_id[0]) && in_array($message_id[0], array('m', 'p', 't'))) |
|
57
|
|
|
{ |
|
58
|
|
|
$message_type = $message_id[0]; |
|
59
|
|
|
$message_id = substr($message_id, 1); |
|
60
|
|
|
} |
|
61
|
3 |
|
else |
|
62
|
|
|
{ |
|
63
|
|
|
$message_type = 'm'; |
|
64
|
|
|
} |
|
65
|
3 |
|
|
|
66
|
|
|
// So far so good. |
|
67
|
|
|
$mail_result = true; |
|
68
|
3 |
|
|
|
69
|
|
|
// If the recipient list isn't an array, make it one. |
|
70
|
|
|
$to_array = is_array($to) ? $to : array($to); |
|
71
|
|
|
|
|
72
|
3 |
|
// Once upon a time, Hotmail could not interpret non-ASCII mails. |
|
73
|
|
|
// In honour of those days, it's still called the 'hotmail fix'. |
|
74
|
3 |
|
if ($hotmail_fix === null) |
|
75
|
3 |
|
{ |
|
76
|
|
|
$hotmail_to = array(); |
|
77
|
3 |
|
foreach ($to_array as $i => $to_address) |
|
78
|
|
|
{ |
|
79
|
|
|
if (preg_match('~@(att|comcast|bellsouth)\.[a-zA-Z\.]{2,6}$~i', $to_address) === 1) |
|
80
|
1 |
|
{ |
|
81
|
|
|
$hotmail_to[] = $to_address; |
|
82
|
|
|
$to_array = array_diff($to_array, array($to_address)); |
|
83
|
|
|
} |
|
84
|
|
|
} |
|
85
|
3 |
|
|
|
86
|
|
|
// Call this function recursively for the hotmail addresses. |
|
87
|
|
|
if (!empty($hotmail_to)) |
|
88
|
|
|
{ |
|
89
|
|
|
$mail_result = sendmail($hotmail_to, $subject, $message, $from, $message_type . $message_id, $send_html, $priority, true, $is_private, $from_wrapper, $reference); |
|
90
|
|
|
} |
|
91
|
3 |
|
|
|
92
|
|
|
// The remaining addresses no longer need the fix. |
|
93
|
|
|
$hotmail_fix = false; |
|
94
|
3 |
|
|
|
95
|
|
|
// No other addresses left? Return instantly. |
|
96
|
|
|
if (empty($to_array)) |
|
97
|
|
|
{ |
|
98
|
|
|
return $mail_result; |
|
99
|
|
|
} |
|
100
|
|
|
} |
|
101
|
3 |
|
|
|
102
|
|
|
// Get rid of entities. |
|
103
|
|
|
$subject = un_htmlspecialchars($subject); |
|
104
|
3 |
|
|
|
105
|
|
|
// Make the message use the proper line breaks. |
|
106
|
|
|
$message = str_replace(array("\r", "\n"), array('', $line_break), $message); |
|
107
|
3 |
|
|
|
108
|
|
|
// Make sure hotmail mails are sent as HTML so that HTML entities work. |
|
109
|
|
|
if ($hotmail_fix && !$send_html) |
|
110
|
|
|
{ |
|
111
|
|
|
$send_html = true; |
|
112
|
|
|
$message = strtr($message, array($line_break => '<br />' . $line_break)); |
|
113
|
|
|
$message = preg_replace('~(' . preg_quote($scripturl, '~') . '(?:[?/][\w\-_%\.,\?&;=#]+)?)~', '<a href="$1">$1</a>', $message); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
3 |
|
// Requirements (draft) for MLM to Support Basic DMARC Compliance |
|
117
|
|
|
// http://www.dmarc.org/supplemental/mailman-project-mlm-dmarc-reqs.html |
|
118
|
|
|
if ($maillist && $from !== null && $from_wrapper !== null) |
|
119
|
|
|
{ |
|
120
|
|
|
// Be sure there is never an email in the from name if using maillist styles |
|
121
|
|
|
$dmarc_from = $from; |
|
122
|
|
|
if (filter_var($dmarc_from, FILTER_VALIDATE_EMAIL)) |
|
123
|
|
|
{ |
|
124
|
|
|
$dmarc_from = str_replace(strstr($dmarc_from, '@'), '', $dmarc_from); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
// Add in the 'via' if desired, helps prevent email clients from learning/replacing legit names/emails |
|
128
|
|
|
if (!empty($modSettings['maillist_sitename']) && empty($modSettings['dmarc_spec_standard'])) |
|
129
|
|
|
// @memo (2014) "via" is still a draft, and it's not yet clear if it will be localized or not. |
|
130
|
|
|
// To play safe, we are keeping it hard-coded, but the string is available for translation. |
|
131
|
|
|
{ |
|
132
|
|
|
$from = $dmarc_from . ' ' . /* $txt['via'] */ |
|
133
|
|
|
'via' . ' ' . $modSettings['maillist_sitename']; |
|
134
|
|
|
} |
|
135
|
|
|
else |
|
136
|
|
|
{ |
|
137
|
|
|
$from = $dmarc_from; |
|
138
|
|
|
} |
|
139
|
|
|
} |
|
140
|
3 |
|
|
|
141
|
3 |
|
// Take care of from / subject encodings |
|
142
|
3 |
|
list (, $from_name, $from_encoding) = mimespecialchars(addcslashes($from !== null ? $from : (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']), '<>()\'\\"'), true, $hotmail_fix, $line_break); |
|
143
|
|
|
list (, $subject) = mimespecialchars($subject, true, $hotmail_fix, $line_break); |
|
144
|
3 |
|
if ($from_encoding !== 'base64') |
|
145
|
|
|
{ |
|
146
|
|
|
$from_name = '"' . $from_name . '"'; |
|
147
|
|
|
} |
|
148
|
3 |
|
|
|
149
|
|
|
// Construct the from / replyTo mail headers, based on if we showing a users name |
|
150
|
|
|
if ($from_wrapper !== null) |
|
151
|
|
|
{ |
|
152
|
|
|
$headers = 'From: ' . $from_name . ' <' . $from_wrapper . '>' . $line_break; |
|
153
|
|
|
|
|
154
|
|
|
// If they reply where is it going to be sent? |
|
155
|
|
|
$headers .= 'Reply-To: "' . (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']) . '" <' . (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '>' . $line_break; |
|
156
|
|
|
if ($reference !== null) |
|
157
|
|
|
{ |
|
158
|
|
|
$headers .= 'References: <' . $reference . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>' . $line_break; |
|
159
|
|
|
} |
|
160
|
|
|
} |
|
161
|
|
|
else |
|
162
|
3 |
|
{ |
|
163
|
3 |
|
// Standard ElkArte headers |
|
164
|
|
|
$headers = 'From: ' . $from_name . ' <' . (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from']) . '>' . $line_break; |
|
165
|
|
|
$headers .= ($from !== null && strpos($from, '@') !== false) ? 'Reply-To: <' . $from . '>' . $line_break : ''; |
|
166
|
|
|
} |
|
167
|
3 |
|
|
|
168
|
|
|
// We'll need this later for the envelope fix, too, so keep it |
|
169
|
|
|
$return_path = (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])); |
|
170
|
3 |
|
|
|
171
|
3 |
|
// Return path, date, mailer |
|
172
|
3 |
|
$headers .= 'Return-Path: ' . $return_path . $line_break; |
|
173
|
|
|
$headers .= 'Date: ' . gmdate('D, d M Y H:i:s') . ' -0000' . $line_break; |
|
174
|
|
|
$headers .= 'X-Mailer: ELK' . $line_break; |
|
175
|
3 |
|
|
|
176
|
|
|
// For maillist, digests or newsletters we include a few more headers for compliance |
|
177
|
|
|
if ($maillist || $priority > 3) |
|
178
|
|
|
{ |
|
179
|
|
|
// Lets try to avoid auto replies |
|
180
|
|
|
$headers .= 'X-Auto-Response-Suppress: All' . $line_break; |
|
181
|
|
|
$headers .= 'Auto-Submitted: auto-generated' . $line_break; |
|
182
|
|
|
|
|
183
|
|
|
// Indicate its a list server to avoid spam tagging and to help client filters |
|
184
|
|
|
// http://www.ietf.org/rfc/rfc2369.txt |
|
185
|
|
|
$headers .= 'List-Id: <' . (!empty($modSettings['maillist_sitename_address']) ? $modSettings['maillist_sitename_address'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '>' . $line_break; |
|
186
|
|
|
$headers .= 'List-Unsubscribe: <' . $boardurl . '/index.php?action=profile;area=notification>' . $line_break; |
|
187
|
|
|
$headers .= 'List-Owner: <mailto:' . (!empty($modSettings['maillist_sitename_help']) ? $modSettings['maillist_sitename_help'] : (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'])) . '> (' . (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $context['forum_name']) . ')' . $line_break; |
|
188
|
|
|
} |
|
189
|
3 |
|
|
|
190
|
|
|
// Pass this to the integration before we start modifying the output -- it'll make it easier later. |
|
191
|
|
|
if (in_array(false, call_integration_hook('integrate_outgoing_email', array(&$subject, &$message, &$headers)), true)) |
|
192
|
|
|
{ |
|
193
|
|
|
return false; |
|
194
|
|
|
} |
|
195
|
3 |
|
|
|
196
|
|
|
// Save the original message... |
|
197
|
|
|
$orig_message = $message; |
|
198
|
3 |
|
|
|
199
|
|
|
// The mime boundary separates the different alternative versions. |
|
200
|
|
|
$mime_boundary = 'ELK-' . md5($message . time()); |
|
201
|
3 |
|
|
|
202
|
3 |
|
// Using mime, as it allows to send a plain unencoded alternative. |
|
203
|
3 |
|
$headers .= 'Mime-Version: 1.0' . $line_break; |
|
204
|
|
|
$headers .= 'Content-Type: multipart/alternative; boundary="' . $mime_boundary . '"' . $line_break; |
|
205
|
|
|
$headers .= 'Content-Transfer-Encoding: 7bit' . $line_break; |
|
206
|
3 |
|
|
|
207
|
|
|
// Sending HTML? Let's plop in some basic stuff, then. |
|
208
|
|
|
if ($send_html) |
|
209
|
|
|
{ |
|
210
|
|
|
$no_html_message = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $orig_message); |
|
211
|
|
|
$no_html_message = un_htmlspecialchars(strip_tags(strtr($no_html_message, array('</title>' => $line_break)))); |
|
212
|
|
|
|
|
213
|
|
|
// But, then, dump it and use a plain one for dinosaur clients. |
|
214
|
|
|
list (, $plain_message) = mimespecialchars($no_html_message, false, true, $line_break); |
|
215
|
|
|
$message = $plain_message . $line_break . '--' . $mime_boundary . $line_break; |
|
216
|
|
|
|
|
217
|
|
|
// This is the plain text version. Even if no one sees it, we need it for spam checkers. |
|
218
|
|
|
list ($charset, $plain_charset_message, $encoding) = mimespecialchars($no_html_message, false, false, $line_break); |
|
219
|
|
|
$message .= 'Content-Type: text/plain; charset=' . $charset . $line_break; |
|
220
|
|
|
$message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break; |
|
221
|
|
|
$message .= $plain_charset_message . $line_break . '--' . $mime_boundary . $line_break; |
|
222
|
|
|
|
|
223
|
|
|
// This is the actual HTML message, prim and proper. If we wanted images, they could be inlined here (with multipart/related, etc.) |
|
224
|
|
|
list ($charset, $html_message, $encoding) = mimespecialchars($orig_message, false, $hotmail_fix, $line_break); |
|
225
|
|
|
$message .= 'Content-Type: text/html; charset=' . $charset . $line_break; |
|
226
|
|
|
$message .= 'Content-Transfer-Encoding: ' . ($encoding == '' ? '7bit' : $encoding) . $line_break . $line_break; |
|
227
|
|
|
$message .= $html_message . $line_break . '--' . $mime_boundary . '--'; |
|
228
|
|
|
} |
|
229
|
|
|
// Text is good too. |
|
230
|
3 |
|
else |
|
231
|
3 |
|
{ |
|
232
|
|
|
// Send a plain message first, for the older web clients. |
|
233
|
|
|
$plain_message = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $orig_message); |
|
234
|
3 |
|
list (, $plain_message) = mimespecialchars($plain_message, false, true, $line_break); |
|
235
|
3 |
|
|
|
236
|
3 |
|
$message = $plain_message . $line_break . '--' . $mime_boundary . $line_break; |
|
237
|
3 |
|
|
|
238
|
|
|
// Now add an encoded message using the forum's character set. |
|
239
|
|
|
list ($charset, $encoded_message, $encoding) = mimespecialchars($orig_message, false, false, $line_break); |
|
240
|
|
|
$message .= 'Content-Type: text/plain; charset=' . $charset . $line_break; |
|
241
|
3 |
|
$message .= 'Content-Transfer-Encoding: ' . $encoding . $line_break . $line_break; |
|
242
|
|
|
$message .= $encoded_message . $line_break . '--' . $mime_boundary . '--'; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
// Are we using the mail queue, if so this is where we butt in... |
|
246
|
3 |
|
if (!empty($modSettings['mail_queue']) && $priority != 0) |
|
247
|
|
|
{ |
|
248
|
|
|
return AddMailQueue(false, $to_array, $subject, $message, $headers, $send_html, $priority, $is_private, $message_type . $message_id); |
|
249
|
|
|
} |
|
250
|
|
|
// If it's a priority mail, send it now - note though that this should NOT be used for sending many at once. |
|
251
|
|
|
elseif (!empty($modSettings['mail_queue']) && !empty($modSettings['mail_period_limit'])) |
|
252
|
|
|
{ |
|
253
|
|
|
list ($last_mail_time, $mails_this_minute) = @explode('|', $modSettings['mail_recent']); |
|
254
|
|
|
if (empty($mails_this_minute) || time() > $last_mail_time + 60) |
|
255
|
|
|
{ |
|
256
|
|
|
$new_queue_stat = time() . '|' . 1; |
|
257
|
|
|
} |
|
258
|
|
|
else |
|
259
|
|
|
{ |
|
260
|
|
|
$new_queue_stat = $last_mail_time . '|' . ((int) $mails_this_minute + 1); |
|
261
|
|
|
} |
|
262
|
3 |
|
|
|
263
|
|
|
updateSettings(array('mail_recent' => $new_queue_stat)); |
|
264
|
3 |
|
} |
|
265
|
3 |
|
|
|
266
|
|
|
// SMTP or sendmail? |
|
267
|
|
|
if ($use_sendmail) |
|
268
|
|
|
{ |
|
269
|
|
|
$subject = strtr($subject, array("\r" => '', "\n" => '')); |
|
270
|
3 |
|
if (!empty($modSettings['mail_strip_carriage'])) |
|
271
|
3 |
|
{ |
|
272
|
|
|
$message = strtr($message, array("\r" => '')); |
|
273
|
3 |
|
$headers = strtr($headers, array("\r" => '')); |
|
274
|
|
|
} |
|
275
|
3 |
|
$sent = array(); |
|
276
|
3 |
|
$need_break = substr($headers, -1) === "\n" || substr($headers, -1) === "\r" ? false : true; |
|
277
|
3 |
|
|
|
278
|
|
|
foreach ($to_array as $key => $to) |
|
279
|
|
|
{ |
|
280
|
3 |
|
$unq_id = ''; |
|
281
|
|
|
$unq_head = ''; |
|
282
|
|
|
$unq_head_array = array(); |
|
283
|
|
|
|
|
284
|
|
|
// If we are using the post by email functions, then we generate "reply to mail" security keys |
|
285
|
|
|
if ($maillist && !empty($message_id) && $priority != 4) |
|
286
|
|
|
{ |
|
287
|
|
|
$unq_head_array[0] = md5($boardurl . microtime() . rand()); |
|
288
|
|
|
$unq_head_array[1] = $message_type; |
|
289
|
|
|
$unq_head_array[2] = $message_id; |
|
290
|
3 |
|
$unq_head = $unq_head_array[0] . '-' . $unq_head_array[1] . $unq_head_array[2]; |
|
291
|
|
|
$unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . $unq_head . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>'; |
|
292
|
3 |
|
$message = mail_insert_key($message, $unq_head, $line_break); |
|
293
|
|
|
} |
|
294
|
|
|
elseif (empty($modSettings['mail_no_message_id'])) |
|
295
|
|
|
{ |
|
296
|
|
|
$unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . md5($boardurl . microtime()) . '-' . $message_id . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>'; |
|
297
|
3 |
|
} |
|
298
|
3 |
|
|
|
299
|
|
|
// This is frequently not set, or not set according to the needs of PBE and bounce detection |
|
300
|
3 |
|
// We have to use ini_set, since "-f <address>" doesn't work on windows systems, so we need both |
|
301
|
3 |
|
$old_return = ini_set('sendmail_from', $return_path); |
|
302
|
|
|
if (!mail(strtr($to, array("\r" => '', "\n" => '')), $subject, $message, $headers . $unq_id, '-f ' . $return_path)) |
|
303
|
|
|
{ |
|
304
|
|
|
\ElkArte\Errors\Errors::instance()->log_error(sprintf($txt['mail_send_unable'], $to)); |
|
305
|
|
|
$mail_result = false; |
|
306
|
|
|
} |
|
307
|
|
|
else |
|
308
|
|
|
{ |
|
309
|
|
|
// Keep our post via email log |
|
310
|
|
|
if (!empty($unq_head)) |
|
311
|
|
|
{ |
|
312
|
|
|
$unq_head_array[] = time(); |
|
313
|
|
|
$unq_head_array[] = $to; |
|
314
|
|
|
$sent[] = $unq_head_array; |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
// Track total emails sent |
|
318
|
|
|
if (!empty($modSettings['trackStats'])) |
|
319
|
|
|
{ |
|
320
|
|
|
trackStats(array('email' => '+')); |
|
321
|
3 |
|
} |
|
322
|
|
|
} |
|
323
|
|
|
|
|
324
|
3 |
|
// Put it back |
|
325
|
|
|
ini_set('sendmail_from', $old_return); |
|
326
|
|
|
|
|
327
|
|
|
// Wait, wait, I'm still sending here! |
|
328
|
3 |
|
detectServer()->setTimeLimit(300); |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
3 |
|
// Log each email that we sent so they can be replied to |
|
332
|
|
|
if (!empty($sent)) |
|
333
|
|
|
{ |
|
334
|
|
|
require_once(SUBSDIR . '/Maillist.subs.php'); |
|
335
|
|
|
log_email($sent); |
|
336
|
|
|
} |
|
337
|
|
|
} |
|
338
|
|
|
else |
|
339
|
|
|
// SMTP protocol it is |
|
340
|
|
|
{ |
|
341
|
3 |
|
$mail_result = $mail_result && smtp_mail($to_array, $subject, $message, $headers, $priority, $message_type . $message_id); |
|
342
|
|
|
} |
|
343
|
|
|
|
|
344
|
3 |
|
// Clear out the stat cache. |
|
345
|
|
|
trackStats(); |
|
346
|
|
|
|
|
347
|
|
|
// Everything go smoothly? |
|
348
|
|
|
return $mail_result; |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Add an email to the mail queue. |
|
353
|
|
|
* |
|
354
|
|
|
* @param bool $flush = false |
|
355
|
|
|
* @param string[] $to_array = array() |
|
356
|
|
|
* @param string $subject = '' |
|
357
|
|
|
* @param string $message = '' |
|
358
|
|
|
* @param string $headers = '' |
|
359
|
|
|
* @param bool $send_html = false |
|
360
|
|
|
* @param int $priority = 3 |
|
361
|
|
|
* @param bool $is_private |
|
362
|
|
|
* @param string|null $message_id |
|
363
|
|
|
* @return bool |
|
364
|
|
|
* @package Mail |
|
365
|
|
|
*/ |
|
366
|
|
|
function AddMailQueue($flush = false, $to_array = array(), $subject = '', $message = '', $headers = '', $send_html = false, $priority = 3, $is_private = false, $message_id = '') |
|
367
|
|
|
{ |
|
368
|
|
|
global $context; |
|
369
|
|
|
|
|
370
|
|
|
$db = database(); |
|
371
|
|
|
|
|
372
|
|
|
static $cur_insert = array(); |
|
373
|
|
|
static $cur_insert_len = 0; |
|
374
|
|
|
|
|
375
|
|
|
if ($cur_insert_len == 0) |
|
376
|
|
|
{ |
|
377
|
|
|
$cur_insert = array(); |
|
378
|
|
|
} |
|
379
|
|
|
|
|
380
|
|
|
// If we're flushing, make the final inserts - also if we're near the MySQL length limit! |
|
381
|
|
|
if (($flush || $cur_insert_len > 800000) && !empty($cur_insert)) |
|
382
|
|
|
{ |
|
383
|
|
|
// Only do these once. |
|
384
|
|
|
$cur_insert_len = 0; |
|
385
|
|
|
|
|
386
|
|
|
// Dump the data... |
|
387
|
|
|
$db->insert('', |
|
388
|
|
|
'{db_prefix}mail_queue', |
|
389
|
|
|
array( |
|
390
|
|
|
'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string', 'subject' => 'string-255', |
|
391
|
|
|
'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255', |
|
392
|
|
|
), |
|
393
|
|
|
$cur_insert, |
|
394
|
|
|
array('id_mail') |
|
395
|
|
|
); |
|
396
|
|
|
|
|
397
|
|
|
$cur_insert = array(); |
|
398
|
|
|
$context['flush_mail'] = false; |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
// If we're flushing we're done. |
|
402
|
|
|
if ($flush) |
|
403
|
|
|
{ |
|
404
|
|
|
$nextSendTime = time() + 10; |
|
405
|
|
|
|
|
406
|
|
|
$db->query('', ' |
|
407
|
|
|
UPDATE {db_prefix}settings |
|
408
|
|
|
SET |
|
409
|
|
|
value = {string:nextSendTime} |
|
410
|
|
|
WHERE variable = {string:mail_next_send} |
|
411
|
|
|
AND value = {string:no_outstanding}', |
|
412
|
|
|
array( |
|
413
|
|
|
'nextSendTime' => $nextSendTime, |
|
414
|
|
|
'mail_next_send' => 'mail_next_send', |
|
415
|
|
|
'no_outstanding' => '0', |
|
416
|
|
|
) |
|
417
|
|
|
); |
|
418
|
|
|
|
|
419
|
|
|
return true; |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
// Ensure we tell obExit to flush. |
|
423
|
|
|
$context['flush_mail'] = true; |
|
424
|
|
|
|
|
425
|
|
|
foreach ($to_array as $to) |
|
426
|
|
|
{ |
|
427
|
|
|
// Will this insert go over MySQL's limit? |
|
428
|
|
|
$this_insert_len = strlen($to) + strlen($message) + strlen($headers) + 700; |
|
429
|
|
|
|
|
430
|
|
|
// Insert limit of 1M (just under the safety) is reached? |
|
431
|
|
|
if ($this_insert_len + $cur_insert_len > 1000000) |
|
432
|
|
|
{ |
|
433
|
|
|
// Flush out what we have so far. |
|
434
|
|
|
$db->insert('', |
|
435
|
|
|
'{db_prefix}mail_queue', |
|
436
|
|
|
array( |
|
437
|
|
|
'time_sent' => 'int', 'recipient' => 'string-255', 'body' => 'string', 'subject' => 'string-255', |
|
438
|
|
|
'headers' => 'string-65534', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255', |
|
439
|
|
|
), |
|
440
|
|
|
$cur_insert, |
|
441
|
|
|
array('id_mail') |
|
442
|
|
|
); |
|
443
|
|
|
|
|
444
|
|
|
// Clear this out. |
|
445
|
|
|
$cur_insert = array(); |
|
446
|
|
|
$cur_insert_len = 0; |
|
447
|
|
|
} |
|
448
|
|
|
|
|
449
|
|
|
// Now add the current insert to the array... |
|
450
|
|
|
$cur_insert[] = array(time(), (string) $to, (string) $message, (string) $subject, (string) $headers, ($send_html ? 1 : 0), $priority, (int) $is_private, (string) $message_id); |
|
451
|
|
|
$cur_insert_len += $this_insert_len; |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
// If they are using SSI there is a good chance obExit will never be called. So lets be nice and flush it for them. |
|
455
|
|
|
if (ELK === 'SSI') |
|
456
|
|
|
{ |
|
457
|
|
|
return AddMailQueue(true); |
|
458
|
|
|
} |
|
459
|
|
|
|
|
460
|
|
|
return true; |
|
461
|
|
|
} |
|
462
|
|
|
|
|
463
|
|
|
/** |
|
464
|
|
|
* Prepare text strings for sending as email body or header. |
|
465
|
|
|
* |
|
466
|
|
|
* What it does: |
|
467
|
|
|
* |
|
468
|
|
|
* - In case there are higher ASCII characters in the given string, this |
|
469
|
|
|
* function will attempt the transport method 'quoted-printable'. |
|
470
|
|
|
* - Otherwise the transport method '7bit' is used. |
|
471
|
|
|
* |
|
472
|
|
|
* @param string $string |
|
473
|
|
|
* @param bool $with_charset = true |
|
474
|
|
|
* @param bool $hotmail_fix = false, with hotmail_fix set all higher ASCII |
|
475
|
|
|
* characters are converted to HTML entities to assure proper display of the mail |
|
476
|
|
|
* @param string $line_break |
|
477
|
|
|
* @param string|null $custom_charset = null, if set, it uses this character set |
|
478
|
|
|
* @return string[] an array containing the character set, the converted string and the transport method. |
|
479
|
|
|
* @package Mail |
|
480
|
3 |
|
*/ |
|
481
|
|
|
function mimespecialchars($string, $with_charset = true, $hotmail_fix = false, $line_break = "\r\n", $custom_charset = null) |
|
482
|
|
|
{ |
|
483
|
3 |
|
$charset = $custom_charset !== null ? $custom_charset : 'UTF-8'; |
|
484
|
|
|
|
|
485
|
|
|
// This is the fun part.... |
|
486
|
|
|
if (preg_match_all('~&#(\d{3,8});~', $string, $matches) !== 0 && !$hotmail_fix) |
|
487
|
|
|
{ |
|
488
|
|
|
// Let's, for now, assume there are only 'ish characters. |
|
489
|
|
|
$simple = true; |
|
490
|
|
|
|
|
491
|
|
|
foreach ($matches[1] as $entity) |
|
492
|
|
|
{ |
|
493
|
|
|
if ($entity > 128) |
|
494
|
|
|
{ |
|
495
|
|
|
$simple = false; |
|
496
|
|
|
} |
|
497
|
|
|
} |
|
498
|
|
|
unset($matches); |
|
499
|
|
|
|
|
500
|
|
|
if ($simple) |
|
501
|
|
|
{ |
|
502
|
|
|
$string = preg_replace_callback('~&#(\d{3,7});~', 'mimespecialchars_callback', $string); |
|
503
|
|
|
} |
|
504
|
|
|
else |
|
505
|
|
|
{ |
|
506
|
|
|
$string = preg_replace_callback('~&#(\d{3,7});~', 'fixchar__callback', $string); |
|
507
|
|
|
|
|
508
|
|
|
// Unicode, baby. |
|
509
|
|
|
$charset = 'UTF-8'; |
|
510
|
|
|
} |
|
511
|
3 |
|
} |
|
512
|
|
|
|
|
513
|
|
|
// Convert all special characters to HTML entities...just for Hotmail :-\ |
|
514
|
3 |
|
if ($hotmail_fix) |
|
515
|
|
|
{ |
|
516
|
|
|
// Convert all 'special' characters to HTML entities. |
|
517
|
3 |
|
return array($charset, preg_replace_callback('~([\x80-\x{10FFFF}])~u', 'entityConvert', $string), '7bit'); |
|
518
|
|
|
} |
|
519
|
|
|
// We don't need to mess with the line if no special characters were in it.. |
|
520
|
|
|
elseif (!$hotmail_fix && preg_match('~([^\x09\x0A\x0D\x20-\x7F])~', $string) === 1) |
|
521
|
|
|
{ |
|
522
|
|
|
// Base64 encode. |
|
523
|
|
|
$string = str_replace("\x00", '', $string); |
|
524
|
|
|
$string = base64_encode($string); |
|
525
|
|
|
|
|
526
|
|
|
$string = $with_charset |
|
527
|
|
|
? '=?' . $charset . '?B?' . $string . '?=' |
|
528
|
|
|
: chunk_split($string, 76, $line_break); |
|
529
|
|
|
|
|
530
|
3 |
|
return array($charset, $string, 'base64'); |
|
531
|
|
|
} |
|
532
|
|
|
else |
|
533
|
|
|
{ |
|
534
|
|
|
return array($charset, $string, '7bit'); |
|
535
|
|
|
} |
|
536
|
|
|
} |
|
537
|
|
|
|
|
538
|
|
|
/** |
|
539
|
|
|
* Converts out of ascii range characters in to HTML entities |
|
540
|
|
|
* |
|
541
|
|
|
* - Character codes <= 128 are left as is |
|
542
|
|
|
* - Callback function of preg_replace_callback, used just for hotmail address |
|
543
|
|
|
* |
|
544
|
|
|
* @param mixed[] $match |
|
545
|
|
|
* |
|
546
|
|
|
* @return mixed|string |
|
547
|
|
|
* @package Mail |
|
548
|
|
|
* |
|
549
|
|
|
*/ |
|
550
|
|
|
function entityConvert($match) |
|
551
|
|
|
{ |
|
552
|
|
|
$c = $match[1]; |
|
553
|
|
|
$c_strlen = strlen($c); |
|
554
|
|
|
$c_ord = ord($c[0]); |
|
555
|
|
|
|
|
556
|
|
|
if ($c_strlen === 1 && $c_ord <= 0x7F) |
|
557
|
|
|
{ |
|
558
|
|
|
return $c; |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
if ($c_strlen === 2 && $c_ord >= 0xC0 && $c_ord <= 0xDF) |
|
562
|
|
|
{ |
|
563
|
|
|
return '&#' . ((($c_ord ^ 0xC0) << 6) + (ord($c[1]) ^ 0x80)) . ';'; |
|
564
|
|
|
} |
|
565
|
|
|
|
|
566
|
|
|
if ($c_strlen === 3 && $c_ord >= 0xE0 && $c_ord <= 0xEF) |
|
567
|
|
|
{ |
|
568
|
|
|
return '&#' . ((($c_ord ^ 0xE0) << 12) + ((ord($c[1]) ^ 0x80) << 6) + (ord($c[2]) ^ 0x80)) . ';'; |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
if ($c_strlen === 4 && $c_ord >= 0xF0 && $c_ord <= 0xF7) |
|
572
|
|
|
{ |
|
573
|
|
|
return '&#' . ((($c_ord ^ 0xF0) << 18) + ((ord($c[1]) ^ 0x80) << 12) + ((ord($c[2]) ^ 0x80) << 6) + (ord($c[3]) ^ 0x80)) . ';'; |
|
574
|
|
|
} |
|
575
|
|
|
|
|
576
|
|
|
return ''; |
|
577
|
|
|
} |
|
578
|
|
|
|
|
579
|
|
|
/** |
|
580
|
|
|
* Callback for the preg_replace in mimespecialchars |
|
581
|
|
|
* |
|
582
|
|
|
* @param mixed[] $match |
|
583
|
|
|
* |
|
584
|
|
|
* @return string |
|
585
|
|
|
* @package Mail |
|
586
|
|
|
* |
|
587
|
|
|
*/ |
|
588
|
|
|
function mimespecialchars_callback($match) |
|
589
|
|
|
{ |
|
590
|
|
|
return chr($match[1]); |
|
591
|
|
|
} |
|
592
|
|
|
|
|
593
|
|
|
/** |
|
594
|
|
|
* Sends mail, like mail() but over SMTP. |
|
595
|
|
|
* |
|
596
|
|
|
* - It expects no slashes or entities. |
|
597
|
|
|
* |
|
598
|
|
|
* @param string[] $mail_to_array - array of strings (email addresses) |
|
599
|
|
|
* @param string $subject email subject |
|
600
|
|
|
* @param string $message email message |
|
601
|
|
|
* @param string $headers |
|
602
|
|
|
* @param int $priority |
|
603
|
|
|
* @param string|null $message_id |
|
604
|
|
|
* @return bool whether it sent or not. |
|
605
|
|
|
* @throws \Exception |
|
606
|
|
|
* @internal |
|
607
|
|
|
* @package Mail |
|
608
|
|
|
*/ |
|
609
|
|
|
function smtp_mail($mail_to_array, $subject, $message, $headers, $priority, $message_id = null) |
|
610
|
|
|
{ |
|
611
|
|
|
global $modSettings, $webmaster_email, $txt, $scripturl; |
|
612
|
|
|
|
|
613
|
|
|
$modSettings['smtp_host'] = trim($modSettings['smtp_host']); |
|
614
|
|
|
|
|
615
|
|
|
if ($message_id !== null && isset($message_id[0]) && in_array($message_id[0], array('m', 'p', 't'))) |
|
616
|
|
|
{ |
|
617
|
|
|
$message_type = $message_id[0]; |
|
618
|
|
|
$message_id = substr($message_id, 1); |
|
619
|
|
|
} |
|
620
|
|
|
else |
|
621
|
|
|
{ |
|
622
|
|
|
$message_type = 'm'; |
|
623
|
|
|
} |
|
624
|
|
|
|
|
625
|
|
|
// Try POP3 before SMTP? |
|
626
|
|
|
// @todo There's no interface for this yet. |
|
627
|
|
|
if ($modSettings['mail_type'] == 2 && $modSettings['smtp_username'] != '' && $modSettings['smtp_password'] != '') |
|
628
|
|
|
{ |
|
629
|
|
|
$socket = fsockopen($modSettings['smtp_host'], 110, $errno, $errstr, 2); |
|
630
|
|
|
if (!$socket && (substr($modSettings['smtp_host'], 0, 5) === 'smtp.' || substr($modSettings['smtp_host'], 0, 11) === 'ssl://smtp.')) |
|
631
|
|
|
{ |
|
632
|
|
|
$socket = fsockopen(strtr($modSettings['smtp_host'], array('smtp.' => 'pop.')), 110, $errno, $errstr, 2); |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
if ($socket) |
|
636
|
|
|
{ |
|
637
|
|
|
fgets($socket, 256); |
|
638
|
|
|
fputs($socket, 'USER ' . $modSettings['smtp_username'] . "\r\n"); |
|
639
|
|
|
fgets($socket, 256); |
|
640
|
|
|
fputs($socket, 'PASS ' . base64_decode($modSettings['smtp_password']) . "\r\n"); |
|
641
|
|
|
fgets($socket, 256); |
|
642
|
|
|
fputs($socket, 'QUIT' . "\r\n"); |
|
643
|
|
|
|
|
644
|
|
|
fclose($socket); |
|
645
|
|
|
} |
|
646
|
|
|
} |
|
647
|
|
|
|
|
648
|
|
|
// Try to connect to the SMTP server... if it doesn't exist, only wait three seconds. |
|
649
|
|
|
if (!$socket = fsockopen($modSettings['smtp_host'], empty($modSettings['smtp_port']) ? 25 : $modSettings['smtp_port'], $errno, $errstr, 3)) |
|
650
|
|
|
{ |
|
651
|
|
|
// Maybe we can still save this? The port might be wrong. |
|
652
|
|
|
if (substr($modSettings['smtp_host'], 0, 4) === 'ssl:' && (empty($modSettings['smtp_port']) || $modSettings['smtp_port'] == 25)) |
|
653
|
|
|
{ |
|
654
|
|
|
if (($socket = fsockopen($modSettings['smtp_host'], 465, $errno, $errstr, 3))) |
|
655
|
|
|
{ |
|
656
|
|
|
\ElkArte\Errors\Errors::instance()->log_error($txt['smtp_port_ssl']); |
|
657
|
|
|
} |
|
658
|
|
|
} |
|
659
|
|
|
|
|
660
|
|
|
// Unable to connect! Don't show any error message, but just log one and try to continue anyway. |
|
661
|
|
|
if (!$socket) |
|
662
|
|
|
{ |
|
663
|
|
|
\ElkArte\Errors\Errors::instance()->log_error($txt['smtp_no_connect'] . ': ' . $errno . ' : ' . $errstr); |
|
664
|
|
|
|
|
665
|
|
|
return false; |
|
666
|
|
|
} |
|
667
|
|
|
} |
|
668
|
|
|
|
|
669
|
|
|
// Wait for a response of 220, without "-" continue. |
|
670
|
|
|
if (!server_parse(null, $socket, '220')) |
|
671
|
|
|
{ |
|
672
|
|
|
return false; |
|
673
|
|
|
} |
|
674
|
|
|
|
|
675
|
|
|
// This should be set in the ACP |
|
676
|
|
|
if (empty($modSettings['smtp_client'])) |
|
677
|
|
|
{ |
|
678
|
|
|
$modSettings['smtp_client'] = detectServer()->getFQDN(empty($modSettings['smtp_host']) ? '' : $modSettings['smtp_host']); |
|
679
|
|
|
updateSettings(array('smtp_client' => $modSettings['smtp_client'])); |
|
680
|
|
|
} |
|
681
|
|
|
|
|
682
|
|
|
if ($modSettings['mail_type'] == 1 && $modSettings['smtp_username'] != '' && $modSettings['smtp_password'] != '') |
|
683
|
|
|
{ |
|
684
|
|
|
// EHLO could be understood to mean encrypted hello... |
|
685
|
|
|
if (server_parse('EHLO ' . $modSettings['smtp_client'], $socket, null) == '250') |
|
686
|
|
|
{ |
|
687
|
|
|
if (!empty($modSettings['smtp_starttls'])) |
|
688
|
|
|
{ |
|
689
|
|
|
server_parse('STARTTLS', $socket, null); |
|
690
|
|
|
stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); |
|
691
|
|
|
server_parse('EHLO ' . $modSettings['smtp_client'], $socket, null); |
|
692
|
|
|
} |
|
693
|
|
|
|
|
694
|
|
|
if (!server_parse('AUTH LOGIN', $socket, '334')) |
|
695
|
|
|
{ |
|
696
|
|
|
return false; |
|
697
|
|
|
} |
|
698
|
|
|
// Send the username and password, encoded. |
|
699
|
|
|
if (!server_parse(base64_encode($modSettings['smtp_username']), $socket, '334')) |
|
700
|
|
|
{ |
|
701
|
|
|
return false; |
|
702
|
|
|
} |
|
703
|
|
|
// The password is already encoded ;) |
|
704
|
|
|
if (!server_parse($modSettings['smtp_password'], $socket, '235')) |
|
705
|
|
|
{ |
|
706
|
|
|
return false; |
|
707
|
|
|
} |
|
708
|
|
|
} |
|
709
|
|
|
elseif (!server_parse('HELO ' . $modSettings['smtp_client'], $socket, '250')) |
|
710
|
|
|
{ |
|
711
|
|
|
return false; |
|
712
|
|
|
} |
|
713
|
|
|
} |
|
714
|
|
|
else |
|
715
|
|
|
{ |
|
716
|
|
|
// Just say "helo". |
|
717
|
|
|
if (!server_parse('HELO ' . $modSettings['smtp_client'], $socket, '250')) |
|
718
|
|
|
{ |
|
719
|
|
|
return false; |
|
720
|
|
|
} |
|
721
|
|
|
} |
|
722
|
|
|
|
|
723
|
|
|
// Fix the message for any lines beginning with a period! (the first is ignored, you see.) |
|
724
|
|
|
$message = strtr($message, array("\r\n" . '.' => "\r\n" . '..')); |
|
725
|
|
|
|
|
726
|
|
|
$sent = array(); |
|
727
|
|
|
$need_break = substr($headers, -1) === "\n" || substr($headers, -1) === "\r" ? false : true; |
|
728
|
|
|
$real_headers = $headers; |
|
729
|
|
|
$line_break = "\r\n"; |
|
730
|
|
|
|
|
731
|
|
|
// !! Theoretically, we should be able to just loop the RCPT TO. |
|
732
|
|
|
$mail_to_array = array_values($mail_to_array); |
|
733
|
|
|
foreach ($mail_to_array as $i => $mail_to) |
|
734
|
|
|
{ |
|
735
|
|
|
// the keys are must unique for every mail you see |
|
736
|
|
|
$unq_id = ''; |
|
737
|
|
|
$unq_head = ''; |
|
738
|
|
|
$unq_head_array = array(); |
|
739
|
|
|
|
|
740
|
|
|
// Using the post by email functions, and not a digest (priority 4) |
|
741
|
|
|
// then generate "reply to mail" keys and place them in the message |
|
742
|
|
|
if (!empty($modSettings['maillist_enabled']) && !empty($message_id) && $priority != 4) |
|
743
|
|
|
{ |
|
744
|
|
|
$unq_head_array[0] = md5($scripturl . microtime() . rand()); |
|
745
|
|
|
$unq_head_array[1] = $message_type; |
|
746
|
|
|
$unq_head_array[2] = $message_id; |
|
747
|
|
|
$unq_head = $unq_head_array[0] . '-' . $unq_head_array[1] . $unq_head_array[2]; |
|
748
|
|
|
$unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . $unq_head . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>'; |
|
749
|
|
|
$message = mail_insert_key($message, $unq_head, $line_break); |
|
750
|
|
|
} |
|
751
|
|
|
|
|
752
|
|
|
// Fix up the headers for this email! |
|
753
|
|
|
$headers = $real_headers . $unq_id; |
|
754
|
|
|
|
|
755
|
|
|
// Reset the connection to send another email. |
|
756
|
|
|
if ($i !== 0) |
|
757
|
|
|
{ |
|
758
|
|
|
if (!server_parse('RSET', $socket, '250')) |
|
759
|
|
|
{ |
|
760
|
|
|
return false; |
|
761
|
|
|
} |
|
762
|
|
|
} |
|
763
|
|
|
|
|
764
|
|
|
// From, to, and then start the data... |
|
765
|
|
|
if (!server_parse('MAIL FROM: <' . (empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from']) . '>', $socket, '250')) |
|
766
|
|
|
{ |
|
767
|
|
|
return false; |
|
768
|
|
|
} |
|
769
|
|
|
|
|
770
|
|
|
if (!server_parse('RCPT TO: <' . $mail_to . '>', $socket, '250')) |
|
771
|
|
|
{ |
|
772
|
|
|
return false; |
|
773
|
|
|
} |
|
774
|
|
|
|
|
775
|
|
|
if (!server_parse('DATA', $socket, '354')) |
|
776
|
|
|
{ |
|
777
|
|
|
return false; |
|
778
|
|
|
} |
|
779
|
|
|
|
|
780
|
|
|
fputs($socket, 'Subject: ' . $subject . $line_break); |
|
781
|
|
|
if (strlen($mail_to) > 0) |
|
782
|
|
|
{ |
|
783
|
|
|
fputs($socket, 'To: <' . $mail_to . '>' . $line_break); |
|
784
|
|
|
} |
|
785
|
|
|
fputs($socket, $headers . $line_break . $line_break); |
|
786
|
|
|
fputs($socket, $message . $line_break); |
|
787
|
|
|
|
|
788
|
|
|
// Send a ., or in other words "end of data". |
|
789
|
|
|
if (!server_parse('.', $socket, '250')) |
|
790
|
|
|
{ |
|
791
|
|
|
return false; |
|
792
|
|
|
} |
|
793
|
|
|
|
|
794
|
|
|
// track the number of emails sent |
|
795
|
|
|
if (!empty($modSettings['trackStats'])) |
|
796
|
|
|
{ |
|
797
|
|
|
trackStats(array('email' => '+')); |
|
798
|
|
|
} |
|
799
|
|
|
|
|
800
|
|
|
// Keep our post via email log |
|
801
|
|
|
if (!empty($unq_head)) |
|
802
|
|
|
{ |
|
803
|
|
|
$unq_head_array[] = time(); |
|
804
|
|
|
$unq_head_array[] = $mail_to; |
|
805
|
|
|
$sent[] = $unq_head_array; |
|
806
|
|
|
} |
|
807
|
|
|
|
|
808
|
|
|
// Almost done, almost done... don't stop me just yet! |
|
809
|
|
|
detectServer()->setTimeLimit(300); |
|
810
|
|
|
} |
|
811
|
|
|
|
|
812
|
|
|
// say our goodbyes |
|
813
|
|
|
fputs($socket, 'QUIT' . $line_break); |
|
814
|
|
|
fclose($socket); |
|
815
|
|
|
|
|
816
|
|
|
// Log each email |
|
817
|
|
|
if (!empty($sent)) |
|
818
|
|
|
{ |
|
819
|
|
|
require_once(SUBSDIR . '/Maillist.subs.php'); |
|
820
|
|
|
log_email($sent); |
|
821
|
|
|
} |
|
822
|
|
|
|
|
823
|
|
|
return true; |
|
824
|
|
|
} |
|
825
|
|
|
|
|
826
|
|
|
/** |
|
827
|
|
|
* Parse a message to the SMTP server. |
|
828
|
|
|
* |
|
829
|
|
|
* - Sends the specified message to the server, and checks for the expected response. |
|
830
|
|
|
* |
|
831
|
|
|
* @param string $message - the message to send |
|
832
|
|
|
* @param resource $socket - socket to send on |
|
833
|
|
|
* @param string $response - the expected response code |
|
834
|
|
|
* @return string|bool it responded as such. |
|
835
|
|
|
* @throws \Exception |
|
836
|
|
|
* @internal |
|
837
|
|
|
* @package Mail |
|
838
|
|
|
*/ |
|
839
|
|
|
function server_parse($message, $socket, $response) |
|
840
|
|
|
{ |
|
841
|
|
|
global $txt; |
|
842
|
|
|
|
|
843
|
|
|
if ($message !== null) |
|
844
|
|
|
{ |
|
845
|
|
|
fputs($socket, $message . "\r\n"); |
|
846
|
|
|
} |
|
847
|
|
|
|
|
848
|
|
|
// No response yet. |
|
849
|
|
|
$server_response = ''; |
|
850
|
|
|
|
|
851
|
|
|
while (substr($server_response, 3, 1) !== ' ') |
|
852
|
|
|
{ |
|
853
|
|
|
if (!($server_response = fgets($socket, 256))) |
|
854
|
|
|
{ |
|
855
|
|
|
// @todo Change this message to reflect that it may mean bad user/password/server issues/etc. |
|
856
|
|
|
\ElkArte\Errors\Errors::instance()->log_error($txt['smtp_bad_response']); |
|
857
|
|
|
|
|
858
|
|
|
return false; |
|
859
|
|
|
} |
|
860
|
|
|
} |
|
861
|
|
|
|
|
862
|
|
|
if ($response === null) |
|
863
|
|
|
{ |
|
864
|
|
|
return substr($server_response, 0, 3); |
|
865
|
|
|
} |
|
866
|
|
|
|
|
867
|
|
|
if (substr($server_response, 0, 3) !== $response) |
|
868
|
|
|
{ |
|
869
|
|
|
\ElkArte\Errors\Errors::instance()->log_error($txt['smtp_error'] . $server_response); |
|
870
|
|
|
|
|
871
|
|
|
return false; |
|
872
|
|
|
} |
|
873
|
|
|
|
|
874
|
|
|
return true; |
|
875
|
|
|
} |
|
876
|
|
|
|
|
877
|
|
|
/** |
|
878
|
|
|
* Adds the unique security key in to an email |
|
879
|
|
|
* |
|
880
|
|
|
* - adds the key in to (each) message body section |
|
881
|
|
|
* - safety net for clients that strip out the message-id and in-reply-to headers |
|
882
|
|
|
* |
|
883
|
|
|
* @param string $message |
|
884
|
|
|
* @param string $unq_head |
|
885
|
|
|
* @param string $line_break |
|
886
|
|
|
* |
|
887
|
|
|
* @return mixed|null|string|string[] |
|
888
|
|
|
* @package Mail |
|
889
|
|
|
* |
|
890
|
|
|
*/ |
|
891
|
|
|
function mail_insert_key($message, $unq_head, $line_break) |
|
892
|
|
|
{ |
|
893
|
|
|
// Append the key to the bottom of each message section, plain, html, encoded, etc |
|
894
|
|
|
$message = preg_replace('~^(.*?)(' . $line_break . '--ELK-[a-z0-9]{32})~s', "$1{$line_break}{$line_break}[{$unq_head}]{$line_break}$2", $message); |
|
895
|
|
|
$message = preg_replace('~(Content-Type: text/plain;.*?Content-Transfer-Encoding: 7bit' . $line_break . $line_break . ')(.*?)(' . $line_break . '--ELK-[a-z0-9]{32})~s', "$1$2{$line_break}{$line_break}[{$unq_head}]{$line_break}$3", $message); |
|
896
|
|
|
$message = preg_replace('~(Content-Type: text/html;.*?Content-Transfer-Encoding: 7bit' . $line_break . $line_break . ')(.*?)(' . $line_break . '--ELK-[a-z0-9]{32})~s', "$1$2<br /><br />[{$unq_head}]<br />$3", $message); |
|
897
|
|
|
|
|
898
|
|
|
// base64 the harder one to insert our key |
|
899
|
|
|
// Find the sections, un-do the chunk_split, add in the new key, and re chunky it |
|
900
|
|
|
if (preg_match('~(Content-Transfer-Encoding: base64' . $line_break . $line_break . ')(.*?)(' . $line_break . '--ELK-[a-z0-9]{32})~s', $message, $match)) |
|
901
|
|
|
{ |
|
902
|
|
|
// un-chunk, add in our encoded key header, and re chunk, all so we match RFC 2045 semantics. |
|
903
|
|
|
$encoded_message = base64_decode(str_replace($line_break, '', $match[2])); |
|
904
|
|
|
$encoded_message .= $line_break . $line_break . '[' . $unq_head . ']' . $line_break; |
|
905
|
|
|
$encoded_message = base64_encode($encoded_message); |
|
906
|
|
|
$encoded_message = chunk_split($encoded_message, 76, $line_break); |
|
907
|
|
|
$message = str_replace($match[2], $encoded_message, $message); |
|
908
|
|
|
} |
|
909
|
|
|
|
|
910
|
|
|
return $message; |
|
911
|
|
|
} |
|
912
|
|
|
|
|
913
|
|
|
/** |
|
914
|
|
|
* Load a template from EmailTemplates language file. |
|
915
|
|
|
* |
|
916
|
|
|
* @param string $template |
|
917
|
|
|
* @param mixed[] $replacements |
|
918
|
|
|
* @param string $lang = '' |
|
919
|
3 |
|
* @param bool $loadLang = true |
|
920
|
|
|
* @param string[] $suffixes - Additional suffixes to find and return |
|
921
|
|
|
* @param string[] $additional_files - Additional language files to load |
|
922
|
3 |
|
* |
|
923
|
|
|
* @return array |
|
924
|
3 |
|
* @throws \ElkArte\Exceptions\Exception email_no_template |
|
925
|
3 |
|
* @package Mail |
|
926
|
|
|
* |
|
927
|
|
|
*/ |
|
928
|
|
|
function loadEmailTemplate($template, $replacements = array(), $lang = '', $loadLang = true, $suffixes = array(), $additional_files = array()) |
|
929
|
|
|
{ |
|
930
|
3 |
|
global $txt, $mbname, $scripturl, $settings, $boardurl, $modSettings; |
|
931
|
|
|
|
|
932
|
|
|
// First things first, load up the email templates language file, if we need to. |
|
933
|
|
|
if ($loadLang) |
|
934
|
|
|
{ |
|
935
|
|
|
$lang_loader = new LangLoader($lang); |
|
936
|
|
|
$lang_loader->load('EmailTemplates'); |
|
937
|
|
|
if (!empty($modSettings['maillist_enabled'])) |
|
938
|
|
|
{ |
|
939
|
3 |
|
$lang_loader->load('MaillistTemplates'); |
|
940
|
|
|
} |
|
941
|
|
|
|
|
942
|
|
|
if (!empty($additional_files)) |
|
943
|
|
|
{ |
|
944
|
|
|
foreach ($additional_files as $file) |
|
945
|
3 |
|
{ |
|
946
|
3 |
|
$lang_loader->load($file); |
|
947
|
|
|
} |
|
948
|
3 |
|
} |
|
949
|
|
|
} |
|
950
|
|
|
|
|
951
|
|
|
if (!isset($txt[$template . '_subject']) || !isset($txt[$template . '_body'])) |
|
952
|
|
|
{ |
|
953
|
|
|
throw new \ElkArte\Exceptions\Exception('email_no_template', 'template', array($template)); |
|
954
|
|
|
} |
|
955
|
|
|
|
|
956
|
|
|
$ret = array( |
|
957
|
|
|
'subject' => $txt[$template . '_subject'], |
|
958
|
3 |
|
'body' => $txt[$template . '_body'], |
|
959
|
3 |
|
); |
|
960
|
3 |
|
if (!empty($suffixes)) |
|
961
|
3 |
|
{ |
|
962
|
3 |
|
foreach ($suffixes as $key) |
|
963
|
3 |
|
{ |
|
964
|
3 |
|
$ret[$key] = $txt[$template . '_' . $key]; |
|
965
|
3 |
|
} |
|
966
|
3 |
|
} |
|
967
|
|
|
|
|
968
|
|
|
// Add in the default replacements. |
|
969
|
|
|
$replacements += array( |
|
970
|
3 |
|
'FORUMNAME' => $mbname, |
|
971
|
3 |
|
'FORUMNAMESHORT' => (!empty($modSettings['maillist_sitename']) ? $modSettings['maillist_sitename'] : $mbname), |
|
972
|
|
|
'EMAILREGARDS' => (!empty($modSettings['maillist_sitename_regards']) ? $modSettings['maillist_sitename_regards'] : ''), |
|
973
|
3 |
|
'FORUMURL' => $boardurl, |
|
974
|
|
|
'SCRIPTURL' => $scripturl, |
|
975
|
3 |
|
'THEMEURL' => $settings['theme_url'], |
|
976
|
3 |
|
'IMAGESURL' => $settings['images_url'], |
|
977
|
|
|
'DEFAULT_THEMEURL' => $settings['default_theme_url'], |
|
978
|
|
|
'REGARDS' => replaceBasicActionUrl($txt['regards_team']), |
|
979
|
|
|
); |
|
980
|
3 |
|
|
|
981
|
|
|
// Split the replacements up into two arrays, for use with str_replace |
|
982
|
3 |
|
$find = array(); |
|
983
|
|
|
$replace = array(); |
|
984
|
3 |
|
|
|
985
|
|
|
foreach ($replacements as $f => $r) |
|
986
|
|
|
{ |
|
987
|
|
|
$find[] = '{' . $f . '}'; |
|
988
|
3 |
|
$replace[] = $r; |
|
989
|
|
|
} |
|
990
|
|
|
|
|
991
|
|
|
// Do the variable replacements. |
|
992
|
|
|
foreach ($ret as $key => $val) |
|
993
|
|
|
{ |
|
994
|
|
|
$val = str_replace($find, $replace, $val); |
|
995
|
|
|
// Now deal with the {USER.variable} items. |
|
996
|
|
|
$ret[$key] = preg_replace_callback('~{USER.([^}]+)}~', 'user_info_callback', $val); |
|
997
|
|
|
} |
|
998
|
|
|
|
|
999
|
|
|
// Finally return the email to the caller so they can send it out. |
|
1000
|
|
|
return $ret; |
|
1001
|
|
|
} |
|
1002
|
|
|
|
|
1003
|
|
|
/** |
|
1004
|
|
|
* Prepare subject and message of an email for the preview box |
|
1005
|
|
|
* |
|
1006
|
|
|
* Used in action_mailingcompose and RetrievePreview (Xml.controller.php) |
|
1007
|
|
|
* |
|
1008
|
|
|
* @package Mail |
|
1009
|
|
|
*/ |
|
1010
|
|
|
function prepareMailingForPreview() |
|
1011
|
|
|
{ |
|
1012
|
|
|
global $context, $modSettings, $scripturl, $txt; |
|
1013
|
|
|
|
|
1014
|
|
|
ThemeLoader::loadLanguageFile('Errors'); |
|
1015
|
|
|
require_once(SUBSDIR . '/Post.subs.php'); |
|
1016
|
|
|
|
|
1017
|
|
|
$processing = array( |
|
1018
|
|
|
'preview_subject' => 'subject', |
|
1019
|
|
|
'preview_message' => 'message' |
|
1020
|
|
|
); |
|
1021
|
|
|
|
|
1022
|
|
|
// Use the default time format. |
|
1023
|
|
|
User::$info->time_format = $modSettings['time_format']; |
|
1024
|
|
|
|
|
1025
|
|
|
$variables = array( |
|
1026
|
|
|
'{$board_url}', |
|
1027
|
|
|
'{$current_time}', |
|
1028
|
|
|
'{$latest_member.link}', |
|
1029
|
|
|
'{$latest_member.id}', |
|
1030
|
|
|
'{$latest_member.name}' |
|
1031
|
|
|
); |
|
1032
|
|
|
|
|
1033
|
|
|
$html = $context['send_html']; |
|
1034
|
|
|
|
|
1035
|
|
|
// We might need this in a bit |
|
1036
|
|
|
$cleanLatestMember = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName']; |
|
1037
|
|
|
|
|
1038
|
|
|
$bbc_parser = ParserWrapper::instance(); |
|
1039
|
|
|
|
|
1040
|
|
|
foreach ($processing as $key => $post) |
|
1041
|
|
|
{ |
|
1042
|
|
|
$context[$key] = !empty($_REQUEST[$post]) ? $_REQUEST[$post] : ''; |
|
1043
|
|
|
|
|
1044
|
|
|
if (empty($context[$key]) && empty($_REQUEST['xml'])) |
|
1045
|
|
|
{ |
|
1046
|
|
|
$context['post_error']['messages'][] = $txt['error_no_' . $post]; |
|
1047
|
|
|
} |
|
1048
|
|
|
elseif (!empty($_REQUEST['xml'])) |
|
1049
|
|
|
{ |
|
1050
|
|
|
continue; |
|
1051
|
|
|
} |
|
1052
|
|
|
|
|
1053
|
|
|
preparsecode($context[$key]); |
|
1054
|
|
|
|
|
1055
|
|
|
// Sending as html then we convert any bbc |
|
1056
|
|
|
if ($html) |
|
1057
|
|
|
{ |
|
1058
|
|
|
$enablePostHTML = $modSettings['enablePostHTML']; |
|
1059
|
|
|
$modSettings['enablePostHTML'] = $context['send_html']; |
|
1060
|
|
|
$context[$key] = $bbc_parser->parseEmail($context[$key]); |
|
1061
|
|
|
$modSettings['enablePostHTML'] = $enablePostHTML; |
|
1062
|
|
|
} |
|
1063
|
|
|
|
|
1064
|
|
|
// Replace in all the standard things. |
|
1065
|
|
|
$context[$key] = str_replace($variables, |
|
1066
|
|
|
array( |
|
1067
|
|
|
!empty($context['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl, |
|
1068
|
|
|
standardTime(forum_time(), false), |
|
1069
|
|
|
!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), |
|
1070
|
|
|
$modSettings['latestMember'], |
|
1071
|
|
|
$cleanLatestMember |
|
1072
|
|
|
), $context[$key]); |
|
1073
|
|
|
} |
|
1074
|
|
|
} |
|
1075
|
|
|
|
|
1076
|
|
|
/** |
|
1077
|
|
|
* Callback function for load email template on subject and body |
|
1078
|
|
|
* Uses capture group 1 in array |
|
1079
|
|
|
* |
|
1080
|
|
|
* @param mixed[] $matches |
|
1081
|
|
|
* @return string |
|
1082
|
|
|
* @package Mail |
|
1083
|
|
|
*/ |
|
1084
|
|
|
function user_info_callback($matches) |
|
1085
|
|
|
{ |
|
1086
|
|
|
if (empty($matches[1])) |
|
1087
|
|
|
{ |
|
1088
|
|
|
return ''; |
|
1089
|
|
|
} |
|
1090
|
|
|
|
|
1091
|
|
|
$use_ref = true; |
|
1092
|
|
|
$ref = User::$info->toArray(); |
|
1093
|
|
|
|
|
1094
|
|
|
foreach (explode('.', $matches[1]) as $index) |
|
1095
|
|
|
{ |
|
1096
|
|
|
if ($use_ref && isset($ref[$index])) |
|
1097
|
|
|
{ |
|
1098
|
|
|
$ref = &$ref[$index]; |
|
1099
|
|
|
} |
|
1100
|
|
|
else |
|
1101
|
|
|
{ |
|
1102
|
|
|
$use_ref = false; |
|
1103
|
|
|
break; |
|
1104
|
|
|
} |
|
1105
|
|
|
} |
|
1106
|
|
|
|
|
1107
|
|
|
return $use_ref ? $ref : $matches[0]; |
|
1108
|
|
|
} |
|
1109
|
|
|
|
|
1110
|
|
|
/** |
|
1111
|
|
|
* This function grabs the mail queue items from the database, according to the params given. |
|
1112
|
|
|
* |
|
1113
|
|
|
* @param int $start The item to start with (for pagination purposes) |
|
1114
|
|
|
* @param int $items_per_page The number of items to show per page |
|
1115
|
|
|
* @param string $sort A string indicating how to sort the results |
|
1116
|
|
|
* @return array |
|
1117
|
|
|
* @throws \Exception |
|
1118
|
|
|
* @package Mail |
|
1119
|
|
|
*/ |
|
1120
|
|
|
function list_getMailQueue($start, $items_per_page, $sort) |
|
1121
|
|
|
{ |
|
1122
|
|
|
global $txt; |
|
1123
|
|
|
|
|
1124
|
|
|
$db = database(); |
|
1125
|
|
|
|
|
1126
|
|
|
return $db->fetchQuery(' |
|
1127
|
|
|
SELECT |
|
1128
|
|
|
id_mail, time_sent, recipient, priority, private, subject |
|
1129
|
|
|
FROM {db_prefix}mail_queue |
|
1130
|
|
|
ORDER BY {raw:sort} |
|
1131
|
|
|
LIMIT {int:start}, {int:items_per_page}', |
|
1132
|
|
|
array( |
|
1133
|
|
|
'start' => $start, |
|
1134
|
|
|
'sort' => $sort, |
|
1135
|
|
|
'items_per_page' => $items_per_page, |
|
1136
|
|
|
) |
|
1137
|
|
|
)->fetch_callback( |
|
1138
|
|
|
function ($row) use ($txt) { |
|
1139
|
|
|
// Private PM/email subjects and similar shouldn't be shown in the mailbox area. |
|
1140
|
|
|
if (!empty($row['private'])) |
|
1141
|
|
|
{ |
|
1142
|
|
|
$row['subject'] = $txt['personal_message']; |
|
1143
|
|
|
} |
|
1144
|
|
|
|
|
1145
|
|
|
return $row; |
|
1146
|
|
|
} |
|
1147
|
|
|
); |
|
1148
|
|
|
} |
|
1149
|
|
|
|
|
1150
|
|
|
/** |
|
1151
|
|
|
* Returns the total count of items in the mail queue. |
|
1152
|
|
|
* |
|
1153
|
|
|
* @return int |
|
1154
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
1155
|
|
|
* @package Mail |
|
1156
|
|
|
*/ |
|
1157
|
|
|
function list_getMailQueueSize() |
|
1158
|
|
|
{ |
|
1159
|
|
|
$db = database(); |
|
1160
|
|
|
|
|
1161
|
|
|
// How many items do we have? |
|
1162
|
|
|
$request = $db->query('', ' |
|
1163
|
|
|
SELECT COUNT(*) AS queue_size |
|
1164
|
|
|
FROM {db_prefix}mail_queue', |
|
1165
|
|
|
array() |
|
1166
|
|
|
); |
|
1167
|
|
|
list ($mailQueueSize) = $request->fetch_row(); |
|
1168
|
|
|
$request->free_result(); |
|
1169
|
|
|
|
|
1170
|
|
|
return $mailQueueSize; |
|
1171
|
|
|
} |
|
1172
|
|
|
|
|
1173
|
|
|
/** |
|
1174
|
|
|
* Deletes items from the mail queue |
|
1175
|
|
|
* |
|
1176
|
|
|
* @param int[] $items |
|
1177
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
1178
|
|
|
* @package Mail |
|
1179
|
|
|
*/ |
|
1180
|
|
|
function deleteMailQueueItems($items) |
|
1181
|
|
|
{ |
|
1182
|
|
|
$db = database(); |
|
1183
|
|
|
|
|
1184
|
|
|
$db->query('', ' |
|
1185
|
|
|
DELETE FROM {db_prefix}mail_queue |
|
1186
|
|
|
WHERE id_mail IN ({array_int:mail_ids})', |
|
1187
|
|
|
array( |
|
1188
|
|
|
'mail_ids' => $items, |
|
1189
|
|
|
) |
|
1190
|
|
|
); |
|
1191
|
|
|
} |
|
1192
|
|
|
|
|
1193
|
|
|
/** |
|
1194
|
|
|
* Get the current mail queue status |
|
1195
|
|
|
* |
|
1196
|
|
|
* @package Mail |
|
1197
|
|
|
*/ |
|
1198
|
|
|
function list_MailQueueStatus() |
|
1199
|
|
|
{ |
|
1200
|
|
|
$db = database(); |
|
1201
|
|
|
|
|
1202
|
|
|
$items = array(); |
|
1203
|
|
|
|
|
1204
|
|
|
// How many items do we have? |
|
1205
|
|
|
$request = $db->query('', ' |
|
1206
|
|
|
SELECT COUNT(*) AS queue_size, MIN(time_sent) AS oldest |
|
1207
|
|
|
FROM {db_prefix}mail_queue', |
|
1208
|
|
|
array() |
|
1209
|
|
|
); |
|
1210
|
|
|
list ($items['mailQueueSize'], $items['mailOldest']) = $request->fetch_row(); |
|
1211
|
|
|
$request->free_result(); |
|
1212
|
|
|
|
|
1213
|
|
|
return $items; |
|
1214
|
|
|
} |
|
1215
|
|
|
|
|
1216
|
|
|
/** |
|
1217
|
|
|
* This function handles updates to account for failed emails. |
|
1218
|
|
|
* |
|
1219
|
|
|
* - It is used to keep track of failed emails attempts and next try. |
|
1220
|
|
|
* |
|
1221
|
|
|
* @param mixed[] $failed_emails |
|
1222
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
1223
|
|
|
* @package Mail |
|
1224
|
|
|
*/ |
|
1225
|
|
|
function updateFailedQueue($failed_emails) |
|
1226
|
|
|
{ |
|
1227
|
|
|
global $modSettings; |
|
1228
|
|
|
|
|
1229
|
|
|
$db = database(); |
|
1230
|
|
|
|
|
1231
|
|
|
// Update the failed attempts check. |
|
1232
|
|
|
$db->replace( |
|
1233
|
|
|
'{db_prefix}settings', |
|
1234
|
|
|
array('variable' => 'string', 'value' => 'string'), |
|
1235
|
|
|
array('mail_failed_attempts', empty($modSettings['mail_failed_attempts']) ? 1 : ++$modSettings['mail_failed_attempts']), |
|
1236
|
|
|
array('variable') |
|
1237
|
|
|
); |
|
1238
|
|
|
|
|
1239
|
|
|
// If we have failed to many times, tell mail to wait a bit and try again. |
|
1240
|
|
|
if ($modSettings['mail_failed_attempts'] > 5) |
|
1241
|
|
|
{ |
|
1242
|
|
|
$db->query('', ' |
|
1243
|
|
|
UPDATE {db_prefix}settings |
|
1244
|
|
|
SET value = {string:next_mail_send} |
|
1245
|
|
|
WHERE variable = {string:mail_next_send} |
|
1246
|
|
|
AND value = {string:last_send}', |
|
1247
|
|
|
array( |
|
1248
|
|
|
'next_mail_send' => time() + 60, |
|
1249
|
|
|
'mail_next_send' => 'mail_next_send', |
|
1250
|
|
|
'last_send' => $modSettings['mail_next_send'], |
|
1251
|
|
|
) |
|
1252
|
|
|
); |
|
1253
|
|
|
} |
|
1254
|
|
|
|
|
1255
|
|
|
// Add our email back to the queue, manually. |
|
1256
|
|
|
$db->insert('insert', |
|
1257
|
|
|
'{db_prefix}mail_queue', |
|
1258
|
|
|
array('time_sent' => 'int', 'recipient' => 'string', 'body' => 'string', 'subject' => 'string', 'headers' => 'string', 'send_html' => 'int', 'priority' => 'int', 'private' => 'int', 'message_id' => 'string-255'), |
|
1259
|
|
|
$failed_emails, |
|
1260
|
|
|
array('id_mail') |
|
1261
|
|
|
); |
|
1262
|
|
|
} |
|
1263
|
|
|
|
|
1264
|
|
|
/** |
|
1265
|
|
|
* Updates the failed attempts to email in the database. |
|
1266
|
|
|
* |
|
1267
|
|
|
* - It sets mail failed attempts value to 0. |
|
1268
|
|
|
* |
|
1269
|
|
|
* @package Mail |
|
1270
|
|
|
*/ |
|
1271
|
|
|
function updateSuccessQueue() |
|
1272
|
|
|
{ |
|
1273
|
|
|
$db = database(); |
|
1274
|
|
|
|
|
1275
|
|
|
$db->query('', ' |
|
1276
|
|
|
UPDATE {db_prefix}settings |
|
1277
|
|
|
SET value = {string:zero} |
|
1278
|
|
|
WHERE variable = {string:mail_failed_attempts}', |
|
1279
|
|
|
array( |
|
1280
|
|
|
'zero' => '0', |
|
1281
|
|
|
'mail_failed_attempts' => 'mail_failed_attempts', |
|
1282
|
|
|
) |
|
1283
|
|
|
); |
|
1284
|
|
|
} |
|
1285
|
|
|
|
|
1286
|
|
|
/** |
|
1287
|
|
|
* Reset to 0 the next send time for emails queue. |
|
1288
|
|
|
*/ |
|
1289
|
|
|
function resetNextSendTime() |
|
1290
|
|
|
{ |
|
1291
|
|
|
global $modSettings; |
|
1292
|
|
|
|
|
1293
|
|
|
$db = database(); |
|
1294
|
|
|
|
|
1295
|
|
|
// Update the setting to zero, yay |
|
1296
|
|
|
// ...unless someone else did. |
|
1297
|
|
|
$db->query('', ' |
|
1298
|
|
|
UPDATE {db_prefix}settings |
|
1299
|
|
|
SET value = {string:no_send} |
|
1300
|
|
|
WHERE variable = {string:mail_next_send} |
|
1301
|
|
|
AND value = {string:last_mail_send}', |
|
1302
|
|
|
array( |
|
1303
|
|
|
'no_send' => '0', |
|
1304
|
|
|
'mail_next_send' => 'mail_next_send', |
|
1305
|
|
|
'last_mail_send' => $modSettings['mail_next_send'], |
|
1306
|
|
|
) |
|
1307
|
|
|
); |
|
1308
|
|
|
} |
|
1309
|
|
|
|
|
1310
|
|
|
/** |
|
1311
|
|
|
* Update the next sending time for mail queue. |
|
1312
|
|
|
* |
|
1313
|
|
|
* - By default, move it 10 seconds for lower per mail_period_limits and 5 seconds for larger mail_period_limits |
|
1314
|
|
|
* - Requires an affected row |
|
1315
|
|
|
* |
|
1316
|
|
|
* @return int|bool |
|
1317
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
1318
|
|
|
* @package Mail |
|
1319
|
|
|
*/ |
|
1320
|
|
|
function updateNextSendTime() |
|
1321
|
|
|
{ |
|
1322
|
|
|
global $modSettings; |
|
1323
|
|
|
|
|
1324
|
|
|
$db = database(); |
|
1325
|
|
|
|
|
1326
|
|
|
// Set a delay based on the per minute limit (mail_period_limit) |
|
1327
|
|
|
$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_period_limit']) && $modSettings['mail_period_limit'] <= 5 ? 10 : 5); |
|
1328
|
|
|
|
|
1329
|
|
|
$request = $db->query('', ' |
|
1330
|
|
|
UPDATE {db_prefix}settings |
|
1331
|
|
|
SET value = {string:next_mail_send} |
|
1332
|
|
|
WHERE variable = {string:mail_next_send} |
|
1333
|
|
|
AND value = {string:last_send}', |
|
1334
|
|
|
array( |
|
1335
|
|
|
'next_mail_send' => time() + $delay, |
|
1336
|
|
|
'mail_next_send' => 'mail_next_send', |
|
1337
|
|
|
'last_send' => $modSettings['mail_next_send'], |
|
1338
|
|
|
) |
|
1339
|
|
|
); |
|
1340
|
|
|
if ($request->affected_rows() === 0) |
|
1341
|
|
|
{ |
|
1342
|
|
|
return false; |
|
1343
|
|
|
} |
|
1344
|
|
|
|
|
1345
|
|
|
return (int) $delay; |
|
1346
|
|
|
} |
|
1347
|
|
|
|
|
1348
|
|
|
/** |
|
1349
|
|
|
* Retrieve all details from the database on the next emails. |
|
1350
|
|
|
* |
|
1351
|
|
|
* @param int $number |
|
1352
|
|
|
* @return array |
|
1353
|
|
|
* @throws \Exception |
|
1354
|
|
|
* @package Mail |
|
1355
|
|
|
*/ |
|
1356
|
|
|
function emailsInfo($number) |
|
1357
|
|
|
{ |
|
1358
|
|
|
$db = database(); |
|
1359
|
|
|
$ids = array(); |
|
1360
|
|
|
$emails = array(); |
|
1361
|
|
|
|
|
1362
|
|
|
// Get the next $number emails, with all that's to know about them and one more. |
|
1363
|
|
|
$db->fetchQuery(' |
|
1364
|
|
|
SELECT /*!40001 SQL_NO_CACHE */ id_mail, recipient, body, subject, headers, send_html, time_sent, priority, private, message_id |
|
1365
|
|
|
FROM {db_prefix}mail_queue |
|
1366
|
|
|
ORDER BY priority ASC, id_mail ASC |
|
1367
|
|
|
LIMIT ' . $number, |
|
1368
|
|
|
array() |
|
1369
|
|
|
)->fetch_callback( |
|
1370
|
|
|
function ($row) use (&$ids, &$emails) { |
|
1371
|
|
|
// Just get the data and go. |
|
1372
|
|
|
$ids[] = $row['id_mail']; |
|
1373
|
|
|
$emails[] = array( |
|
1374
|
|
|
'to' => $row['recipient'], |
|
1375
|
|
|
'body' => $row['body'], |
|
1376
|
|
|
'subject' => $row['subject'], |
|
1377
|
|
|
'headers' => $row['headers'], |
|
1378
|
|
|
'send_html' => $row['send_html'], |
|
1379
|
|
|
'time_sent' => $row['time_sent'], |
|
1380
|
|
|
'priority' => $row['priority'], |
|
1381
|
|
|
'private' => $row['private'], |
|
1382
|
|
|
'message_id' => $row['message_id'], |
|
1383
|
|
|
); |
|
1384
|
|
|
} |
|
1385
|
|
|
); |
|
1386
|
|
|
|
|
1387
|
|
|
return array($ids, $emails); |
|
1388
|
|
|
} |
|
1389
|
|
|
|
|
1390
|
|
|
/** |
|
1391
|
|
|
* Sends a group of emails from the mail queue. |
|
1392
|
|
|
* |
|
1393
|
|
|
* - Allows a batch of emails to be released every 5 to 10 seconds (based on per period limits) |
|
1394
|
|
|
* - If batch size is not set, will determine a size such that it sends in 1/2 the period (buffer) |
|
1395
|
|
|
* |
|
1396
|
|
|
* @param int|bool $batch_size = false the number to send each loop |
|
1397
|
|
|
* @param bool $override_limit = false bypassing our limit flaf |
|
1398
|
|
|
* @param bool $force_send = false |
|
1399
|
|
|
* @return bool |
|
1400
|
|
|
* @package Mail |
|
1401
|
|
|
*/ |
|
1402
|
|
|
function reduceMailQueue($batch_size = false, $override_limit = false, $force_send = false) |
|
1403
|
|
|
{ |
|
1404
|
|
|
global $modSettings, $webmaster_email, $scripturl; |
|
1405
|
|
|
|
|
1406
|
|
|
// Do we have another script to send out the queue? |
|
1407
|
|
|
if (!empty($modSettings['mail_queue_use_cron']) && empty($force_send)) |
|
1408
|
|
|
{ |
|
1409
|
|
|
return false; |
|
1410
|
|
|
} |
|
1411
|
|
|
|
|
1412
|
|
|
// How many emails can we send each time we are called in a period |
|
1413
|
|
|
if (!$batch_size) |
|
1414
|
|
|
{ |
|
1415
|
|
|
// Batch size has been set in the ACP, use it |
|
1416
|
|
|
if (!empty($modSettings['mail_batch_size'])) |
|
1417
|
|
|
{ |
|
1418
|
|
|
$batch_size = $modSettings['mail_batch_size']; |
|
1419
|
|
|
} |
|
1420
|
|
|
// No per period setting or batch size, set to send 5 every 5 seconds, or 60 per minute |
|
1421
|
|
|
elseif (empty($modSettings['mail_period_limit'])) |
|
1422
|
|
|
{ |
|
1423
|
|
|
$batch_size = 5; |
|
1424
|
|
|
} |
|
1425
|
|
|
// A per period limit but no defined batch size, set a batch size |
|
1426
|
|
|
else |
|
1427
|
|
|
{ |
|
1428
|
|
|
// Based on the number of times we will potentially be called each minute |
|
1429
|
|
|
$delay = !empty($modSettings['mail_queue_delay']) ? $modSettings['mail_queue_delay'] : (!empty($modSettings['mail_period_limit']) && $modSettings['mail_period_limit'] <= 5 ? 10 : 5); |
|
1430
|
|
|
$batch_size = ceil($modSettings['mail_period_limit'] / ceil(60 / $delay)); |
|
1431
|
|
|
$batch_size = ($batch_size == 1 && $modSettings['mail_period_limit'] > 1) ? 2 : $batch_size; |
|
1432
|
|
|
} |
|
1433
|
|
|
} |
|
1434
|
|
|
|
|
1435
|
|
|
// If we came with a timestamp, and that doesn't match the next event, then someone else has beaten us. |
|
1436
|
|
|
if (isset($_GET['ts']) && $_GET['ts'] != $modSettings['mail_next_send'] && empty($force_send)) |
|
1437
|
|
|
{ |
|
1438
|
|
|
return false; |
|
1439
|
|
|
} |
|
1440
|
|
|
|
|
1441
|
|
|
// Prepare to send each email, and log that for future proof. |
|
1442
|
|
|
require_once(SUBSDIR . '/Maillist.subs.php'); |
|
1443
|
|
|
|
|
1444
|
|
|
// Set the delay for the next sending |
|
1445
|
|
|
$delay = 0; |
|
1446
|
|
|
if (!$override_limit) |
|
1447
|
|
|
{ |
|
1448
|
|
|
// Update next send time for our mail queue, if there was something to update. Otherwise bail out :P |
|
1449
|
|
|
$delay = updateNextSendTime(); |
|
1450
|
|
|
if ($delay === false) |
|
1451
|
|
|
{ |
|
1452
|
|
|
return false; |
|
1453
|
|
|
} |
|
1454
|
|
|
|
|
1455
|
|
|
$modSettings['mail_next_send'] = time() + $delay; |
|
1456
|
|
|
} |
|
1457
|
|
|
|
|
1458
|
|
|
// If we're not overriding, do we have quota left in this mail period limit? |
|
1459
|
|
|
if (!$override_limit && !empty($modSettings['mail_period_limit'])) |
|
1460
|
|
|
{ |
|
1461
|
|
|
// See if we have quota left to send another batch_size this minute or if we have to wait |
|
1462
|
|
|
list ($mail_time, $mail_number) = isset($modSettings['mail_recent']) ? explode('|', $modSettings['mail_recent']) : array(0, 0); |
|
1463
|
|
|
|
|
1464
|
|
|
// Nothing worth noting... |
|
1465
|
|
|
if (empty($mail_number) || $mail_time < time() - 60) |
|
1466
|
|
|
{ |
|
1467
|
|
|
$mail_time = time(); |
|
1468
|
|
|
$mail_number = $batch_size; |
|
1469
|
|
|
} |
|
1470
|
|
|
// Otherwise we may still have quota to send a few more? |
|
1471
|
|
|
elseif ($mail_number < $modSettings['mail_period_limit']) |
|
1472
|
|
|
{ |
|
1473
|
|
|
// If this is likely one of the last cycles for this period, then send any remaining quota |
|
1474
|
|
|
if (($mail_time - (time() - 60)) < $delay * 2) |
|
1475
|
|
|
{ |
|
1476
|
|
|
$batch_size = $modSettings['mail_period_limit'] - $mail_number; |
|
1477
|
|
|
} |
|
1478
|
|
|
// Some batch sizes may need to be adjusted to fit as we approach the end |
|
1479
|
|
|
elseif ($mail_number + $batch_size > $modSettings['mail_period_limit']) |
|
1480
|
|
|
{ |
|
1481
|
|
|
$batch_size = $modSettings['mail_period_limit'] - $mail_number; |
|
1482
|
|
|
} |
|
1483
|
|
|
|
|
1484
|
|
|
$mail_number += $batch_size; |
|
1485
|
|
|
} |
|
1486
|
|
|
// No more I'm afraid, return! |
|
1487
|
|
|
else |
|
1488
|
|
|
{ |
|
1489
|
|
|
return false; |
|
1490
|
|
|
} |
|
1491
|
|
|
|
|
1492
|
|
|
// Reflect that we're about to send some, do it now to be safe. |
|
1493
|
|
|
updateSettings(array('mail_recent' => $mail_time . '|' . $mail_number)); |
|
1494
|
|
|
} |
|
1495
|
|
|
|
|
1496
|
|
|
// Now we know how many we're sending, let's send them. |
|
1497
|
|
|
list ($ids, $emails) = emailsInfo($batch_size); |
|
1498
|
|
|
|
|
1499
|
|
|
// Delete, delete, delete!!! |
|
1500
|
|
|
if (!empty($ids)) |
|
1501
|
|
|
{ |
|
1502
|
|
|
deleteMailQueueItems($ids); |
|
1503
|
|
|
} |
|
1504
|
|
|
|
|
1505
|
|
|
// Don't believe we have any left after this batch? |
|
1506
|
|
|
if (count($ids) < $batch_size) |
|
1507
|
|
|
{ |
|
1508
|
|
|
resetNextSendTime(); |
|
1509
|
|
|
} |
|
1510
|
|
|
|
|
1511
|
|
|
if (empty($ids)) |
|
1512
|
|
|
{ |
|
1513
|
|
|
return false; |
|
1514
|
|
|
} |
|
1515
|
|
|
|
|
1516
|
|
|
// We have some to send, lets send them! |
|
1517
|
|
|
$sent = array(); |
|
1518
|
|
|
$failed_emails = array(); |
|
1519
|
|
|
|
|
1520
|
|
|
// Use sendmail or SMTP |
|
1521
|
|
|
$use_sendmail = empty($modSettings['mail_type']) || $modSettings['smtp_host'] == ''; |
|
1522
|
|
|
|
|
1523
|
|
|
// Line breaks need to be \r\n only in windows or for SMTP. |
|
1524
|
|
|
$line_break = detectServer()->is('windows') || !$use_sendmail ? "\r\n" : "\n"; |
|
1525
|
|
|
|
|
1526
|
|
|
foreach ($emails as $key => $email) |
|
1527
|
|
|
{ |
|
1528
|
|
|
// So that we have it, just in case it's really needed - see #2245 |
|
1529
|
|
|
$email['body_fail'] = $email['body']; |
|
1530
|
|
|
if ($email['message_id'] !== null && isset($email['message_id'][0]) && in_array($email['message_id'][0], array('m', 'p', 't'))) |
|
1531
|
|
|
{ |
|
1532
|
|
|
$email['message_type'] = $email['message_id'][0]; |
|
1533
|
|
|
$email['message_id'] = substr($email['message_id'], 1); |
|
1534
|
|
|
} |
|
1535
|
|
|
else |
|
1536
|
|
|
{ |
|
1537
|
|
|
$email['message_type'] = 'm'; |
|
1538
|
|
|
} |
|
1539
|
|
|
|
|
1540
|
|
|
// Use the right mail resource |
|
1541
|
|
|
if ($use_sendmail) |
|
1542
|
|
|
{ |
|
1543
|
|
|
$email['subject'] = strtr($email['subject'], array("\r" => '', "\n" => '')); |
|
1544
|
|
|
$email['body_fail'] = $email['body']; |
|
1545
|
|
|
|
|
1546
|
|
|
if (!empty($modSettings['mail_strip_carriage'])) |
|
1547
|
|
|
{ |
|
1548
|
|
|
$email['body'] = strtr($email['body'], array("\r" => '')); |
|
1549
|
|
|
$email['headers'] = strtr($email['headers'], array("\r" => '')); |
|
1550
|
|
|
} |
|
1551
|
|
|
|
|
1552
|
|
|
// See the assignment 10 lines before this and #2245 - repeated here for the strtr |
|
1553
|
|
|
$email['body_fail'] = $email['body']; |
|
1554
|
|
|
$need_break = substr($email['headers'], -1) === "\n" || substr($email['headers'], -1) === "\r" ? false : true; |
|
1555
|
|
|
|
|
1556
|
|
|
// Create our unique reply to email header if this message needs one |
|
1557
|
|
|
$unq_id = ''; |
|
1558
|
|
|
$unq_head = ''; |
|
1559
|
|
|
$unq_head_array = array(); |
|
1560
|
|
|
if (!empty($modSettings['maillist_enabled']) && $email['message_id'] !== null && strpos($email['headers'], 'List-Id: <') !== false) |
|
1561
|
|
|
{ |
|
1562
|
|
|
$unq_head_array[0] = md5($scripturl . microtime() . rand()); |
|
1563
|
|
|
$unq_head_array[1] = $email['message_type']; |
|
1564
|
|
|
$unq_head_array[2] = $email['message_id']; |
|
1565
|
|
|
$unq_head = $unq_head_array[0] . '-' . $unq_head_array[1] . $unq_head_array[2]; |
|
1566
|
|
|
$unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . $unq_head . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>'; |
|
1567
|
|
|
$email['body'] = mail_insert_key($email['body'], $unq_head, $line_break); |
|
1568
|
|
|
} |
|
1569
|
|
|
elseif ($email['message_id'] !== null && empty($modSettings['mail_no_message_id'])) |
|
1570
|
|
|
{ |
|
1571
|
|
|
$unq_id = ($need_break ? $line_break : '') . 'Message-ID: <' . md5($scripturl . microtime()) . '-' . $email['message_id'] . strstr(empty($modSettings['maillist_mail_from']) ? $webmaster_email : $modSettings['maillist_mail_from'], '@') . '>'; |
|
1572
|
|
|
} |
|
1573
|
|
|
|
|
1574
|
|
|
// No point logging a specific error here, as we have no language. PHP error is helpful anyway... |
|
1575
|
|
|
$result = mail(strtr($email['to'], array("\r" => '', "\n" => '')), $email['subject'], $email['body'], $email['headers'] . $unq_id); |
|
1576
|
|
|
|
|
1577
|
|
|
// If it sent, keep a record so we can save it in our allowed to reply log |
|
1578
|
|
|
if (!empty($unq_head) && $result) |
|
1579
|
|
|
{ |
|
1580
|
|
|
$sent[] = array_merge($unq_head_array, array(time(), $email['to'])); |
|
1581
|
|
|
} |
|
1582
|
|
|
|
|
1583
|
|
|
// Track total emails sent |
|
1584
|
|
|
if ($result && !empty($modSettings['trackStats'])) |
|
1585
|
|
|
{ |
|
1586
|
|
|
trackStats(array('email' => '+')); |
|
1587
|
|
|
} |
|
1588
|
|
|
|
|
1589
|
|
|
// Try to stop a timeout, this would be bad... |
|
1590
|
|
|
detectServer()->setTimeLimit(300); |
|
1591
|
|
|
} |
|
1592
|
|
|
else |
|
1593
|
|
|
{ |
|
1594
|
|
|
$result = smtp_mail(array($email['to']), $email['subject'], $email['body'], $email['headers'], $email['priority'], $email['message_type'] . $email['message_id']); |
|
1595
|
|
|
} |
|
1596
|
|
|
|
|
1597
|
|
|
// Hopefully it sent? |
|
1598
|
|
|
if (!$result) |
|
1599
|
|
|
{ |
|
1600
|
|
|
$failed_emails[] = array(time(), $email['to'], $email['body_fail'], $email['subject'], $email['headers'], $email['send_html'], $email['priority'], $email['private'], $email['message_id']); |
|
1601
|
|
|
} |
|
1602
|
|
|
} |
|
1603
|
|
|
|
|
1604
|
|
|
// Clear out the stat cache. |
|
1605
|
|
|
trackStats(); |
|
1606
|
|
|
|
|
1607
|
|
|
// Log each of the sent emails. |
|
1608
|
|
|
if (!empty($sent)) |
|
1609
|
|
|
{ |
|
1610
|
|
|
log_email($sent); |
|
1611
|
|
|
} |
|
1612
|
|
|
|
|
1613
|
|
|
// Any emails that didn't send? |
|
1614
|
|
|
if (!empty($failed_emails)) |
|
1615
|
|
|
{ |
|
1616
|
|
|
// If it failed, add it back to the queue |
|
1617
|
|
|
updateFailedQueue($failed_emails); |
|
1618
|
|
|
|
|
1619
|
|
|
return false; |
|
1620
|
|
|
} |
|
1621
|
|
|
// We were able to send the email, clear our failed attempts. |
|
1622
|
|
|
elseif (!empty($modSettings['mail_failed_attempts'])) |
|
1623
|
|
|
{ |
|
1624
|
|
|
updateSuccessQueue(); |
|
1625
|
|
|
} |
|
1626
|
|
|
|
|
1627
|
|
|
// Had something to send... |
|
1628
|
|
|
return true; |
|
1629
|
|
|
} |
|
1630
|
|
|
|
|
1631
|
|
|
/** |
|
1632
|
|
|
* This function finds email address and few other details of the |
|
1633
|
|
|
* poster of a certain message. |
|
1634
|
2 |
|
* |
|
1635
|
|
|
* @param int $id_msg the id of a message |
|
1636
|
2 |
|
* @param int $topic_id the topic the message belongs to |
|
1637
|
|
|
* @return mixed[] the poster's details |
|
1638
|
|
|
* @throws \ElkArte\Exceptions\Exception |
|
1639
|
|
|
* @todo very similar to mailFromMessage |
|
1640
|
|
|
* @package Mail |
|
1641
|
|
|
*/ |
|
1642
|
|
|
function posterDetails($id_msg, $topic_id) |
|
1643
|
|
|
{ |
|
1644
|
|
|
$db = database(); |
|
1645
|
2 |
|
|
|
1646
|
2 |
|
$request = $db->query('', ' |
|
1647
|
|
|
SELECT |
|
1648
|
|
|
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 |
|
1649
|
2 |
|
FROM {db_prefix}messages AS m |
|
1650
|
2 |
|
LEFT JOIN {db_prefix}members AS mem ON (m.id_member = mem.id_member) |
|
1651
|
|
|
WHERE m.id_msg = {int:id_msg} |
|
1652
|
2 |
|
AND m.id_topic = {int:current_topic} |
|
1653
|
|
|
LIMIT 1', |
|
1654
|
|
|
array( |
|
1655
|
|
|
'current_topic' => $topic_id, |
|
1656
|
|
|
'id_msg' => $id_msg, |
|
1657
|
|
|
) |
|
1658
|
|
|
); |
|
1659
|
|
|
$message = $request->fetch_assoc(); |
|
1660
|
|
|
$request->free_result(); |
|
1661
|
|
|
|
|
1662
|
|
|
return $message; |
|
1663
|
|
|
} |
|
1664
|
|
|
|
|
1665
|
|
|
/** |
|
1666
|
|
|
* Little utility function to calculate how long ago a time was. |
|
1667
|
|
|
* |
|
1668
|
|
|
* @param int|double $time_diff |
|
1669
|
|
|
* @return string |
|
1670
|
|
|
* @package Mail |
|
1671
|
|
|
*/ |
|
1672
|
|
|
function time_since($time_diff) |
|
1673
|
|
|
{ |
|
1674
|
|
|
global $txt; |
|
1675
|
|
|
|
|
1676
|
|
|
if ($time_diff < 0) |
|
1677
|
|
|
{ |
|
1678
|
|
|
$time_diff = 0; |
|
1679
|
|
|
} |
|
1680
|
|
|
|
|
1681
|
|
|
// Just do a bit of an if fest... |
|
1682
|
|
|
if ($time_diff > 86400) |
|
1683
|
|
|
{ |
|
1684
|
|
|
$days = round($time_diff / 86400, 1); |
|
1685
|
|
|
|
|
1686
|
|
|
return sprintf($days == 1 ? $txt['mq_day'] : $txt['mq_days'], $time_diff / 86400); |
|
1687
|
|
|
} |
|
1688
|
|
|
// Hours? |
|
1689
|
|
|
elseif ($time_diff > 3600) |
|
1690
|
|
|
{ |
|
1691
|
|
|
$hours = round($time_diff / 3600, 1); |
|
1692
|
|
|
|
|
1693
|
|
|
return sprintf($hours == 1 ? $txt['mq_hour'] : $txt['mq_hours'], $hours); |
|
1694
|
|
|
} |
|
1695
|
|
|
// Minutes? |
|
1696
|
|
|
elseif ($time_diff > 60) |
|
1697
|
|
|
{ |
|
1698
|
|
|
$minutes = (int) ($time_diff / 60); |
|
1699
|
|
|
|
|
1700
|
|
|
return sprintf($minutes === 1 ? $txt['mq_minute'] : $txt['mq_minutes'], $minutes); |
|
1701
|
|
|
} |
|
1702
|
|
|
// Otherwise must be second |
|
1703
|
|
|
else |
|
1704
|
|
|
{ |
|
1705
|
|
|
return sprintf($time_diff == 1 ? $txt['mq_second'] : $txt['mq_seconds'], $time_diff); |
|
1706
|
|
|
} |
|
1707
|
|
|
} |
|
1708
|
|
|
|