SendMailing()   F
last analyzed

Complexity

Conditions 77
Paths > 20000

Size

Total Lines 377
Code Lines 212

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 77
eloc 212
nc 1400833
nop 1
dl 0
loc 377
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 manages... the news. :P
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.0
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * The news dispatcher; doesn't do anything, just delegates.
21
 * This is the entrance point for all News and Newsletter screens.
22
 * Called by ?action=admin;area=news.
23
 * It does the permission checks, and calls the appropriate function
24
 * based on the requested sub-action.
25
 */
26
function ManageNews()
27
{
28
	global $context, $txt;
29
30
	// First, let's do a quick permissions check for the best error message possible.
31
	isAllowedTo(array('edit_news', 'send_mail', 'admin_forum'));
32
33
	loadTemplate('ManageNews');
34
35
	// Format: 'sub-action' => array('function', 'permission')
36
	$subActions = array(
37
		'editnews' => array('EditNews', 'edit_news'),
38
		'mailingmembers' => array('SelectMailingMembers', 'send_mail'),
39
		'mailingcompose' => array('ComposeMailing', 'send_mail'),
40
		'mailingsend' => array('SendMailing', 'send_mail'),
41
		'settings' => array('ModifyNewsSettings', 'admin_forum'),
42
	);
43
44
	call_integration_hook('integrate_manage_news', array(&$subActions));
45
46
	// Default to sub action 'main' or 'settings' depending on permissions.
47
	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('edit_news') ? 'editnews' : (allowedTo('send_mail') ? 'mailingmembers' : 'settings'));
48
49
	// Have you got the proper permissions?
50
	isAllowedTo($subActions[$_REQUEST['sa']][1]);
51
52
	// Create the tabs for the template.
53
	$context[$context['admin_menu_name']]['tab_data'] = array(
54
		'title' => $txt['news_title'],
55
		'help' => 'edit_news',
56
		'description' => $txt['admin_news_desc'],
57
		'tabs' => array(
58
			'editnews' => array(
59
			),
60
			'mailingmembers' => array(
61
				'description' => $txt['news_mailing_desc'],
62
			),
63
			'settings' => array(
64
				'description' => $txt['news_settings_desc'],
65
			),
66
		),
67
	);
68
69
	// Force the right area...
70
	if (substr($_REQUEST['sa'], 0, 7) == 'mailing')
71
		$context[$context['admin_menu_name']]['current_subsection'] = 'mailingmembers';
72
73
	call_helper($subActions[$_REQUEST['sa']][0]);
74
}
75
76
/**
77
 * Let the administrator(s) edit the news items for the forum.
78
 * It writes an entry into the moderation log.
79
 * This function uses the edit_news administration area.
80
 * Called by ?action=admin;area=news.
81
 * Requires the edit_news permission.
82
 * Can be accessed with ?action=admin;sa=editnews.
83
 *
84
 * Uses a standard list (@see createList())
85
 */
