ManageNews::action_mailingsend()   F
last analyzed

Complexity

Conditions 66
Paths > 20000

Size

Total Lines 386
Code Lines 186

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 4422

Importance

Changes 0
Metric Value
cc 66
eloc 186
nc 37421282
nop 1
dl 0
loc 386
ccs 0
cts 159
cp 0
crap 4422
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
 * Handles all news and newsletter functions for the site
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 dev
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\Txt;
23
use ElkArte\SettingsForm\SettingsForm;
24
25
/**
26
 * ManageNews controller, for news administration screens.
27
 *
28
 * @package News
29
 */
30
class ManageNews extends AbstractController
31
{
32
	/** @var array Members specifically being included in a newsletter */
33
	protected $_members = array();
34
35
	/** @var array Members specifically being excluded from a newsletter */
36
	protected $_exclude_members = array();
37
38
	/**
39
	 * The news dispatcher / delegator
40
	 *
41
	 * What it does:
42
	 *
43
	 * - This is the entrance point for all News and Newsletter screens.
44
	 * - Called by ?action=admin;area=news.
45
	 * - It does the permission checks, and calls the appropriate function
46
	 * based on the requested sub-action.
47
	 *
48
	 * @event integrate_sa_manage_news used to add new subactions
49
	 * @see \ElkArte\AbstractController::action_index()
50
	 */
51
	public function action_index()
52
	{
53
		global $context, $txt;
54
55
		theme()->getTemplates()->load('ManageNews');
56
57
		// Format: 'sub-action' => array('function', 'permission')
58
		$subActions = array(
59
			'editnews' => array(
60
				'controller' => $this,
61
				'function' => 'action_editnews',
62
				'permission' => 'edit_news'),
63
			'mailingmembers' => array(
64
				'controller' => $this,
65
				'function' => 'action_mailingmembers',
66
				'permission' => 'send_mail'),
67
			'mailingcompose' => array(
68
				'controller' => $this,
69
				'function' => 'action_mailingcompose',
70
				'permission' => 'send_mail'),
71
			'mailingsend' => array(
72
				'controller' => $this,
73
				'function' => 'action_mailingsend',
74
				'permission' => 'send_mail'),
75
			'settings' => array(
76
				'controller' => $this,
77
				'function' => 'action_newsSettings_display',
78
				'permission' => 'admin_forum'),
79
		);
80
81
		// Action control
82
		$action = new Action('manage_news');
83
84
		// Give integration its shot via integrate_sa_manage_news
85
		$subAction = $action->initialize($subActions, (allowedTo('edit_news') ? 'editnews' : (allowedTo('send_mail') ? 'mailingmembers' : 'settings')));
86
87
		// Some bits for the template
88
		$context['page_title'] = $txt['news_title'];
89
		$context['sub_action'] = $subAction;
90
91
		// Create the tabs for the template.
92
		$context[$context['admin_menu_name']]['object']->prepareTabData([
93
			'title' => 'news_title',
94
			'help' => 'edit_news',
95
			'description' => 'admin_news_desc',
96
			'tabs' => [
97
				'editnews' => [],
98
				'mailingmembers' => [
99
					'description' => $txt['news_mailing_desc'],
100
				],
101
				'settings' => [
102
					'description' => $txt['news_settings_desc'],
103
				],
104
			],
105
		]);
106
107
		// Force the right area...
108
		if (strpos($subAction, 'mailing') === 0)
109
		{
110
			$context[$context['admin_menu_name']]['current_subsection'] = 'mailingmembers';
111
		}
112
113
		// Call the right function for this sub-action.
114
		$action->dispatch($subAction);
115
	}
116
117
	/**
118
	 * Let the administrator(s) edit the news items for the forum.
119
	 *
120
	 * What it does:
121
	 *
122
	 * - It writes an entry into the moderation log.
123
	 * - This function uses the edit_news administration area.
124
	 * - Called by ?action=admin;area=news.
125
	 * - Requires the edit_news permission.
126
	 * - Can be accessed with ?action=admin;sa=editnews.
127
	 *
128
	 * @event integrate_list_news_lists
129
	 */
130
	public function action_editnews()
131
	{
132
		global $txt, $modSettings, $context;
133
134
		require_once(SUBSDIR . '/Post.subs.php');
135
136
		// The 'remove selected' button was pressed.
137
		if (!empty($this->_req->post->delete_selection) && !empty($this->_req->post->remove))
138
		{
139
			checkSession();
140
141
			// Store the news temporarily in this array.
142
			$temp_news = explode("\n", $modSettings['news']);
143
144
			// Remove the items that were selected.
145
			foreach (array_keys($temp_news) as $i)
146
			{
147
				if (in_array($i, $this->_req->post->remove))
148
				{
149
					unset($temp_news[$i]);
150
				}
151
			}
152
153
			// Update the database.
154
			updateSettings(array('news' => implode("\n", $temp_news)));
155
156
			logAction('news');
157
		}
158
		// The 'Save' button was pressed.
159
		elseif (!empty($this->_req->post->save_items))
160
		{
161
			checkSession();
162
163
			foreach ($this->_req->post->news as $i => $news)
164
			{
165
				if (trim($news) === '')
166
				{
167
					unset($this->_req->post->news[$i]);
168
				}
169
				else
170
				{
171
					$this->_req->post->news[$i] = Util::htmlspecialchars($this->_req->post->news[$i], ENT_QUOTES);
172
					preparsecode($this->_req->post->news[$i]);
173
				}
174
			}
175
176
			// Send the new news to the database.
177
			updateSettings(array('news' => implode("\n", $this->_req->post->news)));
178
179
			// Log this into the moderation log.
180
			logAction('news');
181
		}
182
183
		// We're going to want this for making our list.
184
		require_once(SUBSDIR . '/News.subs.php');
185
186
		$context['page_title'] = $txt['admin_edit_news'];
187
188
		// Use the standard templates for showing this.
189
		$listOptions = array(
190
			'id' => 'news_lists',
191
			'get_items' => array(
192
				'function' => 'getNews',
193
			),
194
			'columns' => array(
195
				'news' => array(
196
					'header' => array(
197
						'value' => $txt['admin_edit_news'],
198
					),
199
					'data' => array(
200
						'function' => static fn($news) => '<textarea class="" id="data_' . $news['id'] . '" rows="3" name="news[]">' . $news['unparsed'] . '</textarea>
201
							<br />
202
							<div id="preview_' . $news['id'] . '"></div>',
203
						'class' => 'newsarea',
204
					),
205
				),
206
				'preview' => array(
207
					'header' => array(
208
						'value' => $txt['preview'],
209
					),
210
					'data' => array(
211
						'function' => static fn($news) => '<div id="box_preview_' . $news['id'] . '">' . $news['parsed'] . '</div>',
212
						'class' => 'newspreview',
213
					),
214
				),
215
				'check' => array(
216
					'header' => array(
217
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
218
						'class' => 'centertext',
219
					),
220
					'data' => array(
221
						'function' => static function ($news) {
222
							if (is_numeric($news['id']))
223
							{
224
								return '<input type="checkbox" name="remove[]" value="' . $news['id'] . '" class="input_check" />';
225
							}
226
							return '';
227
						},
228
						'style' => 'vertical-align: top',
229
					),
230
				),
231
			),
232
			'form' => array(
233
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'news', 'sa' => 'editnews']),
234
				'hidden_fields' => array(
235
					$context['session_var'] => $context['session_id'],
236
				),
237
			),
238
			'additional_rows' => array(
239
				array(
240
					'position' => 'bottom_of_list',
241
					'class' => 'submitbutton',
242
					'value' => '
243
					<input type="submit" name="save_items" value="' . $txt['save'] . '" />
244
					<input type="submit" name="delete_selection" value="' . $txt['editnews_remove_selected'] . '" onclick="return confirm(\'' . $txt['editnews_remove_confirm'] . '\');" />
245
					<span id="moreNewsItems_link" class="hide">
246
						<a class="linkbutton" href="javascript:void(0);" onclick="addAnotherNews(); return false;">' . $txt['editnews_clickadd'] . '</a>
247
					</span>',
248
				),
249
			),
250
			'javascript' => '
251
				document.getElementById("list_news_lists_last").style.display = "none";
252
				document.getElementById("moreNewsItems_link").style.display = "inline";
253
				document.addEventListener("DOMContentLoaded", function() {
254
				    let divs = document.querySelectorAll(\'div[id^="preview_"]\');
255
				    divs.forEach(function (div) {
256
				        let preview_id = div.id.split("_")[1];
257
				        if (last_preview < preview_id && preview_id !== "last")
258
				            last_preview = preview_id;
259
				        make_preview_btn(preview_id);
260
				    });
261
				});',
262
		);
263
264
		theme()->addJavascriptVar([
265
			'last_preview' => 0,
266
		    'txt_preview' => JavaScriptEscape($txt['preview']),
267
		    'txt_news_error_no_news' => JavaScriptEscape($txt['news_error_no_news'])]);
268
269
		// Create the request list.
270
		createList($listOptions);
271
272
		$context['sub_template'] = 'show_list';
273
		$context['default_list'] = 'news_lists';
274
	}
