Issues (1061)

Sources/ManageNews.php (2 issues)

Labels
Severity
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 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
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
								url: "' . $scripturl . '?action=xmlhttp;sa=previews;xml",
236
								data: {item: "newspreview", news: $("#data_" + preview_id).val()},
237
								context: document.body,
238
								success: function(request){
239
									if ($(request).find("error").text() == \'\')
240
										$(document).find("#box_preview_" + preview_id).html($(request).text());
241
									else
242
										$(document).find("#box_preview_" + preview_id).text(\'' . $txt['news_error_no_news'] . '\');
243
								},
244
							});
245
						});
246
					}
247
248
					function addNewsItem ()
249
					{
250
						last_preview++;
251
						$("#list_news_lists_last").before(' . javaScriptEscape('
252
						<tr class="windowbg') . ' + (last_preview % 2 == 0 ? \'\' : \'2\') + ' . javaScriptEscape('">
253
							<td style="width: 50%;">
254
									<textarea id="data_') . ' + last_preview + ' . javaScriptEscape('" rows="3" cols="65" name="news[]" style="width: 95%;"></textarea>
255
									<br>
256
									<div class="floatleft" id="preview_') . ' + last_preview + ' . javaScriptEscape('"></div>
257
							</td>
258
							<td style="width: 45%;">
259
								<div id="box_preview_') . ' + last_preview + ' . javaScriptEscape('" style="overflow: auto; width: 100%; height: 10ex;"></div>
260
							</td>
261
							<td></td>
262
						</tr>') . ');
263
						make_preview_btn(last_preview);
264
					}',
265
	);
266
267
	// Create the request list.
268
	createList($listOptions);
269
270
	// And go!
271
	loadTemplate('ManageNews');
272
	$context['sub_template'] = 'news_lists';
273
}
274
275
/**
276
 * Prepares an array of the forum news items for display in the template
277
 *
278
 * @return array An array of information about the news items
279
 */
280
function list_getNews()
281
{
282
	global $modSettings;
283
284
	$admin_current_news = array();
285
	// Ready the current news.
286
	foreach (explode("\n", $modSettings['news']) as $id => $line)
287
		$admin_current_news[$id] = array(
288
			'id' => $id,
289
			'unparsed' => un_preparsecode($line),
290
			'parsed' => preg_replace('~<([/]?)form[^>]*?[>]*>~i', '<em class="smalltext">&lt;$1form&gt;</em>', parse_bbc($line)),
291
		);
292
293
	$admin_current_news['last'] = array(
294
		'id' => 'last',
295
		'unparsed' => '<div id="moreNewsItems"></div>
296
		<noscript><textarea rows="3" cols="65" name="news[]" style="width: 85%;"></textarea></noscript>',
297
		'parsed' => '<div id="moreNewsItems_preview"></div>',
298
	);
299
300
	return $admin_current_news;
301
}
302
303
/**
304
 * This function allows a user to select the membergroups to send their
305
 * mailing to.
306
 * Called by ?action=admin;area=news;sa=mailingmembers.
307
 * Requires the send_mail permission.
308
 * Form is submitted to ?action=admin;area=news;mailingcompose.
309
 *
310
 * @uses template_email_members()
311
 */
