ManageMaillist::action_edit_parsers()   F
last analyzed

Complexity

Conditions 20
Paths 12321

Size

Total Lines 105
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 420

Importance

Changes 0
Metric Value
cc 20
eloc 53
nc 12321
nop 0
dl 0
loc 105
ccs 0
cts 52
cp 0
crap 420
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains maillist functions that are specifically done by administrators
5
 * and those with approve email permission
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * @version 2.0 Beta 1
12
 *
13
 */
14
15
namespace ElkArte\AdminController;
16
17
use BBC\ParserWrapper;
18
use ElkArte\AbstractController;
19
use ElkArte\Action;
20
use ElkArte\Cache\Cache;
21
use ElkArte\Converters\Html2Md;
22
use ElkArte\EventManager;
23
use ElkArte\Exceptions\Exception;
24
use ElkArte\Helper\DataValidator;
25
use ElkArte\Helper\Util;
26
use ElkArte\Languages\Txt;
27
use ElkArte\Maillist\MaillistPost;
28
use ElkArte\Maillist\MaillistPreview;
29
use ElkArte\SettingsForm\SettingsForm;
30
use ElkArte\User;
31
32
/**
33
 * This class is the administration maillist controller.
34
 *
35
 *  - Handles maillist configuration
36
 *  - Handles the showing, repairing, deleting, and bouncing failed emails
37
 *  - Handles the adding / editing / removing of both filters and parsers
38
 *
39
 * @package Maillist
40
 */
