Passed
Pull Request — development (#3829)
by Spuds
07:44
created

ManageMail::action_test_email()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 3
nop 0
dl 0
loc 36
ccs 0
cts 0
cp 0
crap 42
rs 9.0111
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Handles mail configuration, displays the queue, and allows for the removal of specific items
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\AdminController;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Action;
21
use ElkArte\Helper\Util;
22
use ElkArte\Languages\Loader;
23
use ElkArte\Languages\Txt;
24
use ElkArte\SettingsForm\SettingsForm;
25
use ElkArte\User;
26
27
/**
28
 * This class is the administration mailing controller.
29
 *
30
 * What it does:
31
 *
32
 * - It handles mail configuration,
33
 * - It displays and allows removing items from the mail queue.
34
 * - It handles sending a test email
35
 *
36
 * @package Mail
37
 */
38
class ManageMail extends AbstractController
39
{
40
	/**
41
	 * Main dispatcher.
42
	 *
43
	 * - This function checks permissions and passes control through to the relevant section.
44
	 *
45
	 * @event integrate_sa_manage_mail Used to add more sub actions
46
	 * @see AbstractController::action_index()
47
	 * @uses Help and MangeMail language files
48
	 */
49
	public function action_index()
50
	{
51
		global $context, $txt;
52
53
		Txt::load('Help+ManageMail');
54
55
		$subActions = [
56
			'browse' => [$this, 'action_browse', 'permission' => 'admin_forum'],
57
			'clear' => [$this, 'action_clear', 'permission' => 'admin_forum'],
58
			'settings' => [$this, 'action_mailSettings_display', 'permission' => 'admin_forum'],
59
			'test' => [$this, 'action_test_email', 'permission' => 'admin_forum'],
60
		];
61
62
		// Action control
63
		$action = new Action('manage_mail');
64
65
		// By default, we want to browse, call integrate_sa_manage_mail
66
		$subAction = $action->initialize($subActions, 'browse');
67
68
		// Final bits
69
		$context['sub_action'] = $subAction;
70
		$context['page_title'] = $txt['mailqueue_title'];
71
72
		// Load up all the tabs...
73
		$context[$context['admin_menu_name']]['object']->prepareTabData([
74
			'title' => 'mailqueue_title',
75
			'class' => 'i-envelope',
76
			'description' => 'mailqueue_desc',
77
			'tabs' => [
78
				'test' => [
79
					'description' => $txt['mail_send_desc'],
80
				],
81
			]
82
		]);
83
84
		// Call the right function for this sub-action.
85
		$action->dispatch($subAction);
86
	}
87
88
	/**
89
	 * Allows viewing and modify the mail settings.
90
	 *
91
	 * @event integrate_save_mail_settings
92
	 * @uses show_settings sub template
93
	 */
94
	public function action_mailSettings_display(): void
95
	{
96
		global $txt, $context, $txtBirthdayEmails;
97
98
		// Some important context stuff
99
		$context['page_title'] = $txt['mail_settings'];
100
		$context['sub_template'] = 'show_settings';
101
102
		// Initialize the form
103
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
104
105
		// Initialize it with our settings
106
		$config_vars = $this->_settings();
107
		$settingsForm->setConfigVars($config_vars);
108
109
		// Piece of redundant code, for the JavaScript
110
		$processedBirthdayEmails = [];
111
		foreach ($txtBirthdayEmails as $key => $value)
112
		{
113
			$index = substr($key, 0, strrpos($key, '_'));
114
			$element = substr($key, strrpos($key, '_') + 1);
115
			$processedBirthdayEmails[$index][$element] = $value;
116
		}
117
118
		// Saving?
119
		if ($this->_req->hasQuery('save'))
120
		{
121
			// Make the SMTP password a little harder to see in a backup etc.
122
			$smtpPassword = (array) ($this->_req->getPost('smtp_password', null, []));
123
			if (!empty($smtpPassword[1])) {
124
				$smtpPassword[0] = base64_encode($smtpPassword[0] ?? '');
125
				$smtpPassword[1] = base64_encode($smtpPassword[1]);
126
			}
127
			$this->_req->post->smtp_password = $smtpPassword;
128
129
			checkSession();
130
131
			// We don't want to save the subject and body previews.
132
			unset($config_vars['birthday_subject'], $config_vars['birthday_body']);
133
			$settingsForm->setConfigVars($config_vars);
134
			call_integration_hook('integrate_save_mail_settings');
135
136
			// You cannot send more per page load than you can per minute
137
			if (!empty($this->_req->post->mail_batch_size))
138
			{
139
				$this->_req->post->mail_batch_size = min((int) $this->_req->post->mail_batch_size, (int) $this->_req->post->mail_period_limit);
140
			}
141
142
			// If not supplied, attempt to set a FQDN value for the SMTP client
143
			if (empty($this->_req->post->smtp_client) && $this->_req->post->mail_type === '1')
144
			{
145
				$this->_req->post->smtp_client = detectServer()->getFQDN();
146
			}
147
148
			$settingsForm->setConfigValues((array) $this->_req->post);
149
			$settingsForm->save();
150
			redirectexit('action=admin;area=mailqueue;sa=settings');
151
		}
152
153
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'mailqueue', 'sa' => 'settings', 'save']);
154
		$context['settings_title'] = $txt['mailqueue_settings'];
155
156
		// Prepare the config form
157
		$settingsForm->prepare();
158
159
		// Build a little JS so the birthday mail can be seen
160
		$javascript = '
161
			var bDay = {';
162
163
		$i = 0;
164
		foreach ($processedBirthdayEmails as $index => $email)
165
		{
166
			$is_last = ++$i === count($processedBirthdayEmails);
167
			$javascript .= '
168
				' . $index . ': {
169
				subject: ' . JavaScriptEscape($email['subject']) . ',
170
				body: ' . JavaScriptEscape(nl2br($email['body'])) . '
171
			}' . ($is_last ? '' : ',');
172
		}
173
174 2
		theme()->addInlineJavascript($javascript . '
175
		};
176 2
		
177
		function fetch_birthday_preview()
178
		{
179 2
			var index = document.getElementById(\'birthday_email\').value;
180
181 2
			document.getElementById(\'birthday_subject\').innerHTML = bDay[index].subject;
182 2
			document.getElementById(\'birthday_body\').innerHTML = bDay[index].body;
183
		}', true);
184 2
	}
185 2
186 2
	/**
187
	 * Retrieve and return mail administration settings.
188 2
	 *
189 2
	 * @event integrate_modify_mail_settings Add new settings
190 2
	 */
191
	private function _settings()
192
	{
193 2
		global $txt, $modSettings, $txtBirthdayEmails;
194
195 2
		// We need $txtBirthdayEmails
196
		if (empty($txtBirthdayEmails))
197
		{
198
			$txtBirthdayEmails = [];
199
		}
200 2
201
		$lang_loader = new Loader(null, $txtBirthdayEmails, database(), 'txtBirthdayEmails');
202
		$lang_loader->load('EmailTemplates');
203 2
204
		$body = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_body'];
205 2
		$subject = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_subject'];
206
207
		$emails = [];
208
		$processedBirthdayEmails = [];
209
		foreach ($txtBirthdayEmails as $key => $value)
210
		{
211 2
			$index = substr($key, 0, strrpos($key, '_'));
212 2
			$element = substr($key, strrpos($key, '_') + 1);
213 2
			$processedBirthdayEmails[$index][$element] = $value;
214 2
		}
215
216
		foreach (array_keys($processedBirthdayEmails) as $index)
217
		{
218 2
			$emails[$index] = $index;
219
		}
220 2
221
		$config_vars = [
222
			// Mail queue stuff, this rocks ;)
223
			['check', 'mail_queue'],
224
			['int', 'mail_period_limit'],
225
			['int', 'mail_batch_size'],
226 2
			'',
227
			// SMTP stuff.
228 2
			['select', 'mail_type', [$txt['mail_type_default'], 'SMTP']],
229
			['text', 'smtp_host'],
230
			['text', 'smtp_client'],
231
			['text', 'smtp_port'],
232
			['check', 'smtp_starttls'],
233
			['text', 'smtp_username'],
234
			['password', 'smtp_password'],
235
			'',
236
			['select', 'birthday_email', $emails, 'value' => ['subject' => $subject, 'body' => $body], 'javascript' => 'onchange="fetch_birthday_preview()"'],
237
			'birthday_subject' => ['var_message', 'birthday_subject', 'message' => $processedBirthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['subject'], 'disabled' => true, 'size' => strlen($subject) + 3],
238
			'birthday_body' => ['var_message', 'birthday_body', 'message' => nl2br($body), 'disabled' => true, 'size' => ceil(strlen($body) / 25)],
239
		];
240
241
		// Add new settings with a nice hook, makes them available for admin settings search as well
242
		call_integration_hook('integrate_modify_mail_settings', [&$config_vars]);
243
244
		return $config_vars;
245
	}
246
247
	/**
248
	 * Return the form settings for use in admin search
249
	 */
250
	public function settings_search()
251
	{
252
		return $this->_settings();
253
	}
254
255
	/**
256
	 * This function clears the mail queue of all emails, and at the end redirects to browse.
257
	 *
258
	 * - Note force clearing the queue may cause a site to exceed hosting mail limit quotas
259
	 * - Some hosts simply lose these excess emails; others queue them server side, up to a limit
260
	 */
261
	public function action_clear()
262
	{
263
		global $modSettings;
264
265
		checkSession('get');
266
267
		// This is certainly needed!
268
		require_once(SUBSDIR . '/Mail.subs.php');
269
270
		// Set a number to send each loop
271
		$number_to_send = empty($modSettings['mail_period_limit']) ? 25 : $modSettings['mail_period_limit'];
272
273
		// If we don't yet have the total to clear, find it.
274
		$all_emails = $this->_req->getQuery('te', 'intval', list_getMailQueueSize());
275
276
		// If we don't know how many we sent, it must be because... we didn't send any!
277
		$sent_emails = $this->_req->getQuery('sent', 'intval', 0);
278
279
		// Send this batch, then go for a short break...
280
		while (reduceMailQueue($number_to_send, true, true) === true)
281
		{
282
			// Sent another batch
283
			$sent_emails += $number_to_send;
284
			$this->_pauseMailQueueClear($all_emails, $sent_emails);
285
		}
286
287
		$this->action_browse();
288
		return null;
289
	}
290
291
	/**
292
	 * Used for pausing the mail queue.
293
	 *
294
	 * @param int $all_emails total emails to be sent
295
	 * @param int $sent_emails number of emails sent so far
296
	 */
297
	private function _pauseMailQueueClear(int $all_emails, int $sent_emails): void
298
	{
299
		global $context, $txt, $time_start;
300
301
		// Try to get more time...
302
		detectServer()->setTimeLimit(600);
303
304
		// Have we already used our maximum time?
305
		if (time() - array_sum(explode(' ', $time_start)) < 5)
306
		{
307
			return;
308
		}
309
310
		$context['continue_get_data'] = '?action=admin;area=mailqueue;sa=clear;te=' . $all_emails . ';sent=' . $sent_emails . ';' . $context['session_var'] . '=' . $context['session_id'];
311
		$context['page_title'] = $txt['not_done_title'];
312
		$context['continue_post_data'] = '';
313
		$context['continue_countdown'] = '10';
314
		$context['sub_template'] = 'not_done';
315
316
		// Keep browse selected.
317
		$context['selected'] = 'browse';
318
319
		// What percent through are we?
320
		$context['continue_percent'] = round(($sent_emails / $all_emails) * 100, 1);
321
322
		// Never more than 100%!
323
		$context['continue_percent'] = min($context['continue_percent'], 100);
324
325
		obExit();
326
	}
327
328
	/**
329
	 * Display the mail queue...
330
	 *
331
	 * @uses ManageMail template
332
	 */
333
	public function action_browse(): void
334
	{
335
		global $context, $txt;
336
337
		require_once(SUBSDIR . '/Mail.subs.php');
338
		theme()->getTemplates()->load('ManageMail');
339
340
		// First, are we deleting something from the queue?
341
		if (isset($this->_req->post->delete))
342
		{
343
			checkSession('post');
344
			deleteMailQueueItems($this->_req->post->delete);
345
		}
346
347
		// Fetch the number of items in the current queue
348
		$status = list_MailQueueStatus();
349
350
		$context['oldest_mail'] = empty($status['mailOldest']) ? $txt['mailqueue_oldest_not_available'] : time_since(time() - $status['mailOldest']);
351
		$context['mail_queue_size'] = comma_format($status['mailQueueSize']);
352
353
		// Build our display list
354
		$listOptions = [
355
			'id' => 'mail_queue',
356
			'title' => $txt['mailqueue_browse'],
357
			'items_per_page' => 20,
358
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'mailqueue']),
359
			'default_sort_col' => 'age',
360
			'no_items_label' => $txt['mailqueue_no_items'],
361
			'get_items' => [
362
				'function' => 'list_getMailQueue',
363
			],
364
			'get_count' => [
365
				'function' => 'list_getMailQueueSize',
366
			],
367
			'columns' => [
368
				'subject' => [
369
					'header' => [
370
						'value' => $txt['mailqueue_subject'],
371
					],
372
					'data' => [
373
						'function' => static fn($rowData) => Util::shorten_text(Util::htmlspecialchars($rowData['subject'], 50)),
374
						'class' => 'smalltext',
375
					],
376
					'sort' => [
377
						'default' => 'subject',
378
						'reverse' => 'subject DESC',
379
					],
380
				],
381
				'recipient' => [
382
					'header' => [
383
						'value' => $txt['mailqueue_recipient'],
384
					],
385
					'data' => [
386
						'sprintf' => [
387
							'format' => '<a href="mailto:%1$s">%1$s</a>',
388
							'params' => [
389
								'recipient' => true,
390
							],
391
						],
392
					],
393
					'sort' => [
394
						'default' => 'recipient',
395
						'reverse' => 'recipient DESC',
396
					],
397
				],
398
				'priority' => [
399
					'header' => [
400
						'value' => $txt['mailqueue_priority'],
401
						'class' => 'centertext',
402
					],
403
					'data' => [
404
						'function' => static function ($rowData) {
405
							global $txt;
406
407
							// We probably have a text label with your priority.
408
							$txtKey = sprintf('mq_mpriority_%1$s', $rowData['priority']);
409
410
							// But if not, revert to priority 0.
411
							return $txt[$txtKey] ?? $txt['mq_mpriority_1'];
412
						},
413
						'class' => 'centertext smalltext',
414
					],
415
					'sort' => [
416
						'default' => 'priority',
417
						'reverse' => 'priority DESC',
418
					],
419
				],
420
				'age' => [
421
					'header' => [
422
						'value' => $txt['mailqueue_age'],
423
					],
424
					'data' => [
425
						'function' => static fn($rowData) => time_since(time() - $rowData['time_sent']),
426
						'class' => 'smalltext',
427
					],
428
					'sort' => [
429
						'default' => 'time_sent',
430
						'reverse' => 'time_sent DESC',
431
					],
432
				],
433
				'check' => [
434
					'header' => [
435
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
436
					],
437
					'data' => [
438
						'function' => static fn($rowData) => '<input type="checkbox" name="delete[]" value="' . $rowData['id_mail'] . '" class="input_check" />',
439
						'class' => 'centertext',
440
					],
441
				],
442
			],
443
			'form' => [
444
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'mailqueue']),
445
				'include_start' => true,
446
				'include_sort' => true,
447
			],
448
			'additional_rows' => [
449
				[
450
					'position' => 'bottom_of_list',
451
					'class' => 'submitbutton',
452
					'value' => '
453
						<input type="submit" name="delete_redirects" value="' . $txt['quickmod_delete_selected'] . '" onclick="return confirm(\'' . $txt['quickmod_confirm'] . '\');" />
454
						<a class="linkbutton" href="' . getUrl('admin', ['action' => 'admin', 'area' => 'mailqueue', 'sa' => 'clear', '{session_data}']) . '" onclick="return confirm(\'' . $txt['mailqueue_clear_list_warning'] . '\');">' . $txt['mailqueue_clear_list'] . '</a> ',
455
				],
456
			],
457
		];
458
459
		createList($listOptions);
460
	}