86
function EditNews()
87
{
88
	global $txt, $modSettings, $context, $sourcedir, $scripturl;
89
	global $smcFunc;
90
91
	require_once($sourcedir . '/Subs-Post.php');
92
93
	// The 'remove selected' button was pressed.
94
	if (!empty($_POST['delete_selection']) && !empty($_POST['remove']))
95
	{
96
		checkSession();
97
98
		// Store the news temporarily in this array.
99
		$temp_news = explode("\n", $modSettings['news']);
100
101
		// Remove the items that were selected.
102
		foreach ($temp_news as $i => $news)
103
			if (in_array($i, $_POST['remove']))
104
				unset($temp_news[$i]);
105
106
		// Update the database.
107
		updateSettings(array('news' => implode("\n", $temp_news)));
108
109
		$context['saved_successful'] = true;
110
111
		logAction('news');
112
	}
113
	// The 'Save' button was pressed.
114
	elseif (!empty($_POST['save_items']))
115
	{
116
		checkSession();
117
118
		foreach ($_POST['news'] as $i => $news)
119
		{
120
			if (trim($news) == '')
121
				unset($_POST['news'][$i]);
122
			else
123
			{
124
				$_POST['news'][$i] = $smcFunc['htmlspecialchars']($_POST['news'][$i], ENT_QUOTES);
125
				preparsecode($_POST['news'][$i]);
126
			}
127
		}
128
129
		// Send the new news to the database.
130
		updateSettings(array('news' => implode("\n", $_POST['news'])));
131
132
		$context['saved_successful'] = true;
133
134
		// Log this into the moderation log.
135
		logAction('news');
136
	}
137
138
	// We're going to want this for making our list.
139
	require_once($sourcedir . '/Subs-List.php');
140
141
	$context['page_title'] = $txt['admin_edit_news'];
142
143
	// Use the standard templates for showing this.
144
	$listOptions = array(
145
		'id' => 'news_lists',
146
		'get_items' => array(
147
			'function' => 'list_getNews',
148
		),
149
		'columns' => array(
150
			'news' => array(
151
				'header' => array(
152
					'value' => $txt['admin_edit_news'],
153
					'class' => 'half_table',
154
				),
155
				'data' => array(
156
					'function' => function($news)
157
					{
158
						if (is_numeric($news['id']))
159
							return '
160
								<textarea id="data_' . $news['id'] . '" rows="3" cols="50" name="news[]" class="padding block">' . $news['unparsed'] . '</textarea>
161
								<div class="floatleft" id="preview_' . $news['id'] . '"></div>';
162
						else
163
							return $news['unparsed'];
164
					},
165
					'class' => 'half_table',
166
				),
167
			),
168
			'preview' => array(
169
				'header' => array(
170
					'value' => $txt['preview'],
171
					'class' => 'half_table',
172
				),
173
				'data' => array(
174
					'function' => function($news)
175
					{
176
						return '<div id="box_preview_' . $news['id'] . '" style="overflow: auto; width: 100%; height: 10ex;">' . $news['parsed'] . '</div>';
177
					},
178
					'class' => 'half_table',
179
				),
180
			),
181
			'check' => array(
182
				'header' => array(
183
					'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
184
					'class' => 'centercol icon',
185
				),
186
				'data' => array(
187
					'function' => function($news)
188
					{
189
						if (is_numeric($news['id']))
190
							return '<input type="checkbox" name="remove[]" value="' . $news['id'] . '">';
191
						else
192
							return '';
193
					},
194
					'class' => 'centercol icon',
195
				),
196
			),
197
		),
198
		'form' => array(
199
			'href' => $scripturl . '?action=admin;area=news;sa=editnews',
200
			'hidden_fields' => array(
201
				$context['session_var'] => $context['session_id'],
202
			),
203
		),
204
		'additional_rows' => array(
205
			array(
206
				'position' => 'bottom_of_list',
207
				'value' => '
208
				<span id="moreNewsItems_link" class="floatleft" style="display: none;">
209
					<a class="button" href="javascript:void(0);" onclick="addNewsItem(); return false;">' . $txt['editnews_clickadd'] . '</a>
210
				</span>
211
				<input type="submit" name="save_items" value="' . $txt['save'] . '" class="button">
212
				<input type="submit" name="delete_selection" value="' . $txt['editnews_remove_selected'] . '" data-confirm="' . $txt['editnews_remove_confirm'] . '" class="button you_sure">',
213
			),
214
		),
215
		'javascript' => '
216
					document.getElementById(\'list_news_lists_last\').style.display = "none";
217
					document.getElementById("moreNewsItems_link").style.display = "";
218
					var last_preview = 0;
219
220
					$(document).ready(function () {
221
						$("div[id ^= \'preview_\']").each(function () {
222
							var preview_id = $(this).attr(\'id\').split(\'_\')[1];
223
							if (last_preview < preview_id)
224
								last_preview = preview_id;
225
							make_preview_btn(preview_id);
226
						});
227
					});
228
229
					function make_preview_btn (preview_id)
230
					{
231
						$("#preview_" + preview_id).addClass("button");
232
						$("#preview_" + preview_id).text(\'' . $txt['preview'] . '\').click(function () {
233
							$.ajax({
234
								type: "POST",
235
								headers: {
236
									"X-SMF-AJAX": 1
237
								},
238
								xhrFields: {
239
									withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false
240
								},
241
								url: "' . $scripturl . '?action=xmlhttp;sa=previews;xml",
242
								data: {item: "newspreview", news: $("#data_" + preview_id).val()},
243
								context: document.body,
244
								success: function(request){
245
									if ($(request).find("error").text() == \'\')
246
										$(document).find("#box_preview_" + preview_id).html($(request).text());
247
									else
248
										$(document).find("#box_preview_" + preview_id).text(\'' . $txt['news_error_no_news'] . '\');
249
								},
250
							});
251
						});
252
					}
253
254
					function addNewsItem ()
255
					{
256
						last_preview++;
257
						$("#list_news_lists_last").before(' . javaScriptEscape('
258
						<tr class="windowbg') . ' + (last_preview % 2 == 0 ? \'\' : \'2\') + ' . javaScriptEscape('">
259
							<td style="width: 50%;">
260
									<textarea id="data_') . ' + last_preview + ' . javaScriptEscape('" rows="3" cols="65" name="news[]" style="width: 95%;"></textarea>
261
									<br>
262
									<div class="floatleft" id="preview_') . ' + last_preview + ' . javaScriptEscape('"></div>
263
							</td>
264
							<td style="width: 45%;">
265
								<div id="box_preview_') . ' + last_preview + ' . javaScriptEscape('" style="overflow: auto; width: 100%; height: 10ex;"></div>
266
							</td>
267
							<td></td>
268
						</tr>') . ');
269
						make_preview_btn(last_preview);
270
					}',
271
	);
272
273
	// Create the request list.
274
	createList($listOptions);
275
276
	// And go!
277
	loadTemplate('ManageNews');
278
	$context['sub_template'] = 'news_lists';
279
}
280
281
/**
282
 * Prepares an array of the forum news items for display in the template
283
 *
284
 * @return array An array of information about the news items
285
 */
286
function list_getNews()
287
{
288
	global $modSettings;
289
290
	$admin_current_news = array();
291
	// Ready the current news.
292
	foreach (explode("\n", $modSettings['news']) as $id => $line)
293
		$admin_current_news[$id] = array(
294
			'id' => $id,
295
			'unparsed' => un_preparsecode($line),
296
			'parsed' => preg_replace('~<([/]?)form[^>]*?[>]*>~i', '<em class="smalltext">&lt;$1form&gt;</em>', parse_bbc($line)),
297
		);
298
299
	$admin_current_news['last'] = array(
300
		'id' => 'last',
301
		'unparsed' => '<div id="moreNewsItems"></div>
302
		<noscript><textarea rows="3" cols="65" name="news[]" style="width: 85%;"></textarea></noscript>',
303
		'parsed' => '<div id="moreNewsItems_preview"></div>',
304
	);
305
306
	return $admin_current_news;
307
}
308
309
/**
310
 * This function allows a user to select the membergroups to send their
311
 * mailing to.
312
 * Called by ?action=admin;area=news;sa=mailingmembers.
313
 * Requires the send_mail permission.
314
 * Form is submitted to ?action=admin;area=news;mailingcompose.
315
 *
316
 * @uses template_email_members()
317
 */
318
function SelectMailingMembers()
319
{
320
	global $txt, $context, $modSettings, $smcFunc;
321
322
	// Is there any confirm message?
323
	$context['newsletter_sent'] = isset($_SESSION['newsletter_sent']) ? $_SESSION['newsletter_sent'] : '';
324
325
	$context['page_title'] = $txt['admin_newsletters'];
326
327
	$context['sub_template'] = 'email_members';
328
329
	$context['groups'] = array();
330
	$postGroups = array();
331
	$normalGroups = array();
332
333
	// If we have post groups disabled then we need to give a "ungrouped members" option.
334
	if (empty($modSettings['permission_enable_postgroups']))
335
	{
336
		$context['groups'][0] = array(
337
			'id' => 0,
338
			'name' => $txt['membergroups_members'],
339
			'member_count' => 0,
340
		);
341
		$normalGroups[0] = 0;
342
	}
343
344
	// Get all the extra groups as well as Administrator and Global Moderator.
345
	$request = $smcFunc['db_query']('', '
346
		SELECT mg.id_group, mg.group_name, mg.min_posts
347
		FROM {db_prefix}membergroups AS mg' . (empty($modSettings['permission_enable_postgroups']) ? '
348
		WHERE mg.min_posts = {int:min_posts}' : '') . '
349
		GROUP BY mg.id_group, mg.min_posts, mg.group_name
350
		ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name',
351
		array(
352
			'min_posts' => -1,
353
			'newbie_group' => 4,
354
		)
355
	);
356
	while ($row = $smcFunc['db_fetch_assoc']($request))
357
	{
358
		$context['groups'][$row['id_group']] = array(
359
			'id' => $row['id_group'],
360
			'name' => $row['group_name'],
361
			'member_count' => 0,
362
		);
363
364
		if ($row['min_posts'] == -1)
365
			$normalGroups[$row['id_group']] = $row['id_group'];
366
		else
367
			$postGroups[$row['id_group']] = $row['id_group'];
368
	}
369
	$smcFunc['db_free_result']($request);
370
371
	// If we have post groups, let's count the number of members...
372
	if (!empty($postGroups))
373
	{
374
		$query = $smcFunc['db_query']('', '
375
			SELECT mem.id_post_group AS id_group, COUNT(*) AS member_count
376
			FROM {db_prefix}members AS mem
377
			WHERE mem.id_post_group IN ({array_int:post_group_list})
378
			GROUP BY mem.id_post_group',
379
			array(
380
				'post_group_list' => $postGroups,
381
			)
382
		);
383
		while ($row = $smcFunc['db_fetch_assoc']($query))
384
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
385
		$smcFunc['db_free_result']($query);
386
	}
387
388
	if (!empty($normalGroups))
389
	{
390
		// Find people who are members of this group...
391
		$query = $smcFunc['db_query']('', '
392
			SELECT id_group, COUNT(*) AS member_count
393
			FROM {db_prefix}members
394
			WHERE id_group IN ({array_int:normal_group_list})
395
			GROUP BY id_group',
396
			array(
397
				'normal_group_list' => $normalGroups,
398
			)
399
		);
400
		while ($row = $smcFunc['db_fetch_assoc']($query))
401
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
402
		$smcFunc['db_free_result']($query);
403
404
		// Also do those who have it as an additional membergroup - this ones more yucky...
405
		$query = $smcFunc['db_query']('', '
406
			SELECT mg.id_group, COUNT(*) AS member_count
407
			FROM {db_prefix}membergroups AS mg
408
				INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string}
409
					AND mem.id_group != mg.id_group
410
					AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0)
411
			WHERE mg.id_group IN ({array_int:normal_group_list})
412
			GROUP BY mg.id_group',
413
			array(
414
				'normal_group_list' => $normalGroups,
415
				'blank_string' => '',
416
			)
417
		);
418
		while ($row = $smcFunc['db_fetch_assoc']($query))
419
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
420
		$smcFunc['db_free_result']($query);
421
	}
422
423
	// Any moderators?
424
	$request = $smcFunc['db_query']('', '
425
		SELECT COUNT(DISTINCT id_member) AS num_distinct_mods
426
		FROM {db_prefix}moderators
427
		LIMIT 1',
428
		array(
429
		)
430
	);
431
	list ($context['groups'][3]['member_count']) = $smcFunc['db_fetch_row']($request);
432
	$smcFunc['db_free_result']($request);
433
434
	$context['can_send_pm'] = allowedTo('pm_send');
435
436
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
437
}
438
439
/**
440
 * Prepare subject and message of an email for the preview box
441
 * Used in ComposeMailing and RetrievePreview (Xml.php)
442
 */
443
function prepareMailingForPreview()
444
{
445
	global $context, $modSettings, $scripturl, $user_info, $txt;
446
	loadLanguage('Errors');
447
448
	$processing = array('preview_subject' => 'subject', 'preview_message' => 'message');
449
450
	// Use the default time format.
451
	$user_info['time_format'] = $modSettings['time_format'];
452
453
	$variables = array(
454
		'{$board_url}',
455
		'{$current_time}',
456
		'{$latest_member.link}',
457
		'{$latest_member.id}',
458
		'{$latest_member.name}'
459
	);
460
461
	$html = $context['send_html'];
462
463
	// We might need this in a bit
464
	$cleanLatestMember = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
465
466
	foreach ($processing as $key => $post)
467
	{
468
		$context[$key] = !empty($_REQUEST[$post]) ? $_REQUEST[$post] : '';
469
470
		if (empty($context[$key]) && empty($_REQUEST['xml']))
471
			$context['post_error']['messages'][] = $txt['error_no_' . $post];
472
		elseif (!empty($_REQUEST['xml']))
473
			continue;
474
475
		preparsecode($context[$key]);
476
		if ($html)
477
		{
478
			$enablePostHTML = $modSettings['enablePostHTML'];
479
			$modSettings['enablePostHTML'] = $context['send_html'];
480
			$context[$key] = parse_bbc($context[$key]);
481
			$modSettings['enablePostHTML'] = $enablePostHTML;
482
		}
483
484
		// Replace in all the standard things.
485
		$context[$key] = str_replace($variables,
486
			array(
487
				!empty($context['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
488
				timeformat(time(), false),
489
				!empty($context['send_html']) ? '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $cleanLatestMember . '</a>' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . ']' . $cleanLatestMember . '[/url]' : $cleanLatestMember),
490
				$modSettings['latestMember'],
491
				$cleanLatestMember
492
			), $context[$key]);
493
	}
494
}
495
496
/**
497
 * Shows a form to edit a forum mailing and its recipients.
498
 * Called by ?action=admin;area=news;sa=mailingcompose.
499
 * Requires the send_mail permission.
500
 * Form is submitted to ?action=admin;area=news;sa=mailingsend.
501
 *
502
 * @uses template_email_members_compose()
503
 */
504
function ComposeMailing()
505
{
506
	global $txt, $sourcedir, $context, $smcFunc;
507
508
	// Setup the template!
509
	$context['page_title'] = $txt['admin_newsletters'];
510
	$context['sub_template'] = 'email_members_compose';
511
512
	$context['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : $smcFunc['htmlspecialchars']($context['forum_name'] . ': ' . $txt['subject']);
513
	$context['message'] = !empty($_POST['message']) ? $_POST['message'] : $smcFunc['htmlspecialchars']($txt['message'] . "\n\n" . sprintf($txt['regards_team'], $context['forum_name']) . "\n\n" . '{$board_url}');
514
515
	// Needed for the WYSIWYG editor.
516
	require_once($sourcedir . '/Subs-Editor.php');
517
518
	// Now create the editor.
519
	$editorOptions = array(
520
		'id' => 'message',
521
		'value' => $context['message'],
522
		'height' => '150px',
523
		'width' => '100%',
524
		'labels' => array(
525
			'post_button' => $txt['sendtopic_send'],
526
		),
527
		'preview_type' => 2,
528
		'required' => true,
529
	);
530
	create_control_richedit($editorOptions);
531
	// Store the ID for old compatibility.
532
	$context['post_box_name'] = $editorOptions['id'];
533
534
	if (isset($context['preview']))
535
	{
536
		require_once($sourcedir . '/Subs-Post.php');
537
		$context['recipients']['members'] = !empty($_POST['members']) ? explode(',', $_POST['members']) : array();
538
		$context['recipients']['exclude_members'] = !empty($_POST['exclude_members']) ? explode(',', $_POST['exclude_members']) : array();
539
		$context['recipients']['groups'] = !empty($_POST['groups']) ? explode(',', $_POST['groups']) : array();
540
		$context['recipients']['exclude_groups'] = !empty($_POST['exclude_groups']) ? explode(',', $_POST['exclude_groups']) : array();
541
		$context['recipients']['emails'] = !empty($_POST['emails']) ? explode(';', $_POST['emails']) : array();
542
		$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
543
		$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
544
		$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
545
		$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
546
547
		return prepareMailingForPreview();
0 ignored issues
show
Bug introduced by
Are you sure the usage of prepareMailingForPreview() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
548
	}
549
550
	// Start by finding any members!
551
	$toClean = array();
552
	if (!empty($_POST['members']))
553
		$toClean[] = 'members';
554
	if (!empty($_POST['exclude_members']))
555
		$toClean[] = 'exclude_members';
556
	if (!empty($toClean))
557
	{
558
		require_once($sourcedir . '/Subs-Auth.php');
559
		foreach ($toClean as $type)
560
		{
561
			// Remove the quotes.
562
			$_POST[$type] = strtr($_POST[$type], array('\\"' => '"'));
563
564
			preg_match_all('~"([^"]+)"~', $_POST[$type], $matches);
565
			$_POST[$type] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST[$type]))));
566
567
			foreach ($_POST[$type] as $index => $member)
568
				if (strlen(trim($member)) > 0)
569
					$_POST[$type][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($member)));
570
				else
571
					unset($_POST[$type][$index]);
572
573
			// Find the members
574
			$_POST[$type] = implode(',', array_keys(findMembers($_POST[$type])));
575
		}