312
function SelectMailingMembers()
313
{
314
	global $txt, $context, $modSettings, $smcFunc;
315
316
	// Is there any confirm message?
317
	$context['newsletter_sent'] = isset($_SESSION['newsletter_sent']) ? $_SESSION['newsletter_sent'] : '';
318
319
	$context['page_title'] = $txt['admin_newsletters'];
320
321
	$context['sub_template'] = 'email_members';
322
323
	$context['groups'] = array();
324
	$postGroups = array();
325
	$normalGroups = array();
326
327
	// If we have post groups disabled then we need to give a "ungrouped members" option.
328
	if (empty($modSettings['permission_enable_postgroups']))
329
	{
330
		$context['groups'][0] = array(
331
			'id' => 0,
332
			'name' => $txt['membergroups_members'],
333
			'member_count' => 0,
334
		);
335
		$normalGroups[0] = 0;
336
	}
337
338
	// Get all the extra groups as well as Administrator and Global Moderator.
339
	$request = $smcFunc['db_query']('', '
340
		SELECT mg.id_group, mg.group_name, mg.min_posts
341
		FROM {db_prefix}membergroups AS mg' . (empty($modSettings['permission_enable_postgroups']) ? '
342
		WHERE mg.min_posts = {int:min_posts}' : '') . '
343
		GROUP BY mg.id_group, mg.min_posts, mg.group_name
344
		ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name',
345
		array(
346
			'min_posts' => -1,
347
			'newbie_group' => 4,
348
		)
349
	);
350
	while ($row = $smcFunc['db_fetch_assoc']($request))
351
	{
352
		$context['groups'][$row['id_group']] = array(
353
			'id' => $row['id_group'],
354
			'name' => $row['group_name'],
355
			'member_count' => 0,
356
		);
357
358
		if ($row['min_posts'] == -1)
359
			$normalGroups[$row['id_group']] = $row['id_group'];
360
		else
361
			$postGroups[$row['id_group']] = $row['id_group'];
362
	}
363
	$smcFunc['db_free_result']($request);
364
365
	// If we have post groups, let's count the number of members...
366
	if (!empty($postGroups))
367
	{
368
		$query = $smcFunc['db_query']('', '
369
			SELECT mem.id_post_group AS id_group, COUNT(*) AS member_count
370
			FROM {db_prefix}members AS mem
371
			WHERE mem.id_post_group IN ({array_int:post_group_list})
372
			GROUP BY mem.id_post_group',
373
			array(
374
				'post_group_list' => $postGroups,
375
			)
376
		);
377
		while ($row = $smcFunc['db_fetch_assoc']($query))
378
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
379
		$smcFunc['db_free_result']($query);
380
	}
381
382
	if (!empty($normalGroups))
383
	{
384
		// Find people who are members of this group...
385
		$query = $smcFunc['db_query']('', '
386
			SELECT id_group, COUNT(*) AS member_count
387
			FROM {db_prefix}members
388
			WHERE id_group IN ({array_int:normal_group_list})
389
			GROUP BY id_group',
390
			array(
391
				'normal_group_list' => $normalGroups,
392
			)
393
		);
394
		while ($row = $smcFunc['db_fetch_assoc']($query))
395
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
396
		$smcFunc['db_free_result']($query);
397
398
		// Also do those who have it as an additional membergroup - this ones more yucky...
399
		$query = $smcFunc['db_query']('', '
400
			SELECT mg.id_group, COUNT(*) AS member_count
401
			FROM {db_prefix}membergroups AS mg
402
				INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string}
403
					AND mem.id_group != mg.id_group
404
					AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0)
405
			WHERE mg.id_group IN ({array_int:normal_group_list})
406
			GROUP BY mg.id_group',
407
			array(
408
				'normal_group_list' => $normalGroups,
409
				'blank_string' => '',
410
			)
411
		);
412
		while ($row = $smcFunc['db_fetch_assoc']($query))
413
			$context['groups'][$row['id_group']]['member_count'] += $row['member_count'];
414
		$smcFunc['db_free_result']($query);
415
	}
416
417
	// Any moderators?
418
	$request = $smcFunc['db_query']('', '
419
		SELECT COUNT(DISTINCT id_member) AS num_distinct_mods
420
		FROM {db_prefix}moderators
421
		LIMIT 1',
422
		array(
423
		)
424
	);
425
	list ($context['groups'][3]['member_count']) = $smcFunc['db_fetch_row']($request);
426
	$smcFunc['db_free_result']($request);
427
428
	$context['can_send_pm'] = allowedTo('pm_send');
429
430
	loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
431
}
432
433
/**
434
 * Prepare subject and message of an email for the preview box
435
 * Used in ComposeMailing and RetrievePreview (Xml.php)
436
 */