275
276
	/**
277
	 * This function allows a user to select the membergroups to send their mailing to.
278
	 *
279
	 * What it does:
280
	 *
281
	 * - Called by ?action=admin;area=news;sa=mailingmembers.
282
	 * - Requires the send_mail permission.
283
	 * - Form is submitted to ?action=admin;area=news;mailingcompose.
284
	 *
285
	 * @uses the ManageNews template and email_members sub template.
286
	 */
287
	public function action_mailingmembers()
288
	{
289
		global $txt, $context;
290
291
		require_once(SUBSDIR . '/Membergroups.subs.php');
292
		require_once(SUBSDIR . '/News.subs.php');
293
294
		// Setup the template
295
		$context['page_title'] = $txt['admin_newsletters'];
296
		$context['sub_template'] = 'email_members';
297
		loadJavascriptFile('suggest.js', array('defer' => true));
298
299
		// We need group data, including which groups we have and who is in them
300
		$allgroups = getBasicMembergroupData(array('all'), array(), null, true);
301
		$groups = $allgroups['groups'];
302
303
		// All of the members in post based and member based groups
304
		$pg = array();
305
		foreach ($allgroups['postgroups'] as $postgroup)
306
		{
307
			$pg[] = $postgroup['id'];
308
		}
309
310
		$mg = array();
311
		foreach ($allgroups['membergroups'] as $membergroup)
312
		{
313
			$mg[] = $membergroup['id'];
314
		}
315
316
		// How many are in each group
317
		$mem_groups = membersInGroups($pg, $mg, true, true);
318
		foreach ($mem_groups as $id_group => $member_count)
319
		{
320
			if (isset($groups[$id_group]['member_count']))
321
			{
322
				$groups[$id_group]['member_count'] += $member_count;
323
			}
324
			else
325
			{
326
				$groups[$id_group]['member_count'] = $member_count;
327
			}
328
		}
329
330
		// Generate the include and exclude group select lists for the template
331
		foreach ($groups as $group)
332
		{
333
			$groups[$group['id']]['status'] = 'on';
334
			$groups[$group['id']]['is_postgroup'] = in_array($group['id'], $pg);
335
		}
336
337
		$context['groups'] = array(
338
			'select_group' => $txt['admin_newsletters_select_groups'],
339
			'member_groups' => $groups,
340
		);
341
342
		foreach ($groups as $group)
343
		{
344
			$groups[$group['id']]['status'] = 'off';
345
		}
346
347
		$context['exclude_groups'] = array(
348
			'select_group' => $txt['admin_newsletters_exclude_groups'],
349
			'member_groups' => $groups,
350
		);
351
352
		// Needed if for the PM option in the mail to all
353
		$context['can_send_pm'] = allowedTo('pm_send');
354
	}