576
	}
577
578
	if (isset($_POST['member_list']) && is_array($_POST['member_list']))
579
	{
580
		$members = array();
581
		foreach ($_POST['member_list'] as $member_id)
582
			$members[] = (int) $member_id;
583
		$_POST['members'] = implode(',', $members);
584
	}
585
586
	if (isset($_POST['exclude_member_list']) && is_array($_POST['exclude_member_list']))
587
	{
588
		$members = array();
589
		foreach ($_POST['exclude_member_list'] as $member_id)
590
			$members[] = (int) $member_id;
591
		$_POST['exclude_members'] = implode(',', $members);
592
	}
593
594
	// Clean the other vars.
595
	SendMailing(true);
596
597
	// We need a couple strings from the email template file
598
	loadLanguage('EmailTemplates');
599
600
	// Get a list of all full banned users.  Use their Username and email to find them.  Only get the ones that can't login to turn off notification.
601
	$request = $smcFunc['db_query']('', '
602
		SELECT DISTINCT mem.id_member
603
		FROM {db_prefix}ban_groups AS bg
604
			INNER JOIN {db_prefix}ban_items AS bi ON (bg.id_ban_group = bi.id_ban_group)
605
			INNER JOIN {db_prefix}members AS mem ON (bi.id_member = mem.id_member)
606
		WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
607
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
608
		array(
609
			'cannot_access' => 1,
610
			'cannot_login' => 1,
611
			'current_time' => time(),
612
		)
613
	);