437
function prepareMailingForPreview()
438
{
439
	global $context, $modSettings, $scripturl, $user_info, $txt;
440
	loadLanguage('Errors');
441
442
	$processing = array('preview_subject' => 'subject', 'preview_message' => 'message');
443
444
	// Use the default time format.
445
	$user_info['time_format'] = $modSettings['time_format'];
446
447
	$variables = array(
448
		'{$board_url}',
449
		'{$current_time}',
450
		'{$latest_member.link}',
451
		'{$latest_member.id}',
452
		'{$latest_member.name}'
453
	);
454
455
	$html = $context['send_html'];
456
457
	// We might need this in a bit
458
	$cleanLatestMember = empty($context['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
459
460
	foreach ($processing as $key => $post)
461
	{
462
		$context[$key] = !empty($_REQUEST[$post]) ? $_REQUEST[$post] : '';
463
464
		if (empty($context[$key]) && empty($_REQUEST['xml']))
465
			$context['post_error']['messages'][] = $txt['error_no_' . $post];
466
		elseif (!empty($_REQUEST['xml']))
467
			continue;
468
469
		preparsecode($context[$key]);
470
		if ($html)
471
		{
472
			$enablePostHTML = $modSettings['enablePostHTML'];
473
			$modSettings['enablePostHTML'] = $context['send_html'];
474
			$context[$key] = parse_bbc($context[$key]);
475
			$modSettings['enablePostHTML'] = $enablePostHTML;
476
		}
477
478
		// Replace in all the standard things.
479
		$context[$key] = str_replace($variables,
480
			array(
481
				!empty($context['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
482
				timeformat(forum_time(), false),
483
				!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),
484
				$modSettings['latestMember'],
485
				$cleanLatestMember
486
			), $context[$key]);
487
	}
488
}
489
490
/**
491
 * Shows a form to edit a forum mailing and its recipients.
492
 * Called by ?action=admin;area=news;sa=mailingcompose.
493
 * Requires the send_mail permission.
494
 * Form is submitted to ?action=admin;area=news;sa=mailingsend.
495
 *
496
 * @uses template_email_members_compose()
497
 */
498
function ComposeMailing()
499
{
500
	global $txt, $sourcedir, $context, $smcFunc;
501
502
	// Setup the template!
503
	$context['page_title'] = $txt['admin_newsletters'];
504
	$context['sub_template'] = 'email_members_compose';
505
506
	$context['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : $smcFunc['htmlspecialchars']($context['forum_name'] . ': ' . $txt['subject']);
507
	$context['message'] = !empty($_POST['message']) ? $_POST['message'] : $smcFunc['htmlspecialchars']($txt['message'] . "\n\n" . $txt['regards_team'] . "\n\n" . '{$board_url}');
508
509
	// Needed for the WYSIWYG editor.
510
	require_once($sourcedir . '/Subs-Editor.php');
511
512
	// Now create the editor.
513
	$editorOptions = array(
514
		'id' => 'message',
515
		'value' => $context['message'],
516
		'height' => '150px',
517
		'width' => '100%',
518
		'labels' => array(
519
			'post_button' => $txt['sendtopic_send'],
520
		),
521
		'preview_type' => 2,
522
		'required' => true,
523
	);
524
	create_control_richedit($editorOptions);
525
	// Store the ID for old compatibility.
526
	$context['post_box_name'] = $editorOptions['id'];
527
528
	if (isset($context['preview']))
529
	{
530
		require_once($sourcedir . '/Subs-Post.php');
531
		$context['recipients']['members'] = !empty($_POST['members']) ? explode(',', $_POST['members']) : array();
532
		$context['recipients']['exclude_members'] = !empty($_POST['exclude_members']) ? explode(',', $_POST['exclude_members']) : array();
533
		$context['recipients']['groups'] = !empty($_POST['groups']) ? explode(',', $_POST['groups']) : array();
534
		$context['recipients']['exclude_groups'] = !empty($_POST['exclude_groups']) ? explode(',', $_POST['exclude_groups']) : array();
535
		$context['recipients']['emails'] = !empty($_POST['emails']) ? explode(';', $_POST['emails']) : array();
536
		$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
537
		$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
538
		$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
539
		$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
540
541
		return prepareMailingForPreview();
0 ignored issues
show
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...
542
	}
543
544
	// Start by finding any members!
545
	$toClean = array();
546
	if (!empty($_POST['members']))
547
		$toClean[] = 'members';
548
	if (!empty($_POST['exclude_members']))
549
		$toClean[] = 'exclude_members';
550
	if (!empty($toClean))
551
	{
552
		require_once($sourcedir . '/Subs-Auth.php');
553
		foreach ($toClean as $type)
554
		{
555
			// Remove the quotes.
556
			$_POST[$type] = strtr($_POST[$type], array('\\"' => '"'));
557
558
			preg_match_all('~"([^"]+)"~', $_POST[$type], $matches);
559
			$_POST[$type] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST[$type]))));
560
561
			foreach ($_POST[$type] as $index => $member)
562
				if (strlen(trim($member)) > 0)
563
					$_POST[$type][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($member)));
564
				else
565
					unset($_POST[$type][$index]);
566
567
			// Find the members
568
			$_POST[$type] = implode(',', array_keys(findMembers($_POST[$type])));
569
		}
570
	}