355
356
	/**
357
	 * Shows a form to edit a forum mailing and its recipients.
358
	 *
359
	 * What it does:
360
	 *
361
	 * - Called by ?action=admin;area=news;sa=mailingcompose.
362
	 * - Requires the send_mail permission.
363
	 * - Form is submitted to ?action=admin;area=news;sa=mailingsend.
364
	 *
365
	 * @uses ManageNews template, email_members_compose sub-template.
366
	 */
367
	public function action_mailingcompose()
368
	{
369
		global $txt, $context;
370
371
		// Setup the template!
372
		$context['page_title'] = $txt['admin_newsletters'];
373
		$context['sub_template'] = 'email_members_compose';
374
		$context['subject'] = empty($this->_req->post->subject) ? $context['forum_name'] . ': ' . htmlspecialchars($txt['subject'], ENT_COMPAT, 'UTF-8') : $this->_req->post->subject;
375
		$context['message'] = empty($this->_req->post->message) ? htmlspecialchars($txt['message'] . "\n\n" . replaceBasicActionUrl($txt['regards_team']) . "\n\n" . '{$board_url}', ENT_COMPAT, 'UTF-8') : $this->_req->post->message;
376
377
		// Needed for the WYSIWYG editor.
378
		require_once(SUBSDIR . '/Editor.subs.php');
379
380
		// Now create the editor.
381
		$editorOptions = array(
382
			'id' => 'message',
383
			'value' => $context['message'],
384
			'height' => '250px',
385
			'width' => '100%',
386
			'labels' => array(
387
				'post_button' => $txt['sendtopic_send'],
388
			),
389
			'smiley_container' => 'smileyBox_message',
390
			'bbc_container' => 'bbcBox_message',
391
			'preview_type' => 2,
392
		);
393
		create_control_richedit($editorOptions);
394
395
		if (isset($context['preview']))
396
		{
397
			require_once(SUBSDIR . '/Mail.subs.php');
398
			$context['recipients']['members'] = empty($this->_req->post->members) ? array() : explode(',', $this->_req->post->members);
399
			$context['recipients']['exclude_members'] = empty($this->_req->post->exclude_members) ? array() : explode(',', $this->_req->post->exclude_members);
400
			$context['recipients']['groups'] = empty($this->_req->post->groups) ? array() : explode(',', $this->_req->post->groups);
401
			$context['recipients']['exclude_groups'] = empty($this->_req->post->exclude_groups) ? array() : explode(',', $this->_req->post->exclude_groups);
402
			$context['recipients']['emails'] = empty($this->_req->post->emails) ? array() : explode(';', $this->_req->post->emails);
403
			$context['email_force'] = $this->_req->getPost('email_force', 'isset', false);
404
			$context['total_emails'] = $this->_req->getPost('total_emails', 'intval', 0);
405
			$context['max_id_member'] = $this->_req->getPost('max_id_member', 'intval', 0);
406
			$context['send_pm'] = $this->_req->getPost('send_pm', 'isset', false);
407
			$context['send_html'] = $this->_req->getPost('send_html', 'isset', false);
408
409
			prepareMailingForPreview();
410
411
			return null;
412
		}
413
414
		// Start by finding any manually entered members!
415
		$this->_toClean();
416
417
		// Add in any members chosen from the auto select dropdown.
418
		$this->_toAddOrExclude();
419
420
		// Clean the other vars.
421
		$this->action_mailingsend(true);
422
423
		// We need a couple strings from the email template file
424
		Txt::load('EmailTemplates');
425
		require_once(SUBSDIR . '/News.subs.php');
426
427
		// Get a list of all full banned users.  Use their Username and email to find them.
428
		// Only get the ones that can't login to turn off notification.
429
		$context['recipients']['exclude_members'] = excludeBannedMembers();
430
431
		// Did they select moderators - if so add them as specific members...
432
		if ((!empty($context['recipients']['groups']) && in_array(3, $context['recipients']['groups'])) || (!empty($context['recipients']['exclude_groups']) && in_array(3, $context['recipients']['exclude_groups'])))
433
		{
434
			$mods = getModerators();
435
436
			foreach ($mods as $row)
437
			{
438
				if (in_array(3, $context['recipients']))
439
				{
440
					$context['recipients']['exclude_members'][] = $row;
441
				}
442
				else
443
				{
444
					$context['recipients']['members'][] = $row;
445
				}
446
			}
447
		}
448
449
		require_once(SUBSDIR . '/Members.subs.php');
450
451
		// For progress bar!
452
		$context['total_emails'] = count($context['recipients']['emails']);
453
		$context['max_id_member'] = maxMemberID();
454
455
		// Make sure to fully load the array with the form choices
456
		$context['recipients']['members'] = array_merge($this->_members, $context['recipients']['members']);
457
		$context['recipients']['exclude_members'] = array_merge($this->_exclude_members, $context['recipients']['exclude_members']);
458
459
		// Clean up the arrays.
460
		$context['recipients']['members'] = array_unique($context['recipients']['members']);
461
		$context['recipients']['exclude_members'] = array_unique($context['recipients']['exclude_members']);
462
463
		return true;
464
	}