41
class ManageMaillist extends AbstractController
42
{
43
	/**
44
	 * Main dispatcher.
45
	 *
46
	 * This function checks permissions and passes control to the sub action.
47
	 *
48
	 * @event integrate_sa_manage_maillist Used to add more sub actions
49
	 * @see AbstractController::action_index()
50
	 * @uses Maillist template
51
	 */
52
	public function action_index()
53
	{
54
		global $context, $txt;
55
56
		// Template & language
57
		theme()->getTemplates()->load('Maillist');
58
		Txt::load('Maillist');
59
60
		// All the functions available
61
		$subActions = [
62
			'emaillist' => [$this, 'action_unapproved_email', 'permission' => 'approve_emails'],
63
			'approve' => [$this, 'action_approve_email', 'permission' => 'approve_emails'],
64
			'delete' => [$this, 'action_delete_email', 'permission' => 'approve_emails'],
65
			'bounce' => [$this, 'action_bounce_email', 'permission' => 'approve_emails'],
66
			'emailtemplates' => [$this, 'action_view_bounce_templates', 'permission' => 'approve_emails'],
67
			'view' => [$this, 'action_view_email', 'permission' => 'approve_emails'],
68
			'emailsettings' => [$this, 'action_settings', 'permission' => 'admin_forum'],
69
			'emailfilters' => [$this, 'action_list_filters', 'permission' => 'admin_forum'],
70
			'editfilter' => [$this, 'action_edit_filters', 'permission' => 'admin_forum'],
71
			'deletefilter' => [$this, 'action_delete_filters', 'permission' => 'admin_forum'],
72
			'emailparser' => [$this, 'action_list_parsers', 'permission' => 'admin_forum'],
73
			'editparser' => [$this, 'action_edit_parsers', 'permission' => 'admin_forum'],
74
			'deleteparser' => [$this, 'action_delete_parsers', 'permission' => 'admin_forum'],
75
			'sortparsers' => [$this, 'action_sort_parsers', 'permission' => 'admin_forum'],
76
			'sortfilters' => [$this, 'action_sort_filters', 'permission' => 'admin_forum'],
77
		];
78
79
		// Action Controller
80
		$action = new Action('manage_maillist');
81
82
		// Help is needed in most places, so load it up front
83
		require_once(SUBSDIR . '/Maillist.subs.php');
84
85
		// Default to sub action 'emaillist' if none was given, call integrate_sa_manage_maillist
86
		$subAction = $action->initialize($subActions, 'emaillist');
87
88
		// Final bits
89
		$context['page_title'] = $txt['ml_admin_configuration'];
90
		$context['sub_action'] = $subAction;
91
92
		// Create the tab area for the template.
93
		$context[$context['admin_menu_name']]['object']->prepareTabData([
94
			'title' => 'ml_admin_configuration',
95
			'help' => 'maillist_help_short',
96
			'description' => 'ml_configuration_desc',
97
			'tabs' => [
98
				'emailfilters' => [
99
					'description' => $txt['filters_title'],
100
				],
101
				'emailparser' => [
102
					'description' => $txt['parsers_title'],
103
				],
104
			]
105
		]);
106
107
		// If you have the permissions, then go Play
108
		$action->dispatch($subAction);
109
	}
110
111
	/**
112
	 * Main listing of failed emails.
113
	 *
114
	 * What it does
115
	 *
116
	 * - Shows the sender, key and subject of the email
117
	 * - Will show the found key if it was missing or possible sender if it was wrong
118
	 * - Icons/Actions to view, bounce, delete, or approve a failure
119
	 * - Accessed by ?action=admin;area=maillist;sa=emaillist
120
	 *
121
	 * @event integrate_list_view_email_errors
122
	 * @uses showlist sub template
123
	 */
124
	public function action_unapproved_email(): void
125
	{
126
		global $context, $modSettings, $txt;
127
128
		// Set an id if none was supplied
129
		$id = $this->_req->getQuery('e_id', 'intval', 0);
130
		if (empty($id) || $id <= 0)
131
		{
132
			$id = 0;
133
		}
134
135
		createToken('admin-ml', 'get');
136
137
		// Build the list option array to display the email data
138
		$listOptions = [
139
			'id' => 'view_email_errors',
140
			'title' => $txt['ml_emailerror'],
141
			'items_per_page' => $modSettings['defaultMaxMessages'],
142
			'no_items_label' => $txt['ml_emailerror_none'],
143
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist']),
144
			'default_sort_col' => 'id_email',
145
			'get_items' => [
146
				'function' => fn(int $start, int $items_per_page, string $sort = '', int $id = 0): array => $this->list_maillist_unapproved($start, $items_per_page, $sort, $id),
147
				'params' => [
148
					$id,
149
				],
150
			],
151
			'get_count' => [
152
				'function' => 'list_maillist_count_unapproved',
153
			],
154
			'columns' => [
155
				'id_email' => [
156
					'header' => [
157
						'value' => $txt['id'],
158
						'class' => 'nowrap',
159
					],
160
					'data' => [
161
						'db' => 'id_email',
162
					],
163
					'sort' => [
164
						'default' => 'id_email ',
165
						'reverse' => 'id_email DESC',
166
					],
167
				],
168
				'error' => [
169
					'header' => [
170
						'value' => $txt['error'],
171
					],
172
					'data' => [
173
						'function' => static function ($rowData) {
174
							$error = $rowData['error_code'];
175
							if ($error === 'error_pm_not_found')
176
							{
177
								return '<span class="error">' . $rowData['error'] . '<span>';
178
							}
179
180
							return $rowData['error'];
181
						},
182
					],
183
					'sort' => [
184
						'default' => 'error ',
185
						'reverse' => 'error DESC',
186
					],
187
				],
188
				'subject' => [
189
					'header' => [
190
						'value' => $txt['subject'],
191
					],
192
					'data' => [
193
						'db' => 'subject',
194
					],
195
					'sort' => [
196
						'default' => 'subject',
197
						'reverse' => 'subject DESC',
198
					],
199
				],
200
				'key' => [
201
					'header' => [
202
						'value' => $txt['key'],
203
					],
204
					'data' => [
205
						'db' => 'key',
206
						'class' => 'wordbreak'
207
					],
208
					'sort' => [
209
						'default' => 'message_key',
210
						'reverse' => 'message_key DESC',
211
					],
212
				],
213
				'message' => [
214
					'header' => [
215
						'value' => $txt['message_id'],
216
					],
217
					'data' => [
218
						'sprintf' => [
219
							'format' => '<a href="%1$s">%2$s</a>',
220
							'params' => [
221
								'link' => true,
222
								'message' => true,
223
							],
224
						],
225
					],
226
					'sort' => [
227
						'default' => 'message_id',
228
						'reverse' => 'message_id DESC',
229
					],
230
				],
231
				'from' => [
232
					'header' => [
233
						'value' => $txt['from'],
234
					],
235
					'data' => [
236
						'db' => 'from',
237
					],
238
					'sort' => [
239
						'default' => 'email_from',
240
						'reverse' => 'email_from DESC',
241
					],
242
				],
243
				'type' => [
244
					'header' => [
245
						'value' => $txt['message_type'],
246
					],
247
					'data' => [
248
						'function' => static function ($rowData) {
249
							global $txt;
250
251
							// Do we have a type?
252
							if (empty($rowData['type']))
253
							{
254
								return $txt['not_applicable'];
255
							}
256
257
							// Personal?
258
							if ($rowData['type'] === 'p')
259
							{
260
								return $txt['personal_message'];
261
							}
262
263
							// New Topic?
264
							if ($rowData['type'] === 'x')
265
							{
266
								return $txt['new_topic'];
267
							}
268
269
							return $txt['topic'] . ' ' . $txt['reply'];
270
						},
271
					],
272
					'sort' => [
273
						'default' => 'message_type',
274
						'reverse' => 'message_type DESC',
275
					],
276
				],
277
				'action' => [
278
					'header' => [
279
						'value' => $txt['message_action'],
280
					],
281
					'data' => [
282
						'function' => static function ($rowData) {
283
							global $context, $txt;
284
285
							$id = $rowData['id_email'] . ';';
286
							$commands = [];
287
							$security = $context['session_var'] . '=' . $context['session_id'] . ';' . $context['admin-ml_token_var'] . '=' . $context['admin-ml_token'];
288
289
							if ($rowData['error_code'] === 'error_pm_not_found')
290
							{
291
								$commands[] = '<a href="?action=admin;area=maillist;sa=approve;item=' . $id . $security . '" onclick="return confirm(' . JavaScriptEscape($txt['pm_approve_warning']) . ') && submitThisOnce(this);"><i class="icon i-check" title="' . $txt['approve'] . '"></i></a>&nbsp;';
292
							}
293
							else
294
							{
295
								$commands[] = '<a href="?action=admin;area=maillist;sa=approve;item=' . $id . $security . '"><i class="icon i-check" title="' . $txt['approve'] . '"></i></a>&nbsp;';
296
							}
297
298
							$commands[] = '<a href="?action=admin;area=maillist;sa=delete;item=' . $id . $security . '" onclick="return confirm(' . JavaScriptEscape($txt['delete_warning']) . ') && submitThisOnce(this);" accesskey="d"><i class="icon i-delete" title="' . $txt['delete'] . '"></i></a><br />';
299
							$commands[] = '<a href="?action=admin;area=maillist;sa=bounce;item=' . $id . $security . '"><i class="icon i-sign-out" title="' . $txt['bounce'] . '"></i></a>&nbsp;';
300
							$commands[] = '<a href="?action=admin;area=maillist;sa=view;item=' . $id . $security . '"><i class="icon i-view" title="' . $txt['view'] . '"></i></a>';
301
302
							return implode('', $commands);
303
						},
304
					],
305
					'class' => 'listaction',
306
				],
307
			],
308
			'form' => [
309
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emaillist']),
310
				'include_sort' => true,
311
				'include_start' => true,
312
			],
313
			'additional_rows' => [
314
				[
315
					'position' => 'after_title',
316
					'class' => isset($_SESSION['email_error'], $_SESSION['email_error_type']) ? 'successbox' : (isset($_SESSION['email_error']) ? 'errorbox' : 'description'),
317
					'value' => $this->_req->session->email_error ?? $txt['heading'],
318
				],
319
			],
320
		];
321
322
		// Clear any errors
323
		unset($_SESSION['email_error'], $_SESSION['email_error_type']);
324
325
		// Set the context values for the template
326
		$context['page_title'] = $txt['emailerror_title'];
327
		$context['sub_template'] = 'show_list';
328
		$context['default_list'] = 'view_email_errors';
329
330
		// Create the list.
331
		createList($listOptions);
332
	}
333
334
	/**
335
	 * Show a failed email for review by the moderation team
336
	 *
337
	 * What it does:
338
	 *
339
	 * - Will not show a PM if it has been identified as such
340
	 * - Accessed by ?action=admin;area=maillist;sa=view;item=?
341
	 *
342
	 * @uses show_email sub template
343
	 */
344
	public function action_view_email(): void
345
	{
346
		global $txt, $context;
347
348
		checkSession('get');
349
		validateToken('admin-ml', 'get');
350
351
		$id = $this->_req->getQuery('item', 'intval');
352
		if (!empty($id))
353
		{
354
			// Load up the email details, no funny biz ;)
355
			$temp_email = list_maillist_unapproved($id);
356
357
			if (!empty($temp_email))
358
			{
359
				if ($temp_email[0]['type'] !== 'p' && allowedTo('approve_emails'))
360
				{
361
					// The raw email that failed
362
					$data = $temp_email[0]['body'];
363
364
					// Read/parse this message for viewing
365
					$controller = new MaillistPreview(new EventManager());
366
					$controller->setUser(User::$info);
367
					$result = $controller->action_pbe_preview($data);
368
					$text = $result['body'] ?? '';
369
					$email_to = $result['to'] ?? '';
370
				}
371
				else
372
				{
373
					// PM's mean just that ...
374
					$text = $txt['noaccess'];
375
					$email_to = $txt['private'];
376
				}
377
			}
378
			else
379
			{
380
				$text = $txt['badid'];
381
			}
382
		}
383
		else
384
		{
385
			$text = $txt['badid'];
386
		}
387
388
		$parser = ParserWrapper::instance();
389
390
		// Prep and show the template with what we found
391
		$context['body'] = $parser->parseEmail($text);
392
		$context['to'] = $email_to ?? '';
393
		$context['notice_subject'] = $temp_email[0]['subject'] ?? '';
394
		$context['notice_from'] = $temp_email[0]['from'] ?? '';
395
		$context['page_title'] = $txt['show_notice'];
396
		$context['error_code'] = isset($temp_email[0]['error_code'], $txt[$temp_email[0]['error_code']]) ? $txt[$temp_email[0]['error_code']] : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $temp_email does not seem to be defined for all execution paths leading up to this point.
Loading history...
397
		$context['sub_template'] = 'show_email';
398
	}
399
400
	/**
401
	 * Deletes an entry from the database
402
	 *
403
	 * What it does:
404
	 *
405
	 * - Flushes the moderator menu to-do numbers so the menu numbers update
406
	 * - Accessed by ?action=admin;area=maillist;sa=delete;item=?
407
	 * - Redirects to ?action=admin;area=maillist;sa=emaillist
408
	 */
409
	public function action_delete_email(): void
410
	{
411
		checkSession('get');
412
		validateToken('admin-ml', 'get');
413
414
		$id = $this->_req->getQuery('item', 'intval');
415
416
		// Remove this entry
417
		if (!empty($id))
418
		{
419
			maillist_delete_error_entry($id);
420
		}
421
422
		// Flush the cache
423
		Cache::instance()->remove('num_menu_errors');
424
425
		// Back to the failed list we go
426
		redirectexit('action=admin;area=maillist;sa=emaillist');
427
	}
428
429
	/**
430
	 * Attempts to approve and post a failed email
431
	 *
432
	 * What it does:
433
	 *
434
	 * - Reviews the data to see if the email error function fixed typical issues like key and wrong id
435
	 * - Submits the fixed email to the main function which will post it or fail it again
436
	 * - If successful will remove the entry from the failed log.
437
	 * - Accessed by ?action=admin;area=maillist;sa=approve;item=?
438
	 * - Redirects to action=admin;area=maillist;sa=emaillist.
439
	 */
440
	public function action_approve_email(): void
441
	{
442
		global $txt;
443
444
		checkSession('get');
445
		validateToken('admin-ml', 'get');
446
447
		// Get the id to approve
448
		$id = $this->_req->getQuery('item', 'intval');
449
450
		if (!empty($id) && $id !== -1)
451
		{
452
			// Load up the email data
453
			$temp_email = list_maillist_unapproved($id);
454
			if (!empty($temp_email))
455
			{
456
				// Do we have the necessary data to approve this; after all, it failed for a reason yes?
457
				if (!empty($temp_email[0]['key']) && (!in_array($temp_email[0]['error_code'], ['error_no_message', 'error_not_find_board', 'error_topic_gone'])))
458
				{
459
					// Set up the details needed to get this posted
460
					$key = $temp_email[0]['key'] . '-' . $temp_email[0]['type'] . $temp_email[0]['message'];
461
					$data = $temp_email[0]['body'];
462
463
					// Unknown from email?  Update the message ONLY if we found an appropriate one during the error checking process
464
					if (in_array($temp_email[0]['error_code'], ['error_not_find_member', 'error_key_sender_match']))
465
					{
466
						// did we actually find a potential correct name, if so we post from the valid member?
467
						$check_emails = array_pad(explode('=>', $temp_email[0]['from']), 2, '');
468
469
						if (!empty($check_emails[1]))
470
						{
471
							$data = preg_replace('~(From: )(.*<)?(' . preg_quote(trim($check_emails[0]), '~') . ')(>)?(\n)~i', '$1$2' . trim($check_emails[1]) . '$4$5', $data);
472
						}
473
					}
474
475
					// Let's TRY AGAIN to make a post!
476
					$controller = new MaillistPost(new EventManager());
477
					$controller->setUser(User::$info);
478
					$text = $controller->action_pbe_post($data, true, $key);
479
480
					// Assuming all went well, remove this entry and file since we are done.
481
					if ($text)
482
					{
483
						maillist_delete_error_entry($id);
484
485
						// Flush the menu count cache
486
						Cache::instance()->remove('num_menu_errors');
487
488
						$_SESSION['email_error'] = $txt['approved'];
489
						$_SESSION['email_error_type'] = 1;
490
					}
491
					else
492
					{
493
						$_SESSION['email_error'] = $txt['error_approved'];
494
					}
495
				}
496
				else
497
				{
498
					$_SESSION['email_error'] = $txt['cant_approve'];
499
				}
500
			}
501
			else
502
			{
503
				$_SESSION['email_error'] = $txt['badid'];
504
			}
505
		}
506
		else
507
		{
508
			$_SESSION['email_error'] = $txt['badid'];
509
		}
510
511
		// back to the list we go
512
		redirectexit('action=admin;area=maillist;sa=emaillist');
513
	}
514
515
	/**
516
	 * Allows the admin to choose from predefined and custom templates
517
	 *
518
	 * What it does:
519
	 *
520
	 * - Uses the selected template to send a bounce notification with
521
	 * details as specified by the template.
522
	 * - Accessed by ?action=admin;area=maillist;sa=bounce;item=?
523
	 * - Redirects to action=admin;area=maillist;sa=bounced
524
	 * - Provides {MEMBER}, {SCRIPTURL}, {FORUMNAME}, {REGARDS}, {SUBJECT}, {ERROR},
525
	 * {FORUMNAMESHORT}, {EMAILREGARDS} replaceable values to the template.
526
	 *
527
	 * @uses bounce_email sub-template
528
	 */
529
	public function action_bounce_email(): void
530
	{
531
		global $context, $txt, $modSettings, $scripturl, $mbname;
532
533
		if ($this->_req->hasQuery('bounce'))
534
		{
535
			checkSession('get');
536
			validateToken('admin-ml', 'get');
537
		}
538
539
		require_once(SUBSDIR . '/Mail.subs.php');
540
541
		// We should have been sent an email ID
542
		$id = $this->_req->get('item', 'intval');
543
		if (!empty($id))
544
		{
545
			// Load up the email details, no funny biz y'all ;)
546
			$temp_email = list_maillist_unapproved($id);
547
548
			if (!empty($temp_email))
549
			{
550
				// Set the options
551
				$this->_req->post->item = (int) $temp_email[0]['id_email'];
552
				$fullerrortext = $txt[$temp_email[0]['error_code']];
553
554
				// Build the template selection area, first the standard ones
555
				$bounce = ['bounce', 'inform'];
556
				foreach ($bounce as $k => $type)
557
				{
558
					$context['bounce_templates'][$k]['body'] = $txt['ml_' . $type . '_body'];
559
					$context['bounce_templates'][$k]['subject'] = $txt['ml_' . $type . '_subject'];
560
					$context['bounce_templates'][$k]['title'] = $txt['ml_' . $type . '_title'];
561
				}
562
563
				// And now any custom ones available for this moderator
564
				$context['bounce_templates'] += array_merge($context['bounce_templates'], maillist_templates('bnctpl', $txt['ml_bounce_template_subject_default']));
565
566
				// Replace all the variables in the templates
567
				foreach ($context['bounce_templates'] as $k => $name)
568
				{
569
					$context['bounce_templates'][$k]['body'] = strtr($name['body'], [
570
						'{MEMBER}' => un_htmlspecialchars($temp_email[0]['name']),
571
						'{SCRIPTURL}' => $scripturl,
572
						'{FORUMNAME}' => $mbname,
573
						'{REGARDS}' => replaceBasicActionUrl($txt['regards_team']),
574
						'{SUBJECT}' => $temp_email[0]['subject'],
575
						'{ERROR}' => $fullerrortext,
576
						'{FORUMNAMESHORT}' => (empty($modSettings['maillist_sitename']) ? $mbname : $modSettings['maillist_sitename']),
577
						'{EMAILREGARDS}' => (empty($modSettings['maillist_sitename_regards']) ? '' : $modSettings['maillist_sitename_regards']),
578
					]);
579
				}
580
			}
581
			else
582
			{
583
				$context['settings_message'] = $txt['badid'];
584
			}
585
		}
586
		else
587
		{
588
			$context['settings_message'] = $txt['badid'];
589
		}
590
591
		// Check if they are sending the notice
592
		if (isset($this->_req->post->bounce, $temp_email))
593
		{
594
			checkSession();
595
			validateToken('admin-ml');
596
597
			// They did check the box, how else could they have posted
598
			if (isset($this->_req->post->warn_notify))
599
			{
600
				// let's make sure we have the items to send it
601
				$check_emails = explode('=>', $temp_email[0]['from']);
602
				$to = trim($check_emails[0]);
603
				$subject = trim($this->_req->post->warn_sub);
604
				$body = trim($this->_req->post->warn_body);
605
606
				if ($body === '' || $body === '0' || ($subject === '' || $subject === '0'))
607
				{
608
					$context['settings_message'] = $txt['bad_bounce'];
609
				}
610
				else
611
				{
612
					// Time for someone to get a "we're so sorry" message!
613
					$mark_down = new Html2Md($body);
614
					$body = $mark_down->get_markdown();
615
					sendmail($to, $subject, $body, null, null, false, 5);
616
					redirectexit('action=admin;area=maillist;bounced');
617
				}
618
			}
619
		}
620
621
		// Prepare and show the template
622
		createToken('admin-ml');
623
		$context['warning_data'] = ['notify' => '', 'notify_subject' => '', 'notify_body' => ''];
624
		$context['body'] = isset($fullerrortext) ? ParserWrapper::instance()->parseEmail($fullerrortext) : '';
625
		$context['item'] = $this->_req->hasPost('item') ?? '';
626
		$context['notice_to'] = $txt['to'] . ' ' . isset($temp_email[0]['from']) !== '' ? $temp_email[0]['from'] : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $temp_email does not seem to be defined for all execution paths leading up to this point.
Loading history...
627
		$context['page_title'] = $txt['bounce_title'];
628
		$context['sub_template'] = 'bounce_email';
629
	}
630
631
	/**
632
	 * List all the filters in the system
633
	 *
634
	 * What it does:
635
	 *
636
	 * - Allows adding/editing or deleting filters.
637
	 * - Filters are used to alter text in a post, to remove crud that comes with emails.
638
	 * - Filters can be defined as regex, the system will check it for valid syntax.
639
	 * - Accessed by ?action=admin;area=maillist;sa=emailfilters;
640
	 *
641
	 * @event integrate_list_email_filter
642
	 */
643
	public function action_list_filters(): void
644
	{
645
		global $context, $txt, $modSettings;
646
647
		$id = 0;
648
649
		// Build the listoption array to display the filters
650
		$listOptions = [
651
			'id' => 'email_filter',
652
			'title' => $txt['filters'],
653
			'items_per_page' => $modSettings['defaultMaxMessages'],
654
			'no_items_label' => $txt['no_filters'],
655
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailfilters']),
656
			'default_sort_col' => 'name',
657
			'get_items' => [
658
				'function' => fn(int $start, int $items_per_page, string $sort, int $id, string $style): array => $this->load_filter_parser($start, $items_per_page, $sort, $id, $style),
659
				'params' => [
660
					$id,
661
					'filter'
662
				],
663
			],
664
			'get_count' => [
665
				'function' => fn(int $id, string $style): int => $this->count_filter_parser($id, $style),
666
				'params' => [
667
					$id,
668
					'filter'
669
				],
670
			],
671
			'columns' => [
672
				'name' => [
673
					'header' => [
674
						'value' => $txt['filter_name'],
675
						'style' => 'white-space: nowrap;'
676
					],
677
					'data' => [
678
						'db' => 'filter_name',
679
					],
680
					'sort' => [
681
						'default' => 'filter_name, id_filter',
682
						'reverse' => 'filter_name DESC, id_filter DESC',
683
					],
684
				],
685
				'from' => [
686
					'header' => [
687
						'value' => $txt['filter_from'],
688
					],
689
					'data' => [
690
						'db' => 'filter_from',
691
					],
692
					'sort' => [
693
						'default' => 'filter_from, id_filter',
694
						'reverse' => 'filter_from DESC, id_filter DESC',
695
					],
696
				],
697
				'to' => [
698
					'header' => [
699
						'value' => $txt['filter_to'],
700
						'style' => 'width:10em;',
701
					],
702
					'data' => [
703
						'db' => 'filter_to',
704
					],
705
					'sort' => [
706
						'default' => 'filter_to, id_filter',
707
						'reverse' => 'filter_to DESC, id_filter DESC',
708
					],
709
				],
710
				'type' => [
711
					'header' => [
712
						'value' => $txt['filter_type'],
713
					],
714
					'data' => [
715
						'db' => 'filter_type',
716
					],
717
					'sort' => [
718
						'default' => 'filter_type, id_filter',
719
						'reverse' => 'filter_type DESC, id_filter DESC',
720
					],
721
				],
722
				'action' => [
723
					'header' => [
724
						'value' => $txt['message_action'],
725
						'class' => 'centertext',
726
					],
727
					'data' => [
728
						'sprintf' => [
729
							'format' => '
730
								<a href="?action=admin;area=maillist;sa=editfilter;f_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '">
731
									<i class="icon i-modify" title="' . $txt['modify'] . '"></i>
732
								</a>
733
								<a href="?action=admin;area=maillist;sa=deletefilter;f_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '" onclick="return confirm(' . JavaScriptEscape($txt['filter_delete_warning']) . ') && submitThisOnce(this);" accesskey="d">
734
									<i class="icon i-delete" title="' . $txt['delete'] . '"></i>
735
								</a>',
736
							'params' => [
737
								'id_filter' => true,
738
							],
739
						],
740
						'class' => 'centertext',
741
						'style' => 'white-space:nowrap;',
742
					],
743
				],
744
			],
745
			'form' => [
746
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editfilter']),
747
				'include_sort' => true,
748
				'include_start' => true,
749
				'hidden_fields' => [
750
					$context['session_var'] => $context['session_id'],
751
				],
752
			],
753
			'additional_rows' => [
754
				[
755
					'position' => 'top_of_list',
756
					'class' => $this->_req->hasQuery('saved') ? 'successbox' : '',
757
					'value' => $this->_req->hasQuery('saved') ? $txt['saved'] : '',
758
				],
759
				[
760
					'position' => 'below_table_data',
761
					'class' => 'submitbutton',
762
					'value' => '
763
						<input type="submit" name="addfilter" value="' . $txt['add_filter'] . '" />
764
						<a class="linkbutton" href="' . getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortfilters']) . '">' . $txt['sort_filter'] . '</a>',
765
				],
766
			],
767
		];
768
769
		// Set the context values
770
		$context['page_title'] = $txt['filters'];
771
		$context['sub_template'] = 'show_list';
772
		$context['default_list'] = 'email_filter';
773
774
		// Create the list.
775
		createList($listOptions);
776
	}
777
778
	/**
779
	 * Show a full list of all the filters in the system for drag/drop sorting
780
	 *
781
	 * @event integrate_list_sort_email_fp
782
	 */
783
	public function action_sort_filters(): void
784
	{
785
		global $context, $txt;
786
787
		$id = 0;
788
		$token = createToken('admin-sort');
789
790
		// build the listoption array to display the data
791
		$listOptions = [
792
			'id' => 'sort_email_fp',
793
			'title' => $txt['sort_filter'],
794
			'sortable' => true,
795
			'items_per_page' => 0,
796
			'no_items_label' => $txt['no_filters'],
797
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortfilters']),
798
			'get_items' => [
799
				'function' => fn(int $start, int $items_per_page, string $sort, int $id, string $style): array => $this->load_filter_parser($start, $items_per_page, $sort, $id, $style),
800
				'params' => [
801
					$id,
802
					'filter'
803
				],
804
			],
805
			'get_count' => [
806
				'function' => fn(int $id, string $style): int => $this->count_filter_parser($id, $style),
807
				'params' => [
808
					$id,
809
					'filter'
810
				],
811
			],
812
			'columns' => [
813
				'filterorder' => [
814
					'header' => [
815
						'value' => '',
816
						'class' => 'hide',
817
					],
818
					'data' => [
819
						'db' => 'filter_order',
820
						'class' => 'hide',
821
					],
822
				],
823
				'name' => [
824
					'header' => [
825
						'value' => $txt['filter_name'],
826
						'style' => 'white-space: nowrap;width: 10em'
827
					],
828
					'data' => [
829
						'db' => 'filter_name',
830
					],
831
				],
832
				'from' => [
833
					'header' => [
834
						'value' => $txt['filter_from'],
835
					],
836
					'data' => [
837
						'db' => 'filter_from',
838
					],
839
				],
840
				'to' => [
841
					'header' => [
842
						'value' => $txt['filter_to'],
843
						'style' => 'width:10em;',
844
					],
845
					'data' => [
846
						'db' => 'filter_to',
847
					],
848
				],
849
				'type' => [
850
					'header' => [
851
						'value' => $txt['filter_type'],
852
					],
853
					'data' => [
854
						'db' => 'filter_type',
855
					],
856
				],
857
			],
858
			'form' => [
859
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortfilters']),
860
				'hidden_fields' => [
861
					$context['session_var'] => $context['session_id'],
862
				],
863
			],
864
			'additional_rows' => [
865
				[
866
					'position' => 'after_title',
867
					'value' => $txt['filter_sort_description'],
868
				],
869
			],
870
			'javascript' => '
871
				$().elkSortable({
872
					sa: "parserorder",
873
					placeholder: "ui-state-highlight",
874
					containment: "#sort_email_fp",
875
					error: "' . $txt['admin_order_error'] . '",
876
					title: "' . $txt['admin_order_title'] . '",
877
					href: "?action=admin;area=maillist;sa=sortfilters",
878
					token: {token_var: "' . $token['admin-sort_token_var'] . '", token_id: "' . $token['admin-sort_token'] . '"}
879
				});
880
			',
881
		];
882
883
		// Set the context values
884
		$context['page_title'] = $txt['filters'];
885
		$context['sub_template'] = 'show_list';
886
		$context['default_list'] = 'sort_email_fp';
887
888
		// Create the list.
889
		createList($listOptions);
890
	}
891
892
	/**
893
	 * Returns the number of filters or parsers in the system
894
	 *
895
	 * - Callback for createList()
896
	 *
897
	 * @param int $id 0 for all of a certain style
898
	 * @param string $style either filter or parser
899
	 *
900
	 * @return int
901
	 */
902
	public function count_filter_parser(int $id, string $style): int
903
	{
904
		return list_count_filter_parser($id, $style);
905
	}
906
907
	/**
908
	 * Returns the details for the filters or parsers in the system
909
	 *
910
	 * - Callback for createList()
911
	 *
912
	 * @param int $start The item to start with (for pagination purposes)
913
	 * @param int $items_per_page The number of items to show per page
914
	 * @param string $sort A string indicating how to sort the results
915
	 * @param int $id
916
	 * @param string $style
917
	 *
918
	 * @return array
919
	 */
920
	public function load_filter_parser(int $start, int $items_per_page, string $sort, int $id, string $style): array
921
	{
922
		return list_get_filter_parser($start, $items_per_page, $sort, $id, $style);
923
	}
924
925
	/**
926
	 * Edit or Add a filter
927
	 *
928
	 * - If regex checks for proper syntax before saving to the database
929
	 *
930
	 * @event integrate_save_filter_settings
931
	 *
932
	 */
933
	public function action_edit_filters(): void
934
	{
935
		global $context, $txt, $modSettings;
936
937
		// Editing an existing filter?
938
		if ($this->_req->hasQuery('f_id'))
939
		{
940
			// Needs to be an int!
941
			$id = $this->_req->getQuery('f_id', 'intval', 0);
942
			if ($id <= 0)
943
			{
944
				throw new Exception('error_no_id_filter');
945
			}
946
947
			// Load it up and set it as the current values
948
			$row = maillist_load_filter_parser($id, 'filter');
949
			$modSettings['id_filter'] = $row['id_filter'];
950
			$modSettings['filter_type'] = $row['filter_type'];
951
			$modSettings['filter_to'] = $row['filter_to'];
952
			$modSettings['filter_from'] = $row['filter_from'];
953
			$modSettings['filter_name'] = $row['filter_name'];
954
955
			// Some items for the form
956
			$context['page_title'] = $txt['edit_filter'];
957
			$context['editing'] = true;
958
			$context['settings_message'] = [];
959
		}
960
		else
961
		{
962
			// Set up placeholders for adding a new one instead
963
			$modSettings['filter_type'] = '';
964
			$modSettings['filter_to'] = '';
965
			$modSettings['filter_from'] = '';
966
			$modSettings['filter_name'] = '';
967
968
			$context['page_title'] = $txt['add_filter'];
969
			$context['editing'] = false;
970
			$context['settings_message'] = [];
971
		}
972
973
		// Initialize the form
974
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
975
976
		// Initialize it with our settings
977
		$config_vars = $this->_filtersSettings();
978
		$settingsForm->setConfigVars($config_vars);
979
980
		// Saving the new or edited entry?
981
		if ($this->_req->hasQuery('save'))
982
		{
983
			checkSession();
984
985
			call_integration_hook('integrate_save_filter_settings');
986
987
			// Editing an entry?
988
			$editRaw = $this->_req->getQuery('edit', 'trim|strval', '');
989
			$isEdit = ($editRaw !== '' && $editRaw !== 'new');
990
			$editId = $isEdit ? (int) $editRaw : -1;
991
			$editName = $isEdit ? 'id_filter' : '';
992
993
			// If its regex we do a quick check for validity
994
			if ($this->_req->post->filter_type === 'regex')
995
			{
996
				$valid = @preg_replace($this->_req->post->filter_from, $this->_req->post->filter_to, 'ElkArte') !== null;
997
				if (!$valid)
998
				{
999
					// Seems bad ... reload the form, set the message
1000
					$context['error_type'] = 'notice';
1001
					$context['settings_message'][] = $txt['regex_invalid'];
1002
					$modSettings['filter_type'] = $this->_req->post->filter_type;
1003
					$modSettings['filter_to'] = $this->_req->post->filter_to;
1004
					$modSettings['filter_from'] = $this->_req->post->filter_from;
1005
					$modSettings['filter_name'] = $this->_req->post->filter_name;
1006
				}
1007
			}
1008
1009
			if (empty($this->_req->post->filter_type) || empty($this->_req->post->filter_from))
1010
			{
1011
				$context['error_type'] = 'notice';
1012
				$context['settings_message'][] = $txt['filter_invalid'];
1013
			}
1014
1015
			// if we are good to save, so save it ;)
1016
			if (empty($context['settings_message']))
1017
			{
1018
				// And ... it's a filter
1019
				$config_vars[] = ['text', 'filter_style'];
1020
				$this->_req->post->filter_style = 'filter';
1021
1022
				MaillistSettings::saveTableSettings($config_vars, 'postby_emails_filters', $this->_req->post, ['id_filter'], $editId, $editName);
1023
				redirectexit('action=admin;area=maillist;sa=emailfilters;saved');
1024
			}
1025
		}
1026
1027
		// Prepare some final context for the template
1028
		$title = $this->_req->hasQuery('saved') ? 'saved_filter' : ($context['editing'] === true ? 'edit_filter' : 'add_filter');
1029
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editfilter', 'edit' => $context['editing'] ? $modSettings['id_filter'] : 'new', 'save']);
1030
		$context['settings_title'] = $txt[$title];
1031
		$context['breadcrumbs'][] = [
1032
			'url' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editfilter']),
1033
			'name' => ($context['editing']) ? $txt['edit_filter'] : $txt['add_filter'],
1034
		];
1035
1036
		// Load and show
1037
		$settingsForm->prepare();
1038
		theme()->getTemplates()->load('Admin');
1039
		loadCSSFile('admin.css');
1040
		$context['sub_template'] = 'show_settings';
1041
	}
1042
1043
	/**
1044
	 * Initialize Mailist settings form.
1045
	 *
1046
	 * @event integrate_modify_maillist_filter_settings Add new settings to the maillist filter area
1047
	 */
1048
	private function _filtersSettings()
1049
	{
1050
		global $txt;
1051
1052
		$config_vars = [
1053
			['text', 'filter_name', 25, 'subtext' => $txt['filter_name_desc']],
1054
			['select', 'filter_type',
1055
				[
1056
					'standard' => $txt['option_standard'],
1057
					'regex' => $txt['option_regex'],
1058
				],
1059
			],
1060
			['large_text', 'filter_from', 4, 'subtext' => $txt['filter_from_desc']],
1061
			['text', 'filter_to', 25, 'subtext' => $txt['filter_to_desc']],
1062
		];
1063
1064
		call_integration_hook('integrate_modify_maillist_filter_settings', [&$config_vars]);
1065
1066
		return $config_vars;
1067
	}
1068
1069
	/**
1070
	 * Return the form settings for use in admin search
1071
	 */
1072
	public function filter_search()
1073
	{
1074
		global $modSettings;
1075
1076
		if (empty($modSettings['maillist_enabled']))
1077
		{
1078
			return ['check', 'filter_name'];
1079
		}
1080
1081
		return $this->_filtersSettings();
1082
	}
1083
1084
	/**
1085
	 * Deletes a filter from the system / database
1086
	 */
1087
	public function action_delete_filters(): void
1088
	{
1089
		// Removing the filter?
1090
		if ($this->_req->hasQuery('f_id'))
1091
		{
1092
			checkSession('get');
1093
			$id = $this->_req->getQuery('f_id', 'intval', 0);
1094
1095
			maillist_delete_filter_parser($id);
1096
			redirectexit('action=admin;area=maillist;sa=emailfilters;deleted');
1097
		}
1098
	}
1099
1100
	/**
1101
	 * Show a list of all the parsers in the system
1102
	 *
1103
	 * What it does:
1104
	 *
1105
	 * - Allows adding/editing or deleting parsers
1106
	 * - Parsers are used to split a message at a line of text
1107
	 * - Parsers can only be defined as regex, the system will check it for valid syntax
1108
	 * - Accessed by ?action=admin;area=maillist;sa=emailparser;
1109
	 *
1110
	 * @event integrate_list_email_parser
1111
	 */
1112
	public function action_list_parsers(): void
1113
	{
1114
		global $context, $txt, $modSettings;
1115
1116
		$id = 0;
1117
1118
		// Build the listoption array to display the data
1119
		$listOptions = [
1120
			'id' => 'email_parser',
1121
			'title' => $txt['parsers'],
1122
			'items_per_page' => $modSettings['defaultMaxMessages'],
1123
			'no_items_label' => $txt['no_parsers'],
1124
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailparser']),
1125
			'get_items' => [
1126
				'function' => fn(int $start, int $items_per_page, string $sort, int $id, string $style): array => $this->load_filter_parser($start, $items_per_page, $sort, $id, $style),
1127
				'params' => [
1128
					$id,
1129
					'parser'
1130
				],
1131
			],
1132
			'get_count' => [
1133
				'function' => fn(int $id, string $style): int => $this->count_filter_parser($id, $style),
1134
				'params' => [
1135
					$id,
1136
					'parser'
1137
				],
1138
			],
1139
			'columns' => [
1140
				'name' => [
1141
					'header' => [
1142
						'value' => $txt['parser_name'],
1143
						'style' => 'white-space: nowrap;'
1144
					],
1145
					'data' => [
1146
						'db' => 'filter_name',
1147
					],
1148
					'sort' => [
1149
						'default' => 'filter_name',
1150
						'reverse' => 'filter_name DESC',
1151
					],
1152
				],
1153
				'from' => [
1154
					'header' => [
1155
						'value' => $txt['parser_from'],
1156
					],
1157
					'data' => [
1158
						'db' => 'filter_from',
1159
					],
1160
					'sort' => [
1161
						'default' => 'filter_from',
1162
						'reverse' => 'filter_from DESC',
1163
					],
1164
				],
1165
				'type' => [
1166
					'header' => [
1167
						'value' => $txt['parser_type'],
1168
					],
1169
					'data' => [
1170
						'db' => 'filter_type',
1171
					],
1172
					'sort' => [
1173
						'default' => 'filter_type',
1174
						'reverse' => 'filter_type DESC',
1175
					],
1176
				],
1177
				'action' => [
1178
					'header' => [
1179
						'value' => $txt['message_action'],
1180
						'class' => 'centertext',
1181
					],
1182
					'data' => [
1183
						'sprintf' => [
1184
							'format' => '
1185
								<a href="?action=admin;area=maillist;sa=editparser;f_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '">
1186
									<i class="icon i-modify" title="' . $txt['modify'] . '"></i>
1187
								</a>
1188
								<a href="?action=admin;area=maillist;sa=deleteparser;f_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '" onclick="return confirm(' . JavaScriptEscape($txt['parser_delete_warning']) . ') && submitThisOnce(this);" accesskey="d">
1189
									<i class="icon i-delete" title="' . $txt['delete'] . '"></i>
1190
								</a>',
1191
							'params' => [
1192
								'id_filter' => true,
1193
							],
1194
						],
1195
						'class' => 'centertext',
1196
						'style' => 'white-space:nowrap;',
1197
					],
1198
				],
1199
			],
1200
			'form' => [
1201
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editparser']),
1202
				'include_sort' => true,
1203
				'include_start' => true,
1204
				'hidden_fields' => [
1205
					$context['session_var'] => $context['session_id'],
1206
				],
1207
			],
1208
			'additional_rows' => [
1209
				[
1210
					'position' => 'top_of_list',
1211
					'class' => $this->_req->hasQuery('saved') ? 'successbox' : '',
1212
					'value' => $this->_req->hasQuery('saved') ? $txt['saved'] : '',
1213
				],
1214
				[
1215
					'position' => 'below_table_data',
1216
					'class' => 'submitbutton',
1217
					'value' => '
1218
                        <input type="submit" name="addparser" value="' . $txt['add_parser'] . '" />
1219
                        <a class="linkbutton" href="' . getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortparsers']) . '">' . $txt['sort_parser'] . '</a>',
1220
				],
1221
			],
1222
		];
1223
1224
		// Set the context values
1225
		$context['page_title'] = $txt['parsers'];
1226
		$context['sub_template'] = 'show_list';
1227
		$context['default_list'] = 'email_parser';
1228
1229
		// Create the list.
1230
		createList($listOptions);
1231
	}
1232
1233
	/**
1234
	 * Show a full list of all the parsers in the system for drag/drop sorting
1235
	 *
1236
	 * @event integrate_list_email_parser
1237
	 */
1238
	public function action_sort_parsers(): void
1239
	{
1240
		global $context, $txt;
1241
1242
		$id = 0;
1243
		$token = createToken('admin-sort');
1244
1245
		// Build the listoption array to display the data
1246
		$listOptions = [
1247
			'id' => 'sort_email_fp',
1248
			'title' => $txt['sort_parser'],
1249
			'sortable' => true,
1250
			'items_per_page' => 0,
1251
			'no_items_label' => $txt['no_parsers'],
1252
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortparsers']),
1253
			'get_items' => [
1254
				'function' => fn(int $start, int $items_per_page, string $sort, int $id, string $style): array => $this->load_filter_parser($start, $items_per_page, $sort, $id, $style),
1255
				'params' => [
1256
					$id,
1257
					'parser'
1258
				],
1259
			],
1260
			'get_count' => [
1261
				'function' => fn(int $id, string $style): int => $this->count_filter_parser($id, $style),
1262
				'params' => [
1263
					$id,
1264
					'parser'
1265
				],
1266
			],
1267
			'columns' => [
1268
				'filterorder' => [
1269
					'header' => [
1270
						'value' => '',
1271
						'class' => 'hide',
1272
					],
1273
					'data' => [
1274
						'db' => 'filter_order',
1275
						'class' => 'hide',
1276
					],
1277
				],
1278
				'name' => [
1279
					'header' => [
1280
						'value' => $txt['parser_name'],
1281
						'style' => 'white-space: nowrap;width: 10em'
1282
					],
1283
					'data' => [
1284
						'db' => 'filter_name',
1285
					],
1286
				],
1287
				'from' => [
1288
					'header' => [
1289
						'value' => $txt['parser_from'],
1290
					],
1291
					'data' => [
1292
						'db' => 'filter_from',
1293
					],
1294
				],
1295
				'type' => [
1296
					'header' => [
1297
						'value' => $txt['parser_type'],
1298
					],
1299
					'data' => [
1300
						'db' => 'filter_type',
1301
					],
1302
				],
1303
			],
1304
			'form' => [
1305
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'sortparsers']),
1306
				'hidden_fields' => [
1307
					$context['session_var'] => $context['session_id'],
1308
				],
1309
			],
1310
			'additional_rows' => [
1311
				[
1312
					'position' => 'after_title',
1313
					'value' => $txt['parser_sort_description'],
1314
				],
1315
			],
1316
			'javascript' => '
1317
				$().elkSortable({
1318
					sa: "parserorder",
1319
					placeholder: "ui-state-highlight",
1320
					containment: "#sort_email_fp",
1321
					error: "' . $txt['admin_order_error'] . '",
1322
					title: "' . $txt['admin_order_title'] . '",
1323
					href: "?action=admin;;area=maillist;sa=sortparsers",
1324
					token: {token_var: "' . $token['admin-sort_token_var'] . '", token_id: "' . $token['admin-sort_token'] . '"}
1325
				});
1326
			',
1327
		];