571
572
	if (isset($_POST['member_list']) && is_array($_POST['member_list']))
573
	{
574
		$members = array();
575
		foreach ($_POST['member_list'] as $member_id)
576
			$members[] = (int) $member_id;
577
		$_POST['members'] = implode(',', $members);
578
	}
579
580
	if (isset($_POST['exclude_member_list']) && is_array($_POST['exclude_member_list']))
581
	{
582
		$members = array();
583
		foreach ($_POST['exclude_member_list'] as $member_id)
584
			$members[] = (int) $member_id;
585
		$_POST['exclude_members'] = implode(',', $members);
586
	}
587
588
	// Clean the other vars.
589
	SendMailing(true);
590
591
	// We need a couple strings from the email template file
592
	loadLanguage('EmailTemplates');
593
594
	// 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.
595
	$request = $smcFunc['db_query']('', '
596
		SELECT DISTINCT mem.id_member
597
		FROM {db_prefix}ban_groups AS bg
598
			INNER JOIN {db_prefix}ban_items AS bi ON (bg.id_ban_group = bi.id_ban_group)
599
			INNER JOIN {db_prefix}members AS mem ON (bi.id_member = mem.id_member)
600
		WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
601
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
602
		array(
603
			'cannot_access' => 1,
604
			'cannot_login' => 1,
605
			'current_time' => time(),
606
		)
607
	);
608
	while ($row = $smcFunc['db_fetch_assoc']($request))
609
		$context['recipients']['exclude_members'][] = $row['id_member'];
610
	$smcFunc['db_free_result']($request);
611
612
	$request = $smcFunc['db_query']('', '
613
		SELECT DISTINCT bi.email_address
614
		FROM {db_prefix}ban_items AS bi
615
			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
616
		WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
617
			AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})
618
			AND bi.email_address != {string:blank_string}',
619
		array(
620
			'cannot_access' => 1,
621
			'cannot_login' => 1,
622
			'current_time' => time(),
623
			'blank_string' => '',
624
		)
625
	);
626
	$condition_array = array();
627
	$condition_array_params = array();
628
	$count = 0;
629
	while ($row = $smcFunc['db_fetch_assoc']($request))
630
	{
631
		$condition_array[] = '{string:email_' . $count . '}';
632
		$condition_array_params['email_' . $count++] = $row['email_address'];
633
	}
634
	$smcFunc['db_free_result']($request);
635
636
	if (!empty($condition_array))
637
	{
638
		$request = $smcFunc['db_query']('', '
639
			SELECT id_member
640
			FROM {db_prefix}members
641
			WHERE email_address IN(' . implode(', ', $condition_array) . ')',
642
			$condition_array_params
643
		);
644
		while ($row = $smcFunc['db_fetch_assoc']($request))
645
			$context['recipients']['exclude_members'][] = $row['id_member'];
646
		$smcFunc['db_free_result']($request);
647
	}
648
649
	// Did they select moderators - if so add them as specific members...
650
	if ((!empty($context['recipients']['groups']) && in_array(3, $context['recipients']['groups'])) || (!empty($context['recipients']['exclude_groups']) && in_array(3, $context['recipients']['exclude_groups'])))
651
	{
652
		$request = $smcFunc['db_query']('', '
653
			SELECT DISTINCT mem.id_member AS identifier
654
			FROM {db_prefix}members AS mem
655
				INNER JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member)
656
			WHERE mem.is_activated = {int:is_activated}',
657
			array(
658
				'is_activated' => 1,
659
			)
660
		);
661
		while ($row = $smcFunc['db_fetch_assoc']($request))
662
		{
663
			if (in_array(3, $context['recipients']))
664
				$context['recipients']['exclude_members'][] = $row['identifier'];
665
			else
666
				$context['recipients']['members'][] = $row['identifier'];
667
		}
668
		$smcFunc['db_free_result']($request);
669
	}
670
671
	// For progress bar!
672
	$context['total_emails'] = count($context['recipients']['emails']);
673
	$request = $smcFunc['db_query']('', '
674
		SELECT COUNT(*)
675
		FROM {db_prefix}members',
676
		array(
677
		)
678
	);
679
	list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
680
	$smcFunc['db_free_result']($request);