465
466
	/**
467
	 * If they did not use auto select function on the include/exclude members then
468
	 * we need to look them up from the supplied "one","two" string
469
	 */
470
	private function _toClean()
471
	{
472
		$toClean = array();
473
		if (!empty($this->_req->post->members))
474
		{
475
			$toClean['_members'] = 'members';
476
		}
477
478
		if (!empty($this->_req->post->exclude_members))
479
		{
480
			$toClean['_exclude_members'] = 'exclude_members';
481
		}
482
483
		// Manual entries found?
484
		if (!empty($toClean))
485
		{
486
			require_once(SUBSDIR . '/Auth.subs.php');
487
			foreach ($toClean as $key => $type)
488
			{
489
				// Remove the quotes.
490
				$temp = strtr((string) $this->_req->post->{$type}, array('\\"' => '"'));
491
492
				// Break it up in to an array for processing
493
				preg_match_all('~"([^"]+)"~', $this->_req->post->{$type}, $matches);
494
				$temp = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $temp))));
495
496
				// Clean the valid ones, drop the mangled ones
497
				foreach ($temp as $index => $member)
498
				{
499
					if (trim($member) !== '')
500
					{
501
						$temp[$index] = Util::htmlspecialchars(Util::strtolower(trim($member)));
502
					}
503
					else
504
					{
505
						unset($temp[$index]);
506
					}
507
				}
508
509
				// Find the members
510
				$this->{$key} = array_keys(findMembers($temp));
511
			}
512
		}
513
	}
514
515
	/**
516
	 * Members may have been chosen via autoselection pulldown for both Add or Exclude
517
	 * this will process them and combine them to any manually added ones.
518
	 */