1328
1329
		// Set the context values
1330
		$context['page_title'] = $txt['parsers'];
1331
		$context['sub_template'] = 'show_list';
1332
		$context['default_list'] = 'sort_email_fp';
1333
1334
		// Create the list.
1335
		createList($listOptions);
1336
	}
1337
1338
	/**
1339
	 * Adds or Edits an existing parser
1340
	 *
1341
	 * - All parsers are assumed regex
1342
	 *
1343
	 * @event integrate_save_parser_settings
1344
	 */
1345
	public function action_edit_parsers(): void
1346
	{
1347
		global $context, $txt, $modSettings;
1348
1349
		// Editing an existing filter?
1350
		if ($this->_req->hasQuery('f_id'))
1351
		{
1352
			// Needs to be an int!
1353
			$id = $this->_req->getQuery('f_id', 'intval', 0);
1354
			if ($id <= 0)
1355
			{
1356
				throw new Exception('error_no_id_filter');
1357
			}
1358
1359
			// Load this filter so we can edit it
1360
			$row = maillist_load_filter_parser($id, 'parser');
1361
1362
			$modSettings['id_filter'] = $row['id_filter'];
1363
			$modSettings['filter_type'] = $row['filter_type'];
1364
			$modSettings['filter_from'] = $row['filter_from'];
1365
			$modSettings['filter_name'] = $row['filter_name'];
1366
1367
			$context['page_title'] = $txt['edit_parser'];
1368
			$context['editing'] = true;
1369
		}
1370
		else
1371
		{
1372
			// Set up placeholders for adding a new one instead
1373
			$modSettings['filter_type'] = '';
1374
			$modSettings['filter_name'] = '';
1375
			$modSettings['filter_from'] = '';
1376
1377
			// To the template we go
1378
			$context['page_title'] = $txt['add_parser'];
1379
			$context['editing'] = false;
1380
		}
1381
1382
		// Initialize the form
1383
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
1384
1385
		// Initialize it with our settings
1386
		$config_vars = $this->_parsersSettings();
1387
		$settingsForm->setConfigVars($config_vars);
1388
1389
		// Check if they are saving the changes
1390
		if ($this->_req->hasQuery('save'))
1391
		{
1392
			checkSession();
1393
1394
			call_integration_hook('integrate_save_parser_settings');
1395
1396
			// Editing a parser?
1397
			$editRaw = $this->_req->hasQuery('edit') ? $this->_req->getQuery('edit', 'trim|strval', '') : '';
1398
			$editId = ($editRaw !== '' && $editRaw !== 'new') ? (int) $editRaw : -1;
1399
			$editName = ($editRaw !== '' && $editRaw !== 'new') ? 'id_filter' : '';
1400
1401
			// Test the regex
1402
			if ($this->_req->post->filter_type === 'regex' && !empty($this->_req->post->filter_from))
1403
			{
1404
				$valid = preg_replace($this->_req->post->filter_from, '', 'ElkArte') !== null;
1405
				if (!$valid)
1406
				{
1407
					// Regex did not compute ... Danger, Will Robinson
1408
					$context['settings_message'] = $txt['regex_invalid'];
1409
					$context['error_type'] = 'notice';
1410
1411
					$modSettings['filter_type'] = $this->_req->post->filter_type;
1412
					$modSettings['filter_from'] = $this->_req->post->filter_from;
1413
					$modSettings['filter_name'] = $this->_req->post->filter_name;
1414
				}
1415
			}
1416
1417
			if (empty($this->_req->post->filter_type) || empty($this->_req->post->filter_from))
1418
			{
1419
				$context['error_type'] = 'notice';
1420
				$context['settings_message'][] = $txt['filter_invalid'];
1421
			}
1422
1423
			// All clear to save?
1424
			if (empty($context['settings_message']))
1425
			{
1426
				// Shhh ... it's really a parser
1427
				$config_vars[] = ['text', 'filter_style'];
1428
				$this->_req->post->filter_style = 'parser';
1429
1430
				// Save, log, show
1431
				MaillistSettings::saveTableSettings($config_vars, 'postby_emails_filters', $this->_req->post, ['id_filter'], $editId, $editName);
1432
				redirectexit('action=admin;area=maillist;sa=emailparser;saved');
1433
			}
1434
		}
1435
1436
		// Prepare the context for viewing
1437
		$title = ($this->_req->hasQuery('saved') && $this->_req->getQuery('saved', 'trim|strval', '') == '1') ? 'saved_parser' : ($context['editing'] === true ? 'edit_parser' : 'add_parser');
1438
		$context['settings_title'] = $txt[$title];
1439
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editparser', 'edit' => $context['editing'] ? $modSettings['id_filter'] : 'new', 'save']);
1440
		$context['breadcrumbs'][] = [
1441
			'url' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'editparser']),
