PageRenderTime 215ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/sources/admin/ManageNews.php

https://github.com/Arantor/Elkarte
PHP | 990 lines | 711 code | 125 blank | 154 comment | 82 complexity | 8f19983a629ddcb26ea0c3588784be76 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file manages... the news. :P
  16. *
  17. */
  18. if (!defined('ELKARTE'))
  19. die('No access...');
  20. /**
  21. * The news dispatcher; doesn't do anything, just delegates.
  22. * This is the entrance point for all News and Newsletter screens.
  23. * Called by ?action=admin;area=news.
  24. * It does the permission checks, and calls the appropriate function
  25. * based on the requested sub-action.
  26. */
  27. function ManageNews()
  28. {
  29. global $context, $txt, $scripturl;
  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. loadTemplate('ManageNews');
  33. // Format: 'sub-action' => array('function', 'permission')
  34. $subActions = array(
  35. 'editnews' => array('EditNews', 'edit_news'),
  36. 'mailingmembers' => array('SelectMailingMembers', 'send_mail'),
  37. 'mailingcompose' => array('ComposeMailing', 'send_mail'),
  38. 'mailingsend' => array('SendMailing', 'send_mail'),
  39. 'settings' => array('ModifyNewsSettings', 'admin_forum'),
  40. );
  41. call_integration_hook('integrate_manage_news', array($subActions));
  42. // Default to sub action 'main' or 'settings' depending on permissions.
  43. $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('edit_news') ? 'editnews' : (allowedTo('send_mail') ? 'mailingmembers' : 'settings'));
  44. // Have you got the proper permissions?
  45. isAllowedTo($subActions[$_REQUEST['sa']][1]);
  46. // Create the tabs for the template.
  47. $context[$context['admin_menu_name']]['tab_data'] = array(
  48. 'title' => $txt['news_title'],
  49. 'help' => 'edit_news',
  50. 'description' => $txt['admin_news_desc'],
  51. 'tabs' => array(
  52. 'editnews' => array(
  53. ),
  54. 'mailingmembers' => array(
  55. 'description' => $txt['news_mailing_desc'],
  56. ),
  57. 'settings' => array(
  58. 'description' => $txt['news_settings_desc'],
  59. ),
  60. ),
  61. );
  62. // Force the right area...
  63. if (substr($_REQUEST['sa'], 0, 7) == 'mailing')
  64. $context[$context['admin_menu_name']]['current_subsection'] = 'mailingmembers';
  65. $subActions[$_REQUEST['sa']][0]();
  66. }
  67. /**
  68. * Let the administrator(s) edit the news items for the forum.
  69. * It writes an entry into the moderation log.
  70. * This function uses the edit_news administration area.
  71. * Called by ?action=admin;area=news.
  72. * Requires the edit_news permission.
  73. * Can be accessed with ?action=admin;sa=editnews.
  74. *
  75. */
  76. function EditNews()
  77. {
  78. global $txt, $modSettings, $context, $user_info, $scripturl;
  79. global $smcFunc;
  80. require_once(SUBSDIR . '/Post.subs.php');
  81. // The 'remove selected' button was pressed.
  82. if (!empty($_POST['delete_selection']) && !empty($_POST['remove']))
  83. {
  84. checkSession();
  85. // Store the news temporarily in this array.
  86. $temp_news = explode("\n", $modSettings['news']);
  87. // Remove the items that were selected.
  88. foreach ($temp_news as $i => $news)
  89. if (in_array($i, $_POST['remove']))
  90. unset($temp_news[$i]);
  91. // Update the database.
  92. updateSettings(array('news' => implode("\n", $temp_news)));
  93. logAction('news');
  94. }
  95. // The 'Save' button was pressed.
  96. elseif (!empty($_POST['save_items']))
  97. {
  98. checkSession();
  99. foreach ($_POST['news'] as $i => $news)
  100. {
  101. if (trim($news) == '')
  102. unset($_POST['news'][$i]);
  103. else
  104. {
  105. $_POST['news'][$i] = $smcFunc['htmlspecialchars']($_POST['news'][$i], ENT_QUOTES);
  106. preparsecode($_POST['news'][$i]);
  107. }
  108. }
  109. // Send the new news to the database.
  110. updateSettings(array('news' => implode("\n", $_POST['news'])));
  111. // Log this into the moderation log.
  112. logAction('news');
  113. }
  114. // We're going to want this for making our list.
  115. require_once(SUBSDIR . '/List.subs.php');
  116. $context['page_title'] = $txt['admin_edit_news'];
  117. // Use the standard templates for showing this.
  118. $listOptions = array(
  119. 'id' => 'news_lists',
  120. 'get_items' => array(
  121. 'function' => 'list_getNews',
  122. ),
  123. 'columns' => array(
  124. 'news' => array(
  125. 'header' => array(
  126. 'value' => $txt['admin_edit_news'],
  127. ),
  128. 'data' => array(
  129. 'function' => create_function('$news', '
  130. if (is_numeric($news[\'id\']))
  131. return \'<textarea id="data_\' . $news[\'id\'] . \'" rows="3" cols="50" name="news[]" style="\' . (isBrowser(\'is_ie8\') ? \'width: 635px; max-width: 85%; min-width: 85%\' : \'width 100%;margin 0 5em\') . \';">\' . $news[\'unparsed\'] . \'</textarea>
  132. <br />
  133. <div class="floatleft" id="preview_\' . $news[\'id\'] . \'"></div>\';
  134. else
  135. return $news[\'unparsed\'];
  136. '),
  137. 'style' => 'width: 50%;',
  138. ),
  139. ),
  140. 'preview' => array(
  141. 'header' => array(
  142. 'value' => $txt['preview'],
  143. ),
  144. 'data' => array(
  145. 'function' => create_function('$news', '
  146. return \'<div id="box_preview_\' . $news[\'id\'] . \'" style="overflow: auto; width: 100%; height: 10ex;">\' . $news[\'parsed\'] . \'</div>\';
  147. '),
  148. 'style' => 'width: 45%;',
  149. ),
  150. ),
  151. 'check' => array(
  152. 'header' => array(
  153. 'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
  154. 'class' => 'centercol',
  155. ),
  156. 'data' => array(
  157. 'function' => create_function('$news', '
  158. if (is_numeric($news[\'id\']))
  159. return \'<input type="checkbox" name="remove[]" value="\' . $news[\'id\'] . \'" class="input_check" />\';
  160. else
  161. return \'\';
  162. '),
  163. 'class' => 'centercol',
  164. ),
  165. ),
  166. ),
  167. 'form' => array(
  168. 'href' => $scripturl . '?action=admin;area=news;sa=editnews',
  169. 'hidden_fields' => array(
  170. $context['session_var'] => $context['session_id'],
  171. ),
  172. ),
  173. 'additional_rows' => array(
  174. array(
  175. 'position' => 'bottom_of_list',
  176. 'value' => '
  177. <span id="moreNewsItems_link" class="floatleft" style="display: none;">
  178. <a class="button_link" href="javascript:void(0);" onclick="addNewsItem(); return false;">' . $txt['editnews_clickadd'] . '</a>
  179. </span>
  180. <input type="submit" name="save_items" value="' . $txt['save'] . '" class="button_submit" />
  181. <input type="submit" name="delete_selection" value="' . $txt['editnews_remove_selected'] . '" onclick="return confirm(\'' . $txt['editnews_remove_confirm'] . '\');" class="button_submit" />',
  182. ),
  183. ),
  184. 'javascript' => '
  185. document.getElementById(\'list_news_lists_last\').style.display = "none";
  186. document.getElementById("moreNewsItems_link").style.display = "";
  187. var last_preview = 0;
  188. $(document).ready(function () {
  189. $("div[id ^= \'preview_\']").each(function () {
  190. var preview_id = $(this).attr(\'id\').split(\'_\')[1];
  191. if (last_preview < preview_id)
  192. last_preview = preview_id;
  193. make_preview_btn(preview_id);
  194. });
  195. });
  196. function make_preview_btn (preview_id)
  197. {
  198. $("#preview_" + preview_id).addClass("button_link");
  199. $("#preview_" + preview_id).text(\'' . $txt['preview'] . '\').click(function () {
  200. $.ajax({
  201. type: "POST",
  202. url: "' . $scripturl . '?action=xmlhttp;sa=previews;xml",
  203. data: {item: "newspreview", news: $("#data_" + preview_id).val()},
  204. context: document.body,
  205. success: function(request){
  206. if ($(request).find("error").text() == \'\')
  207. $(document).find("#box_preview_" + preview_id).html($(request).text());
  208. else
  209. $(document).find("#box_preview_" + preview_id).text(\'' . $txt['news_error_no_news'] . '\');
  210. },
  211. });
  212. });
  213. }
  214. function addNewsItem ()
  215. {
  216. last_preview++;
  217. $("#list_news_lists_last").before(' . javaScriptEscape('
  218. <tr class="windowbg') . ' + (last_preview % 2 == 0 ? \'\' : \'2\') + ' . javaScriptEscape('">
  219. <td style="width: 50%;">
  220. <textarea id="data_') . ' + last_preview + ' . javaScriptEscape('" rows="3" cols="65" name="news[]" style="' . (isBrowser('is_ie8') ? 'width: 635px; max-width: 85%; min-width: 85%' : 'width: 95%') . ';"></textarea>
  221. <br />
  222. <div class="floatleft" id="preview_') . ' + last_preview + ' . javaScriptEscape('"></div>
  223. </td>
  224. <td style="width: 45%;">
  225. <div id="box_preview_') . ' + last_preview + ' . javaScriptEscape('" style="overflow: auto; width: 100%; height: 10ex;"></div>
  226. </td>
  227. <td></td>
  228. </tr>') . ');
  229. make_preview_btn(last_preview);
  230. }',
  231. );
  232. // Create the request list.
  233. createList($listOptions);
  234. $context['sub_template'] = 'show_list';
  235. $context['default_list'] = 'news_lists';
  236. }
  237. /**
  238. * Prepares an array of the forum news items for display in the template
  239. *
  240. * @return array
  241. */
  242. function list_getNews()
  243. {
  244. global $modSettings;
  245. $admin_current_news = array();
  246. // Ready the current news.
  247. foreach (explode("\n", $modSettings['news']) as $id => $line)
  248. $admin_current_news[$id] = array(
  249. 'id' => $id,
  250. 'unparsed' => un_preparsecode($line),
  251. 'parsed' => preg_replace('~<([/]?)form[^>]*?[>]*>~i', '<em class="smalltext">&lt;$1form&gt;</em>', parse_bbc($line)),
  252. );
  253. $admin_current_news['last'] = array(
  254. 'id' => 'last',
  255. 'unparsed' => '<div id="moreNewsItems"></div>
  256. <noscript><textarea rows="3" cols="65" name="news[]" style="' . (isBrowser('is_ie8') ? 'width: 635px; max-width: 85%; min-width: 85%' : 'width: 85%') . ';"></textarea></noscript>',
  257. 'parsed' => '<div id="moreNewsItems_preview"></div>',
  258. );
  259. return $admin_current_news;
  260. }
  261. /**
  262. * This function allows a user to select the membergroups to send their mailing to.
  263. * Called by ?action=admin;area=news;sa=mailingmembers.
  264. * Requires the send_mail permission.
  265. * Form is submitted to ?action=admin;area=news;mailingcompose.
  266. *
  267. * @uses the ManageNews template and email_members sub template.
  268. */
  269. function SelectMailingMembers()
  270. {
  271. global $txt, $context, $modSettings, $smcFunc;
  272. require_once(SUBSDIR . '/Membergroups.subs.php');
  273. $context['page_title'] = $txt['admin_newsletters'];
  274. $context['sub_template'] = 'email_members';
  275. $context['groups'] = array();
  276. $postGroups = array();
  277. $normalGroups = array();
  278. // Are we going to show the advanced options?
  279. $context['show_advanced_options'] = empty($context['admin_preferences']['apn']);
  280. // If we have post groups disabled then we need to give a "ungrouped members" option.
  281. if (empty($modSettings['permission_enable_postgroups']))
  282. {
  283. $context['groups'][0] = array(
  284. 'id' => 0,
  285. 'name' => $txt['membergroups_members'],
  286. 'member_count' => 0,
  287. );
  288. $normalGroups[0] = 0;
  289. }
  290. // Get all the extra groups as well as Administrator and Global Moderator.
  291. $request = $smcFunc['db_query']('', '
  292. SELECT mg.id_group, mg.group_name, mg.min_posts
  293. FROM {db_prefix}membergroups AS mg' . (empty($modSettings['permission_enable_postgroups']) ? '
  294. WHERE mg.min_posts = {int:min_posts}' : '') . '
  295. GROUP BY mg.id_group, mg.min_posts, mg.group_name
  296. ORDER BY mg.min_posts, CASE WHEN mg.id_group < {int:newbie_group} THEN mg.id_group ELSE 4 END, mg.group_name',
  297. array(
  298. 'min_posts' => -1,
  299. 'newbie_group' => 4,
  300. )
  301. );
  302. while ($row = $smcFunc['db_fetch_assoc']($request))
  303. {
  304. $context['groups'][$row['id_group']] = array(
  305. 'id' => $row['id_group'],
  306. 'name' => $row['group_name'],
  307. 'member_count' => 0,
  308. );
  309. if ($row['min_posts'] == -1)
  310. $normalGroups[$row['id_group']] = $row['id_group'];
  311. else
  312. $postGroups[$row['id_group']] = $row['id_group'];
  313. }
  314. $smcFunc['db_free_result']($request);
  315. $groups = membersInGroups($postGroups, $normalGroups, true, true);
  316. // @todo not sure why += wouldn't = be enough?
  317. foreach ($groups as $id_group => $member_count)
  318. $context['groups'][$id_group]['member_count'] += $member_count;
  319. $context['can_send_pm'] = allowedTo('pm_send');
  320. }
  321. /**
  322. * Shows a form to edit a forum mailing and its recipients.
  323. * Called by ?action=admin;area=news;sa=mailingcompose.
  324. * Requires the send_mail permission.
  325. * Form is submitted to ?action=admin;area=news;sa=mailingsend.
  326. *
  327. * @uses ManageNews template, email_members_compose sub-template.
  328. */
  329. function ComposeMailing()
  330. {
  331. global $txt, $context, $smcFunc, $scripturl, $modSettings;
  332. // Setup the template!
  333. $context['page_title'] = $txt['admin_newsletters'];
  334. $context['sub_template'] = 'email_members_compose';
  335. $context['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : htmlspecialchars($context['forum_name'] . ': ' . $txt['subject']);
  336. $context['message'] = !empty($_POST['message']) ? $_POST['message'] : htmlspecialchars($txt['message'] . "\n\n" . $txt['regards_team'] . "\n\n" . '{$board_url}');
  337. // Needed for the WYSIWYG editor.
  338. require_once(SUBSDIR . '/Editor.subs.php');
  339. // Now create the editor.
  340. $editorOptions = array(
  341. 'id' => 'message',
  342. 'value' => $context['message'],
  343. 'height' => '175px',
  344. 'width' => '100%',
  345. 'labels' => array(
  346. 'post_button' => $txt['sendtopic_send'],
  347. ),
  348. 'preview_type' => 2,
  349. );
  350. create_control_richedit($editorOptions);
  351. // Store the ID for old compatibility.
  352. $context['post_box_name'] = $editorOptions['id'];
  353. if (isset($context['preview']))
  354. {
  355. require_once(SUBSDIR . '/Mail.subs.php');
  356. $context['recipients']['members'] = !empty($_POST['members']) ? explode(',', $_POST['members']) : array();
  357. $context['recipients']['exclude_members'] = !empty($_POST['exclude_members']) ? explode(',', $_POST['exclude_members']) : array();
  358. $context['recipients']['groups'] = !empty($_POST['groups']) ? explode(',', $_POST['groups']) : array();
  359. $context['recipients']['exclude_groups'] = !empty($_POST['exclude_groups']) ? explode(',', $_POST['exclude_groups']) : array();
  360. $context['recipients']['emails'] = !empty($_POST['emails']) ? explode(';', $_POST['emails']) : array();
  361. $context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
  362. $context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
  363. $context['max_id_member'] = !empty($_POST['max_id_member']) ? (int) $_POST['max_id_member'] : 0;
  364. $context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
  365. $context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
  366. return prepareMailingForPreview();
  367. }
  368. // Start by finding any members!
  369. $toClean = array();
  370. if (!empty($_POST['members']))
  371. $toClean[] = 'members';
  372. if (!empty($_POST['exclude_members']))
  373. $toClean[] = 'exclude_members';
  374. if (!empty($toClean))
  375. {
  376. require_once(SUBSDIR . '/Auth.subs.php');
  377. foreach ($toClean as $type)
  378. {
  379. // Remove the quotes.
  380. $_POST[$type] = strtr($_POST[$type], array('\\"' => '"'));
  381. preg_match_all('~"([^"]+)"~', $_POST[$type], $matches);
  382. $_POST[$type] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST[$type]))));
  383. foreach ($_POST[$type] as $index => $member)
  384. if (strlen(trim($member)) > 0)
  385. $_POST[$type][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($member)));
  386. else
  387. unset($_POST[$type][$index]);
  388. // Find the members
  389. $_POST[$type] = implode(',', array_keys(findMembers($_POST[$type])));
  390. }
  391. }
  392. if (isset($_POST['member_list']) && is_array($_POST['member_list']))
  393. {
  394. $members = array();
  395. foreach ($_POST['member_list'] as $member_id)
  396. $members[] = (int) $member_id;
  397. $_POST['members'] = implode(',', $members);
  398. }
  399. if (isset($_POST['exclude_member_list']) && is_array($_POST['exclude_member_list']))
  400. {
  401. $members = array();
  402. foreach ($_POST['exclude_member_list'] as $member_id)
  403. $members[] = (int) $member_id;
  404. $_POST['exclude_members'] = implode(',', $members);
  405. }
  406. // Clean the other vars.
  407. SendMailing(true);
  408. // We need a couple strings from the email template file
  409. loadLanguage('EmailTemplates');
  410. // 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.
  411. $request = $smcFunc['db_query']('', '
  412. SELECT DISTINCT mem.id_member
  413. FROM {db_prefix}ban_groups AS bg
  414. INNER JOIN {db_prefix}ban_items AS bi ON (bg.id_ban_group = bi.id_ban_group)
  415. INNER JOIN {db_prefix}members AS mem ON (bi.id_member = mem.id_member)
  416. WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
  417. AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})',
  418. array(
  419. 'cannot_access' => 1,
  420. 'cannot_login' => 1,
  421. 'current_time' => time(),
  422. )
  423. );
  424. while ($row = $smcFunc['db_fetch_assoc']($request))
  425. $context['recipients']['exclude_members'][] = $row['id_member'];
  426. $smcFunc['db_free_result']($request);
  427. $request = $smcFunc['db_query']('', '
  428. SELECT DISTINCT bi.email_address
  429. FROM {db_prefix}ban_items AS bi
  430. INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
  431. WHERE (bg.cannot_access = {int:cannot_access} OR bg.cannot_login = {int:cannot_login})
  432. AND (COALESCE(bg.expire_time, 1=1) OR bg.expire_time > {int:current_time})
  433. AND bi.email_address != {string:blank_string}',
  434. array(
  435. 'cannot_access' => 1,
  436. 'cannot_login' => 1,
  437. 'current_time' => time(),
  438. 'blank_string' => '',
  439. )
  440. );
  441. $condition_array = array();
  442. $condition_array_params = array();
  443. $count = 0;
  444. while ($row = $smcFunc['db_fetch_assoc']($request))
  445. {
  446. $condition_array[] = '{string:email_' . $count . '}';
  447. $condition_array_params['email_' . $count++] = $row['email_address'];
  448. }
  449. $smcFunc['db_free_result']($request);
  450. if (!empty($condition_array))
  451. {
  452. $request = $smcFunc['db_query']('', '
  453. SELECT id_member
  454. FROM {db_prefix}members
  455. WHERE email_address IN(' . implode(', ', $condition_array) .')',
  456. $condition_array_params
  457. );
  458. while ($row = $smcFunc['db_fetch_assoc']($request))
  459. $context['recipients']['exclude_members'][] = $row['id_member'];
  460. $smcFunc['db_free_result']($request);
  461. }
  462. // Did they select moderators - if so add them as specific members...
  463. if ((!empty($context['recipients']['groups']) && in_array(3, $context['recipients']['groups'])) || (!empty($context['recipients']['exclude_groups']) && in_array(3, $context['recipients']['exclude_groups'])))
  464. {
  465. $request = $smcFunc['db_query']('', '
  466. SELECT DISTINCT mem.id_member AS identifier
  467. FROM {db_prefix}members AS mem
  468. INNER JOIN {db_prefix}moderators AS mods ON (mods.id_member = mem.id_member)
  469. WHERE mem.is_activated = {int:is_activated}',
  470. array(
  471. 'is_activated' => 1,
  472. )
  473. );
  474. while ($row = $smcFunc['db_fetch_assoc']($request))
  475. {
  476. if (in_array(3, $context['recipients']))
  477. $context['recipients']['exclude_members'][] = $row['identifier'];
  478. else
  479. $context['recipients']['members'][] = $row['identifier'];
  480. }
  481. $smcFunc['db_free_result']($request);
  482. }
  483. // For progress bar!
  484. $context['total_emails'] = count($context['recipients']['emails']);
  485. $request = $smcFunc['db_query']('', '
  486. SELECT MAX(id_member)
  487. FROM {db_prefix}members',
  488. array(
  489. )
  490. );
  491. list ($context['max_id_member']) = $smcFunc['db_fetch_row']($request);
  492. $smcFunc['db_free_result']($request);
  493. // Clean up the arrays.
  494. $context['recipients']['members'] = array_unique($context['recipients']['members']);
  495. $context['recipients']['exclude_members'] = array_unique($context['recipients']['exclude_members']);
  496. }
  497. /**
  498. * Handles the sending of the forum mailing in batches.
  499. * Called by ?action=admin;area=news;sa=mailingsend
  500. * Requires the send_mail permission.
  501. * Redirects to itself when more batches need to be sent.
  502. * Redirects to ?action=admin after everything has been sent.
  503. *
  504. * @param bool $clean_only = false; if set, it will only clean the variables, put them in context, then return.
  505. * @uses the ManageNews template and email_members_send sub template.
  506. */
  507. function SendMailing($clean_only = false)
  508. {
  509. global $txt, $context, $smcFunc;
  510. global $scripturl, $modSettings, $user_info;
  511. if (isset($_POST['preview']))
  512. {
  513. $context['preview'] = true;
  514. return ComposeMailing();
  515. }
  516. // How many to send at once? Quantity depends on whether we are queueing or not.
  517. // @todo Might need an interface? (used in Post.controller.php too with different limits)
  518. $num_at_once = empty($modSettings['mail_queue']) ? 60 : 1000;
  519. // If by PM's I suggest we half the above number.
  520. if (!empty($_POST['send_pm']))
  521. $num_at_once /= 2;
  522. checkSession();
  523. // Where are we actually to?
  524. $context['start'] = isset($_REQUEST['start']) ? $_REQUEST['start'] : 0;
  525. $context['email_force'] = !empty($_POST['email_force']) ? 1 : 0;
  526. $context['send_pm'] = !empty($_POST['send_pm']) ? 1 : 0;
  527. $context['total_emails'] = !empty($_POST['total_emails']) ? (int) $_POST['total_emails'] : 0;
  528. $context['max_id_member'] = !empty($_POST['max_id_member']) ? (int) $_POST['max_id_member'] : 0;
  529. $context['send_html'] = !empty($_POST['send_html']) ? '1' : '0';
  530. $context['parse_html'] = !empty($_POST['parse_html']) ? '1' : '0';
  531. // Create our main context.
  532. $context['recipients'] = array(
  533. 'groups' => array(),
  534. 'exclude_groups' => array(),
  535. 'members' => array(),
  536. 'exclude_members' => array(),
  537. 'emails' => array(),
  538. );
  539. // Have we any excluded members?
  540. if (!empty($_POST['exclude_members']))
  541. {
  542. $members = explode(',', $_POST['exclude_members']);
  543. foreach ($members as $member)
  544. {
  545. if ($member >= $context['start'])
  546. $context['recipients']['exclude_members'][] = (int) $member;
  547. }
  548. }
  549. // What about members we *must* do?
  550. if (!empty($_POST['members']))
  551. {
  552. $members = explode(',', $_POST['members']);
  553. foreach ($members as $member)
  554. {
  555. if ($member >= $context['start'])
  556. $context['recipients']['members'][] = (int) $member;
  557. }
  558. }
  559. // Cleaning groups is simple - although deal with both checkbox and commas.
  560. if (!empty($_POST['groups']))
  561. {
  562. if (is_array($_POST['groups']))
  563. {
  564. foreach ($_POST['groups'] as $group => $dummy)
  565. $context['recipients']['groups'][] = (int) $group;
  566. }
  567. else
  568. {
  569. $groups = explode(',', $_POST['groups']);
  570. foreach ($groups as $group)
  571. $context['recipients']['groups'][] = (int) $group;
  572. }
  573. }
  574. // Same for excluded groups
  575. if (!empty($_POST['exclude_groups']))
  576. {
  577. if (is_array($_POST['exclude_groups']))
  578. {
  579. foreach ($_POST['exclude_groups'] as $group => $dummy)
  580. $context['recipients']['exclude_groups'][] = (int) $group;
  581. }
  582. else
  583. {
  584. $groups = explode(',', $_POST['exclude_groups']);
  585. foreach ($groups as $group)
  586. $context['recipients']['exclude_groups'][] = (int) $group;
  587. }
  588. }
  589. // Finally - emails!
  590. if (!empty($_POST['emails']))
  591. {
  592. $addressed = array_unique(explode(';', strtr($_POST['emails'], array("\n" => ';', "\r" => ';', ',' => ';'))));
  593. foreach ($addressed as $curmem)
  594. {
  595. $curmem = trim($curmem);
  596. if ($curmem != '')
  597. $context['recipients']['emails'][$curmem] = $curmem;
  598. }
  599. }
  600. // If we're only cleaning drop out here.
  601. if ($clean_only)
  602. return;
  603. // Some functions we will need
  604. require_once(SUBSDIR . '/Mail.subs.php');
  605. if ($context['send_pm'])
  606. require_once(SUBSDIR . '/PersonalMessage.subs.php');
  607. // We are relying too much on writing to superglobals...
  608. $_POST['subject'] = !empty($_POST['subject']) ? $_POST['subject'] : '';
  609. $_POST['message'] = !empty($_POST['message']) ? $_POST['message'] : '';
  610. // Save the message and its subject in $context
  611. $context['subject'] = htmlspecialchars($_POST['subject']);
  612. $context['message'] = htmlspecialchars($_POST['message']);
  613. // Prepare the message for sending it as HTML
  614. if (!$context['send_pm'] && !empty($_POST['send_html']))
  615. {
  616. // Prepare the message for HTML.
  617. if (!empty($_POST['parse_html']))
  618. $_POST['message'] = str_replace(array("\n", ' '), array('<br />' . "\n", '&nbsp; '), $_POST['message']);
  619. // This is here to prevent spam filters from tagging this as spam.
  620. if (preg_match('~\<html~i', $_POST['message']) == 0)
  621. {
  622. if (preg_match('~\<body~i', $_POST['message']) == 0)
  623. $_POST['message'] = '<html><head><title>' . $_POST['subject'] . '</title></head>' . "\n" . '<body>' . $_POST['message'] . '</body></html>';
  624. else
  625. $_POST['message'] = '<html>' . $_POST['message'] . '</html>';
  626. }
  627. }
  628. if (empty($_POST['message']) || empty($_POST['subject']))
  629. {
  630. $context['preview'] = true;
  631. return ComposeMailing();
  632. }
  633. // Use the default time format.
  634. $user_info['time_format'] = $modSettings['time_format'];
  635. $variables = array(
  636. '{$board_url}',
  637. '{$current_time}',
  638. '{$latest_member.link}',
  639. '{$latest_member.id}',
  640. '{$latest_member.name}'
  641. );
  642. // We might need this in a bit
  643. $cleanLatestMember = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($modSettings['latestRealName']) : $modSettings['latestRealName'];
  644. // Replace in all the standard things.
  645. $_POST['message'] = str_replace($variables,
  646. array(
  647. !empty($_POST['send_html']) ? '<a href="' . $scripturl . '">' . $scripturl . '</a>' : $scripturl,
  648. timeformat(forum_time(), false),
  649. !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]' : $cleanLatestMember),
  650. $modSettings['latestMember'],
  651. $cleanLatestMember
  652. ), $_POST['message']);
  653. $_POST['subject'] = str_replace($variables,
  654. array(
  655. $scripturl,
  656. timeformat(forum_time(), false),
  657. $modSettings['latestRealName'],
  658. $modSettings['latestMember'],
  659. $modSettings['latestRealName']
  660. ), $_POST['subject']);
  661. $from_member = array(
  662. '{$member.email}',
  663. '{$member.link}',
  664. '{$member.id}',
  665. '{$member.name}'
  666. );
  667. // If we still have emails, do them first!
  668. $i = 0;
  669. foreach ($context['recipients']['emails'] as $k => $email)
  670. {
  671. // Done as many as we can?
  672. if ($i >= $num_at_once)
  673. break;
  674. // Don't sent it twice!
  675. unset($context['recipients']['emails'][$k]);
  676. // Dammit - can't PM emails!
  677. if ($context['send_pm'])
  678. continue;
  679. $to_member = array(
  680. $email,
  681. !empty($_POST['send_html']) ? '<a href="mailto:' . $email . '">' . $email . '</a>' : $email,
  682. '??',
  683. $email
  684. );
  685. sendmail($email, str_replace($from_member, $to_member, $_POST['subject']), str_replace($from_member, $to_member, $_POST['message']), null, null, !empty($_POST['send_html']), 5);
  686. // Done another...
  687. $i++;
  688. }
  689. // Got some more to send this batch?
  690. $last_id_member = 0;
  691. if ($i < $num_at_once)
  692. {
  693. // Need to build quite a query!
  694. $sendQuery = '(';
  695. $sendParams = array();
  696. if (!empty($context['recipients']['groups']))
  697. {
  698. // Take the long route...
  699. $queryBuild = array();
  700. foreach ($context['recipients']['groups'] as $group)
  701. {
  702. $sendParams['group_' . $group] = $group;
  703. $queryBuild[] = 'mem.id_group = {int:group_' . $group . '}';
  704. if (!empty($group))
  705. {
  706. $queryBuild[] = 'FIND_IN_SET({int:group_' . $group . '}, mem.additional_groups) != 0';
  707. $queryBuild[] = 'mem.id_post_group = {int:group_' . $group . '}';
  708. }
  709. }
  710. if (!empty($queryBuild))
  711. $sendQuery .= implode(' OR ', $queryBuild);
  712. }
  713. if (!empty($context['recipients']['members']))
  714. {
  715. $sendQuery .= ($sendQuery == '(' ? '' : ' OR ') . 'mem.id_member IN ({array_int:members})';
  716. $sendParams['members'] = $context['recipients']['members'];
  717. }
  718. $sendQuery .= ')';
  719. // If we've not got a query then we must be done!
  720. if ($sendQuery == '()')
  721. redirectexit('action=admin');
  722. // Anything to exclude?
  723. if (!empty($context['recipients']['exclude_groups']) && in_array(0, $context['recipients']['exclude_groups']))
  724. $sendQuery .= ' AND mem.id_group != {int:regular_group}';
  725. if (!empty($context['recipients']['exclude_members']))
  726. {
  727. $sendQuery .= ' AND mem.id_member NOT IN ({array_int:exclude_members})';
  728. $sendParams['exclude_members'] = $context['recipients']['exclude_members'];
  729. }
  730. // Force them to have it?
  731. if (empty($context['email_force']))
  732. $sendQuery .= ' AND mem.notify_announcements = {int:notify_announcements}';
  733. // Get the smelly people - note we respect the id_member range as it gives us a quicker query.
  734. $result = $smcFunc['db_query']('', '
  735. SELECT mem.id_member, mem.email_address, mem.real_name, mem.id_group, mem.additional_groups, mem.id_post_group
  736. FROM {db_prefix}members AS mem
  737. WHERE mem.id_member > {int:min_id_member}
  738. AND mem.id_member < {int:max_id_member}
  739. AND ' . $sendQuery . '
  740. AND mem.is_activated = {int:is_activated}
  741. ORDER BY mem.id_member ASC
  742. LIMIT {int:atonce}',
  743. array_merge($sendParams, array(
  744. 'min_id_member' => $context['start'],
  745. 'max_id_member' => $context['start'] + $num_at_once - $i,
  746. 'atonce' => $num_at_once - $i,
  747. 'regular_group' => 0,
  748. 'notify_announcements' => 1,
  749. 'is_activated' => 1,
  750. ))
  751. );
  752. while ($row = $smcFunc['db_fetch_assoc']($result))
  753. {
  754. $last_id_member = $row['id_member'];
  755. // What groups are we looking at here?
  756. if (empty($row['additional_groups']))
  757. $groups = array($row['id_group'], $row['id_post_group']);
  758. else
  759. $groups = array_merge(
  760. array($row['id_group'], $row['id_post_group']),
  761. explode(',', $row['additional_groups'])
  762. );
  763. // Excluded groups?
  764. if (array_intersect($groups, $context['recipients']['exclude_groups']))
  765. continue;
  766. // We might need this
  767. $cleanMemberName = empty($_POST['send_html']) || $context['send_pm'] ? un_htmlspecialchars($row['real_name']) : $row['real_name'];
  768. // Replace the member-dependant variables
  769. $message = str_replace($from_member,
  770. array(
  771. $row['email_address'],
  772. !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]' : $cleanMemberName),
  773. $row['id_member'],
  774. $cleanMemberName,
  775. ), $_POST['message']);
  776. $subject = str_replace($from_member,
  777. array(
  778. $row['email_address'],
  779. $row['real_name'],
  780. $row['id_member'],
  781. $row['real_name'],
  782. ), $_POST['subject']);
  783. // Send the actual email - or a PM!
  784. if (!$context['send_pm'])
  785. sendmail($row['email_address'], $subject, $message, null, null, !empty($_POST['send_html']), 5);
  786. else
  787. sendpm(array('to' => array($row['id_member']), 'bcc' => array()), $subject, $message);
  788. }
  789. $smcFunc['db_free_result']($result);
  790. }
  791. // If used our batch assume we still have a member.
  792. if ($i >= $num_at_once)
  793. $last_id_member = $context['start'];
  794. // Or we didn't have one in range?
  795. elseif (empty($last_id_member) && $context['start'] + $num_at_once < $context['max_id_member'])
  796. $last_id_member = $context['start'] + $num_at_once;
  797. // If we have no id_member then we're done.
  798. elseif (empty($last_id_member) && empty($context['recipients']['emails']))
  799. {
  800. // Log this into the admin log.
  801. logAction('newsletter', array(), 'admin');
  802. redirectexit('action=admin');
  803. }
  804. $context['start'] = $last_id_member;
  805. // Working out progress is a black art of sorts.
  806. $percentEmails = $context['total_emails'] == 0 ? 0 : ((count($context['recipients']['emails']) / $context['total_emails']) * ($context['total_emails'] / ($context['total_emails'] + $context['max_id_member'])));
  807. $percentMembers = ($context['start'] / $context['max_id_member']) * ($context['max_id_member'] / ($context['total_emails'] + $context['max_id_member']));
  808. $context['percentage_done'] = round(($percentEmails + $percentMembers) * 100, 2);
  809. $context['page_title'] = $txt['admin_newsletters'];
  810. $context['sub_template'] = 'email_members_send';
  811. }
  812. /**
  813. * Set general news and newsletter settings and permissions.
  814. * Called by ?action=admin;area=news;sa=settings.
  815. * Requires the forum_admin permission.
  816. *
  817. * @uses ManageNews template, news_settings sub-template.
  818. * @param bool $return_config = false
  819. */
  820. function ModifyNewsSettings($return_config = false)
  821. {
  822. global $context, $modSettings, $txt, $scripturl;
  823. $config_vars = array(
  824. array('title', 'settings'),
  825. // Inline permissions.
  826. array('permissions', 'edit_news', 'help' => ''),
  827. array('permissions', 'send_mail'),
  828. '',
  829. // Just the remaining settings.
  830. array('check', 'xmlnews_enable', 'onclick' => 'document.getElementById(\'xmlnews_maxlen\').disabled = !this.checked;document.getElementById(\'xmlnews_limit\').disabled = !this.checked;'),
  831. array('text', 'xmlnews_maxlen', 'subtext' => $txt['xmlnews_maxlen_note'], 10),
  832. array('text', 'xmlnews_limit', 'subtext' => $txt['xmlnews_limit_note'], 10),
  833. );
  834. call_integration_hook('integrate_modify_news_settings', array($config_vars));
  835. if ($return_config)
  836. return $config_vars;
  837. $context['page_title'] = $txt['admin_edit_news'] . ' - ' . $txt['settings'];
  838. $context['sub_template'] = 'show_settings';
  839. // Needed for the settings template.
  840. require_once(ADMINDIR . '/ManageServer.php');
  841. // Wrap it all up nice and warm...
  842. $context['post_url'] = $scripturl . '?action=admin;area=news;save;sa=settings';
  843. $context['permissions_excluded'] = array(-1);
  844. // Add some javascript at the bottom...
  845. addInlineJavascript('
  846. document.getElementById("xmlnews_maxlen").disabled = !document.getElementById("xmlnews_enable").checked;
  847. document.getElementById("xmlnews_limit").disabled = !document.getElementById("xmlnews_enable").checked;', true);
  848. // Saving the settings?
  849. if (isset($_GET['save']))
  850. {
  851. checkSession();
  852. call_integration_hook('integrate_save_news_settings');
  853. saveDBSettings($config_vars);
  854. redirectexit('action=admin;area=news;sa=settings');
  855. }
  856. // We need this for the in-line permissions
  857. createToken('admin-mp');
  858. prepareDBSettingContext($config_vars);
  859. }