519
	private function _toAddOrExclude()
520
	{
521
		// Members selected (via auto select) to specifically get the newsletter
522
		if (is_array($this->_req->getPost('member_list')))
523
		{
524
			$members = array();
525
			foreach ($this->_req->post->member_list as $member_id)
526
			{
527
				$members[] = (int) $member_id;
528
			}
529
530
			$this->_members = array_unique(array_merge($this->_members, $members));
531
		}
532
533
		// Members selected (via auto select) to specifically not get the newsletter
534
		if (is_array($this->_req->getPost('exclude_member_list')))
535
		{
536
			$members = array();
537
			foreach ($this->_req->post->exclude_member_list as $member_id)
538
			{
539
				$members[] = (int) $member_id;
540
			}
541
542
			$this->_exclude_members = array_unique(array_merge($this->_exclude_members, $members));
543
		}
544
	}
545
546
	/**
547
	 * Handles the sending of the forum mailing in batches.
548
	 *
549
	 * What it does:
550
	 *
551
	 * - Called by ?action=admin;area=news;sa=mailingsend
552
	 * - Requires the send_mail permission.
553
	 * - Redirects to itself when more batches need to be sent.
554
	 * - Redirects to ?action=admin after everything has been sent.
555
	 *
556
	 * @param bool $clean_only = false; if set, it will only clean the variables, put them in context, then return.
557
	 *
558
	 * @return null|void
559
	 * @uses ManageNews template and email_members_send sub template.
560
	 *
561
	 */
562
	public function action_mailingsend($clean_only = false)