1442
			'name' => ($context['editing']) ? $txt['edit_parser'] : $txt['add_parser'],
1443
		];
1444
1445
		// prep it, load it, show it
1446
		$settingsForm->prepare();
1447
		theme()->getTemplates()->load('Admin');
1448
		loadCSSFile('admin.css');
1449
		$context['sub_template'] = 'show_settings';
1450
	}
1451
1452
	/**
1453
	 * Initialize Mailist settings form.
1454
	 *
1455
	 * @event integrate_modify_maillist_parser_settings Add settings to the maillist parser screen
1456
	 */
1457
	private function _parsersSettings()
1458
	{
1459
		global $txt;
1460
1461
		$config_vars = [
1462
			['text', 'filter_name', 25, 'subtext' => $txt['parser_name_desc']],
1463
			['select', 'filter_type', 'subtext' => $txt['parser_type_desc'],
1464
				[
1465
					'regex' => $txt['option_regex'],
1466
					'standard' => $txt['option_standard'],
1467
				],
1468
			],
1469
			['large_text', 'filter_from', 4, 'subtext' => $txt['parser_from_desc']],
1470
		];
1471
1472
		call_integration_hook('integrate_modify_maillist_parser_settings', [&$config_vars]);
1473
1474
		return $config_vars;
1475
	}