461
462
	/**
463
	 * Test email action
464
	 */
465
	public function action_test_email(): void
466
	{
467
		global $context, $txt;
468
469
		require_once(SUBSDIR . '/Mail.subs.php');
470
471
		theme()->getTemplates()->load('ManageMail');
472
		$context['page_title'] = $txt['mail_test'];
473
		$context['sub_template'] = 'mail_test';
474
475
		if (isset($this->_req->post->send))
476
		{
477
			checkSession();
478
			validateToken('admin-mailtest');
479
480
			$sendTo = $this->_req->getPost('send_to', 'trim');
481
			$subject = $this->_req->getPost('subject', 'Util::htmlspecialchars', '');
482
			$message = $this->_req->getPost('message', 'Util::htmlspecialchars', '');
483
484
			$sendTo = $sendTo ?: User::$info->email;
485
			if (empty($subject) || empty($message))
486
			{
487
				$result = false;
488
			}
489
			else
490
			{
491
				// Let 'er rip!
492
				$result = sendmail($sendTo, $subject, $message, null, null, true, 0);
493
			}
494
495
			redirectexit('action=admin;area=mailqueue;sa=test;result=' . ($result ? 'pass' : 'fail'));
496
		}
497
498
		createToken('admin-mailtest');
499
500
		$context['result'] = $this->_req->getQuery('result', 'trim', '');
501
	}
502
}
503