563
	{
564
		global $txt, $context, $scripturl, $modSettings;
565
566
		// A nice successful screen if you did it
567
		if (isset($this->_req->query->success))
568
		{
569
			$context['sub_template'] = 'email_members_succeeded';
570
			theme()->getTemplates()->load('ManageNews');
571
572
			return null;
573
		}
574
575
		// If just previewing we prepare a message and return it for viewing
576
		if (isset($this->_req->post->preview))
577
		{
578
			$context['preview'] = true;
579
580
			$this->action_mailingcompose();
581
			return;
582
		}
583
584
		// How many to send at once? Quantity depends on whether we are queueing or not.
585
		// @todo Might need an interface? (used in Post.controller.php too with different limits)
586
		$num_at_once = empty($modSettings['mail_queue']) ? 60 : 1000;
587
588
		// If by PM's I suggest we half the above number.
589
		if (!empty($this->_req->post->send_pm))
590
		{
591
			$num_at_once /= 2;
592
		}
593
594
		checkSession();
595
596
		// Where are we actually to?
597
		$context['start'] = $this->_req->getPost('start', 'intval', 0);
598
		$context['email_force'] = $this->_req->getPost('email_force', 'isset', false);
599
		$context['total_emails'] = $this->_req->getPost('total_emails', 'intval', 0);
600
		$context['max_id_member'] = $this->_req->getPost('max_id_member', 'intval', 0);
601
		$context['send_pm'] = $this->_req->getPost('send_pm', 'isset', false);
602
		$context['send_html'] = $this->_req->getPost('send_html', 'isset', false);
603
		$context['parse_html'] = $this->_req->getPost('parse_html', 'isset', false);
604
605
		// Create our main context.
606
		$context['recipients'] = array(
607
			'groups' => array(),
608
			'exclude_groups' => array(),
609
			'members' => array(),
610
			'exclude_members' => array(),
611
			'emails' => array(),
612
		);
613
614
		// Have we any excluded members?
615
		if (!empty($this->_req->post->exclude_members))
616
		{
617
			$members = explode(',', $this->_req->post->exclude_members);
618
			foreach ($members as $member)
619
			{
620
				if ($member >= $context['start'])
621
				{
622
					$context['recipients']['exclude_members'][] = (int) $member;
623
				}
624
			}
625
		}
626
627
		// What about members we *must* do?
628
		if (!empty($this->_req->post->members))
629
		{
630
			$members = explode(',', $this->_req->post->members);
631
			foreach ($members as $member)
632
			{
633
				if ($member >= $context['start'])
634
				{
635
					$context['recipients']['members'][] = (int) $member;
636
				}
637
			}
638
		}
639
640
		// Cleaning groups is simple - although deal with both checkbox and commas.
641
		if (is_array($this->_req->getPost('groups')))
642
		{
643
			foreach ($this->_req->post->groups as $group => $dummy)
644
			{
645
				$context['recipients']['groups'][] = (int) $group;
646
			}
647
		}
648
		elseif ($this->_req->getPost('groups', 'trim', '') !== '')
649
		{
650
			$groups = explode(',', $this->_req->post->groups);
651
			foreach ($groups as $group)
652
			{
653
				$context['recipients']['groups'][] = (int) $group;
654
			}
655
		}
656
657
		// Same for excluded groups
658
		if (is_array($this->_req->getPost('exclude_groups')))
659
		{
660
			foreach ($this->_req->post->exclude_groups as $group => $dummy)
661
			{
662
				$context['recipients']['exclude_groups'][] = (int) $group;
663
			}
664
		}
665
		elseif ($this->_req->getPost('exclude_groups', 'trim', '') !== '')
666
		{
667
			$groups = explode(',', $this->_req->post->exclude_groups);
668
			foreach ($groups as $group)
669
			{
670
				$context['recipients']['exclude_groups'][] = (int) $group;
671
			}
672
		}
673
674
		// Finally - emails!
675
		if (!empty($this->_req->post->emails))
676
		{
677
			$addressed = array_unique(explode(';', strtr($this->_req->post->emails, array("\n" => ';', "\r" => ';', ',' => ';'))));
678
			foreach ($addressed as $curmem)
679
			{
680
				$curmem = trim($curmem);
681
				if ($curmem !== '')
682
				{
683
					$context['recipients']['emails'][$curmem] = $curmem;
684
				}
685
			}
686
		}
687
688
		// If we're only cleaning drop out here.
689
		if ($clean_only)
690
		{
691
			return null;
692
		}
693
694
		// Some functions we will need
695
		require_once(SUBSDIR . '/Mail.subs.php');
696
		if ($context['send_pm'])
697
		{
698
			require_once(SUBSDIR . '/PersonalMessage.subs.php');
699
		}
700
701
		$base_subject = $this->_req->getPost('subject', 'trim|strval', '');
702
		$base_message = $this->_req->getPost('message', 'strval', '');
703
704
		// Save the message and its subject in $context
705
		$context['subject'] = htmlspecialchars($base_subject, ENT_COMPAT, 'UTF-8');
706
		$context['message'] = htmlspecialchars($base_message, ENT_COMPAT, 'UTF-8');
707
708
		// Prepare the message for sending it as HTML
709
		if (!$context['send_pm'] && !empty($context['send_html']))
710
		{
711
			// Prepare the message for HTML.
712
			if (!empty($context['parse_html']))
713
			{
714
				$base_message = str_replace(array("\n", '  '), array('<br />' . "\n", '&nbsp; '), $base_message);
715
			}
716
717
			// This is here to prevent spam filters from tagging this as spam.
718
			if (preg_match('~<html~i', $base_message) == 0)
719
			{
720
				if (preg_match('~<body~i', $base_message) == 0)
721
				{
722
					$base_message = '<html><head><title>' . $base_subject . '</title></head>' . "\n" . '<body>' . $base_message . '</body></html>';
723
				}
724
				else
725
				{
726
					$base_message = '<html>' . $base_message . '</html>';
727
				}
728
			}
729
		}
730
731
		if (empty($base_message) || empty($base_subject))
732
		{
733
			$context['preview'] = true;
734
735
			$this->action_mailingcompose();
736
			return;
737
		}
738
739
		// Use the default time format.
740
		$this->user->time_format = $modSettings['time_format'];
741
742
		$variables = array(
743
			'{$board_url}',
744
			'{$current_time}',
745
			'{$latest_member.link}',
746
			'{$latest_member.id}',
747
			'{$latest_member.name}'
748
		);
749
750
		// We might need this in a bit
751
		$cleanLatestMember = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
752
753
		// Replace in all the standard things.
754
		$base_message = str_replace($variables,
755
			array(
756
				empty($context['send_html']) ? $scripturl : '<a href="' . $scripturl . '">' . $scripturl . '</a>',
757
				standardTime(forum_time(), false),
758
				empty($context['send_html']) ? ($context['send_pm'] ? '[url=' . getUrl('profile', ['action' => 'profile', 'u' => $modSettings['latestMember'], 'name' => $cleanLatestMember]) . ']' . $cleanLatestMember . '[/url]' : $cleanLatestMember) : ('<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $modSettings['latestMember'], 'name' => $cleanLatestMember]) . '">' . $cleanLatestMember . '</a>'),
759
				$modSettings['latestMember'],
760
				$cleanLatestMember
761
			), $base_message);
762
763
		$base_subject = str_replace($variables,
764
			array(
765
				$scripturl,
766
				standardTime(forum_time(), false),
767
				$modSettings['latestRealName'],
768
				$modSettings['latestMember'],
769
				$modSettings['latestRealName']
770
			), $base_subject);
771
772
		$from_member = array(
773
			'{$member.email}',
774
			'{$member.link}',
775
			'{$member.id}',
776
			'{$member.name}'
777
		);
778
779
		// If we still have emails, do them first!
780
		$i = 0;
781
		foreach ($context['recipients']['emails'] as $k => $email)
782
		{
783
			// Done as many as we can?
784
			if ($i >= $num_at_once)
785
			{
786
				break;
787
			}
788
789
			// Don't sent it twice!
790
			unset($context['recipients']['emails'][$k]);
791
792
			// Dammit - can't PM emails!
793
			if ($context['send_pm'])
794
			{
795
				continue;
796
			}
797
798
			$to_member = array(
799
				$email,
800
				empty($context['send_html']) ? $email : '<a href="mailto:' . $email . '">' . $email . '</a>',
801
				'??',
802
				$email
803
			);
804
805
			sendmail($email, str_replace($from_member, $to_member, $base_subject), str_replace($from_member, $to_member, $base_message), null, null, !empty($context['send_html']), 5);
806
807
			// Done another...
808
			$i++;
809
		}
810
811
		// Got some more to send this batch?
812
		$last_id_member = 0;
813
		if ($i < $num_at_once)
814
		{
815
			// Need to build quite a query!
816
			$sendQuery = '(';
817
			$sendParams = array();
818
			if (!empty($context['recipients']['groups']))
819
			{
820
				// Take the long route...
821
				$queryBuild = array();
822
				foreach ($context['recipients']['groups'] as $group)
823
				{
824
					$sendParams['group_' . $group] = $group;
825
					$queryBuild[] = 'mem.id_group = {int:group_' . $group . '}';
826
					if (!empty($group))
827
					{
828
						$queryBuild[] = 'FIND_IN_SET({int:group_' . $group . '}, mem.additional_groups) != 0';
829
						$queryBuild[] = 'mem.id_post_group = {int:group_' . $group . '}';
830
					}
831
				}
832
833
				if (!empty($queryBuild))
834
				{
835
					$sendQuery .= implode(' OR ', $queryBuild);
836
				}
837
			}
838
839
			if (!empty($context['recipients']['members']))
840
			{
841
				$sendQuery .= ($sendQuery === '(' ? '' : ' OR ') . 'mem.id_member IN ({array_int:members})';
842
				$sendParams['members'] = $context['recipients']['members'];
843
			}
844
845
			$sendQuery .= ')';
846
847
			// If we've not got a query then we must be done!
848
			if ($sendQuery === '()')
849
			{
850
				redirectexit('action=admin');
851
			}
852
853
			// Anything to exclude?
854
			if (!empty($context['recipients']['exclude_groups']) && in_array(0, $context['recipients']['exclude_groups']))
855
			{
856
				$sendQuery .= ' AND mem.id_group != {int:regular_group}';
857
			}
858
859
			if (!empty($context['recipients']['exclude_members']))
860
			{
861
				$sendQuery .= ' AND mem.id_member NOT IN ({array_int:exclude_members})';
862
				$sendParams['exclude_members'] = $context['recipients']['exclude_members'];
863
			}
864
865
			// Force them to have it?
866
			if (empty($context['email_force']))
867
			{
868
				$sendQuery .= ' AND mem.notify_announcements = {int:notify_announcements}';
869
			}
870
871
			require_once(SUBSDIR . '/News.subs.php');
872
873
			// Get the smelly people - note we respect the id_member range as it gives us a quicker query.
874
			$recipients = getNewsletterRecipients($sendQuery, $sendParams, $context['start'], $num_at_once, $i);
875
876
			foreach ($recipients as $row)
877
			{
878
				$last_id_member = $row['id_member'];
879
880
				// What groups are we looking at here?
881
				$groups = array_merge([$row['id_group'], $row['id_post_group']], (empty($row['additional_groups']) ? [] : explode(',', $row['additional_groups'])));
882
883
				// Excluded groups?
884
				if (array_intersect($groups, $context['recipients']['exclude_groups']) !== [])
885
				{
886
					continue;
887
				}
888
889
				// We might need this
890
				$cleanMemberName = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($row['real_name']) : $row['real_name'];
891
892
				// Replace the member-dependant variables
893
				$message = str_replace($from_member,
894
					array(
895
						$row['email_address'],
896
						empty($context['send_html']) ? ($context['send_pm'] ? '[url=' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $cleanMemberName]) . ']' . $cleanMemberName . '[/url]' : $cleanMemberName) : ('<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $cleanMemberName]) . '">' . $cleanMemberName . '</a>'),
897
						$row['id_member'],
898
						$cleanMemberName,
899
					), $base_message);
900
901
				$subject = str_replace($from_member,
902
					array(
903
						$row['email_address'],
904
						$row['real_name'],
905
						$row['id_member'],
906
						$row['real_name'],
907
					), $base_subject);
908
909
				// Send the actual email - or a PM!
910
				if (!$context['send_pm'])
911
				{
912
					sendmail($row['email_address'], $subject, $message, null, null, !empty($context['send_html']), 5);
913
				}
914
				else
915
				{
916
					sendpm(array('to' => array($row['id_member']), 'bcc' => array()), $subject, $message);
917
				}
918
			}
919
		}
920
921
		// If used our batch assume we still have a member.
922
		if ($i >= $num_at_once)
923
		{
924
			$last_id_member = $context['start'];
925
		}
926
		// Or we didn't have one in range?
927
		elseif (empty($last_id_member) && $context['start'] + $num_at_once < $context['max_id_member'])
928
		{
929
			$last_id_member = $context['start'] + $num_at_once;
930
		}
931
		// If we have no id_member then we're done.
932
		elseif (empty($last_id_member) && empty($context['recipients']['emails']))
933
		{
934
			// Log this into the admin log.
935
			logAction('newsletter', array(), 'admin');
936
			redirectexit('action=admin;area=news;sa=mailingsend;success');
937
		}
938
939
		$context['start'] = $last_id_member;
940
941
		// Working out progress is a black art of sorts.
942
		$percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['max_id_member'])));