614
	while ($row = $smcFunc['db_fetch_assoc']($request))
615
		$context['recipients']['exclude_members'][] = $row['id_member'];
616
	$smcFunc['db_free_result']($request);
617
618
	$request = $smcFunc['db_query']('', '
619
		SELECT DISTINCT bi.email_address
620
		FROM {db_prefix}ban_items AS bi
621
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
622
		WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
623
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})
624
			AND bi.email_address != {string:blank_string}',
625
		array(
626
			'cannot_access' => 1,
627
			'cannot_login' => 1,
628
			'current_time' => time(),
629
			'blank_string' => '',
630
		)
631
	);
632
	$condition_array = array();
633
	$condition_array_params = array();
634
	$count = 0;
635
	while ($row = $smcFunc['db_fetch_assoc']($request))
636
	{
637
		$condition_array[] = '{string:email_' . $count . '}';
638
		$condition_array_params['email_' . $count++] = $row['email_address'];
639
	}
640
	$smcFunc['db_free_result']($request);
641
642
	if (!empty($condition_array))
643
	{
644
		$request = $smcFunc['db_query']('', '
645
			SELECT id_member
646
			FROM {db_prefix}members
647
			WHERE email_address IN(' . implode(', ', $condition_array) . ')',
648
			$condition_array_params
649
		);
650
		while ($row = $smcFunc['db_fetch_assoc']($request))
651
			$context['recipients']['exclude_members'][] = $row['id_member'];
652
		$smcFunc['db_free_result']($request);
653
	}