681
682
	// Clean up the arrays.
683
	$context['recipients']['members'] = array_unique($context['recipients']['members']);
684
	$context['recipients']['exclude_members'] = array_unique($context['recipients']['exclude_members']);
685
}
686
687
/**
688
 * Handles the sending of the forum mailing in batches.
689
 * Called by ?action=admin;area=news;sa=mailingsend
690
 * Requires the send_mail permission.
691
 * Redirects to itself when more batches need to be sent.
692
 * Redirects to ?action=admin;area=news;sa=mailingmembers after everything has been sent.
693
 * @uses template_email_members_send()
694
 *
695
 * @param bool $clean_only If set, it will only clean the variables, put them in context, then return.
696
 */
697
function SendMailing($clean_only = false)
698
{
699
	global $txt, $sourcedir, $context, $smcFunc;
700
	global $scripturl, $modSettings, $user_info;
701
702
	if (isset($_POST['preview']))
703
	{
704
		$context['preview'] = true;
705
		return ComposeMailing();
0 ignored issues
show
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...
706
	}
707
708
	// How many to send at once? Quantity depends on whether we are queueing or not.
709
	// @todo Might need an interface? (used in Post.php too with different limits)
710
	$num_at_once = 1000;
711
712
	// If by PM's I suggest we half the above number.
713
	if (!empty($_POST['send_pm']))
714
		$num_at_once /= 2;
715
716
	checkSession();
717
718
	// Where are we actually to?
719
	$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
720
	$context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
721
	$context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
722
	$context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
723
	$context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
724
	$context['parse_html'] = !empty($_POST['parse_html']) ? '1' : '0';
725
726
	//One can't simply nullify things around
727
	if (empty($_REQUEST['total_members']))
728
	{
729
		$request = $smcFunc['db_query']('', '
730
			SELECT COUNT(*)
731
			FROM {db_prefix}members',
732
			array(
733
			)
734
		);
735
		list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
736
		$smcFunc['db_free_result']($request);
737
	}
738
	else
739
	{
740
		$context['total_members'] = (int) $_REQUEST['total_members'];
741
	}
742
743
	// Create our main context.
744
	$context['recipients'] = array(
745
		'groups' => array(),
746
		'exclude_groups' => array(),
747
		'members' => array(),
748
		'exclude_members' => array(),
749
		'emails' => array(),
750
	);
751
752
	// Have we any excluded members?
753
	if (!empty($_POST['exclude_members']))
754
	{
755
		$members = explode(',', $_POST['exclude_members']);
756
		foreach ($members as $member)
757
			if ($member >= $context['start'])
758
				$context['recipients']['exclude_members'][] = (int) $member;
759
	}
760
761
	// What about members we *must* do?
762
	if (!empty($_POST['members']))
763
	{
764
		$members = explode(',', $_POST['members']);
765
		foreach ($members as $member)
766
			if ($member >= $context['start'])
767
				$context['recipients']['members'][] = (int) $member;
768
	}
769
	// Cleaning groups is simple - although deal with both checkbox and commas.
770
	if (isset($_POST['groups']))
771
	{
772
		if (is_array($_POST['groups']))
773
		{
774
			foreach ($_POST['groups'] as $group => $dummy)
775
				$context['recipients']['groups'][] = (int) $group;
776
		}
777
		else
778
		{
779
			$groups = explode(',', $_POST['groups']);
780
			foreach ($groups as $group)
781
				$context['recipients']['groups'][] = (int) $group;
782
		}
783
	}
784
	// Same for excluded groups
785
	if (isset($_POST['exclude_groups']))
786
	{
787
		if (is_array($_POST['exclude_groups']))
788
		{
789
			foreach ($_POST['exclude_groups'] as $group => $dummy)
790
				$context['recipients']['exclude_groups'][] = (int) $group;
791
		}
792
		// Ignore an empty string - we don't want to exclude "Regular Members" unless it's specifically selected
793
		elseif ($_POST['exclude_groups'] != '')
794
		{
795
			$groups = explode(',', $_POST['exclude_groups']);
796
			foreach ($groups as $group)
797
				$context['recipients']['exclude_groups'][] = (int) $group;
798
		}
799
	}
800
	// Finally - emails!
801
	if (!empty($_POST['emails']))
802
	{
803
		$addressed = array_unique(explode(';', strtr($_POST['emails'], array("\n" => ';', "\r" => ';', ',' => ';'))));
804
		foreach ($addressed as $curmem)
805
		{
806
			$curmem = trim($curmem);
807
			if ($curmem != '' && filter_var($curmem, FILTER_VALIDATE_EMAIL))
808
				$context['recipients']['emails'][$curmem] = $curmem;
809
		}
810
	}
811
812
	// If we're only cleaning drop out here.
813
	if ($clean_only)
814
		return;
815
816
	require_once($sourcedir . '/Subs-Post.php');
817
818
	// We are relying too much on writing to superglobals...
819
	$_POST['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : '';
820
	$_POST['message'] = !empty($_POST['message']) ? $_POST['message'] : '';
821
822
	// Save the message and its subject in $context
823
	$context['subject'] = $smcFunc['htmlspecialchars']($_POST['subject'], ENT_QUOTES);
824
	$context['message'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
825
826
	// Include an unsubscribe link if necessary.
827
	if (!$context['send_pm'])
828
	{
829
		$include_unsubscribe = true;
830
		$_POST['message'] .= "\n\n" . '{$member.unsubscribe}';
831
	}
832
833
	// Prepare the message for sending it as HTML
834
	if (!$context['send_pm'] && !empty($_POST['send_html']))
835
	{
836
		// Prepare the message for HTML.
837
		if (!empty($_POST['parse_html']))
838
			$_POST['message'] = str_replace(array("\n", '  '), array('<br>' . "\n", '&nbsp; '), $_POST['message']);
839
840
		// This is here to prevent spam filters from tagging this as spam.
841
		if (preg_match('~\<html~i', $_POST['message']) == 0)
842
		{
843
			if (preg_match('~\<body~i', $_POST['message']) == 0)
844
				$_POST['message'] = '<html><head><title>' . $_POST['subject'] . '</title></head>' . "\n" . '<body>' . $_POST['message'] . '</body></html>';
845
			else
846
				$_POST['message'] = '<html>' . $_POST['message'] . '</html>';
847
		}
848
	}
849
850
	if (empty($_POST['message']) || empty($_POST['subject']))
851
	{
852
		$context['preview'] = true;
853
		return ComposeMailing();
854
	}
855
856
	// Use the default time format.
857
	$user_info['time_format'] = $modSettings['time_format'];
858
859
	$variables = array(
860
		'{$board_url}',
861
		'{$current_time}',
862
		'{$latest_member.link}',
863
		'{$latest_member.id}',
864
		'{$latest_member.name}'
865
	);
866
867
	// We might need this in a bit
868
	$cleanLatestMember = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
869
870
	// Replace in all the standard things.
871
	$_POST['message'] = str_replace($variables,
872
		array(
873
			!empty($_POST['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
874
			timeformat(forum_time(), false),
875
			!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']),
876
			$modSettings['latestMember'],
877
			$cleanLatestMember
878
		), $_POST['message']);
879
	$_POST['subject'] = str_replace($variables,
880
		array(
881
			$scripturl,
882
			timeformat(forum_time(), false),
883
			$modSettings['latestRealName'],
884
			$modSettings['latestMember'],
885
			$modSettings['latestRealName']
886
		), $_POST['subject']);
887
888
	$from_member = array(
889
		'{$member.email}',
890
		'{$member.link}',
891
		'{$member.id}',
892
		'{$member.name}',
893
		'{$member.unsubscribe}',
894
	);
895
896
	// If we still have emails, do them first!
897
	$i = 0;
898
	foreach ($context['recipients']['emails'] as $k => $email)
899
	{
900
		// Done as many as we can?
901
		if ($i >= $num_at_once)
902
			break;
903
904
		// Don't sent it twice!
905
		unset($context['recipients']['emails'][$k]);
906
907
		// Dammit - can't PM emails!
908
		if ($context['send_pm'])
909
			continue;
910
911
		// Non-members can't subscribe or unsubscribe from anything...
912
		$unsubscribe_link = '';
913
914
		$to_member = array(
915
			$email,
916
			!empty($_POST['send_html']) ? '<a href="mailto:' . $email . '">' . $email . '</a>' : $email,
917
			'??',
918
			$email,
919
			$unsubscribe_link,
920
		);
921
922
		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);
923
924
		// Done another...
925
		$i++;
926
	}
927
928
	if ($i < $num_at_once)
929
	{
930
		// Need to build quite a query!
931
		$sendQuery = '(';
932
		$sendParams = array();
933
		if (!empty($context['recipients']['groups']))
934
		{
935
			// Take the long route...
936
			$queryBuild = array();
937
			foreach ($context['recipients']['groups'] as $group)
938
			{
939
				$sendParams['group_' . $group] = $group;
940
				$queryBuild[] = 'mem.id_group = {int:group_' . $group . '}';
941
				if (!empty($group))
942
				{
943
					$queryBuild[] = 'FIND_IN_SET({int:group_' . $group . '}, mem.additional_groups) != 0';
944
					$queryBuild[] = 'mem.id_post_group = {int:group_' . $group . '}';
945
				}
946
			}
947
			if (!empty($queryBuild))
948
				$sendQuery .= implode(' OR ', $queryBuild);
949
		}
950
		if (!empty($context['recipients']['members']))
951
		{
952
			$sendQuery .= ($sendQuery == '(' ? '' : ' OR ') . 'mem.id_member IN ({array_int:members})';
953
			$sendParams['members'] = $context['recipients']['members'];
954
		}
955
956
		$sendQuery .= ')';
957
958
		// If we've not got a query then we must be done!
959
		if ($sendQuery == '()')
960
		{
961
			// Set a confirmation message.
962
			$_SESSION['newsletter_sent'] = 'queue_done';
963
			redirectexit('action=admin;area=news;sa=mailingmembers');
964
		}
965
966
		// Anything to exclude?
967
		if (!empty($context['recipients']['exclude_groups']) && in_array(0, $context['recipients']['exclude_groups']))
968
			$sendQuery .= ' AND mem.id_group != {int:regular_group}';
969
		if (!empty($context['recipients']['exclude_members']))
970
		{
971
			$sendQuery .= ' AND mem.id_member NOT IN ({array_int:exclude_members})';
972
			$sendParams['exclude_members'] = $context['recipients']['exclude_members'];
973
		}
974
975
		// Get the smelly people - note we respect the id_member range as it gives us a quicker query.
976
		$result = $smcFunc['db_query']('', '
977
			SELECT mem.id_member, mem.email_address, mem.real_name, mem.id_group, mem.additional_groups, mem.id_post_group
978
			FROM {db_prefix}members AS mem
979
			WHERE ' . $sendQuery . '
980
				AND mem.is_activated = {int:is_activated}
981
			ORDER BY mem.id_member ASC
982
			LIMIT {int:start}, {int:atonce}',
983
			array_merge($sendParams, array(
984
				'start' => $context['start'],
985
				'atonce' => $num_at_once,
986
				'regular_group' => 0,
987
				'is_activated' => 1,
988
			))
989
		);
990
		$rows = array();
991
		while ($row = $smcFunc['db_fetch_assoc']($result))
992
		{
993
			$rows[$row['id_member']] = $row;
994
		}
995
		$smcFunc['db_free_result']($result);
996
997
		// Load their alert preferences
998
		require_once($sourcedir . '/Subs-Notify.php');
999
		$prefs = getNotifyPrefs(array_keys($rows), 'announcements', true);
1000
1001
		foreach ($rows as $row)
1002
		{
1003
			// Force them to have it?
1004
			if (empty($context['email_force']) && empty($prefs[$row['id_member']]['announcements']))
1005
				continue;
1006
1007
			// What groups are we looking at here?
1008
			if (empty($row['additional_groups']))
1009
				$groups = array($row['id_group'], $row['id_post_group']);
1010
			else
1011
				$groups = array_merge(
1012
					array($row['id_group'], $row['id_post_group']),
1013
					explode(',', $row['additional_groups'])
1014
				);
1015
1016
			// Excluded groups?
1017
			if (array_intersect($groups, $context['recipients']['exclude_groups']))
1018
				continue;
1019
1020
			// We might need this
1021
			$cleanMemberName = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($row['real_name']) : $row['real_name'];
1022
1023
			if (!empty($include_unsubscribe))
1024
			{
1025
				$token = createUnsubscribeToken($row['id_member'], $row['email_address'], 'announcements');
1026
				$unsubscribe_link = sprintf($txt['unsubscribe_announcements_' . (!empty($_POST['send_html']) ? 'html' : 'plain')], $scripturl . '?action=notifyannouncements;u=' . $row['id_member'] . ';token=' . $token);
1027
			}
1028
			else
1029
				$unsubscribe_link = '';
1030
1031
			// Replace the member-dependant variables
1032
			$message = str_replace($from_member,
1033
				array(
1034
					$row['email_address'],
1035
					!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']),
1036
					$row['id_member'],
1037
					$cleanMemberName,
1038
					$unsubscribe_link,
1039
				), $_POST['message']);
1040
1041
			$subject = str_replace($from_member,
1042
				array(
1043
					$row['email_address'],
1044
					$row['real_name'],
1045
					$row['id_member'],
1046
					$row['real_name'],
1047
				), $_POST['subject']);
1048
1049
			// Send the actual email - or a PM!
1050
			if (!$context['send_pm'])
1051
				sendmail($row['email_address'], $subject, $message, null, 'news', !empty($_POST['send_html']), 5);
1052
			else
1053
				sendpm(array('to' => array($row['id_member']), 'bcc' => array()), $subject, $message);
1054
		}
1055
	}
1056
1057
	$context['start'] = $context['start'] + $num_at_once;
1058
	if (empty($context['recipients']['emails']) && ($context['start'] >= $context['total_members']))
1059
	{
1060
		// Log this into the admin log.
1061
		logAction('newsletter', array(), 'admin');
1062
		$_SESSION['newsletter_sent'] = 'queue_done';
1063
		redirectexit('action=admin;area=news;sa=mailingmembers');
1064
	}
1065
1066
	// Working out progress is a black art of sorts.
1067
	$percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['total_members'])));
1068
	$percentMembers = ($context['start'] / $context['total_members']) * ($context['total_members'] / ($context['total_emails'] + $context['total_members']));
1069
	$context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2);