943
		$percentMembers = ($context['start'] / $context['max_id_member']) * ($context['max_id_member'] / ($context['total_emails'] + $context['max_id_member']));
944
		$context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2);
945
946
		$context['page_title'] = $txt['admin_newsletters'];
947
		$context['sub_template'] = 'email_members_send';
948
	}
949
950
	/**
951
	 * Set general news and newsletter settings and permissions.
952
	 *
953
	 * What it does:
954
	 *
955
	 * - Called by ?action=admin;area=news;sa=settings.
956
	 * - Requires the forum_admin permission.
957
	 *
958
	 * @event integrate_save_news_settings save new news settings
959
	 * @uses ManageNews template, news_settings sub-template.
960
	 */
961
	public function action_newsSettings_display()
962
	{
963
		global $context, $txt;
964
965
		// Initialize the form
966
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
967
968
		// Initialize it with our settings
969
		$settingsForm->setConfigVars($this->_settings());
970
971
		// Add some javascript at the bottom...
972
		theme()->addInlineJavascript('
973
			document.getElementById("xmlnews_maxle").disabled = !document.getElementById("xmlnews_enable").checked;
974
			document.getElementById("xmlnews_limit").disabled = !document.getElementById("xmlnews_enable").checked;', true);
975
976
		// Wrap it all up nice and warm...
977
		$context['page_title'] = $txt['admin_edit_news'] . ' - ' . $txt['settings'];
978
		$context['sub_template'] = 'show_settings';
979
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'news', 'save', 'sa' => 'settings']);
980
981
		// Saving the settings?