654
655
	// Did they select moderators - if so add them as specific members...
656
	if ((!empty($context['recipients']['groups']) && in_array(3, $context['recipients']['groups'])) || (!empty($context['recipients']['exclude_groups']) && in_array(3, $context['recipients']['exclude_groups'])))
657
	{
658
		$request = $smcFunc['db_query']('', '
659
			SELECT DISTINCT mem.id_member AS identifier
660
			FROM {db_prefix}members AS mem
661
				INNER JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member)
662
			WHERE mem.is_activated = {int:is_activated}',
663
			array(
664
				'is_activated' => 1,
665
			)
666
		);
667
		while ($row = $smcFunc['db_fetch_assoc']($request))
668
		{
669
			if (in_array(3, $context['recipients']))
670
				$context['recipients']['exclude_members'][] = $row['identifier'];
671
			else
672
				$context['recipients']['members'][] = $row['identifier'];
673
		}
674
		$smcFunc['db_free_result']($request);
675
	}
676
677
	// For progress bar!
678
	$context['total_emails'] = count($context['recipients']['emails']);
679
	$request = $smcFunc['db_query']('', '
680
		SELECT COUNT(*)
681
		FROM {db_prefix}members',
682
		array(
683
		)
684
	);
685
	list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
686
	$smcFunc['db_free_result']($request);
687
688
	// Clean up the arrays.
689
	$context['recipients']['members'] = array_unique($context['recipients']['members']);
690
	$context['recipients']['exclude_members'] = array_unique($context['recipients']['exclude_members']);
691
}
692
693
/**
694
 * Handles the sending of the forum mailing in batches.
695
 * Called by ?action=admin;area=news;sa=mailingsend
696
 * Requires the send_mail permission.
697
 * Redirects to itself when more batches need to be sent.
698
 * Redirects to ?action=admin;area=news;sa=mailingmembers after everything has been sent.
699
 * @uses template_email_members_send()
700
 *
701
 * @param bool $clean_only If set, it will only clean the variables, put them in context, then return.
702
 */