1070
1071
	$context['page_title'] = $txt['admin_newsletters'];
1072
	$context['sub_template'] = 'email_members_send';
1073
}
1074
1075
/**
1076
 * Set general news and newsletter settings and permissions.
1077
 * Called by ?action=admin;area=news;sa=settings.
1078
 * Requires the forum_admin permission.
1079
 * @uses template_show_settings()
1080
 *
1081
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
1082
 * @return void|array Returns nothing or returns the config_vars array if $return_config is true
1083
 */
1084
function ModifyNewsSettings($return_config = false)
1085
{
1086
	global $context, $sourcedir, $txt, $scripturl;
1087
1088
	$config_vars = array(
1089
		array('title', 'settings'),
1090
		// Inline permissions.
1091
		array('permissions', 'edit_news', 'help' => ''),
1092
		array('permissions', 'send_mail'),
1093
		'',
1094
1095
		// Just the remaining settings.
1096
		array('check', 'xmlnews_enable', 'onclick' => 'document.getElementById(\'xmlnews_maxlen\').disabled = !this.checked;'),
1097
		array('int', 'xmlnews_maxlen', 'subtext' => $txt['xmlnews_maxlen_note'], 10),
1098
		array('check', 'xmlnews_attachments', 'subtext' => $txt['xmlnews_attachments_note']),
1099
	);
1100
1101
	call_integration_hook('integrate_modify_news_settings', array(&$config_vars));
1102
1103
	if ($return_config)
1104
		return $config_vars;
1105
1106
	$context['page_title'] = $txt['admin_edit_news'] . ' - ' . $txt['settings'];
1107
	$context['sub_template'] = 'show_settings';
1108
1109
	// Needed for the settings template.
1110
	require_once($sourcedir . '/ManageServer.php');
1111
1112
	// Wrap it all up nice and warm...
1113
	$context['post_url'] = $scripturl . '?action=admin;area=news;save;sa=settings';
1114
1115
	// Add some javascript at the bottom...
1116
	addInlineJavaScript('
1117
	document.getElementById("xmlnews_maxlen").disabled = !document.getElementById("xmlnews_enable").checked;', true);
1118
1119
	// Saving the settings?
1120
	if (isset($_GET['save']))
1121
	{
1122
		checkSession();
1123
1124
		call_integration_hook('integrate_save_news_settings');
1125
1126
		saveDBSettings($config_vars);
1127
		$_SESSION['adm-save'] = true;
1128
		redirectexit('action=admin;area=news;sa=settings');
1129
	}
1130
1131
	// We need this for the in-line permissions
1132
	createToken('admin-mp');
1133
1134
	prepareDBSettingContext($config_vars);
1135
}
1136
1137
?>