982
		if (isset($this->_req->query->save))
983
		{
984
			checkSession();
985
986
			call_integration_hook('integrate_save_news_settings');
987
988
			$settingsForm->setConfigValues((array) $this->_req->post);
989
			$settingsForm->save();
990
			redirectexit('action=admin;area=news;sa=settings');
991
		}
992
993
		$settingsForm->prepare();
994
	}
995
996
	/**
997
	 * Get the settings of the forum related to news.
998
	 *
999
	 * @event integrate_modify_news_settings add new news settings
1000
	 */
1001
	private function _settings()
1002
	{
1003
		global $txt;
1004
1005
		$config_vars = array(
1006
			array('title', 'settings'),
1007
			// Inline permissions.
1008
			array('permissions', 'edit_news', 'help' => '', 'collapsed' => true),
1009
			array('permissions', 'send_mail', 'collapsed' => true),
1010
			'',
1011
			// Just the remaining settings.
1012
			array('check', 'xmlnews_enable', 'onclick' => "document.getElementById('xmlnews_maxlen').disabled = !this.checked;document.getElementById('xmlnews_limit').disabled = !this.checked;"),
1013
			array('int', 'xmlnews_maxlen', 'subtext' => $txt['xmlnews_maxlen_note'], 10),
1014
			array('int', 'xmlnews_limit', 'subtext' => $txt['xmlnews_limit_note'], 10),
1015
		);
1016
1017
		// Add new settings with a nice hook, makes them available for admin settings search as well
1018
		call_integration_hook('integrate_modify_news_settings');
1019
1020
		return $config_vars;
1021
	}
1022
1023 2
	/**
1024
	 * Return the form settings for use in admin search
1025 2
	 */
1026
	public function settings_search()
1027
	{
1028 2
		return $this->_settings();
1029
	}
1030
}
1031