703
function SendMailing($clean_only = false)
704
{
705
	global $txt, $sourcedir, $context, $smcFunc;
706
	global $scripturl, $modSettings, $user_info;
707
	global $webmaster_email;
708
709
	if (isset($_POST['preview']))
710
	{
711
		$context['preview'] = true;
712
		return ComposeMailing();
0 ignored issues
show
Bug introduced by
Are you sure the usage of ComposeMailing() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
713
	}
714
715
	// How many to send at once? Quantity depends on whether we are queueing or not.
716
	// @todo Might need an interface? (used in Post.php too with different limits)
717
	$num_at_once = 1000;
718
719
	// If by PM's I suggest we half the above number.
720
	if (!empty($_POST['send_pm']))
721
		$num_at_once /= 2;
722
723
	checkSession();
724
725
	// Where are we actually to?
726
	$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
727
	$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
728
	$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
729
	$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
730
	$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
731
	$context['parse_html'] = !empty($_POST['parse_html']) ? '1' : '0';
732
733
	//One can't simply nullify things around
734
	if (empty($_REQUEST['total_members']))
735
	{
736
		$request = $smcFunc['db_query']('', '
737
			SELECT COUNT(*)
738
			FROM {db_prefix}members',
739
			array(
740
			)
741
		);
742
		list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
743
		$smcFunc['db_free_result']($request);
744
	}
745
	else
746
	{
747
		$context['total_members'] = (int) $_REQUEST['total_members'];
748
	}
749
750
	// Create our main context.
751
	$context['recipients'] = array(
752
		'groups' => array(),
753
		'exclude_groups' => array(),
754
		'members' => array(),
755
		'exclude_members' => array(),
756
		'emails' => array(),
757
	);
758
759
	// Have we any excluded members?
760
	if (!empty($_POST['exclude_members']))
761
	{
762
		$members = explode(',', $_POST['exclude_members']);
763
		foreach ($members as $member)
764
			if ($member >= $context['start'])
765
				$context['recipients']['exclude_members'][] = (int) $member;
766
	}
767
768
	// What about members we *must* do?
769
	if (!empty($_POST['members']))
770
	{
771
		$members = explode(',', $_POST['members']);
772
		foreach ($members as $member)
773
			if ($member >= $context['start'])
774
				$context['recipients']['members'][] = (int) $member;
775
	}
776
	// Cleaning groups is simple - although deal with both checkbox and commas.
777
	if (isset($_POST['groups']))
778
	{
779
		if (is_array($_POST['groups']))
780
		{
781
			foreach ($_POST['groups'] as $group => $dummy)
782
				$context['recipients']['groups'][] = (int) $group;
783
		}
784
		else
785
		{
786
			$groups = explode(',', $_POST['groups']);
787
			foreach ($groups as $group)
788
				$context['recipients']['groups'][] = (int) $group;
789
		}
790
	}
791
	// Same for excluded groups
792
	if (isset($_POST['exclude_groups']))
793
	{
794
		if (is_array($_POST['exclude_groups']))
795
		{
796
			foreach ($_POST['exclude_groups'] as $group => $dummy)
797
				$context['recipients']['exclude_groups'][] = (int) $group;
798
		}
799
		// Ignore an empty string - we don't want to exclude "Regular Members" unless it's specifically selected
800
		elseif ($_POST['exclude_groups'] != '')
801
		{
802
			$groups = explode(',', $_POST['exclude_groups']);
803
			foreach ($groups as $group)
804
				$context['recipients']['exclude_groups'][] = (int) $group;
805
		}
806
	}
807
	// Finally - emails!
808
	if (!empty($_POST['emails']))
809
	{
810
		$addressed = array_unique(explode(';', strtr($_POST['emails'], array("\n" => ';', "\r" => ';', ',' => ';'))));
811
		foreach ($addressed as $curmem)
812
		{
813
			$curmem = trim($curmem);
814
			if ($curmem != '' && filter_var($curmem, FILTER_VALIDATE_EMAIL))
815
				$context['recipients']['emails'][$curmem] = $curmem;
816
		}
817
	}
818
819
	// If we're only cleaning drop out here.
820
	if ($clean_only)
821
		return;
822
823
	require_once($sourcedir . '/Subs-Post.php');
824
825
	// We are relying too much on writing to superglobals...
826
	$_POST['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : '';
827
	$_POST['message'] = !empty($_POST['message']) ? $_POST['message'] : '';
828
829
	// Save the message and its subject in $context
830
	$context['subject'] = $smcFunc['htmlspecialchars']($_POST['subject'], ENT_QUOTES);
831
	$context['message'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
832
833
	// Include an unsubscribe link if necessary.
834
	if (!$context['send_pm'])
835
	{
836
		$include_unsubscribe = true;
837
		$_POST['message'] .= "\n\n" . '{$member.unsubscribe}';
838
	}
839
840
	// Prepare the message for sending it as HTML
841
	if (!$context['send_pm'] && !empty($_POST['send_html']))
842
	{
843
		// Prepare the message for HTML.
844
		if (!empty($_POST['parse_html']))
845
			$_POST['message'] = str_replace(array("\n", '  '), array('<br>' . "\n", '&nbsp; '), $_POST['message']);
846
847
		// This is here to prevent spam filters from tagging this as spam.
848
		if (preg_match('~\<html~i', $_POST['message']) == 0)
849
		{
850
			if (preg_match('~\<body~i', $_POST['message']) == 0)
851
				$_POST['message'] = '<html><head><title>' . $_POST['subject'] . '</title></head>' . "\n" . '<body>' . $_POST['message'] . '</body></html>';
852
			else
853
				$_POST['message'] = '<html>' . $_POST['message'] . '</html>';
854
		}
855
	}
856
857
	if (empty($_POST['message']) || empty($_POST['subject']))
858
	{
859
		$context['preview'] = true;
860
		return ComposeMailing();
861
	}
862
863
	// Use the default time format.
864
	$user_info['time_format'] = $modSettings['time_format'];
865
866
	$variables = array(
867
		'{$board_url}',
868
		'{$current_time}',
869
		'{$latest_member.link}',
870
		'{$latest_member.id}',
871
		'{$latest_member.name}'
872
	);
873
874
	// We might need this in a bit
875
	$cleanLatestMember = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
876
877
	// Replace in all the standard things.
878
	$_POST['message'] = str_replace($variables,
879
		array(
880
			!empty($_POST['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
881
			timeformat(time(), false),
882
			!empty($_POST['send_html']) ? '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $cleanLatestMember . '</a>' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . ']' . $cleanLatestMember . '[/url]' : $scripturl . '?action=profile;u=' . $modSettings['latestMember']),
883
			$modSettings['latestMember'],
884
			$cleanLatestMember
885
		), $_POST['message']);
886
	$_POST['subject'] = str_replace($variables,
887
		array(
888
			$scripturl,
889
			timeformat(time(), false),
890
			$modSettings['latestRealName'],
891
			$modSettings['latestMember'],
892
			$modSettings['latestRealName']
893
		), $_POST['subject']);
894
895
	$from_member = array(
896
		'{$member.email}',
897
		'{$member.link}',
898
		'{$member.id}',
899
		'{$member.name}',
900
		'{$member.unsubscribe}',
901
	);
902
903
	// If we still have emails, do them first!
904
	$i = 0;
905
	foreach ($context['recipients']['emails'] as $k => $email)
906
	{
907
		// Done as many as we can?
908
		if ($i >= $num_at_once)
909
			break;
910
911
		// Don't sent it twice!
912
		unset($context['recipients']['emails'][$k]);
913
914
		// Dammit - can't PM emails!
915
		if ($context['send_pm'])
916
			continue;
917
918
		// Non-members can't unsubscribe via the automated system.
919
		$unsubscribe_link = sprintf($txt['unsubscribe_announcements_manual'], empty($modSettings['mail_from']) ? $webmaster_email : $modSettings['mail_from']);
920
921
		$to_member = array(
922
			$email,
923
			!empty($_POST['send_html']) ? '<a href="mailto:' . $email . '">' . $email . '</a>' : $email,
924
			'??',
925
			$email,
926
			$unsubscribe_link,
927
		);
928
929
		sendmail($email, str_replace($from_member, $to_member, $_POST['subject']), str_replace($from_member, $to_member, $_POST['message']), null, 'news', !empty($_POST['send_html']), 5);
930
931
		// Done another...
932
		$i++;
933
	}
934
935
	if ($i < $num_at_once)
936
	{
937
		// Need to build quite a query!
938
		$sendQuery = '(';
939
		$sendParams = array();
940
		if (!empty($context['recipients']['groups']))
941
		{
942
			// Take the long route...
943
			$queryBuild = array();
944
			foreach ($context['recipients']['groups'] as $group)
945
			{
946
				$sendParams['group_' . $group] = $group;
947
				$queryBuild[] = 'mem.id_group = {int:group_' . $group . '}';
948
				if (!empty($group))
949
				{
950
					$queryBuild[] = 'FIND_IN_SET({int:group_' . $group . '}, mem.additional_groups) != 0';
951
					$queryBuild[] = 'mem.id_post_group = {int:group_' . $group . '}';
952
				}
953
			}
954
			if (!empty($queryBuild))
955
				$sendQuery .= implode(' OR ', $queryBuild);
956
		}
957
		if (!empty($context['recipients']['members']))
958
		{
959
			$sendQuery .= ($sendQuery == '(' ? '' : ' OR ') . 'mem.id_member IN ({array_int:members})';
960
			$sendParams['members'] = $context['recipients']['members'];
961
		}
962
963
		$sendQuery .= ')';
964
965
		// If we've not got a query then we must be done!
966
		if ($sendQuery == '()')
967
		{
968
			// Set a confirmation message.
969
			$_SESSION['newsletter_sent'] = 'queue_done';
970
			redirectexit('action=admin;area=news;sa=mailingmembers');
971
		}
972
973
		// Anything to exclude?
974
		if (!empty($context['recipients']['exclude_groups']) && in_array(0, $context['recipients']['exclude_groups']))
975
			$sendQuery .= ' AND mem.id_group != {int:regular_group}';
976
		if (!empty($context['recipients']['exclude_members']))
977
		{
978
			$sendQuery .= ' AND mem.id_member NOT IN ({array_int:exclude_members})';
979
			$sendParams['exclude_members'] = $context['recipients']['exclude_members'];
980
		}
981
982
		// Get the smelly people - note we respect the id_member range as it gives us a quicker query.
983
		$result = $smcFunc['db_query']('', '
984
			SELECT mem.id_member, mem.email_address, mem.real_name, mem.id_group, mem.additional_groups, mem.id_post_group
985
			FROM {db_prefix}members AS mem
986
			WHERE ' . $sendQuery . '
987
				AND mem.is_activated = {int:is_activated}
988
			ORDER BY mem.id_member ASC
989
			LIMIT {int:start}, {int:atonce}',
990
			array_merge($sendParams, array(
991
				'start' => $context['start'],
992
				'atonce' => $num_at_once,
993
				'regular_group' => 0,
994
				'is_activated' => 1,
995
			))
996
		);
997
		$rows = array();
998
		while ($row = $smcFunc['db_fetch_assoc']($result))
999
		{
1000
			$rows[$row['id_member']] = $row;
1001
		}
1002
		$smcFunc['db_free_result']($result);
1003
1004
		// Load their alert preferences
1005
		require_once($sourcedir . '/Subs-Notify.php');
1006
		$prefs = getNotifyPrefs(array_keys($rows), 'announcements', true);
1007
1008
		foreach ($rows as $row)
1009
		{
1010
			// Force them to have it?
1011
			if (empty($context['email_force']) && empty($prefs[$row['id_member']]['announcements']))
1012
				continue;
1013
1014
			// What groups are we looking at here?
1015
			if (empty($row['additional_groups']))
1016
				$groups = array($row['id_group'], $row['id_post_group']);
1017
			else
1018
				$groups = array_merge(
1019
					array($row['id_group'], $row['id_post_group']),
1020
					explode(',', $row['additional_groups'])
1021
				);
1022
1023
			// Excluded groups?
1024
			if (array_intersect($groups, $context['recipients']['exclude_groups']))
1025
				continue;
1026
1027
			// We might need this
1028
			$cleanMemberName = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($row['real_name']) : $row['real_name'];
1029
1030
			if (!empty($include_unsubscribe))
1031
			{
1032
				$token = createUnsubscribeToken($row['id_member'], $row['email_address'], 'announcements');
1033
				$unsubscribe_link = sprintf($txt['unsubscribe_announcements_' . (!empty($_POST['send_html']) ? 'html' : 'plain')], $scripturl . '?action=notifyannouncements;u=' . $row['id_member'] . ';token=' . $token);
1034
			}
1035
			else
1036
				$unsubscribe_link = '';
1037
1038
			// Replace the member-dependant variables
1039
			$message = str_replace($from_member,
1040
				array(
1041
					$row['email_address'],
1042
					!empty($_POST['send_html']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $cleanMemberName . '</a>' : ($context['send_pm'] ? '[url=' . $scripturl . '?action=profile;u=' . $row['id_member'] . ']' . $cleanMemberName . '[/url]' : $scripturl . '?action=profile;u=' . $row['id_member']),
1043
					$row['id_member'],
1044
					$cleanMemberName,
1045
					$unsubscribe_link,
1046
				), $_POST['message']);
1047
1048
			$subject = str_replace($from_member,
1049
				array(
1050
					$row['email_address'],
1051
					$row['real_name'],
1052
					$row['id_member'],
1053
					$row['real_name'],
1054
				), $_POST['subject']);
1055
1056
			// Send the actual email - or a PM!
1057
			if (!$context['send_pm'])
1058
				sendmail($row['email_address'], $subject, $message, null, 'news', !empty($_POST['send_html']), 5);
1059
			else
1060
				sendpm(array('to' => array($row['id_member']), 'bcc' => array()), $subject, $message);
1061
		}
1062
	}
1063
1064
	$context['start'] = $context['start'] + $num_at_once;
1065
	if (empty($context['recipients']['emails']) && ($context['start'] >= $context['total_members']))
1066
	{
1067
		// Log this into the admin log.
1068
		logAction('newsletter', array(), 'admin');
1069
		$_SESSION['newsletter_sent'] = 'queue_done';
1070
		redirectexit('action=admin;area=news;sa=mailingmembers');
1071
	}
1072
1073
	// Working out progress is a black art of sorts.
1074
	$percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['total_members'])));
1075
	$percentMembers = ($context['start'] / $context['total_members']) * ($context['total_members'] / ($context['total_emails'] + $context['total_members']));
1076
	$context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2);
1077
1078
	$context['page_title'] = $txt['admin_newsletters'];
1079
	$context['sub_template'] = 'email_members_send';
1080
}
1081
1082
/**
1083
 * Set general news and newsletter settings and permissions.
1084
 * Called by ?action=admin;area=news;sa=settings.
1085
 * Requires the forum_admin permission.
1086
 * @uses template_show_settings()
1087
 *
1088
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
1089
 * @return void|array Returns nothing or returns the config_vars array if $return_config is true
1090
 */