1476
1477
	/**
1478
	 * Return the form settings for use in admin search
1479
	 */
1480
	public function parser_search()
1481
	{
1482
		global $modSettings;
1483
1484
		if (empty($modSettings['maillist_enabled']))
1485
		{
1486
			return ['check', 'filter_name'];
1487
		}
1488
1489
		return $this->_parsersSettings();
1490
	}
1491
1492
	/**
1493
	 * Removes a parser from the system and database
1494
	 */
1495
	public function action_delete_parsers(): void
1496
	{
1497
		// Removing the filter?
1498
		if ($this->_req->hasQuery('f_id'))
1499
		{
1500
			checkSession('get');
1501
			$id = $this->_req->getQuery('f_id', 'intval', 0);
1502
1503
			maillist_delete_filter_parser($id);
1504
			redirectexit('action=admin;area=maillist;sa=emailparser;deleted');
1505
		}
1506
	}
1507
1508
	/**
1509
	 * All the post by email settings, used to control how the feature works
1510
	 *
1511
	 * @event integrate_save_maillist_settings
1512
	 * @uses Admin language
1513
	 */
1514
	public function action_settings(): void
1515
	{
1516
		global $context, $txt, $modSettings;
1517
1518
		// Be nice, show them we did something
1519
		if ($this->_req->hasQuery('saved'))
1520
		{
1521
			$context['settings_message'] = $txt['saved'];
1522
		}
1523
1524
		// Templates and language
1525
		Txt::load('Admin');
1526
		theme()->getTemplates()->load('Admin');
1527
		loadCSSFile('admin.css');
1528
1529
		// Load any existing email => board values used for new topic creation
1530
		$context['maillist_from_to_board'] = [];
1531
		$data = (empty($modSettings['maillist_receiving_address'])) ? [] : Util::unserialize($modSettings['maillist_receiving_address']);
1532
		foreach ($data as $key => $addr)
1533
		{
1534
			$context['maillist_from_to_board'][$key] = [
1535
				'id' => $key,
1536
				'emailfrom' => $addr[0],
1537
				'boardto' => $addr[1],
1538
			];
1539
		}
1540
1541
		// Initialize the maillist settings form
1542
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
1543
1544
		// Initialize it with our settings
1545
		$settingsForm->setConfigVars($this->_settings());
1546
1547
		// Saving settings?
1548
		if ($this->_req->hasQuery('save'))
1549
		{
1550
			checkSession();
1551
1552
			call_integration_hook('integrate_save_maillist_settings');
1553
1554
			$email_error = false;
1555
			$board_error = false;
1556
			$maillist_receiving_address = [];
1557
1558
			// Basic checking of the email addresses
1559
			if (!DataValidator::is_valid($this->_req->post, ['maillist_sitename_address' => 'valid_email'], ['maillist_sitename_address' => 'trim']))
1560
			{
1561
				$email_error = $this->_req->post->maillist_sitename_address;
1562
			}
1563
1564
			if (!DataValidator::is_valid($this->_req->post, ['maillist_sitename_help' => 'valid_email'], ['maillist_sitename_help' => 'trim']))
1565
			{
1566
				$email_error = $this->_req->post->maillist_sitename_help;
1567
			}
1568
1569
			if (!DataValidator::is_valid($this->_req->post, ['maillist_mail_from' => 'valid_email'], ['maillist_mail_from' => 'trim']))
1570
			{
1571
				$email_error = $this->_req->post->maillist_mail_from;
1572
			}
1573
1574
			// Inbound email set up, then we need to check for both valid email and valid board
1575
			if (!$email_error && !empty($this->_req->post->emailfrom))
1576
			{
1577
				// Get the board ids for a quick check
1578
				$boards = maillist_board_list();
1579
1580
				// Check the receiving emails and the board id as well
1581
				$boardtocheck = empty($this->_req->post->boardto) ? [] : $this->_req->post->boardto;
1582
				$addresstocheck = empty($this->_req->post->emailfrom) ? [] : $this->_req->post->emailfrom;
1583
1584
				foreach ($addresstocheck as $key => $checkme)
1585
				{
1586
					// Valid email syntax
1587
					if (!DataValidator::is_valid($addresstocheck, [$key => 'valid_email'], [$key => 'trim']))
1588
					{
1589
						$email_error = $checkme;
1590
						$context['error_type'] = 'notice';
1591
						continue;
1592
					}
1593
1594
					// Valid board id?
1595
					if (!isset($boardtocheck[$key], $boards[$key]))
1596
					{
1597
						$board_error = $checkme;
1598
						$context['error_type'] = 'notice';
1599
						continue;
1600
					}
1601
1602
					// Decipher as [0] emailaddress and [1] board id
1603
					$maillist_receiving_address[] = [$checkme, $boardtocheck[$key]];
1604
				}
1605
			}
1606
1607
			// Enable or disable the fake cron
1608
			enable_maillist_imap_cron(!empty($this->_req->post->maillist_imap_cron));
1609
1610
			// Check and set any errors or give the go-ahead to save
1611
			if ($email_error)
1612
			{
1613
				$context['settings_message'] = sprintf($txt['email_not_valid'], $email_error);
1614
			}
1615
			elseif ($board_error)
1616
			{
1617
				$context['settings_message'] = sprintf($txt['board_not_valid'], $board_error);
1618
			}
1619
			else
1620
			{
1621
				// Clear the moderation count cache
1622
				Cache::instance()->remove('num_menu_errors');
1623
1624
				// Should be off if mail posting is on, we ignore it anyway, but this at least updates the ACP
1625
				if (!empty($this->_req->post->maillist_enabled))
1626
				{
1627
					updateSettings(['disallow_sendBody' => '']);
1628
				}
1629
1630
				updateSettings(['maillist_receiving_address' => serialize($maillist_receiving_address)]);
1631
				$settingsForm->setConfigValues((array) $this->_req->post);
1632
				$settingsForm->save();
1633
				writeLog();
1634
				redirectexit('action=admin;area=maillist;sa=emailsettings;saved');
1635
			}
1636 2
		}
1637
1638 2
		// JavaScript vars for the "add more" buttons in the receive_email callback
1639
		$board_list = maillist_board_list();
1640
		$script = '';
1641
		$i = 0;
1642 2
1643
		// Create the board selection list
1644
		foreach ($board_list as $board_id => $board_name)
1645
		{
1646 2
			$script .= $i++ . ': {id:' . $board_id . ', name:' . JavaScriptEscape($board_name) . '},';
1647 2
		}
1648 2
1649
		theme()->addInlineJavascript('
1650
		var sEmailParent = \'add_more_email_placeholder\',
1651
			oEmailOptionsdt = {size: \'50\', name: \'emailfrom[]\', class: \'input_text\'},
1652
			oEmailOptionsdd = {size: \'1\', type: \'select\', name: \'boardto[]\', class: \'input_select\'},
1653 2
			oEmailSelectData = {' . $script . '};
1654 2
1655 2
			document.getElementById(\'add_more_board_div\').style.display = \'block\';', true
1656 2
		);
1657 2
1658
		$context['boards'] = $board_list;
1659
		$context['settings_title'] = $txt['ml_emailsettings'];
1660
		$context['page_title'] = $txt['ml_emailsettings'];
1661 2
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailsettings', 'save']);
1662
		$context['sub_template'] = 'show_settings';
1663
		$settingsForm->prepare();
1664
	}
1665 2
1666 2
	/**
1667 2
	 * Load up the config var array for settings display etc.
1668 2
	 *
1669 2
	 * @event integrate_modify_maillist_settings
1670
	 */
1671
	private function _settings()
1672
	{
1673 2
		global $txt;
1674
1675
		// Define the menu
1676
		$config_vars = [
1677
			['desc', 'maillist_help'],
1678
			['check', 'maillist_enabled'],
1679
			['check', 'pbe_post_enabled'],
1680
			['check', 'pbe_pm_enabled'],
1681
			['check', 'pbe_no_mod_notices', 'subtext' => $txt['pbe_no_mod_notices_desc'], 'postinput' => $txt['recommended']],
1682
			['check', 'pbe_bounce_detect', 'subtext' => $txt['pbe_bounce_detect_desc'], 'postinput' => $txt['experimental']],
1683 2
			['check', 'pbe_bounce_record', 'subtext' => $txt['pbe_bounce_record_desc'], 'postinput' => $txt['experimental']],
1684
			['title', 'maillist_outbound'],
1685 2
			['desc', 'maillist_outbound_desc'],
1686
			['check', 'maillist_group_mode'],
1687 2
			['check', 'maillist_digest_enabled'],
1688 2
			['text', 'maillist_sitename', 40, 'subtext' => $txt['maillist_sitename_desc'], 'postinput' => $txt['maillist_sitename_post']],
1689 2
			['text', 'maillist_sitename_address', 40, 'subtext' => $txt['maillist_sitename_address_desc'], 'postinput' => $txt['maillist_sitename_address_post']],
1690 2
			['text', 'maillist_mail_from', 40, 'subtext' => $txt['maillist_mail_from_desc'], 'postinput' => $txt['maillist_mail_from_post']],
1691 2
			['text', 'maillist_sitename_help', 40, 'subtext' => $txt['maillist_sitename_help_desc'], 'postinput' => $txt['maillist_sitename_help_post']],
1692
			['text', 'maillist_sitename_regards', 40, 'subtext' => $txt['maillist_sitename_regards_desc']],
1693 2
			['title', 'maillist_inbound'],
1694 2
			['desc', 'maillist_inbound_desc'],
1695 2
			['check', 'maillist_newtopic_change'],
1696 2
			['check', 'maillist_newtopic_needsapproval', 'subtext' => $txt['maillist_newtopic_needsapproval_desc'], 'postinput' => $txt['recommended']],
1697 2
			['callback', 'maillist_receive_email_list'],
1698 2
			['title', 'misc'],
1699 2
			['check', 'maillist_allow_attachments'],
1700
			['int', 'maillist_key_active', 4, 'subtext' => $txt['maillist_key_active_desc']],
1701 2
			'',
1702 2
			['text', 'maillist_leftover_remove', 40, 'subtext' => $txt['maillist_leftover_remove_desc']],
1703
			['text', 'maillist_sig_keys', 40, 'subtext' => $txt['maillist_sig_keys_desc']],
1704
			['int', 'maillist_short_line', 4, 'subtext' => $txt['maillist_short_line_desc']],
1705
		];
1706
1707 2
		// Imap?
1708
		if (!function_exists('imap_open'))
1709 2
		{
1710
			$config_vars = array_merge($config_vars,
1711
				[
1712
					['title', 'maillist_imap_missing'],
1713
				]
1714
			);
1715 2
		}
1716
		else
1717 2
		{
1718
			$config_vars = array_merge($config_vars, [
1719
				['title', 'maillist_imap'],
1720
				['desc', 'maillist_imap_reason'],
1721
				['text', 'maillist_imap_host', 45, 'subtext' => $txt['maillist_imap_host_desc'], 'disabled' => !function_exists('imap_open')],
1722
				['text', 'maillist_imap_mailbox', 20, 'postinput' => $txt['maillist_imap_mailbox_desc'], 'disabled' => !function_exists('imap_open')],
1723
				['text', 'maillist_imap_uid', 20, 'postinput' => $txt['maillist_imap_uid_desc'], 'disabled' => !function_exists('imap_open')],
1724
				['password', 'maillist_imap_pass', 20, 'postinput' => $txt['maillist_imap_pass_desc'], 'disabled' => !function_exists('imap_open')],
1725
				['select', 'maillist_imap_connection',
1726
					[
1727
						'imap' => $txt['maillist_imap_unsecure'],
1728
						'pop3' => $txt['maillist_pop3_unsecure'],
1729
						'imaptls' => $txt['maillist_imap_tls'],
1730
						'imapssl' => $txt['maillist_imap_ssl'],
1731
						'pop3tls' => $txt['maillist_pop3_tls'],
1732
						'pop3ssl' => $txt['maillist_pop3_ssl']
1733
					], 'postinput' => $txt['maillist_imap_connection_desc'], 'disabled' => !function_exists('imap_open'),
1734
				],
1735
				['check', 'maillist_imap_delete', 20, 'subtext' => $txt['maillist_imap_delete_desc'], 'disabled' => !function_exists('imap_open')],
1736
				['check', 'maillist_imap_cron', 20, 'subtext' => $txt['maillist_imap_cron_desc'], 'disabled' => !function_exists('imap_open')],
1737
			]);
1738
		}
1739
1740
		call_integration_hook('integrate_modify_maillist_settings', [&$config_vars]);
1741
1742
		return $config_vars;
1743
	}
1744
1745
	/**
1746
	 * Return the form settings for use in admin search
1747
	 */
1748
	public function settings_search()
1749
	{
1750
		global $modSettings;
1751
1752
		if (empty($modSettings['maillist_enabled']))
1753
		{
1754
			return ['check', 'maillist_enabled'];
1755
		}
1756
1757
		return $this->_settings();
1758
	}
1759
1760
	/**
1761
	 * View all the custom email bounce templates.
1762
	 *
1763
	 * What it does:
1764
	 *
1765
	 * - Shows all the bounce templates in the system available to this user
1766
	 * - Provides for actions to add or delete them
1767
	 * - Accessed by ?action=admin;area=maillist;sa=emailtemplates;
1768
	 *
1769
	 * @event integrate_list_bounce_template_list
1770
	 */
1771
	public function action_view_bounce_templates(): ?bool
1772
	{
1773
		global $modSettings, $context, $txt;
1774
1775
		// We'll need this because bounce templates are stored with warning templates.
1776
		require_once(SUBSDIR . '/Moderation.subs.php');
1777
1778
		// Submitting a new one or editing an existing one then pass this request off
1779
		if ($this->_req->hasPost('add') || $this->_req->hasPost('save') || $this->_req->hasQuery('tid'))
1780
		{
1781
			return $this->action_modify_bounce_templates();
1782
		}
1783
1784
		// Deleting and existing one
1785
		if (isset($this->_req->post->delete) && !empty($this->_req->post->deltpl))
1786
		{
1787
			checkSession();
1788
			validateToken('mod-mlt');
1789
			removeWarningTemplate($this->_req->post->deltpl, 'bnctpl');
1790
		}
1791
1792
		// This is all the information required for showing the email templates.
1793
		$listOptions = [
1794
			'id' => 'email_bounce_template_list',
1795
			'title' => $txt['ml_bounce_templates_title'],
1796
			'items_per_page' => $modSettings['defaultMaxMessages'],
1797
			'no_items_label' => $txt['ml_bounce_templates_none'],
1798
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailtemplates', '{session_data}']),
1799
			'default_sort_col' => 'title',
1800
			'get_items' => [
1801
				'function' => fn(int $start, int $items_per_page, string $sort): array => $this->list_getBounceTemplates($start, $items_per_page, $sort),
1802
			],
1803
			'get_count' => [
1804
				'function' => fn() => $this->list_getBounceTemplateCount(),
1805
				'params' => ['bnctpl'],
1806
			],
1807
			'columns' => [
1808
				'title' => [
1809
					'header' => [
1810
						'value' => $txt['ml_bounce_templates_name'],
1811
					],
1812
					'data' => [
1813
						'sprintf' => [
1814
							'format' => '<a href="' . getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailtemplates', 'tid' => '%1$d']) . '">%2$s</a>',
1815
							'params' => [
1816
								'id_comment' => false,
1817
								'title' => false,
1818
								'body' => false,
1819
							],
1820
						],
1821
					],
1822
					'sort' => [
1823
						'default' => 'template_title',
1824
						'reverse' => 'template_title DESC',
1825
					],
1826
				],
1827
				'creator' => [
1828
					'header' => [
1829
						'value' => $txt['ml_bounce_templates_creator'],
1830
					],
1831
					'data' => [
1832
						'db' => 'creator',
1833
					],
1834
					'sort' => [
1835
						'default' => 'creator_name',
1836
						'reverse' => 'creator_name DESC',
1837
					],
1838
				],
1839
				'time' => [
1840
					'header' => [
1841
						'value' => $txt['ml_bounce_templates_time'],
1842
					],
1843
					'data' => [
1844
						'db' => 'time',
1845
					],
1846
					'sort' => [
1847
						'default' => 'lc.log_time DESC',
1848
						'reverse' => 'lc.log_time',
1849
					],
1850
				],
1851
				'delete' => [
1852
					'header' => [
1853
						'value' => '<input type="checkbox" class="input_check" onclick="invertAll(this, this.form);" />',
1854
						'style' => 'width: 4%;',
1855
						'class' => 'centertext',
1856
					],
1857
					'data' => [
1858
						'function' => static fn($rowData) => '<input type="checkbox" name="deltpl[]" value="' . $rowData['id_comment'] . '" class="input_check" />',
1859
						'class' => 'centertext',
1860
					],
1861
				],
1862
			],
1863
			'form' => [
1864
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'maillist', 'sa' => 'emailtemplates']),
1865
				'token' => 'mod-mlt',
1866
			],
1867
			'additional_rows' => [
1868
				[
1869
					'class' => 'submitbutton',
1870
					'position' => 'below_table_data',
1871
					'value' => '
1872
						<input type="submit" name="delete" value="' . $txt['ml_bounce_template_delete'] . '" onclick="return confirm(\'' . $txt['ml_bounce_template_delete_confirm'] . '\');" />
1873
						<input type="submit" name="add" value="' . $txt['ml_bounce_template_add'] . '" />',
1874
				],
1875
			],
1876
		];
1877
1878
		// Create the template list.
1879
		$context['page_title'] = $txt['ml_bounce_templates_title'];
1880
		createToken('mod-mlt');
1881
1882
		createList($listOptions);
1883
1884
		// Show the list
1885
		$context['sub_template'] = 'show_list';
1886
		$context['default_list'] = 'email_bounce_template_list';
1887
1888
		return null;
1889
	}
1890
1891
	/**
1892
	 * Edit the 'it bounced' template.
1893
	 *
1894
	 * @uses bounce_template sub template
1895
	 */
1896
	public function action_modify_bounce_templates(): bool
1897
	{
1898
		global $context, $txt;
1899
1900
		require_once(SUBSDIR . '/Moderation.subs.php');
1901
1902
		$context['id_template'] = $this->_req->getQuery('tid', 'intval', 0);
1903
		$context['is_edit'] = (bool) $context['id_template'];
1904
1905
		// Standard template things, you know the drill
1906
		$context['page_title'] = $context['is_edit'] ? $txt['ml_bounce_template_modify'] : $txt['ml_bounce_template_add'];
1907
		$context['sub_template'] = 'bounce_template';
1908
1909
		//$context[$context['admin_menu_name']]['current_subsection'] = 'templates';
1910
1911
		// Defaults to show
1912
		$context['template_data'] = [
1913
			'title' => '',
1914
			'body' => $txt['ml_bounce_template_body_default'],
1915
			'subject' => $txt['ml_bounce_template_subject_default'],
1916
			'personal' => false,
1917
			'can_edit_personal' => true,
1918
		];
1919
1920
		// If it's an edit, load it.
1921
		if ($context['is_edit'])
1922
		{
1923
			modLoadTemplate($context['id_template'], 'bnctpl');
1924
		}
1925
1926
		// Wait, we are saving?
1927
		if ($this->_req->hasPost('save'))
1928
		{
1929
			checkSession();
1930
			validateToken('mod-mlt');
1931
1932
			// To check the BBC is good...
1933
			require_once(SUBSDIR . '/Post.subs.php');
1934
1935
			// A bit of cleaning!
1936
			$template_body = trim($this->_req->post->template_body);
1937
			$template_title = trim($this->_req->post->template_title);
1938
1939
			// Need something in both boxes.
1940
			if ($template_body !== '' && $template_body !== '0' && ($template_title !== '' && $template_title !== '0'))
1941
			{
1942
				// Safety first.
1943
				$template_title = Util::htmlspecialchars($template_title);
1944
1945
				// Clean up BBC.
1946
				preparsecode($template_body);
1947
1948
				// But put line breaks back!
1949
				$template_body = strtr($template_body, ['<br />' => "\n"]);
1950
1951
				// Is this personal?
1952
				$recipient_id = empty($this->_req->post->make_personal) ? 0 : $this->user->id;
1953
1954
				// Updating or adding ?
1955
				if ($context['is_edit'])
1956
				{
1957
					// Simple update...
1958
					modAddUpdateTemplate($recipient_id, $template_title, $template_body, $context['id_template'], true, 'bnctpl');
1959
1960
					// If it wasn't visible and now is, they've effectively added it.
1961
					if ($context['template_data']['personal'] && !$recipient_id)
1962
					{
1963
						logAction('add_bounce_template', ['template' => $template_title]);
1964
					}
1965
					// Conversely, if they made it personal, then delete.
1966
					elseif (!$context['template_data']['personal'] && $recipient_id)
1967
					{
1968
						logAction('delete_bounce_template', ['template' => $template_title]);
1969
					}
1970
					// Otherwise just an edit.
1971
					else
1972
					{
1973
						logAction('modify_bounce_template', ['template' => $template_title]);
1974
					}
1975
				}
1976
				else
1977
				{
1978
					modAddUpdateTemplate($recipient_id, $template_title, $template_body, $context['id_template'], false, 'bnctpl');
1979
					logAction('add_bounce_template', ['template' => $template_title]);
1980
				}
1981
1982
				// Get out of town...
1983
				redirectexit('action=admin;area=maillist;sa=emailtemplates');
1984
			}
1985
			else
1986
			{
1987
				$context['warning_errors'] = [];
1988
				$context['template_data']['title'] = empty($template_title) ? '' : $template_title;
1989
				$context['template_data']['body'] = empty($template_body) ? $txt['ml_bounce_template_body_default'] : $template_body;
1990
				$context['template_data']['personal'] = !empty($this->_req->post->make_personal);
1991
1992
				if (empty($template_title))
1993
				{
1994
					$context['warning_errors'][] = $txt['ml_bounce_template_error_no_title'];
1995
				}
1996
1997
				if (empty($template_body))
1998
				{
1999
					$context['warning_errors'][] = $txt['ml_bounce_template_error_no_body'];
2000
				}
2001
			}
2002
		}
2003
2004
		createToken('mod-mlt');
2005
2006
		return true;
2007
	}
2008
2009
	/**
2010
	 * Get all the bounce templates from the system
2011
	 *
2012
	 * - Callback for createList()
2013
	 *
2014
	 * @param int $start The item to start with (for pagination purposes)
2015
	 * @param int $items_per_page The number of items to show per page
2016
	 * @param string $sort A string indicating how to sort the results
2017
	 *
2018
	 * @return array
2019
	 */
2020
	public function list_getBounceTemplates(int $start, int $items_per_page, string $sort): array
2021
	{
2022
		return warningTemplates($start, $items_per_page, $sort, 'bnctpl');
2023
	}
2024
2025
	/**
2026
	 * Get the number of bounce templates in the system
2027
	 *
2028
	 * - Callback for createList() to warningTemplateCount
2029
	 */
2030
	public function list_getBounceTemplateCount(): int
2031
	{
2032
		return warningTemplateCount('bnctpl');
2033
	}
2034
2035
	/**
2036
	 * Get the number of unapproved emails
2037
	 *
2038
	 * - Callback for createList() to list_maillist_unapproved
2039
	 *
2040
	 * @param int $start The item to start with (for pagination purposes)
2041
	 * @param int $items_per_page The number of items to show per page
2042
	 * @param string $sort A string indicating how to sort the results
2043
	 * @param int $id = 0
2044
	 *
2045
	 * @return array
2046
	 */
2047
	public function list_maillist_unapproved(int $start, int $items_per_page, string $sort = '', int $id = 0): array
2048
	{
2049
		return list_maillist_unapproved($id, $start, $items_per_page, $sort);
2050
	}
2051
}
2052