1091
function ModifyNewsSettings($return_config = false)
1092
{
1093
	global $context, $sourcedir, $txt, $scripturl;
1094
1095
	$config_vars = array(
1096
		array('title', 'settings'),
1097
		// Inline permissions.
1098
		array('permissions', 'edit_news', 'help' => ''),
1099
		array('permissions', 'send_mail'),
1100
		'',
1101
1102
		// Just the remaining settings.
1103
		array('check', 'xmlnews_enable', 'onclick' => 'document.getElementById(\'xmlnews_maxlen\').disabled = !this.checked;'),
1104
		array('int', 'xmlnews_maxlen', 'subtext' => $txt['xmlnews_maxlen_note'], 10),
1105
		array('check', 'xmlnews_attachments', 'subtext' => $txt['xmlnews_attachments_note']),
1106
	);
1107
1108
	call_integration_hook('integrate_modify_news_settings', array(&$config_vars));
1109
1110
	if ($return_config)
1111
		return $config_vars;
1112
1113
	$context['page_title'] = $txt['admin_edit_news'] . ' - ' . $txt['settings'];
1114
	$context['sub_template'] = 'show_settings';
1115
1116
	// Needed for the settings template.
1117
	require_once($sourcedir . '/ManageServer.php');
1118
1119
	// Wrap it all up nice and warm...
1120
	$context['post_url'] = $scripturl . '?action=admin;area=news;save;sa=settings';
1121
1122
	// Add some javascript at the bottom...
1123
	addInlineJavaScript('
1124
	document.getElementById("xmlnews_maxlen").disabled = !document.getElementById("xmlnews_enable").checked;', true);
1125
1126
	// Saving the settings?
1127
	if (isset($_GET['save']))
1128
	{
1129
		checkSession();
1130
1131
		call_integration_hook('integrate_save_news_settings');
1132
1133
		saveDBSettings($config_vars);
1134
		$_SESSION['adm-save'] = true;
1135
		redirectexit('action=admin;area=news;sa=settings');
1136
	}
1137
1138
	// We need this for the in-line permissions
1139
	createToken('admin-mp');
1140
1141
	prepareDBSettingContext($config_vars);
1142
}
1143
1144
?>