PageRenderTime 67ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/php/Sources/PersonalMessage.php

https://github.com/dekoza/openshift-smf-2.0.7
PHP | 3621 lines | 2729 code | 447 blank | 445 comment | 621 complexity | f1ab7466f39b46dfab708e048d5b1a44 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Simple Machines Forum (SMF)
  4. *
  5. * @package SMF
  6. * @author Simple Machines http://www.simplemachines.org
  7. * @copyright 2011 Simple Machines
  8. * @license http://www.simplemachines.org/about/smf/license.php BSD
  9. *
  10. * @version 2.0.5
  11. */
  12. if (!defined('SMF'))
  13. die('Hacking attempt...');
  14. /* This file is mainly meant for viewing personal messages. It also sends,
  15. deletes, and marks personal messages. For compatibility reasons, they are
  16. often called "instant messages". The following functions are used:
  17. void MessageMain()
  18. // !!! ?action=pm
  19. void messageIndexBar(string area)
  20. // !!!
  21. void MessageFolder()
  22. // !!! ?action=pm;sa=folder
  23. void prepareMessageContext(type reset = 'subject', bool reset = false)
  24. // !!!
  25. void MessageSearch()
  26. // !!!
  27. void MessageSearch2()
  28. // !!!
  29. void MessagePost()
  30. // !!! ?action=pm;sa=post
  31. void messagePostError(array error_types, array named_recipients, array recipient_ids)
  32. // !!!
  33. void MessagePost2()
  34. // !!! ?action=pm;sa=post2
  35. void WirelessAddBuddy()
  36. // !!!
  37. void MessageActionsApply()
  38. // !!! ?action=pm;sa=pmactions
  39. void MessageKillAllQuery()
  40. // !!! ?action=pm;sa=killall
  41. void MessageKillAll()
  42. // !!! ?action=pm;sa=killall2
  43. void MessagePrune()
  44. // !!! ?action=pm;sa=prune
  45. void deleteMessages(array personal_messages, string folder,
  46. int owner = user)
  47. // !!!
  48. void markMessages(array personal_messages = all, int label = all,
  49. int owner = user)
  50. - marks the specified personal_messages read.
  51. - if label is set, only marks messages with that label.
  52. - if owner is set, marks messages owned by that member id.
  53. void ManageLabels()
  54. // !!!
  55. void MessageSettings()
  56. // !!!
  57. void ReportMessage()
  58. - allows the user to report a personal message to an administrator.
  59. - in the first instance requires that the ID of the message to report
  60. is passed through $_GET.
  61. - allows the user to report to either a particular administrator - or
  62. the whole admin team.
  63. - will forward on a copy of the original message without allowing the
  64. reporter to make changes.
  65. - uses the report_message sub-template.
  66. void ManageRules()
  67. // !!!
  68. void LoadRules()
  69. // !!!
  70. void ApplyRules()
  71. // !!!
  72. */
  73. // This helps organize things...
  74. function MessageMain()
  75. {
  76. global $txt, $scripturl, $sourcedir, $context, $user_info, $user_settings, $smcFunc, $modSettings;
  77. // No guests!
  78. is_not_guest();
  79. // You're not supposed to be here at all, if you can't even read PMs.
  80. isAllowedTo('pm_read');
  81. // This file contains the basic functions for sending a PM.
  82. require_once($sourcedir . '/Subs-Post.php');
  83. loadLanguage('PersonalMessage');
  84. if (WIRELESS && WIRELESS_PROTOCOL == 'wap')
  85. fatal_lang_error('wireless_error_notyet', false);
  86. elseif (WIRELESS)
  87. $context['sub_template'] = WIRELESS_PROTOCOL . '_pm';
  88. else
  89. loadTemplate('PersonalMessage');
  90. // Load up the members maximum message capacity.
  91. if ($user_info['is_admin'])
  92. $context['message_limit'] = 0;
  93. elseif (($context['message_limit'] = cache_get_data('msgLimit:' . $user_info['id'], 360)) === null)
  94. {
  95. // !!! Why do we do this? It seems like if they have any limit we should use it.
  96. $request = $smcFunc['db_query']('', '
  97. SELECT MAX(max_messages) AS top_limit, MIN(max_messages) AS bottom_limit
  98. FROM {db_prefix}membergroups
  99. WHERE id_group IN ({array_int:users_groups})',
  100. array(
  101. 'users_groups' => $user_info['groups'],
  102. )
  103. );
  104. list ($maxMessage, $minMessage) = $smcFunc['db_fetch_row']($request);
  105. $smcFunc['db_free_result']($request);
  106. $context['message_limit'] = $minMessage == 0 ? 0 : $maxMessage;
  107. // Save us doing it again!
  108. cache_put_data('msgLimit:' . $user_info['id'], $context['message_limit'], 360);
  109. }
  110. // Prepare the context for the capacity bar.
  111. if (!empty($context['message_limit']))
  112. {
  113. $bar = ($user_info['messages'] * 100) / $context['message_limit'];
  114. $context['limit_bar'] = array(
  115. 'messages' => $user_info['messages'],
  116. 'allowed' => $context['message_limit'],
  117. 'percent' => $bar,
  118. 'bar' => min(100, (int) $bar),
  119. 'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], round($bar, 1)),
  120. );
  121. }
  122. // a previous message was sent successfully? show a small indication.
  123. if (isset($_GET['done']) && ($_GET['done'] == 'sent'))
  124. $context['pm_sent'] = true;
  125. // Now we have the labels, and assuming we have unsorted mail, apply our rules!
  126. if ($user_settings['new_pm'])
  127. {
  128. $context['labels'] = $user_settings['message_labels'] == '' ? array() : explode(',', $user_settings['message_labels']);
  129. foreach ($context['labels'] as $id_label => $label_name)
  130. $context['labels'][(int) $id_label] = array(
  131. 'id' => $id_label,
  132. 'name' => trim($label_name),
  133. 'messages' => 0,
  134. 'unread_messages' => 0,
  135. );
  136. $context['labels'][-1] = array(
  137. 'id' => -1,
  138. 'name' => $txt['pm_msg_label_inbox'],
  139. 'messages' => 0,
  140. 'unread_messages' => 0,
  141. );
  142. ApplyRules();
  143. updateMemberData($user_info['id'], array('new_pm' => 0));
  144. $smcFunc['db_query']('', '
  145. UPDATE {db_prefix}pm_recipients
  146. SET is_new = {int:not_new}
  147. WHERE id_member = {int:current_member}',
  148. array(
  149. 'current_member' => $user_info['id'],
  150. 'not_new' => 0,
  151. )
  152. );
  153. }
  154. // Load the label data.
  155. if ($user_settings['new_pm'] || ($context['labels'] = cache_get_data('labelCounts:' . $user_info['id'], 720)) === null)
  156. {
  157. $context['labels'] = $user_settings['message_labels'] == '' ? array() : explode(',', $user_settings['message_labels']);
  158. foreach ($context['labels'] as $id_label => $label_name)
  159. $context['labels'][(int) $id_label] = array(
  160. 'id' => $id_label,
  161. 'name' => trim($label_name),
  162. 'messages' => 0,
  163. 'unread_messages' => 0,
  164. );
  165. $context['labels'][-1] = array(
  166. 'id' => -1,
  167. 'name' => $txt['pm_msg_label_inbox'],
  168. 'messages' => 0,
  169. 'unread_messages' => 0,
  170. );
  171. // Looks like we need to reseek!
  172. $result = $smcFunc['db_query']('', '
  173. SELECT labels, is_read, COUNT(*) AS num
  174. FROM {db_prefix}pm_recipients
  175. WHERE id_member = {int:current_member}
  176. AND deleted = {int:not_deleted}
  177. GROUP BY labels, is_read',
  178. array(
  179. 'current_member' => $user_info['id'],
  180. 'not_deleted' => 0,
  181. )
  182. );
  183. while ($row = $smcFunc['db_fetch_assoc']($result))
  184. {
  185. $this_labels = explode(',', $row['labels']);
  186. foreach ($this_labels as $this_label)
  187. {
  188. $context['labels'][(int) $this_label]['messages'] += $row['num'];
  189. if (!($row['is_read'] & 1))
  190. $context['labels'][(int) $this_label]['unread_messages'] += $row['num'];
  191. }
  192. }
  193. $smcFunc['db_free_result']($result);
  194. // Store it please!
  195. cache_put_data('labelCounts:' . $user_info['id'], $context['labels'], 720);
  196. }
  197. // This determines if we have more labels than just the standard inbox.
  198. $context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0;
  199. // Some stuff for the labels...
  200. $context['current_label_id'] = isset($_REQUEST['l']) && isset($context['labels'][(int) $_REQUEST['l']]) ? (int) $_REQUEST['l'] : -1;
  201. $context['current_label'] = &$context['labels'][(int) $context['current_label_id']]['name'];
  202. $context['folder'] = !isset($_REQUEST['f']) || $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent';
  203. // This is convenient. Do you know how annoying it is to do this every time?!
  204. $context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($_GET['start']) ? ';start=' . $_GET['start'] : '') . (isset($_REQUEST['l']) ? ';l=' . $_REQUEST['l'] : '');
  205. $context['can_issue_warning'] = in_array('w', $context['admin_features']) && allowedTo('issue_warning') && $modSettings['warning_settings'][0] == 1;
  206. // Build the linktree for all the actions...
  207. $context['linktree'][] = array(
  208. 'url' => $scripturl . '?action=pm',
  209. 'name' => $txt['personal_messages']
  210. );
  211. // Preferences...
  212. $context['display_mode'] = WIRELESS ? 0 : $user_settings['pm_prefs'] & 3;
  213. $subActions = array(
  214. 'addbuddy' => 'WirelessAddBuddy',
  215. 'manlabels' => 'ManageLabels',
  216. 'manrules' => 'ManageRules',
  217. 'pmactions' => 'MessageActionsApply',
  218. 'prune' => 'MessagePrune',
  219. 'removeall' => 'MessageKillAllQuery',
  220. 'removeall2' => 'MessageKillAll',
  221. 'report' => 'ReportMessage',
  222. 'search' => 'MessageSearch',
  223. 'search2' => 'MessageSearch2',
  224. 'send' => 'MessagePost',
  225. 'send2' => 'MessagePost2',
  226. 'settings' => 'MessageSettings',
  227. );
  228. if (!isset($_REQUEST['sa']) || !isset($subActions[$_REQUEST['sa']]))
  229. {
  230. $_REQUEST['sa'] = '';
  231. MessageFolder();
  232. }
  233. else
  234. {
  235. messageIndexBar($_REQUEST['sa']);
  236. $subActions[$_REQUEST['sa']]();
  237. }
  238. }
  239. // A sidebar to easily access different areas of the section
  240. function messageIndexBar($area)
  241. {
  242. global $txt, $context, $scripturl, $sourcedir, $sc, $modSettings, $settings, $user_info, $options;
  243. $pm_areas = array(
  244. 'folders' => array(
  245. 'title' => $txt['pm_messages'],
  246. 'areas' => array(
  247. 'send' => array(
  248. 'label' => $txt['new_message'],
  249. 'custom_url' => $scripturl . '?action=pm;sa=send',
  250. 'permission' => allowedTo('pm_send'),
  251. ),
  252. 'inbox' => array(
  253. 'label' => $txt['inbox'],
  254. 'custom_url' => $scripturl . '?action=pm',
  255. ),
  256. 'sent' => array(
  257. 'label' => $txt['sent_items'],
  258. 'custom_url' => $scripturl . '?action=pm;f=sent',
  259. ),
  260. ),
  261. ),
  262. 'labels' => array(
  263. 'title' => $txt['pm_labels'],
  264. 'areas' => array(),
  265. ),
  266. 'actions' => array(
  267. 'title' => $txt['pm_actions'],
  268. 'areas' => array(
  269. 'search' => array(
  270. 'label' => $txt['pm_search_bar_title'],
  271. 'custom_url' => $scripturl . '?action=pm;sa=search',
  272. ),
  273. 'prune' => array(
  274. 'label' => $txt['pm_prune'],
  275. 'custom_url' => $scripturl . '?action=pm;sa=prune'
  276. ),
  277. ),
  278. ),
  279. 'pref' => array(
  280. 'title' => $txt['pm_preferences'],
  281. 'areas' => array(
  282. 'manlabels' => array(
  283. 'label' => $txt['pm_manage_labels'],
  284. 'custom_url' => $scripturl . '?action=pm;sa=manlabels',
  285. ),
  286. 'manrules' => array(
  287. 'label' => $txt['pm_manage_rules'],
  288. 'custom_url' => $scripturl . '?action=pm;sa=manrules',
  289. ),
  290. 'settings' => array(
  291. 'label' => $txt['pm_settings'],
  292. 'custom_url' => $scripturl . '?action=pm;sa=settings',
  293. ),
  294. ),
  295. ),
  296. );
  297. // Handle labels.
  298. if (empty($context['currently_using_labels']))
  299. unset($pm_areas['labels']);
  300. else
  301. {
  302. // Note we send labels by id as it will have less problems in the querystring.
  303. $unread_in_labels = 0;
  304. foreach ($context['labels'] as $label)
  305. {
  306. if ($label['id'] == -1)
  307. continue;
  308. // Count the amount of unread items in labels.
  309. $unread_in_labels += $label['unread_messages'];
  310. // Add the label to the menu.
  311. $pm_areas['labels']['areas']['label' . $label['id']] = array(
  312. 'label' => $label['name'] . (!empty($label['unread_messages']) ? ' (<strong>' . $label['unread_messages'] . '</strong>)' : ''),
  313. 'custom_url' => $scripturl . '?action=pm;l=' . $label['id'],
  314. 'unread_messages' => $label['unread_messages'],
  315. 'messages' => $label['messages'],
  316. );
  317. }
  318. if (!empty($unread_in_labels))
  319. $pm_areas['labels']['title'] .= ' (' . $unread_in_labels . ')';
  320. }
  321. $pm_areas['folders']['areas']['inbox']['unread_messages'] = &$context['labels'][-1]['unread_messages'];
  322. $pm_areas['folders']['areas']['inbox']['messages'] = &$context['labels'][-1]['messages'];
  323. if (!empty($context['labels'][-1]['unread_messages']))
  324. {
  325. $pm_areas['folders']['areas']['inbox']['label'] .= ' (<strong>' . $context['labels'][-1]['unread_messages'] . '</strong>)';
  326. $pm_areas['folders']['title'] .= ' (' . $context['labels'][-1]['unread_messages'] . ')';
  327. }
  328. // Do we have a limit on the amount of messages we can keep?
  329. if (!empty($context['message_limit']))
  330. {
  331. $bar = round(($user_info['messages'] * 100) / $context['message_limit'], 1);
  332. $context['limit_bar'] = array(
  333. 'messages' => $user_info['messages'],
  334. 'allowed' => $context['message_limit'],
  335. 'percent' => $bar,
  336. 'bar' => $bar > 100 ? 100 : (int) $bar,
  337. 'text' => sprintf($txt['pm_currently_using'], $user_info['messages'], $bar)
  338. );
  339. }
  340. require_once($sourcedir . '/Subs-Menu.php');
  341. // What page is this, again?
  342. $current_page = $scripturl . '?action=pm' . (!empty($_REQUEST['sa']) ? ';sa=' . $_REQUEST['sa'] : '') . (!empty($context['folder']) ? ';f=' . $context['folder'] : '') . (!empty($context['current_label_id']) ? ';l=' . $context['current_label_id'] : '');
  343. // Set a few options for the menu.
  344. $menuOptions = array(
  345. 'current_area' => $area,
  346. 'disable_url_session_check' => true,
  347. 'toggle_url' => $current_page . ';togglebar',
  348. 'toggle_redirect_url' => $current_page,
  349. );
  350. // Actually create the menu!
  351. $pm_include_data = createMenu($pm_areas, $menuOptions);
  352. unset($pm_areas);
  353. // Make a note of the Unique ID for this menu.
  354. $context['pm_menu_id'] = $context['max_menu_id'];
  355. $context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id'];
  356. // Set the selected item.
  357. $context['menu_item_selected'] = $pm_include_data['current_area'];
  358. // obExit will know what to do!
  359. if (!WIRELESS)
  360. $context['template_layers'][] = 'pm';
  361. }
  362. // A folder, ie. inbox/sent etc.
  363. function MessageFolder()
  364. {
  365. global $txt, $scripturl, $modSettings, $context, $subjects_request;
  366. global $messages_request, $user_info, $recipients, $options, $smcFunc, $memberContext, $user_settings;
  367. // Changing view?
  368. if (isset($_GET['view']))
  369. {
  370. $context['display_mode'] = $context['display_mode'] > 1 ? 0 : $context['display_mode'] + 1;
  371. updateMemberData($user_info['id'], array('pm_prefs' => ($user_settings['pm_prefs'] & 252) | $context['display_mode']));
  372. }
  373. // Make sure the starting location is valid.
  374. if (isset($_GET['start']) && $_GET['start'] != 'new')
  375. $_GET['start'] = (int) $_GET['start'];
  376. elseif (!isset($_GET['start']) && !empty($options['view_newest_pm_first']))
  377. $_GET['start'] = 0;
  378. else
  379. $_GET['start'] = 'new';
  380. // Set up some basic theme stuff.
  381. $context['from_or_to'] = $context['folder'] != 'sent' ? 'from' : 'to';
  382. $context['get_pmessage'] = 'prepareMessageContext';
  383. $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
  384. $labelQuery = $context['folder'] != 'sent' ? '
  385. AND FIND_IN_SET(' . $context['current_label_id'] . ', pmr.labels) != 0' : '';
  386. // Set the index bar correct!
  387. messageIndexBar($context['current_label_id'] == -1 ? $context['folder'] : 'label' . $context['current_label_id']);
  388. // Sorting the folder.
  389. $sort_methods = array(
  390. 'date' => 'pm.id_pm',
  391. 'name' => 'IFNULL(mem.real_name, \'\')',
  392. 'subject' => 'pm.subject',
  393. );
  394. // They didn't pick one, use the forum default.
  395. if (!isset($_GET['sort']) || !isset($sort_methods[$_GET['sort']]))
  396. {
  397. $context['sort_by'] = 'date';
  398. $_GET['sort'] = 'pm.id_pm';
  399. // An overriding setting?
  400. $descending = !empty($options['view_newest_pm_first']);
  401. }
  402. // Otherwise use the defaults: ascending, by date.
  403. else
  404. {
  405. $context['sort_by'] = $_GET['sort'];
  406. $_GET['sort'] = $sort_methods[$_GET['sort']];
  407. $descending = isset($_GET['desc']);
  408. }
  409. $context['sort_direction'] = $descending ? 'down' : 'up';
  410. // Why would you want access to your sent items if you're not allowed to send anything?
  411. if ($context['folder'] == 'sent')
  412. isAllowedTo('pm_send');
  413. // Set the text to resemble the current folder.
  414. $pmbox = $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'];
  415. $txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']);
  416. // Now, build the link tree!
  417. if ($context['current_label_id'] == -1)
  418. $context['linktree'][] = array(
  419. 'url' => $scripturl . '?action=pm;f=' . $context['folder'],
  420. 'name' => $pmbox
  421. );
  422. // Build it further for a label.
  423. if ($context['current_label_id'] != -1)
  424. $context['linktree'][] = array(
  425. 'url' => $scripturl . '?action=pm;f=' . $context['folder'] . ';l=' . $context['current_label_id'],
  426. 'name' => $txt['pm_current_label'] . ': ' . $context['current_label']
  427. );
  428. // Figure out how many messages there are.
  429. if ($context['folder'] == 'sent')
  430. $request = $smcFunc['db_query']('', '
  431. SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
  432. FROM {db_prefix}personal_messages AS pm
  433. WHERE pm.id_member_from = {int:current_member}
  434. AND pm.deleted_by_sender = {int:not_deleted}',
  435. array(
  436. 'current_member' => $user_info['id'],
  437. 'not_deleted' => 0,
  438. )
  439. );
  440. else
  441. $request = $smcFunc['db_query']('', '
  442. SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
  443. FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
  444. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . '
  445. WHERE pmr.id_member = {int:current_member}
  446. AND pmr.deleted = {int:not_deleted}' . $labelQuery,
  447. array(
  448. 'current_member' => $user_info['id'],
  449. 'not_deleted' => 0,
  450. )
  451. );
  452. list ($max_messages) = $smcFunc['db_fetch_row']($request);
  453. $smcFunc['db_free_result']($request);
  454. // Only show the button if there are messages to delete.
  455. $context['show_delete'] = $max_messages > 0;
  456. // Start on the last page.
  457. if (!is_numeric($_GET['start']) || $_GET['start'] >= $max_messages)
  458. $_GET['start'] = ($max_messages - 1) - (($max_messages - 1) % $modSettings['defaultMaxMessages']);
  459. elseif ($_GET['start'] < 0)
  460. $_GET['start'] = 0;
  461. // ... but wait - what if we want to start from a specific message?
  462. if (isset($_GET['pmid']))
  463. {
  464. $pmID = (int) $_GET['pmid'];
  465. // Make sure you have access to this PM.
  466. if (!isAccessiblePM($pmID, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
  467. fatal_lang_error('no_access', false);
  468. $context['current_pm'] = $pmID;
  469. // With only one page of PM's we're gonna want page 1.
  470. if ($max_messages <= $modSettings['defaultMaxMessages'])
  471. $_GET['start'] = 0;
  472. // If we pass kstart we assume we're in the right place.
  473. elseif (!isset($_GET['kstart']))
  474. {
  475. if ($context['folder'] == 'sent')
  476. $request = $smcFunc['db_query']('', '
  477. SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
  478. FROM {db_prefix}personal_messages
  479. WHERE id_member_from = {int:current_member}
  480. AND deleted_by_sender = {int:not_deleted}
  481. AND id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
  482. array(
  483. 'current_member' => $user_info['id'],
  484. 'not_deleted' => 0,
  485. 'id_pm' => $pmID,
  486. )
  487. );
  488. else
  489. $request = $smcFunc['db_query']('', '
  490. SELECT COUNT(' . ($context['display_mode'] == 2 ? 'DISTINCT pm.id_pm_head' : '*') . ')
  491. FROM {db_prefix}pm_recipients AS pmr' . ($context['display_mode'] == 2 ? '
  492. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)' : '') . '
  493. WHERE pmr.id_member = {int:current_member}
  494. AND pmr.deleted = {int:not_deleted}' . $labelQuery . '
  495. AND pmr.id_pm ' . ($descending ? '>' : '<') . ' {int:id_pm}',
  496. array(
  497. 'current_member' => $user_info['id'],
  498. 'not_deleted' => 0,
  499. 'id_pm' => $pmID,
  500. )
  501. );
  502. list ($_GET['start']) = $smcFunc['db_fetch_row']($request);
  503. $smcFunc['db_free_result']($request);
  504. // To stop the page index's being abnormal, start the page on the page the message would normally be located on...
  505. $_GET['start'] = $modSettings['defaultMaxMessages'] * (int) ($_GET['start'] / $modSettings['defaultMaxMessages']);
  506. }
  507. }
  508. // Sanitize and validate pmsg variable if set.
  509. if (isset($_GET['pmsg']))
  510. {
  511. $pmsg = (int) $_GET['pmsg'];
  512. if (!isAccessiblePM($pmsg, $context['folder'] == 'sent' ? 'outbox' : 'inbox'))
  513. fatal_lang_error('no_access', false);
  514. }
  515. // Set up the page index.
  516. $context['page_index'] = constructPageIndex($scripturl . '?action=pm;f=' . $context['folder'] . (isset($_REQUEST['l']) ? ';l=' . (int) $_REQUEST['l'] : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), $_GET['start'], $max_messages, $modSettings['defaultMaxMessages']);
  517. $context['start'] = $_GET['start'];
  518. // Determine the navigation context (especially useful for the wireless template).
  519. $context['links'] = array(
  520. 'first' => $_GET['start'] >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=0' : '',
  521. 'prev' => $_GET['start'] >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=' . ($_GET['start'] - $modSettings['defaultMaxMessages']) : '',
  522. 'next' => $_GET['start'] + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . ($_GET['start'] + $modSettings['defaultMaxMessages']) : '',
  523. 'last' => $_GET['start'] + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . (floor(($max_messages - 1) / $modSettings['defaultMaxMessages']) * $modSettings['defaultMaxMessages']) : '',
  524. 'up' => $scripturl,
  525. );
  526. $context['page_info'] = array(
  527. 'current_page' => $_GET['start'] / $modSettings['defaultMaxMessages'] + 1,
  528. 'num_pages' => floor(($max_messages - 1) / $modSettings['defaultMaxMessages']) + 1
  529. );
  530. // First work out what messages we need to see - if grouped is a little trickier...
  531. if ($context['display_mode'] == 2)
  532. {
  533. // On a non-default sort due to PostgreSQL we have to do a harder sort.
  534. if ($smcFunc['db_title'] == 'PostgreSQL' && $_GET['sort'] != 'pm.id_pm')
  535. {
  536. $sub_request = $smcFunc['db_query']('', '
  537. SELECT MAX({raw:sort}) AS sort_param, pm.id_pm_head
  538. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
  539. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
  540. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
  541. AND pmr.id_member = {int:current_member}
  542. AND pmr.deleted = {int:not_deleted}
  543. ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( '
  544. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
  545. WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
  546. AND pm.deleted_by_sender = {int:not_deleted}' : '1=1') . (empty($pmsg) ? '' : '
  547. AND pm.id_pm = {int:id_pm}') . '
  548. GROUP BY pm.id_pm_head
  549. ORDER BY sort_param' . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
  550. LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''),
  551. array(
  552. 'current_member' => $user_info['id'],
  553. 'not_deleted' => 0,
  554. 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
  555. 'id_pm' => isset($pmsg) ? $pmsg : '0',
  556. 'sort' => $_GET['sort'],
  557. )
  558. );
  559. $sub_pms = array();
  560. while ($row = $smcFunc['db_fetch_assoc']($sub_request))
  561. $sub_pms[$row['id_pm_head']] = $row['sort_param'];
  562. $smcFunc['db_free_result']($sub_request);
  563. $request = $smcFunc['db_query']('', '
  564. SELECT pm.id_pm AS id_pm, pm.id_pm_head
  565. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
  566. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
  567. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
  568. AND pmr.id_member = {int:current_member}
  569. AND pmr.deleted = {int:not_deleted}
  570. ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( '
  571. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})') : '') . '
  572. WHERE ' . (empty($sub_pms) ? '0=1' : 'pm.id_pm IN ({array_int:pm_list})') . '
  573. ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
  574. LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''),
  575. array(
  576. 'current_member' => $user_info['id'],
  577. 'pm_list' => array_keys($sub_pms),
  578. 'not_deleted' => 0,
  579. 'sort' => $_GET['sort'],
  580. 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
  581. )
  582. );
  583. }
  584. else
  585. {
  586. $request = $smcFunc['db_query']('pm_conversation_list', '
  587. SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
  588. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? ($context['sort_by'] == 'name' ? '
  589. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
  590. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
  591. AND pmr.id_member = {int:current_member}
  592. AND pmr.deleted = {int:deleted_by}
  593. ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( '
  594. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
  595. WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {int:current_member}
  596. AND pm.deleted_by_sender = {int:deleted_by}' : '1=1') . (empty($pmsg) ? '' : '
  597. AND pm.id_pm = {int:pmsg}') . '
  598. GROUP BY pm.id_pm_head
  599. ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($_GET['pmsg']) ? '
  600. LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''),
  601. array(
  602. 'current_member' => $user_info['id'],
  603. 'deleted_by' => 0,
  604. 'sort' => $_GET['sort'],
  605. 'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
  606. 'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
  607. )
  608. );
  609. }
  610. }
  611. // This is kinda simple!
  612. else
  613. {
  614. // !!!SLOW This query uses a filesort. (inbox only.)
  615. $request = $smcFunc['db_query']('', '
  616. SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
  617. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' . ($context['sort_by'] == 'name' ? '
  618. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') : '
  619. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm
  620. AND pmr.id_member = {int:current_member}
  621. AND pmr.deleted = {int:is_deleted}
  622. ' . $labelQuery . ')') . ($context['sort_by'] == 'name' ? ( '
  623. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:pm_member})') : '') . '
  624. WHERE ' . ($context['folder'] == 'sent' ? 'pm.id_member_from = {raw:current_member}
  625. AND pm.deleted_by_sender = {int:is_deleted}' : '1=1') . (empty($pmsg) ? '' : '
  626. AND pm.id_pm = {int:pmsg}') . '
  627. ORDER BY ' . ($_GET['sort'] == 'pm.id_pm' && $context['folder'] != 'sent' ? 'pmr.id_pm' : '{raw:sort}') . ($descending ? ' DESC' : ' ASC') . (empty($pmsg) ? '
  628. LIMIT ' . $_GET['start'] . ', ' . $modSettings['defaultMaxMessages'] : ''),
  629. array(
  630. 'current_member' => $user_info['id'],
  631. 'is_deleted' => 0,
  632. 'sort' => $_GET['sort'],
  633. 'pm_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
  634. 'pmsg' => isset($pmsg) ? (int) $pmsg : 0,
  635. )
  636. );
  637. }
  638. // Load the id_pms and initialize recipients.
  639. $pms = array();
  640. $lastData = array();
  641. $posters = $context['folder'] == 'sent' ? array($user_info['id']) : array();
  642. $recipients = array();
  643. while ($row = $smcFunc['db_fetch_assoc']($request))
  644. {
  645. if (!isset($recipients[$row['id_pm']]))
  646. {
  647. if (isset($row['id_member_from']))
  648. $posters[$row['id_pm']] = $row['id_member_from'];
  649. $pms[$row['id_pm']] = $row['id_pm'];
  650. $recipients[$row['id_pm']] = array(
  651. 'to' => array(),
  652. 'bcc' => array()
  653. );
  654. }
  655. // Keep track of the last message so we know what the head is without another query!
  656. if ((empty($pmID) && (empty($options['view_newest_pm_first']) || !isset($lastData))) || empty($lastData) || (!empty($pmID) && $pmID == $row['id_pm']))
  657. $lastData = array(
  658. 'id' => $row['id_pm'],
  659. 'head' => $row['id_pm_head'],
  660. );
  661. }
  662. $smcFunc['db_free_result']($request);
  663. // Make sure that we have been given a correct head pm id!
  664. if ($context['display_mode'] == 2 && !empty($pmID) && $pmID != $lastData['id'])
  665. fatal_lang_error('no_access', false);
  666. if (!empty($pms))
  667. {
  668. // Select the correct current message.
  669. if (empty($pmID))
  670. $context['current_pm'] = $lastData['id'];
  671. // This is a list of the pm's that are used for "full" display.
  672. if ($context['display_mode'] == 0)
  673. $display_pms = $pms;
  674. else
  675. $display_pms = array($context['current_pm']);
  676. // At this point we know the main id_pm's. But - if we are looking at conversations we need the others!
  677. if ($context['display_mode'] == 2)
  678. {
  679. $request = $smcFunc['db_query']('', '
  680. SELECT pm.id_pm, pm.id_member_from, pm.deleted_by_sender, pmr.id_member, pmr.deleted
  681. FROM {db_prefix}personal_messages AS pm
  682. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
  683. WHERE pm.id_pm_head = {int:id_pm_head}
  684. AND ((pm.id_member_from = {int:current_member} AND pm.deleted_by_sender = {int:not_deleted})
  685. OR (pmr.id_member = {int:current_member} AND pmr.deleted = {int:not_deleted}))
  686. ORDER BY pm.id_pm',
  687. array(
  688. 'current_member' => $user_info['id'],
  689. 'id_pm_head' => $lastData['head'],
  690. 'not_deleted' => 0,
  691. )
  692. );
  693. while ($row = $smcFunc['db_fetch_assoc']($request))
  694. {
  695. // This is, frankly, a joke. We will put in a workaround for people sending to themselves - yawn!
  696. if ($context['folder'] == 'sent' && $row['id_member_from'] == $user_info['id'] && $row['deleted_by_sender'] == 1)
  697. continue;
  698. elseif ($row['id_member'] == $user_info['id'] & $row['deleted'] == 1)
  699. continue;
  700. if (!isset($recipients[$row['id_pm']]))
  701. $recipients[$row['id_pm']] = array(
  702. 'to' => array(),
  703. 'bcc' => array()
  704. );
  705. $display_pms[] = $row['id_pm'];
  706. $posters[$row['id_pm']] = $row['id_member_from'];
  707. }
  708. $smcFunc['db_free_result']($request);
  709. }
  710. // This is pretty much EVERY pm!
  711. $all_pms = array_merge($pms, $display_pms);
  712. $all_pms = array_unique($all_pms);
  713. // Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P).
  714. $request = $smcFunc['db_query']('', '
  715. SELECT pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc, pmr.labels, pmr.is_read
  716. FROM {db_prefix}pm_recipients AS pmr
  717. LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
  718. WHERE pmr.id_pm IN ({array_int:pm_list})',
  719. array(
  720. 'pm_list' => $all_pms,
  721. )
  722. );
  723. $context['message_labels'] = array();
  724. $context['message_replied'] = array();
  725. $context['message_unread'] = array();
  726. while ($row = $smcFunc['db_fetch_assoc']($request))
  727. {
  728. if ($context['folder'] == 'sent' || empty($row['bcc']))
  729. $recipients[$row['id_pm']][empty($row['bcc']) ? 'to' : 'bcc'][] = empty($row['id_member_to']) ? $txt['guest_title'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . '">' . $row['to_name'] . '</a>';
  730. if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
  731. {
  732. $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
  733. $context['message_unread'][$row['id_pm']] = $row['is_read'] == 0;
  734. $row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
  735. foreach ($row['labels'] as $v)
  736. {
  737. if (isset($context['labels'][(int) $v]))
  738. $context['message_labels'][$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']);
  739. }
  740. }
  741. }
  742. $smcFunc['db_free_result']($request);
  743. // Make sure we don't load unnecessary data.
  744. if ($context['display_mode'] == 1)
  745. {
  746. foreach ($posters as $k => $v)
  747. if (!in_array($k, $display_pms))
  748. unset($posters[$k]);
  749. }
  750. // Load any users....
  751. $posters = array_unique($posters);
  752. if (!empty($posters))
  753. loadMemberData($posters);
  754. // If we're on grouped/restricted view get a restricted list of messages.
  755. if ($context['display_mode'] != 0)
  756. {
  757. // Get the order right.
  758. $orderBy = array();
  759. foreach (array_reverse($pms) as $pm)
  760. $orderBy[] = 'pm.id_pm = ' . $pm;
  761. // Seperate query for these bits!
  762. $subjects_request = $smcFunc['db_query']('', '
  763. SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.msgtime, IFNULL(mem.real_name, pm.from_name) AS from_name,
  764. IFNULL(mem.id_member, 0) AS not_guest
  765. FROM {db_prefix}personal_messages AS pm
  766. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
  767. WHERE pm.id_pm IN ({array_int:pm_list})
  768. ORDER BY ' . implode(', ', $orderBy) . '
  769. LIMIT ' . count($pms),
  770. array(
  771. 'pm_list' => $pms,
  772. )
  773. );
  774. }
  775. // Execute the query!
  776. $messages_request = $smcFunc['db_query']('', '
  777. SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
  778. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '
  779. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)' : '') . ($context['sort_by'] == 'name' ? '
  780. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = {raw:id_member})' : '') . '
  781. WHERE pm.id_pm IN ({array_int:display_pms})' . ($context['folder'] == 'sent' ? '
  782. GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' : '') . '
  783. ORDER BY ' . ($context['display_mode'] == 2 ? 'pm.id_pm' : $_GET['sort']) . ($descending ? ' DESC' : ' ASC') . '
  784. LIMIT ' . count($display_pms),
  785. array(
  786. 'display_pms' => $display_pms,
  787. 'id_member' => $context['folder'] == 'sent' ? 'pmr.id_member' : 'pm.id_member_from',
  788. )
  789. );
  790. }
  791. else
  792. $messages_request = false;
  793. $context['can_send_pm'] = allowedTo('pm_send');
  794. if (!WIRELESS)
  795. $context['sub_template'] = 'folder';
  796. $context['page_title'] = $txt['pm_inbox'];
  797. // Finally mark the relevant messages as read.
  798. if ($context['folder'] != 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages']))
  799. {
  800. // If the display mode is "old sk00l" do them all...
  801. if ($context['display_mode'] == 0)
  802. markMessages(null, $context['current_label_id']);
  803. // Otherwise do just the current one!
  804. elseif (!empty($context['current_pm']))
  805. markMessages($display_pms, $context['current_label_id']);
  806. }
  807. }
  808. // Get a personal message for the theme. (used to save memory.)
  809. function prepareMessageContext($type = 'subject', $reset = false)
  810. {
  811. global $txt, $scripturl, $modSettings, $context, $messages_request, $memberContext, $recipients, $smcFunc;
  812. global $user_info, $subjects_request;
  813. // Count the current message number....
  814. static $counter = null;
  815. if ($counter === null || $reset)
  816. $counter = $context['start'];
  817. static $temp_pm_selected = null;
  818. if ($temp_pm_selected === null)
  819. {
  820. $temp_pm_selected = isset($_SESSION['pm_selected']) ? $_SESSION['pm_selected'] : array();
  821. $_SESSION['pm_selected'] = array();
  822. }
  823. // If we're in non-boring view do something exciting!
  824. if ($context['display_mode'] != 0 && $subjects_request && $type == 'subject')
  825. {
  826. $subject = $smcFunc['db_fetch_assoc']($subjects_request);
  827. if (!$subject)
  828. {
  829. $smcFunc['db_free_result']($subjects_request);
  830. return false;
  831. }
  832. $subject['subject'] = $subject['subject'] == '' ? $txt['no_subject'] : $subject['subject'];
  833. censorText($subject['subject']);
  834. $output = array(
  835. 'id' => $subject['id_pm'],
  836. 'member' => array(
  837. 'id' => $subject['id_member_from'],
  838. 'name' => $subject['from_name'],
  839. 'link' => $subject['not_guest'] ? '<a href="' . $scripturl . '?action=profile;u=' . $subject['id_member_from'] . '">' . $subject['from_name'] . '</a>' : $subject['from_name'],
  840. ),
  841. 'recipients' => &$recipients[$subject['id_pm']],
  842. 'subject' => $subject['subject'],
  843. 'time' => timeformat($subject['msgtime']),
  844. 'timestamp' => forum_time(true, $subject['msgtime']),
  845. 'number_recipients' => count($recipients[$subject['id_pm']]['to']),
  846. 'labels' => &$context['message_labels'][$subject['id_pm']],
  847. 'fully_labeled' => count($context['message_labels'][$subject['id_pm']]) == count($context['labels']),
  848. 'is_replied_to' => &$context['message_replied'][$subject['id_pm']],
  849. 'is_unread' => &$context['message_unread'][$subject['id_pm']],
  850. 'is_selected' => !empty($temp_pm_selected) && in_array($subject['id_pm'], $temp_pm_selected),
  851. );
  852. return $output;
  853. }
  854. // Bail if it's false, ie. no messages.
  855. if ($messages_request == false)
  856. return false;
  857. // Reset the data?
  858. if ($reset == true)
  859. return @$smcFunc['db_data_seek']($messages_request, 0);
  860. // Get the next one... bail if anything goes wrong.
  861. $message = $smcFunc['db_fetch_assoc']($messages_request);
  862. if (!$message)
  863. {
  864. if ($type != 'subject')
  865. $smcFunc['db_free_result']($messages_request);
  866. return false;
  867. }
  868. // Use '(no subject)' if none was specified.
  869. $message['subject'] = $message['subject'] == '' ? $txt['no_subject'] : $message['subject'];
  870. // Load the message's information - if it's not there, load the guest information.
  871. if (!loadMemberContext($message['id_member_from'], true))
  872. {
  873. $memberContext[$message['id_member_from']]['name'] = $message['from_name'];
  874. $memberContext[$message['id_member_from']]['id'] = 0;
  875. // Sometimes the forum sends messages itself (Warnings are an example) - in this case don't label it from a guest.
  876. $memberContext[$message['id_member_from']]['group'] = $message['from_name'] == $context['forum_name'] ? '' : $txt['guest_title'];
  877. $memberContext[$message['id_member_from']]['link'] = $message['from_name'];
  878. $memberContext[$message['id_member_from']]['email'] = '';
  879. $memberContext[$message['id_member_from']]['show_email'] = showEmailAddress(true, 0);
  880. $memberContext[$message['id_member_from']]['is_guest'] = true;
  881. }
  882. else
  883. {
  884. $memberContext[$message['id_member_from']]['can_view_profile'] = allowedTo('profile_view_any') || ($message['id_member_from'] == $user_info['id'] && allowedTo('profile_view_own'));
  885. $memberContext[$message['id_member_from']]['can_see_warning'] = !isset($context['disabled_fields']['warning_status']) && $memberContext[$message['id_member_from']]['warning_status'] && ($context['user']['can_mod'] || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $message['id_member_from'] == $user_info['id'])));
  886. }
  887. // Censor all the important text...
  888. censorText($message['body']);
  889. censorText($message['subject']);
  890. // Run UBBC interpreter on the message.
  891. $message['body'] = parse_bbc($message['body'], true, 'pm' . $message['id_pm']);
  892. // Send the array.
  893. $output = array(
  894. 'alternate' => $counter % 2,
  895. 'id' => $message['id_pm'],
  896. 'member' => &$memberContext[$message['id_member_from']],
  897. 'subject' => $message['subject'],
  898. 'time' => timeformat($message['msgtime']),
  899. 'timestamp' => forum_time(true, $message['msgtime']),
  900. 'counter' => $counter,
  901. 'body' => $message['body'],
  902. 'recipients' => &$recipients[$message['id_pm']],
  903. 'number_recipients' => count($recipients[$message['id_pm']]['to']),
  904. 'labels' => &$context['message_labels'][$message['id_pm']],
  905. 'fully_labeled' => count($context['message_labels'][$message['id_pm']]) == count($context['labels']),
  906. 'is_replied_to' => &$context['message_replied'][$message['id_pm']],
  907. 'is_unread' => &$context['message_unread'][$message['id_pm']],
  908. 'is_selected' => !empty($temp_pm_selected) && in_array($message['id_pm'], $temp_pm_selected),
  909. );
  910. $counter++;
  911. return $output;
  912. }
  913. function MessageSearch()
  914. {
  915. global $context, $txt, $scripturl, $modSettings, $smcFunc;
  916. if (isset($_REQUEST['params']))
  917. {
  918. $temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
  919. $context['search_params'] = array();
  920. foreach ($temp_params as $i => $data)
  921. {
  922. @list ($k, $v) = explode('|\'|', $data);
  923. $context['search_params'][$k] = $v;
  924. }
  925. }
  926. if (isset($_REQUEST['search']))
  927. $context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
  928. if (isset($context['search_params']['search']))
  929. $context['search_params']['search'] = htmlspecialchars($context['search_params']['search']);
  930. if (isset($context['search_params']['userspec']))
  931. $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec']);
  932. if (!empty($context['search_params']['searchtype']))
  933. $context['search_params']['searchtype'] = 2;
  934. if (!empty($context['search_params']['minage']))
  935. $context['search_params']['minage'] = (int) $context['search_params']['minage'];
  936. if (!empty($context['search_params']['maxage']))
  937. $context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
  938. $context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
  939. $context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
  940. // Create the array of labels to be searched.
  941. $context['search_labels'] = array();
  942. $searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array();
  943. foreach ($context['labels'] as $label)
  944. {
  945. $context['search_labels'][] = array(
  946. 'id' => $label['id'],
  947. 'name' => $label['name'],
  948. 'checked' => !empty($searchedLabels) ? in_array($label['id'], $searchedLabels) : true,
  949. );
  950. }
  951. // Are all the labels checked?
  952. $context['check_all'] = empty($searchedLabels) || count($context['search_labels']) == count($searchedLabels);
  953. // Load the error text strings if there were errors in the search.
  954. if (!empty($context['search_errors']))
  955. {
  956. loadLanguage('Errors');
  957. $context['search_errors']['messages'] = array();
  958. foreach ($context['search_errors'] as $search_error => $dummy)
  959. {
  960. if ($search_error == 'messages')
  961. continue;
  962. $context['search_errors']['messages'][] = $txt['error_' . $search_error];
  963. }
  964. }
  965. $context['simple_search'] = isset($context['search_params']['advanced']) ? empty($context['search_params']['advanced']) : !empty($modSettings['simpleSearch']) && !isset($_REQUEST['advanced']);
  966. $context['page_title'] = $txt['pm_search_title'];
  967. $context['sub_template'] = 'search';
  968. $context['linktree'][] = array(
  969. 'url' => $scripturl . '?action=pm;sa=search',
  970. 'name' => $txt['pm_search_bar_title'],
  971. );
  972. }
  973. function MessageSearch2()
  974. {
  975. global $scripturl, $modSettings, $user_info, $context, $txt;
  976. global $memberContext, $smcFunc;
  977. if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
  978. fatal_lang_error('loadavg_search_disabled', false);
  979. // !!! For the moment force the folder to the inbox.
  980. $context['folder'] = 'inbox';
  981. // Some useful general permissions.
  982. $context['can_send_pm'] = allowedTo('pm_send');
  983. // Some hardcoded veriables that can be tweaked if required.
  984. $maxMembersToSearch = 500;
  985. // Extract all the search parameters.
  986. $search_params = array();
  987. if (isset($_REQUEST['params']))
  988. {
  989. $temp_params = explode('|"|', base64_decode(strtr($_REQUEST['params'], array(' ' => '+'))));
  990. foreach ($temp_params as $i => $data)
  991. {
  992. @list ($k, $v) = explode('|\'|', $data);
  993. $search_params[$k] = $v;
  994. }
  995. }
  996. $context['start'] = isset($_GET['start']) ? (int) $_GET['start'] : 0;
  997. // Store whether simple search was used (needed if the user wants to do another query).
  998. if (!isset($search_params['advanced']))
  999. $search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
  1000. // 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
  1001. if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
  1002. $search_params['searchtype'] = 2;
  1003. // Minimum age of messages. Default to zero (don't set param in that case).
  1004. if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
  1005. $search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
  1006. // Maximum age of messages. Default to infinite (9999 days: param not set).
  1007. if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] != 9999))
  1008. $search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
  1009. $search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
  1010. $search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
  1011. // Default the user name to a wildcard matching every user (*).
  1012. if (!empty($search_params['user_spec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
  1013. $search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
  1014. // This will be full of all kinds of parameters!
  1015. $searchq_parameters = array();
  1016. // If there's no specific user, then don't mention it in the main query.
  1017. if (empty($search_params['userspec']))
  1018. $userQuery = '';
  1019. else
  1020. {
  1021. $userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
  1022. $userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
  1023. preg_match_all('~"([^"]+)"~', $userString, $matches);
  1024. $possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
  1025. for ($k = 0, $n = count($possible_users); $k < $n; $k++)
  1026. {
  1027. $possible_users[$k] = trim($possible_users[$k]);
  1028. if (strlen($possible_users[$k]) == 0)
  1029. unset($possible_users[$k]);
  1030. }
  1031. // Who matches those criteria?
  1032. // !!! This doesn't support sent item searching.
  1033. $request = $smcFunc['db_query']('', '
  1034. SELECT id_member
  1035. FROM {db_prefix}members
  1036. WHERE real_name LIKE {raw:real_name_implode}',
  1037. array(
  1038. 'real_name_implode' => '\'' . implode('\' OR real_name LIKE \'', $possible_users) . '\'',
  1039. )
  1040. );
  1041. // Simply do nothing if there're too many members matching the criteria.
  1042. if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
  1043. $userQuery = '';
  1044. elseif ($smcFunc['db_num_rows']($request) == 0)
  1045. {
  1046. $userQuery = 'AND pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})';
  1047. $searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\'';
  1048. }
  1049. else
  1050. {
  1051. $memberlist = array();
  1052. while ($row = $smcFunc['db_fetch_assoc']($request))
  1053. $memberlist[] = $row['id_member'];
  1054. $userQuery = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND (pm.from_name LIKE {raw:guest_user_name_implode})))';
  1055. $searchq_parameters['guest_user_name_implode'] = '\'' . implode('\' OR pm.from_name LIKE \'', $possible_users) . '\'';
  1056. $searchq_parameters['member_list'] = $memberlist;
  1057. }
  1058. $smcFunc['db_free_result']($request);
  1059. }
  1060. // Setup the sorting variables...
  1061. // !!! Add more in here!
  1062. $sort_columns = array(
  1063. 'pm.id_pm',
  1064. );
  1065. if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
  1066. list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
  1067. $search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'pm.id_pm';
  1068. $search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
  1069. // Sort out any labels we may be searching by.
  1070. $labelQuery = '';
  1071. if ($context['folder'] == 'inbox' && !empty($search_params['advanced']) && $context['currently_using_labels'])
  1072. {
  1073. // Came here from pagination? Put them back into $_REQUEST for sanitization.
  1074. if (isset($search_params['labels']))
  1075. $_REQUEST['searchlabel'] = explode(',', $search_params['labels']);
  1076. // Assuming we have some labels - make them all integers.
  1077. if (!empty($_REQUEST['searchlabel']) && is_array($_REQUEST['searchlabel']))
  1078. {
  1079. foreach ($_REQUEST['searchlabel'] as $key => $id)
  1080. $_REQUEST['searchlabel'][$key] = (int) $id;
  1081. }
  1082. else
  1083. $_REQUEST['searchlabel'] = array();
  1084. // Now that everything is cleaned up a bit, make the labels a param.
  1085. $search_params['labels'] = implode(',', $_REQUEST['searchlabel']);
  1086. // No labels selected? That must be an error!
  1087. if (empty($_REQUEST['searchlabel']))
  1088. $context['search_errors']['no_labels_selected'] = true;
  1089. // Otherwise prepare the query!
  1090. elseif (count($_REQUEST['searchlabel']) != count($context['labels']))
  1091. {
  1092. $labelQuery = '
  1093. AND {raw:label_implode}';
  1094. $labelStatements = array();
  1095. foreach ($_REQUEST['searchlabel'] as $label)
  1096. $labelStatements[] = $smcFunc['db_quote']('FIND_IN_SET({string:label}, pmr.labels) != 0', array(
  1097. 'label' => $label,
  1098. ));
  1099. $searchq_parameters['label_implode'] = '(' . implode(' OR ', $labelStatements) . ')';
  1100. }
  1101. }
  1102. // What are we actually searching for?
  1103. $search_params['search'] = !empty($search_params['search']) ? $search_params['search'] : (isset($_REQUEST['search']) ? $_REQUEST['search'] : '');
  1104. // If we ain't got nothing - we should error!
  1105. if (!isset($search_params['search']) || $search_params['search'] == '')
  1106. $context['search_errors']['invalid_search_string'] = true;
  1107. // Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
  1108. preg_match_all('~(?:^|\s)([-]?)"([^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), $search_params['search'], $matches, PREG_PATTERN_ORDER);
  1109. $searchArray = $matches[2];
  1110. // Remove the phrase parts and extract the words.
  1111. $tempSearch = explode(' ', preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']));
  1112. // A minus sign in front of a word excludes the word.... so...
  1113. $excludedWords = array();
  1114. // .. first, we check for things like -"some words", but not "-some words".
  1115. foreach ($matches[1] as $index => $word)
  1116. if ($word == '-')
  1117. {
  1118. $word = $smcFunc['strtolower'](trim($searchArray[$index]));
  1119. if (strlen($word) > 0)
  1120. $excludedWords[] = $word;
  1121. unset($searchArray[$index]);
  1122. }
  1123. // Now we look for -test, etc.... normaller.
  1124. foreach ($tempSearch as $index => $word)
  1125. if (strpos(trim($word), '-') === 0)
  1126. {
  1127. $word = substr($smcFunc['strtolower'](trim($word)), 1);
  1128. if (strlen($word) > 0)
  1129. $excludedWords[] = $word;
  1130. unset($tempSearch[$index]);
  1131. }
  1132. $searchArray = array_merge($searchArray, $tempSearch);
  1133. // Trim everything and make sure there are no words that are the same.
  1134. foreach ($searchArray as $index => $value)
  1135. {
  1136. $searchArray[$index] = $smcFunc['strtolower'](trim($value));
  1137. if ($searchArray[$index] == '')
  1138. unset($searchArray[$index]);
  1139. else
  1140. {
  1141. // Sort out entities first.
  1142. $searchArray[$index] = $smcFunc['htmlspecialchars']($searchArray[$index]);
  1143. }
  1144. }
  1145. $searchArray = array_unique($searchArray);
  1146. // Create an array of replacements for highlighting.
  1147. $context['mark'] = array();
  1148. foreach ($searchArray as $word)
  1149. $context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
  1150. // This contains *everything*
  1151. $searchWords = array_merge($searchArray, $excludedWords);
  1152. // Make sure at least one word is being searched for.
  1153. if (empty($searchArray))
  1154. $context['search_errors']['invalid_search_string'] = true;
  1155. // Sort out the search query so the user can edit it - if they want.
  1156. $context['search_params'] = $search_params;
  1157. if (isset($context['search_params']['search']))
  1158. $context['search_params']['search'] = htmlspecialchars($context['search_params']['search']);
  1159. if (isset($context['search_params']['userspec']))
  1160. $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec']);
  1161. // Now we have all the parameters, combine them together for pagination and the like...
  1162. $context['params'] = array();
  1163. foreach ($search_params as $k => $v)
  1164. $context['params'][] = $k . '|\'|' . $v;
  1165. $context['params'] = base64_encode(implode('|"|', $context['params']));
  1166. // Compile the subject query part.
  1167. $andQueryParts = array();
  1168. foreach ($searchWords as $index => $word)
  1169. {
  1170. if ($word == '')
  1171. continue;
  1172. if ($search_params['subject_only'])
  1173. $andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}';
  1174. else
  1175. $andQueryParts[] = '(pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '} ' . (in_array($word, $excludedWords) ? 'AND pm.body NOT' : 'OR pm.body') . ' LIKE {string:search_' . $index . '})';
  1176. $searchq_parameters['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%';
  1177. }
  1178. $searchQuery = ' 1=1';
  1179. if (!empty($andQueryParts))
  1180. $searchQuery = implode(!empty($search_params['searchtype']) && $search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts);
  1181. // Age limits?
  1182. $timeQuery = '';
  1183. if (!empty($search_params['minage']))
  1184. $timeQuery .= ' AND pm.msgtime < ' . (time() - $search_params['minage'] * 86400);
  1185. if (!empty($search_params['maxage']))
  1186. $timeQuery .= ' AND pm.msgtime > ' . (time() - $search_params['maxage'] * 86400);
  1187. // If we have errors - return back to the first screen...
  1188. if (!empty($context['search_errors']))
  1189. {
  1190. $_REQUEST['params'] = $context['params'];
  1191. return MessageSearch();
  1192. }
  1193. // Get the amount of results.
  1194. $request = $smcFunc['db_query']('', '
  1195. SELECT COUNT(*)
  1196. FROM {db_prefix}pm_recipients AS pmr
  1197. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
  1198. WHERE ' . ($context['folder'] == 'inbox' ? '
  1199. pmr.id_member = {int:current_member}
  1200. AND pmr.deleted = {int:not_deleted}' : '
  1201. pm.id_member_from = {int:current_member}
  1202. AND pm.deleted_by_sender = {int:not_deleted}') . '
  1203. ' . $userQuery . $labelQuery . $timeQuery . '
  1204. AND (' . $searchQuery . ')',
  1205. array_merge($searchq_parameters, array(
  1206. 'current_member' => $user_info['id'],
  1207. 'not_deleted' => 0,
  1208. ))
  1209. );
  1210. list ($numResults) = $smcFunc['db_fetch_row']($request);
  1211. $smcFunc['db_free_result']($request);
  1212. // Get all the matching messages... using standard search only (No caching and the like!)
  1213. // !!! This doesn't support sent item searching yet.
  1214. $request = $smcFunc['db_query']('', '
  1215. SELECT pm.id_pm, pm.id_pm_head, pm.id_member_from
  1216. FROM {db_prefix}pm_recipients AS pmr
  1217. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
  1218. WHERE ' . ($context['folder'] == 'inbox' ? '
  1219. pmr.id_member = {int:current_member}
  1220. AND pmr.deleted = {int:not_deleted}' : '
  1221. pm.id_member_from = {int:current_member}
  1222. AND pm.deleted_by_sender = {int:not_deleted}') . '
  1223. ' . $userQuery . $labelQuery . $timeQuery . '
  1224. AND (' . $searchQuery . ')
  1225. ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
  1226. LIMIT ' . $context['start'] . ', ' . $modSettings['search_results_per_page'],
  1227. array_merge($searchq_parameters, array(
  1228. 'current_member' => $user_info['id'],
  1229. 'not_deleted' => 0,
  1230. ))
  1231. );
  1232. $foundMessages = array();
  1233. $posters = array();
  1234. $head_pms = array();
  1235. while ($row = $smcFunc['db_fetch_assoc']($request))
  1236. {
  1237. $foundMessages[] = $row['id_pm'];
  1238. $posters[] = $row['id_member_from'];
  1239. $head_pms[$row['id_pm']] = $row['id_pm_head'];
  1240. }
  1241. $smcFunc['db_free_result']($request);
  1242. // Find the real head pms!
  1243. if ($context['display_mode'] == 2 && !empty($head_pms))
  1244. {
  1245. $request = $smcFunc['db_query']('', '
  1246. SELECT MAX(pm.id_pm) AS id_pm, pm.id_pm_head
  1247. FROM {db_prefix}personal_messages AS pm
  1248. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
  1249. WHERE pm.id_pm_head IN ({array_int:head_pms})
  1250. AND pmr.id_member = {int:current_member}
  1251. AND pmr.deleted = {int:not_deleted}
  1252. GROUP BY pm.id_pm_head
  1253. LIMIT {int:limit}',
  1254. array(
  1255. 'head_pms' => array_unique($head_pms),
  1256. 'current_member' => $user_info['id'],
  1257. 'not_deleted' => 0,
  1258. 'limit' => count($head_pms),
  1259. )
  1260. );
  1261. $real_pm_ids = array();
  1262. while ($row = $smcFunc['db_fetch_assoc']($request))
  1263. $real_pm_ids[$row['id_pm_head']] = $row['id_pm'];
  1264. $smcFunc['db_free_result']($request);
  1265. }
  1266. // Load the users...
  1267. $posters = array_unique($posters);
  1268. if (!empty($posters))
  1269. loadMemberData($posters);
  1270. // Sort out the page index.
  1271. $context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=search2;params=' . $context['params'], $_GET['start'], $numResults, $modSettings['search_results_per_page'], false);
  1272. $context['message_labels'] = array();
  1273. $context['message_replied'] = array();
  1274. $context['personal_messages'] = array();
  1275. if (!empty($foundMessages))
  1276. {
  1277. // Now get recipients (but don't include bcc-recipients for your inbox, you're not supposed to know :P!)
  1278. $request = $smcFunc['db_query']('', '
  1279. SELECT
  1280. pmr.id_pm, mem_to.id_member AS id_member_to, mem_to.real_name AS to_name,
  1281. pmr.bcc, pmr.labels, pmr.is_read
  1282. FROM {db_prefix}pm_recipients AS pmr
  1283. LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
  1284. WHERE pmr.id_pm IN ({array_int:message_list})',
  1285. array(
  1286. 'message_list' => $foundMessages,
  1287. )
  1288. );
  1289. while ($row = $smcFunc['db_fetch_assoc']($request))
  1290. {
  1291. if ($context['folder'] == 'sent' || empty($row['bcc']))
  1292. $recipients[$row['id_pm']][empty($row['bcc']) ? 'to' : 'bcc'][] = empty($row['id_member_to']) ? $txt['guest_title'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . '">' . $row['to_name'] . '</a>';
  1293. if ($row['id_member_to'] == $user_info['id'] && $context['folder'] != 'sent')
  1294. {
  1295. $context['message_replied'][$row['id_pm']] = $row['is_read'] & 2;
  1296. $row['labels'] = $row['labels'] == '' ? array() : explode(',', $row['labels']);
  1297. // This is a special need for linking to messages.
  1298. foreach ($row['labels'] as $v)
  1299. {
  1300. if (isset($context['labels'][(int) $v]))
  1301. $context['message_labels'][$row['id_pm']][(int) $v] = array('id' => $v, 'name' => $context['labels'][(int) $v]['name']);
  1302. // Here we find the first label on a message - for linking to posts in results
  1303. if (!isset($context['first_label'][$row['id_pm']]) && !in_array('-1', $row['labels']))
  1304. $context['first_label'][$row['id_pm']] = (int) $v;
  1305. }
  1306. }
  1307. }
  1308. // Prepare the query for the callback!
  1309. $request = $smcFunc['db_query']('', '
  1310. SELECT pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name
  1311. FROM {db_prefix}personal_messages AS pm
  1312. WHERE pm.id_pm IN ({array_int:message_list})
  1313. ORDER BY ' . $search_params['sort'] . ' ' . $search_params['sort_dir'] . '
  1314. LIMIT ' . count($foundMessages),
  1315. array(
  1316. 'message_list' => $foundMessages,
  1317. )
  1318. );
  1319. $counter = 0;
  1320. while ($row = $smcFunc['db_fetch_assoc']($request))
  1321. {
  1322. // If there's no message subject, use the default.
  1323. $row['subject'] = $row['subject'] == '' ? $txt['no_subject'] : $row['subject'];
  1324. // Load this posters context info, if it ain't there then fill in the essentials...
  1325. if (!loadMemberContext($row['id_member_from'], true))
  1326. {
  1327. $memberContext[$row['id_member_from']]['name'] = $row['from_name'];
  1328. $memberContext[$row['id_member_from']]['id'] = 0;
  1329. $memberContext[$row['id_member_from']]['group'] = $txt['guest_title'];
  1330. $memberContext[$row['id_member_from']]['link'] = $row['from_name'];
  1331. $memberContext[$row['id_member_from']]['email'] = '';
  1332. $memberContext[$row['id_member_from']]['show_email'] = showEmailAddress(true, 0);
  1333. $memberContext[$row['id_member_from']]['is_guest'] = true;
  1334. }
  1335. // Censor anything we don't want to see...
  1336. censorText($row['body']);
  1337. censorText($row['subject']);
  1338. // Parse out any BBC...
  1339. $row['body'] = parse_bbc($row['body'], true, 'pm' . $row['id_pm']);
  1340. $href = $scripturl . '?action=pm;f=' . $context['folder'] . (isset($context['first_label'][$row['id_pm']]) ? ';l=' . $context['first_label'][$row['id_pm']] : '') . ';pmid=' . ($context['display_mode'] == 2 && isset($real_pm_ids[$head_pms[$row['id_pm']]]) ? $real_pm_ids[$head_pms[$row['id_pm']]] : $row['id_pm']) . '#msg' . $row['id_pm'];
  1341. $context['personal_messages'][] = array(
  1342. 'id' => $row['id_pm'],
  1343. 'member' => &$memberContext[$row['id_member_from']],
  1344. 'subject' => $row['subject'],
  1345. 'body' => $row['body'],
  1346. 'time' => timeformat($row['msgtime']),
  1347. 'recipients' => &$recipients[$row['id_pm']],
  1348. 'labels' => &$context['message_labels'][$row['id_pm']],
  1349. 'fully_labeled' => count($context['message_labels'][$row['id_pm']]) == count($context['labels']),
  1350. 'is_replied_to' => &$context['message_replied'][$row['id_pm']],
  1351. 'href' => $href,
  1352. 'link' => '<a href="' . $href . '">' . $row['subject'] . '</a>',
  1353. 'counter' => ++$counter,
  1354. );
  1355. }
  1356. $smcFunc['db_free_result']($request);
  1357. }
  1358. // Finish off the context.
  1359. $context['page_title'] = $txt['pm_search_title'];
  1360. $context['sub_template'] = 'search_results';
  1361. $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search';
  1362. $context['linktree'][] = array(
  1363. 'url' => $scripturl . '?action=pm;sa=search',
  1364. 'name' => $txt['pm_search_bar_title'],
  1365. );
  1366. }
  1367. // Send a new message?
  1368. function MessagePost()
  1369. {
  1370. global $txt, $sourcedir, $scripturl, $modSettings;
  1371. global $context, $options, $smcFunc, $language, $user_info;
  1372. isAllowedTo('pm_send');
  1373. loadLanguage('PersonalMessage');
  1374. // Just in case it was loaded from somewhere else.
  1375. if (!WIRELESS)
  1376. {
  1377. loadTemplate('PersonalMessage');
  1378. $context['sub_template'] = 'send';
  1379. }
  1380. // Extract out the spam settings - cause it's neat.
  1381. list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
  1382. // Set the title...
  1383. $context['page_title'] = $txt['send_message'];
  1384. $context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
  1385. // Check whether we've gone over the limit of messages we can send per hour.
  1386. if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1')
  1387. {
  1388. // How many messages have they sent this last hour?
  1389. $request = $smcFunc['db_query']('', '
  1390. SELECT COUNT(pr.id_pm) AS post_count
  1391. FROM {db_prefix}personal_messages AS pm
  1392. INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
  1393. WHERE pm.id_member_from = {int:current_member}
  1394. AND pm.msgtime > {int:msgtime}',
  1395. array(
  1396. 'current_member' => $user_info['id'],
  1397. 'msgtime' => time() - 3600,
  1398. )
  1399. );
  1400. list ($postCount) = $smcFunc['db_fetch_row']($request);
  1401. $smcFunc['db_free_result']($request);
  1402. if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
  1403. fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
  1404. }
  1405. // Quoting/Replying to a message?
  1406. if (!empty($_REQUEST['pmsg']))
  1407. {
  1408. $pmsg = (int) $_REQUEST['pmsg'];
  1409. // Make sure this is yours.
  1410. if (!isAccessiblePM($pmsg))
  1411. fatal_lang_error('no_access', false);
  1412. // Work out whether this is one you've received?
  1413. $request = $smcFunc['db_query']('', '
  1414. SELECT
  1415. id_pm
  1416. FROM {db_prefix}pm_recipients
  1417. WHERE id_pm = {int:id_pm}
  1418. AND id_member = {int:current_member}
  1419. LIMIT 1',
  1420. array(
  1421. 'current_member' => $user_info['id'],
  1422. 'id_pm' => $pmsg,
  1423. )
  1424. );
  1425. $isReceived = $smcFunc['db_num_rows']($request) != 0;
  1426. $smcFunc['db_free_result']($request);
  1427. // Get the quoted message (and make sure you're allowed to see this quote!).
  1428. $request = $smcFunc['db_query']('', '
  1429. SELECT
  1430. pm.id_pm, CASE WHEN pm.id_pm_head = {int:id_pm_head_empty} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head,
  1431. pm.body, pm.subject, pm.msgtime, mem.member_name, IFNULL(mem.id_member, 0) AS id_member,
  1432. IFNULL(mem.real_name, pm.from_name) AS real_name
  1433. FROM {db_prefix}personal_messages AS pm' . (!$isReceived ? '' : '
  1434. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:id_pm})') . '
  1435. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
  1436. WHERE pm.id_pm = {int:id_pm}' . (!$isReceived ? '
  1437. AND pm.id_member_from = {int:current_member}' : '
  1438. AND pmr.id_member = {int:current_member}') . '
  1439. LIMIT 1',
  1440. array(
  1441. 'current_member' => $user_info['id'],
  1442. 'id_pm_head_empty' => 0,
  1443. 'id_pm' => $pmsg,
  1444. )
  1445. );
  1446. if ($smcFunc['db_num_rows']($request) == 0)
  1447. fatal_lang_error('pm_not_yours', false);
  1448. $row_quoted = $smcFunc['db_fetch_assoc']($request);
  1449. $smcFunc['db_free_result']($request);
  1450. // Censor the message.
  1451. censorText($row_quoted['subject']);
  1452. censorText($row_quoted['body']);
  1453. // Add 'Re: ' to it....
  1454. if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix')))
  1455. {
  1456. if ($language === $user_info['language'])
  1457. $context['response_prefix'] = $txt['response_prefix'];
  1458. else
  1459. {
  1460. loadLanguage('index', $language, false);
  1461. $context['response_prefix'] = $txt['response_prefix'];
  1462. loadLanguage('index');
  1463. }
  1464. cache_put_data('response_prefix', $context['response_prefix'], 600);
  1465. }
  1466. $form_subject = $row_quoted['subject'];
  1467. if ($context['reply'] && trim($context['response_prefix']) != '' && $smcFunc['strpos']($form_subject, trim($context['response_prefix'])) !== 0)
  1468. $form_subject = $context['response_prefix'] . $form_subject;
  1469. if (isset($_REQUEST['quote']))
  1470. {
  1471. // Remove any nested quotes and <br />...
  1472. $form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
  1473. if (!empty($modSettings['removeNestedQuotes']))
  1474. $form_message = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $form_message);
  1475. if (empty($row_quoted['id_member']))
  1476. $form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
  1477. else
  1478. $form_message = '[quote author=' . $row_quoted['real_name'] . ' link=action=profile;u=' . $row_quoted['id_member'] . ' date=' . $row_quoted['msgtime'] . ']' . "\n" . $form_message . "\n" . '[/quote]';
  1479. }
  1480. else
  1481. $form_message = '';
  1482. // Do the BBC thang on the message.
  1483. $row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
  1484. // Set up the quoted message array.
  1485. $context['quoted_message'] = array(
  1486. 'id' => $row_quoted['id_pm'],
  1487. 'pm_head' => $row_quoted['pm_head'],
  1488. 'member' => array(
  1489. 'name' => $row_quoted['real_name'],
  1490. 'username' => $row_quoted['member_name'],
  1491. 'id' => $row_quoted['id_member'],
  1492. 'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
  1493. 'link' => !empty($row_quoted['id_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row_quoted['id_member'] . '">' . $row_quoted['real_name'] . '</a>' : $row_quoted['real_name'],
  1494. ),
  1495. 'subject' => $row_quoted['subject'],
  1496. 'time' => timeformat($row_quoted['msgtime']),
  1497. 'timestamp' => forum_time(true, $row_quoted['msgtime']),
  1498. 'body' => $row_quoted['body']
  1499. );
  1500. }
  1501. else
  1502. {
  1503. $context['quoted_message'] = false;
  1504. $form_subject = '';
  1505. $form_message = '';
  1506. }
  1507. $context['recipients'] = array(
  1508. 'to' => array(),
  1509. 'bcc' => array(),
  1510. );
  1511. // Sending by ID? Replying to all? Fetch the real_name(s).
  1512. if (isset($_REQUEST['u']))
  1513. {
  1514. // If the user is replying to all, get all the other members this was sent to..
  1515. if ($_REQUEST['u'] == 'all' && isset($row_quoted))
  1516. {
  1517. // Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
  1518. if ($row_quoted['id_member'] != $user_info['id'])
  1519. $context['recipients']['to'][] = array(
  1520. 'id' => $row_quoted['id_member'],
  1521. 'name' => htmlspecialchars($row_quoted['real_name']),
  1522. );
  1523. // Now to get the others.
  1524. $request = $smcFunc['db_query']('', '
  1525. SELECT mem.id_member, mem.real_name
  1526. FROM {db_prefix}pm_recipients AS pmr
  1527. INNER JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
  1528. WHERE pmr.id_pm = {int:id_pm}
  1529. AND pmr.id_member != {int:current_member}
  1530. AND pmr.bcc = {int:not_bcc}',
  1531. array(
  1532. 'current_member' => $user_info['id'],
  1533. 'id_pm' => $pmsg,
  1534. 'not_bcc' => 0,
  1535. )
  1536. );
  1537. while ($row = $smcFunc['db_fetch_assoc']($request))
  1538. $context['recipients']['to'][] = array(
  1539. 'id' => $row['id_member'],
  1540. 'name' => $row['real_name'],
  1541. );
  1542. $smcFunc['db_free_result']($request);
  1543. }
  1544. else
  1545. {
  1546. $_REQUEST['u'] = explode(',', $_REQUEST['u']);
  1547. foreach ($_REQUEST['u'] as $key => $uID)
  1548. $_REQUEST['u'][$key] = (int) $uID;
  1549. $_REQUEST['u'] = array_unique($_REQUEST['u']);
  1550. $request = $smcFunc['db_query']('', '
  1551. SELECT id_member, real_name
  1552. FROM {db_prefix}members
  1553. WHERE id_member IN ({array_int:member_list})
  1554. LIMIT ' . count($_REQUEST['u']),
  1555. array(
  1556. 'member_list' => $_REQUEST['u'],
  1557. )
  1558. );
  1559. while ($row = $smcFunc['db_fetch_assoc']($request))
  1560. $context['recipients']['to'][] = array(
  1561. 'id' => $row['id_member'],
  1562. 'name' => $row['real_name'],
  1563. );
  1564. $smcFunc['db_free_result']($request);
  1565. }
  1566. // Get a literal name list in case the user has JavaScript disabled.
  1567. $names = array();
  1568. foreach ($context['recipients']['to'] as $to)
  1569. $names[] = $to['name'];
  1570. $context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
  1571. }
  1572. else
  1573. $context['to_value'] = '';
  1574. // Set the defaults...
  1575. $context['subject'] = $form_subject != '' ? $form_subject : $txt['no_subject'];
  1576. $context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
  1577. $context['post_error'] = array();
  1578. $context['copy_to_outbox'] = !empty($options['copy_to_outbox']);
  1579. // And build the link tree.
  1580. $context['linktree'][] = array(
  1581. 'url' => $scripturl . '?action=pm;sa=send',
  1582. 'name' => $txt['new_message']
  1583. );
  1584. $modSettings['disable_wysiwyg'] = !empty($modSettings['disable_wysiwyg']) || empty($modSettings['enableBBC']);
  1585. // Needed for the WYSIWYG editor.
  1586. require_once($sourcedir . '/Subs-Editor.php');
  1587. // Now create the editor.
  1588. $editorOptions = array(
  1589. 'id' => 'message',
  1590. 'value' => $context['message'],
  1591. 'height' => '175px',
  1592. 'width' => '100%',
  1593. 'labels' => array(
  1594. 'post_button' => $txt['send_message'],
  1595. ),
  1596. );
  1597. create_control_richedit($editorOptions);
  1598. // Store the ID for old compatibility.
  1599. $context['post_box_name'] = $editorOptions['id'];
  1600. $context['bcc_value'] = '';
  1601. $context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
  1602. if ($context['require_verification'])
  1603. {
  1604. $verificationOptions = array(
  1605. 'id' => 'pm',
  1606. );
  1607. $context['require_verification'] = create_control_verification($verificationOptions);
  1608. $context['visual_verification_id'] = $verificationOptions['id'];
  1609. }
  1610. // Register this form and get a sequence number in $context.
  1611. checkSubmitOnce('register');
  1612. }
  1613. // An error in the message...
  1614. function messagePostError($error_types, $named_recipients, $recipient_ids = array())
  1615. {
  1616. global $txt, $context, $scripturl, $modSettings;
  1617. global $smcFunc, $user_info, $sourcedir;
  1618. $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send';
  1619. if (!WIRELESS)
  1620. $context['sub_template'] = 'send';
  1621. $context['page_title'] = $txt['send_message'];
  1622. // Got some known members?
  1623. $context['recipients'] = array(
  1624. 'to' => array(),
  1625. 'bcc' => array(),
  1626. );
  1627. if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
  1628. {
  1629. $allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
  1630. $request = $smcFunc['db_query']('', '
  1631. SELECT id_member, real_name
  1632. FROM {db_prefix}members
  1633. WHERE id_member IN ({array_int:member_list})',
  1634. array(
  1635. 'member_list' => $allRecipients,
  1636. )
  1637. );
  1638. while ($row = $smcFunc['db_fetch_assoc']($request))
  1639. {
  1640. $recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
  1641. $context['recipients'][$recipientType][] = array(
  1642. 'id' => $row['id_member'],
  1643. 'name' => $row['real_name'],
  1644. );
  1645. }
  1646. $smcFunc['db_free_result']($request);
  1647. }
  1648. // Set everything up like before....
  1649. $context['subject'] = isset($_REQUEST['subject']) ? $smcFunc['htmlspecialchars']($_REQUEST['subject']) : '';
  1650. $context['message'] = isset($_REQUEST['message']) ? str_replace(array(' '), array('&nbsp; '), $smcFunc['htmlspecialchars']($_REQUEST['message'])) : '';
  1651. $context['copy_to_outbox'] = !empty($_REQUEST['outbox']);
  1652. $context['reply'] = !empty($_REQUEST['replied_to']);
  1653. if ($context['reply'])
  1654. {
  1655. $_REQUEST['replied_to'] = (int) $_REQUEST['replied_to'];
  1656. $request = $smcFunc['db_query']('', '
  1657. SELECT
  1658. pm.id_pm, CASE WHEN pm.id_pm_head = {int:no_id_pm_head} THEN pm.id_pm ELSE pm.id_pm_head END AS pm_head,
  1659. pm.body, pm.subject, pm.msgtime, mem.member_name, IFNULL(mem.id_member, 0) AS id_member,
  1660. IFNULL(mem.real_name, pm.from_name) AS real_name
  1661. FROM {db_prefix}personal_messages AS pm' . ($context['folder'] == 'sent' ? '' : '
  1662. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = {int:replied_to})') . '
  1663. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
  1664. WHERE pm.id_pm = {int:replied_to}' . ($context['folder'] == 'sent' ? '
  1665. AND pm.id_member_from = {int:current_member}' : '
  1666. AND pmr.id_member = {int:current_member}') . '
  1667. LIMIT 1',
  1668. array(
  1669. 'current_member' => $user_info['id'],
  1670. 'no_id_pm_head' => 0,
  1671. 'replied_to' => $_REQUEST['replied_to'],
  1672. )
  1673. );
  1674. if ($smcFunc['db_num_rows']($request) == 0)
  1675. fatal_lang_error('pm_not_yours', false);
  1676. $row_quoted = $smcFunc['db_fetch_assoc']($request);
  1677. $smcFunc['db_free_result']($request);
  1678. censorText($row_quoted['subject']);
  1679. censorText($row_quoted['body']);
  1680. $context['quoted_message'] = array(
  1681. 'id' => $row_quoted['id_pm'],
  1682. 'pm_head' => $row_quoted['pm_head'],
  1683. 'member' => array(
  1684. 'name' => $row_quoted['real_name'],
  1685. 'username' => $row_quoted['member_name'],
  1686. 'id' => $row_quoted['id_member'],
  1687. 'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '',
  1688. 'link' => !empty($row_quoted['id_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row_quoted['id_member'] . '">' . $row_quoted['real_name'] . '</a>' : $row_quoted['real_name'],
  1689. ),
  1690. 'subject' => $row_quoted['subject'],
  1691. 'time' => timeformat($row_quoted['msgtime']),
  1692. 'timestamp' => forum_time(true, $row_quoted['msgtime']),
  1693. 'body' => parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']),
  1694. );
  1695. }
  1696. // Build the link tree....
  1697. $context['linktree'][] = array(
  1698. 'url' => $scripturl . '?action=pm;sa=send',
  1699. 'name' => $txt['new_message']
  1700. );
  1701. // Set each of the errors for the template.
  1702. loadLanguage('Errors');
  1703. $context['post_error'] = array(
  1704. 'messages' => array(),
  1705. );
  1706. foreach ($error_types as $error_type)
  1707. {
  1708. $context['post_error'][$error_type] = true;
  1709. if (isset($txt['error_' . $error_type]))
  1710. {
  1711. if ($error_type == 'long_message')
  1712. $txt['error_' . $error_type] = sprintf($txt['error_' . $error_type], $modSettings['max_messageLength']);
  1713. $context['post_error']['messages'][] = $txt['error_' . $error_type];
  1714. }
  1715. }
  1716. // We need to load the editor once more.
  1717. require_once($sourcedir . '/Subs-Editor.php');
  1718. // Create it...
  1719. $editorOptions = array(
  1720. 'id' => 'message',
  1721. 'value' => $context['message'],
  1722. 'width' => '90%',
  1723. 'labels' => array(
  1724. 'post_button' => $txt['send_message'],
  1725. ),
  1726. );
  1727. create_control_richedit($editorOptions);
  1728. // ... and store the ID again...
  1729. $context['post_box_name'] = $editorOptions['id'];
  1730. // Check whether we need to show the code again.
  1731. $context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
  1732. if ($context['require_verification'])
  1733. {
  1734. require_once($sourcedir . '/Subs-Editor.php');
  1735. $verificationOptions = array(
  1736. 'id' => 'pm',
  1737. );
  1738. $context['require_verification'] = create_control_verification($verificationOptions);
  1739. $context['visual_verification_id'] = $verificationOptions['id'];
  1740. }
  1741. $context['to_value'] = empty($named_recipients['to']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['to']) . '&quot;';
  1742. $context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '&quot;' . implode('&quot;, &quot;', $named_recipients['bcc']) . '&quot;';
  1743. // No check for the previous submission is needed.
  1744. checkSubmitOnce('free');
  1745. // Acquire a new form sequence number.
  1746. checkSubmitOnce('register');
  1747. }
  1748. // Send it!
  1749. function MessagePost2()
  1750. {
  1751. global $txt, $context, $sourcedir;
  1752. global $user_info, $modSettings, $scripturl, $smcFunc;
  1753. isAllowedTo('pm_send');
  1754. require_once($sourcedir . '/Subs-Auth.php');
  1755. loadLanguage('PersonalMessage', '', false);
  1756. // Extract out the spam settings - it saves database space!
  1757. list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
  1758. // Check whether we've gone over the limit of messages we can send per hour - fatal error if fails!
  1759. if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1')
  1760. {
  1761. // How many have they sent this last hour?
  1762. $request = $smcFunc['db_query']('', '
  1763. SELECT COUNT(pr.id_pm) AS post_count
  1764. FROM {db_prefix}personal_messages AS pm
  1765. INNER JOIN {db_prefix}pm_recipients AS pr ON (pr.id_pm = pm.id_pm)
  1766. WHERE pm.id_member_from = {int:current_member}
  1767. AND pm.msgtime > {int:msgtime}',
  1768. array(
  1769. 'current_member' => $user_info['id'],
  1770. 'msgtime' => time() - 3600,
  1771. )
  1772. );
  1773. list ($postCount) = $smcFunc['db_fetch_row']($request);
  1774. $smcFunc['db_free_result']($request);
  1775. if (!empty($postCount) && $postCount >= $modSettings['pm_posts_per_hour'])
  1776. fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
  1777. }
  1778. // If we came from WYSIWYG then turn it back into BBC regardless.
  1779. if (!empty($_POST['message_mode']) && isset($_POST['message']))
  1780. {
  1781. require_once($sourcedir . '/Subs-Editor.php');
  1782. $_POST['message'] = html_to_bbc($_POST['message']);
  1783. // We need to unhtml it now as it gets done shortly.
  1784. $_POST['message'] = un_htmlspecialchars($_POST['message']);
  1785. // We need this in case of errors etc.
  1786. $_REQUEST['message'] = $_POST['message'];
  1787. }
  1788. // Initialize the errors we're about to make.
  1789. $post_errors = array();
  1790. // If your session timed out, show an error, but do allow to re-submit.
  1791. if (checkSession('post', '', false) != '')
  1792. $post_errors[] = 'session_timeout';
  1793. $_REQUEST['subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
  1794. $_REQUEST['to'] = empty($_POST['to']) ? (empty($_GET['to']) ? '' : $_GET['to']) : $_POST['to'];
  1795. $_REQUEST['bcc'] = empty($_POST['bcc']) ? (empty($_GET['bcc']) ? '' : $_GET['bcc']) : $_POST['bcc'];
  1796. // Route the input from the 'u' parameter to the 'to'-list.
  1797. if (!empty($_POST['u']))
  1798. $_POST['recipient_to'] = explode(',', $_POST['u']);
  1799. // Construct the list of recipients.
  1800. $recipientList = array();
  1801. $namedRecipientList = array();
  1802. $namesNotFound = array();
  1803. foreach (array('to', 'bcc') as $recipientType)
  1804. {
  1805. // First, let's see if there's user ID's given.
  1806. $recipientList[$recipientType] = array();
  1807. if (!empty($_POST['recipient_' . $recipientType]) && is_array($_POST['recipient_' . $recipientType]))
  1808. {
  1809. foreach ($_POST['recipient_' . $recipientType] as $recipient)
  1810. $recipientList[$recipientType][] = (int) $recipient;
  1811. }
  1812. // Are there also literal names set?
  1813. if (!empty($_REQUEST[$recipientType]))
  1814. {
  1815. // We're going to take out the "s anyway ;).
  1816. $recipientString = strtr($_REQUEST[$recipientType], array('\\"' => '"'));
  1817. preg_match_all('~"([^"]+)"~', $recipientString, $matches);
  1818. $namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))));
  1819. foreach ($namedRecipientList[$recipientType] as $index => $recipient)
  1820. {
  1821. if (strlen(trim($recipient)) > 0)
  1822. $namedRecipientList[$recipientType][$index] = $smcFunc['htmlspecialchars']($smcFunc['strtolower'](trim($recipient)));
  1823. else
  1824. unset($namedRecipientList[$recipientType][$index]);
  1825. }
  1826. if (!empty($namedRecipientList[$recipientType]))
  1827. {
  1828. $foundMembers = findMembers($namedRecipientList[$recipientType]);
  1829. // Assume all are not found, until proven otherwise.
  1830. $namesNotFound[$recipientType] = $namedRecipientList[$recipientType];
  1831. foreach ($foundMembers as $member)
  1832. {
  1833. $testNames = array(
  1834. $smcFunc['strtolower']($member['username']),
  1835. $smcFunc['strtolower']($member['name']),
  1836. $smcFunc['strtolower']($member['email']),
  1837. );
  1838. if (count(array_intersect($testNames, $namedRecipientList[$recipientType])) !== 0)
  1839. {
  1840. $recipientList[$recipientType][] = $member['id'];
  1841. // Get rid of this username, since we found it.
  1842. $namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames);
  1843. }
  1844. }
  1845. }
  1846. }
  1847. // Selected a recipient to be deleted? Remove them now.
  1848. if (!empty($_POST['delete_recipient']))
  1849. $recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $_POST['delete_recipient']));
  1850. // Make sure we don't include the same name twice
  1851. $recipientList[$recipientType] = array_unique($recipientList[$recipientType]);
  1852. }
  1853. // Are we changing the recipients some how?
  1854. $is_recipient_change = !empty($_POST['delete_recipient']) || !empty($_POST['to_submit']) || !empty($_POST['bcc_submit']);
  1855. // Check if there's at least one recipient.
  1856. if (empty($recipientList['to']) && empty($recipientList['bcc']))
  1857. $post_errors[] = 'no_to';
  1858. // Make sure that we remove the members who did get it from the screen.
  1859. if (!$is_recipient_change)
  1860. {
  1861. foreach ($recipientList as $recipientType => $dummy)
  1862. {
  1863. if (!empty($namesNotFound[$recipientType]))
  1864. {
  1865. $post_errors[] = 'bad_' . $recipientType;
  1866. // Since we already have a post error, remove the previous one.
  1867. $post_errors = array_diff($post_errors, array('no_to'));
  1868. foreach ($namesNotFound[$recipientType] as $name)
  1869. $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
  1870. }
  1871. }
  1872. }
  1873. // Did they make any mistakes?
  1874. if ($_REQUEST['subject'] == '')
  1875. $post_errors[] = 'no_subject';
  1876. if (!isset($_REQUEST['message']) || $_REQUEST['message'] == '')
  1877. $post_errors[] = 'no_message';
  1878. elseif (!empty($modSettings['max_messageLength']) && $smcFunc['strlen']($_REQUEST['message']) > $modSettings['max_messageLength'])
  1879. $post_errors[] = 'long_message';
  1880. else
  1881. {
  1882. // Preparse the message.
  1883. $message = $_REQUEST['message'];
  1884. preparsecode($message);
  1885. // Make sure there's still some content left without the tags.
  1886. if ($smcFunc['htmltrim'](strip_tags(parse_bbc($smcFunc['htmlspecialchars']($message, ENT_QUOTES), false), '<img>')) === '' && (!allowedTo('admin_forum') || strpos($message, '[html]') === false))
  1887. $post_errors[] = 'no_message';
  1888. }
  1889. // Wrong verification code?
  1890. if (!$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'])
  1891. {
  1892. require_once($sourcedir . '/Subs-Editor.php');
  1893. $verificationOptions = array(
  1894. 'id' => 'pm',
  1895. );
  1896. $context['require_verification'] = create_control_verification($verificationOptions, true);
  1897. if (is_array($context['require_verification']))
  1898. {
  1899. $post_errors = array_merge($post_errors, $context['require_verification']);
  1900. }
  1901. }
  1902. // If they did, give a chance to make ammends.
  1903. if (!empty($post_errors) && !$is_recipient_change && !isset($_REQUEST['preview']))
  1904. return messagePostError($post_errors, $namedRecipientList, $recipientList);
  1905. // Want to take a second glance before you send?
  1906. if (isset($_REQUEST['preview']))
  1907. {
  1908. // Set everything up to be displayed.
  1909. $context['preview_subject'] = $smcFunc['htmlspecialchars']($_REQUEST['subject']);
  1910. $context['preview_message'] = $smcFunc['htmlspecialchars']($_REQUEST['message'], ENT_QUOTES);
  1911. preparsecode($context['preview_message'], true);
  1912. // Parse out the BBC if it is enabled.
  1913. $context['preview_message'] = parse_bbc($context['preview_message']);
  1914. // Censor, as always.
  1915. censorText($context['preview_subject']);
  1916. censorText($context['preview_message']);
  1917. // Set a descriptive title.
  1918. $context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject'];
  1919. // Pretend they messed up but don't ignore if they really did :P.
  1920. return messagePostError($post_errors, $namedRecipientList, $recipientList);
  1921. }
  1922. // Adding a recipient cause javascript ain't working?
  1923. elseif ($is_recipient_change)
  1924. {
  1925. // Maybe we couldn't find one?
  1926. foreach ($namesNotFound as $recipientType => $names)
  1927. {
  1928. $post_errors[] = 'bad_' . $recipientType;
  1929. foreach ($names as $name)
  1930. $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name);
  1931. }
  1932. return messagePostError(array(), $namedRecipientList, $recipientList);
  1933. }
  1934. // Before we send the PM, let's make sure we don't have an abuse of numbers.
  1935. elseif (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum')))
  1936. {
  1937. $context['send_log'] = array(
  1938. 'sent' => array(),
  1939. 'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])),
  1940. );
  1941. return messagePostError($post_errors, $namedRecipientList, $recipientList);
  1942. }
  1943. // Protect from message spamming.
  1944. spamProtection('pm');
  1945. // Prevent double submission of this form.
  1946. checkSubmitOnce('check');
  1947. // Do the actual sending of the PM.
  1948. if (!empty($recipientList['to']) || !empty($recipientList['bcc']))
  1949. $context['send_log'] = sendpm($recipientList, $_REQUEST['subject'], $_REQUEST['message'], !empty($_REQUEST['outbox']), null, !empty($_REQUEST['pm_head']) ? (int) $_REQUEST['pm_head'] : 0);
  1950. else
  1951. $context['send_log'] = array(
  1952. 'sent' => array(),
  1953. 'failed' => array()
  1954. );
  1955. // Mark the message as "replied to".
  1956. if (!empty($context['send_log']['sent']) && !empty($_REQUEST['replied_to']) && isset($_REQUEST['f']) && $_REQUEST['f'] == 'inbox')
  1957. {
  1958. $smcFunc['db_query']('', '
  1959. UPDATE {db_prefix}pm_recipients
  1960. SET is_read = is_read | 2
  1961. WHERE id_pm = {int:replied_to}
  1962. AND id_member = {int:current_member}',
  1963. array(
  1964. 'current_member' => $user_info['id'],
  1965. 'replied_to' => (int) $_REQUEST['replied_to'],
  1966. )
  1967. );
  1968. }
  1969. // If one or more of the recipient were invalid, go back to the post screen with the failed usernames.
  1970. if (!empty($context['send_log']['failed']))
  1971. return messagePostError($post_errors, $namesNotFound, array(
  1972. 'to' => array_intersect($recipientList['to'], $context['send_log']['failed']),
  1973. 'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed'])
  1974. ));
  1975. // Message sent successfully?
  1976. if (!empty($context['send_log']) && empty($context['send_log']['failed']))
  1977. $context['current_label_redirect'] = $context['current_label_redirect'] . ';done=sent';
  1978. // Go back to the where they sent from, if possible...
  1979. redirectexit($context['current_label_redirect']);
  1980. }
  1981. // This function lists all buddies for wireless protocols.
  1982. function WirelessAddBuddy()
  1983. {
  1984. global $scripturl, $txt, $user_info, $context, $smcFunc;
  1985. isAllowedTo('pm_send');
  1986. $context['page_title'] = $txt['wireless_pm_add_buddy'];
  1987. $current_buddies = empty($_REQUEST['u']) ? array() : explode(',', $_REQUEST['u']);
  1988. foreach ($current_buddies as $key => $buddy)
  1989. $current_buddies[$key] = (int) $buddy;
  1990. $base_url = $scripturl . '?action=pm;sa=send;u=' . (empty($current_buddies) ? '' : implode(',', $current_buddies) . ',');
  1991. $context['pm_href'] = $scripturl . '?action=pm;sa=send' . (empty($current_buddies) ? '' : ';u=' . implode(',', $current_buddies));
  1992. $context['buddies'] = array();
  1993. if (!empty($user_info['buddies']))
  1994. {
  1995. $request = $smcFunc['db_query']('', '
  1996. SELECT id_member, real_name
  1997. FROM {db_prefix}members
  1998. WHERE id_member IN ({array_int:buddy_list})
  1999. ORDER BY real_name
  2000. LIMIT ' . count($user_info['buddies']),
  2001. array(
  2002. 'buddy_list' => $user_info['buddies'],
  2003. )
  2004. );
  2005. while ($row = $smcFunc['db_fetch_assoc']($request))
  2006. $context['buddies'][] = array(
  2007. 'id' => $row['id_member'],
  2008. 'name' => $row['real_name'],
  2009. 'selected' => in_array($row['id_member'], $current_buddies),
  2010. 'add_href' => $base_url . $row['id_member'],
  2011. );
  2012. $smcFunc['db_free_result']($request);
  2013. }
  2014. }
  2015. // This function performs all additional stuff...
  2016. function MessageActionsApply()
  2017. {
  2018. global $txt, $context, $user_info, $options, $smcFunc;
  2019. checkSession('request');
  2020. if (isset($_REQUEST['del_selected']))
  2021. $_REQUEST['pm_action'] = 'delete';
  2022. if (isset($_REQUEST['pm_action']) && $_REQUEST['pm_action'] != '' && !empty($_REQUEST['pms']) && is_array($_REQUEST['pms']))
  2023. {
  2024. foreach ($_REQUEST['pms'] as $pm)
  2025. $_REQUEST['pm_actions'][(int) $pm] = $_REQUEST['pm_action'];
  2026. }
  2027. if (empty($_REQUEST['pm_actions']))
  2028. redirectexit($context['current_label_redirect']);
  2029. // If we are in conversation, we may need to apply this to every message in the conversation.
  2030. if ($context['display_mode'] == 2 && isset($_REQUEST['conversation']))
  2031. {
  2032. $id_pms = array();
  2033. foreach ($_REQUEST['pm_actions'] as $pm => $dummy)
  2034. $id_pms[] = (int) $pm;
  2035. $request = $smcFunc['db_query']('', '
  2036. SELECT id_pm_head, id_pm
  2037. FROM {db_prefix}personal_messages
  2038. WHERE id_pm IN ({array_int:id_pms})',
  2039. array(
  2040. 'id_pms' => $id_pms,
  2041. )
  2042. );
  2043. $pm_heads = array();
  2044. while ($row = $smcFunc['db_fetch_assoc']($request))
  2045. $pm_heads[$row['id_pm_head']] = $row['id_pm'];
  2046. $smcFunc['db_free_result']($request);
  2047. $request = $smcFunc['db_query']('', '
  2048. SELECT id_pm, id_pm_head
  2049. FROM {db_prefix}personal_messages
  2050. WHERE id_pm_head IN ({array_int:pm_heads})',
  2051. array(
  2052. 'pm_heads' => array_keys($pm_heads),
  2053. )
  2054. );
  2055. // Copy the action from the single to PM to the others.
  2056. while ($row = $smcFunc['db_fetch_assoc']($request))
  2057. {
  2058. if (isset($pm_heads[$row['id_pm_head']]) && isset($_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]]))
  2059. $_REQUEST['pm_actions'][$row['id_pm']] = $_REQUEST['pm_actions'][$pm_heads[$row['id_pm_head']]];
  2060. }
  2061. $smcFunc['db_free_result']($request);
  2062. }
  2063. $to_delete = array();
  2064. $to_label = array();
  2065. $label_type = array();
  2066. foreach ($_REQUEST['pm_actions'] as $pm => $action)
  2067. {
  2068. if ($action === 'delete')
  2069. $to_delete[] = (int) $pm;
  2070. else
  2071. {
  2072. if (substr($action, 0, 4) == 'add_')
  2073. {
  2074. $type = 'add';
  2075. $action = substr($action, 4);
  2076. }
  2077. elseif (substr($action, 0, 4) == 'rem_')
  2078. {
  2079. $type = 'rem';
  2080. $action = substr($action, 4);
  2081. }
  2082. else
  2083. $type = 'unk';
  2084. if ($action == '-1' || $action == '0' || (int) $action > 0)
  2085. {
  2086. $to_label[(int) $pm] = (int) $action;
  2087. $label_type[(int) $pm] = $type;
  2088. }
  2089. }
  2090. }
  2091. // Deleting, it looks like?
  2092. if (!empty($to_delete))
  2093. deleteMessages($to_delete, $context['display_mode'] == 2 ? null : $context['folder']);
  2094. // Are we labeling anything?
  2095. if (!empty($to_label) && $context['folder'] == 'inbox')
  2096. {
  2097. $updateErrors = 0;
  2098. // Get information about each message...
  2099. $request = $smcFunc['db_query']('', '
  2100. SELECT id_pm, labels
  2101. FROM {db_prefix}pm_recipients
  2102. WHERE id_member = {int:current_member}
  2103. AND id_pm IN ({array_int:to_label})
  2104. LIMIT ' . count($to_label),
  2105. array(
  2106. 'current_member' => $user_info['id'],
  2107. 'to_label' => array_keys($to_label),
  2108. )
  2109. );
  2110. while ($row = $smcFunc['db_fetch_assoc']($request))
  2111. {
  2112. $labels = $row['labels'] == '' ? array('-1') : explode(',', trim($row['labels']));
  2113. // Already exists? Then... unset it!
  2114. $ID_LABEL = array_search($to_label[$row['id_pm']], $labels);
  2115. if ($ID_LABEL !== false && $label_type[$row['id_pm']] !== 'add')
  2116. unset($labels[$ID_LABEL]);
  2117. elseif ($label_type[$row['id_pm']] !== 'rem')
  2118. $labels[] = $to_label[$row['id_pm']];
  2119. if (!empty($options['pm_remove_inbox_label']) && $to_label[$row['id_pm']] != '-1' && ($key = array_search('-1', $labels)) !== false)
  2120. unset($labels[$key]);
  2121. $set = implode(',', array_unique($labels));
  2122. if ($set == '')
  2123. $set = '-1';
  2124. // Check that this string isn't going to be too large for the database.
  2125. if ($set > 60)
  2126. $updateErrors++;
  2127. else
  2128. {
  2129. $smcFunc['db_query']('', '
  2130. UPDATE {db_prefix}pm_recipients
  2131. SET labels = {string:labels}
  2132. WHERE id_pm = {int:id_pm}
  2133. AND id_member = {int:current_member}',
  2134. array(
  2135. 'current_member' => $user_info['id'],
  2136. 'id_pm' => $row['id_pm'],
  2137. 'labels' => $set,
  2138. )
  2139. );
  2140. }
  2141. }
  2142. $smcFunc['db_free_result']($request);
  2143. // Any errors?
  2144. // !!! Separate the sprintf?
  2145. if (!empty($updateErrors))
  2146. fatal_lang_error('labels_too_many', true, array($updateErrors));
  2147. }
  2148. // Back to the folder.
  2149. $_SESSION['pm_selected'] = array_keys($to_label);
  2150. redirectexit($context['current_label_redirect'] . (count($to_label) == 1 ? '#msg' . $_SESSION['pm_selected'][0] : ''), count($to_label) == 1 && $context['browser']['is_ie']);
  2151. }
  2152. // Are you sure you want to PERMANENTLY (mostly) delete ALL your messages?
  2153. function MessageKillAllQuery()
  2154. {
  2155. global $txt, $context;
  2156. // Only have to set up the template....
  2157. $context['sub_template'] = 'ask_delete';
  2158. $context['page_title'] = $txt['delete_all'];
  2159. $context['delete_all'] = $_REQUEST['f'] == 'all';
  2160. // And set the folder name...
  2161. $txt['delete_all'] = str_replace('PMBOX', $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'], $txt['delete_all']);
  2162. }
  2163. // Delete ALL the messages!
  2164. function MessageKillAll()
  2165. {
  2166. global $context;
  2167. checkSession('get');
  2168. // If all then delete all messages the user has.
  2169. if ($_REQUEST['f'] == 'all')
  2170. deleteMessages(null, null);
  2171. // Otherwise just the selected folder.
  2172. else
  2173. deleteMessages(null, $_REQUEST['f'] != 'sent' ? 'inbox' : 'sent');
  2174. // Done... all gone.
  2175. redirectexit($context['current_label_redirect']);
  2176. }
  2177. // This function allows the user to delete all messages older than so many days.
  2178. function MessagePrune()
  2179. {
  2180. global $txt, $context, $user_info, $scripturl, $smcFunc;
  2181. // Actually delete the messages.
  2182. if (isset($_REQUEST['age']))
  2183. {
  2184. checkSession();
  2185. // Calculate the time to delete before.
  2186. $deleteTime = max(0, time() - (86400 * (int) $_REQUEST['age']));
  2187. // Array to store the IDs in.
  2188. $toDelete = array();
  2189. // Select all the messages they have sent older than $deleteTime.
  2190. $request = $smcFunc['db_query']('', '
  2191. SELECT id_pm
  2192. FROM {db_prefix}personal_messages
  2193. WHERE deleted_by_sender = {int:not_deleted}
  2194. AND id_member_from = {int:current_member}
  2195. AND msgtime < {int:msgtime}',
  2196. array(
  2197. 'current_member' => $user_info['id'],
  2198. 'not_deleted' => 0,
  2199. 'msgtime' => $deleteTime,
  2200. )
  2201. );
  2202. while ($row = $smcFunc['db_fetch_row']($request))
  2203. $toDelete[] = $row[0];
  2204. $smcFunc['db_free_result']($request);
  2205. // Select all messages in their inbox older than $deleteTime.
  2206. $request = $smcFunc['db_query']('', '
  2207. SELECT pmr.id_pm
  2208. FROM {db_prefix}pm_recipients AS pmr
  2209. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
  2210. WHERE pmr.deleted = {int:not_deleted}
  2211. AND pmr.id_member = {int:current_member}
  2212. AND pm.msgtime < {int:msgtime}',
  2213. array(
  2214. 'current_member' => $user_info['id'],
  2215. 'not_deleted' => 0,
  2216. 'msgtime' => $deleteTime,
  2217. )
  2218. );
  2219. while ($row = $smcFunc['db_fetch_assoc']($request))
  2220. $toDelete[] = $row['id_pm'];
  2221. $smcFunc['db_free_result']($request);
  2222. // Delete the actual messages.
  2223. deleteMessages($toDelete);
  2224. // Go back to their inbox.
  2225. redirectexit($context['current_label_redirect']);
  2226. }
  2227. // Build the link tree elements.
  2228. $context['linktree'][] = array(
  2229. 'url' => $scripturl . '?action=pm;sa=prune',
  2230. 'name' => $txt['pm_prune']
  2231. );
  2232. $context['sub_template'] = 'prune';
  2233. $context['page_title'] = $txt['pm_prune'];
  2234. }
  2235. // Delete the specified personal messages.
  2236. function deleteMessages($personal_messages, $folder = null, $owner = null)
  2237. {
  2238. global $user_info, $smcFunc;
  2239. if ($owner === null)
  2240. $owner = array($user_info['id']);
  2241. elseif (empty($owner))
  2242. return;
  2243. elseif (!is_array($owner))
  2244. $owner = array($owner);
  2245. if ($personal_messages !== null)
  2246. {
  2247. if (empty($personal_messages) || !is_array($personal_messages))
  2248. return;
  2249. foreach ($personal_messages as $index => $delete_id)
  2250. $personal_messages[$index] = (int) $delete_id;
  2251. $where = '
  2252. AND id_pm IN ({array_int:pm_list})';
  2253. }
  2254. else
  2255. $where = '';
  2256. if ($folder == 'sent' || $folder === null)
  2257. {
  2258. $smcFunc['db_query']('', '
  2259. UPDATE {db_prefix}personal_messages
  2260. SET deleted_by_sender = {int:is_deleted}
  2261. WHERE id_member_from IN ({array_int:member_list})
  2262. AND deleted_by_sender = {int:not_deleted}' . $where,
  2263. array(
  2264. 'member_list' => $owner,
  2265. 'is_deleted' => 1,
  2266. 'not_deleted' => 0,
  2267. 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
  2268. )
  2269. );
  2270. }
  2271. if ($folder != 'sent' || $folder === null)
  2272. {
  2273. // Calculate the number of messages each member's gonna lose...
  2274. $request = $smcFunc['db_query']('', '
  2275. SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read
  2276. FROM {db_prefix}pm_recipients
  2277. WHERE id_member IN ({array_int:member_list})
  2278. AND deleted = {int:not_deleted}' . $where . '
  2279. GROUP BY id_member, is_read',
  2280. array(
  2281. 'member_list' => $owner,
  2282. 'not_deleted' => 0,
  2283. 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
  2284. )
  2285. );
  2286. // ...And update the statistics accordingly - now including unread messages!.
  2287. while ($row = $smcFunc['db_fetch_assoc']($request))
  2288. {
  2289. if ($row['is_read'])
  2290. updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages']));
  2291. else
  2292. updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' . $row['num_deleted_messages'], 'unread_messages' => $where == '' ? 0 : 'unread_messages - ' . $row['num_deleted_messages']));
  2293. // If this is the current member we need to make their message count correct.
  2294. if ($user_info['id'] == $row['id_member'])
  2295. {
  2296. $user_info['messages'] -= $row['num_deleted_messages'];
  2297. if (!($row['is_read']))
  2298. $user_info['unread_messages'] -= $row['num_deleted_messages'];
  2299. }
  2300. }
  2301. $smcFunc['db_free_result']($request);
  2302. // Do the actual deletion.
  2303. $smcFunc['db_query']('', '
  2304. UPDATE {db_prefix}pm_recipients
  2305. SET deleted = {int:is_deleted}
  2306. WHERE id_member IN ({array_int:member_list})
  2307. AND deleted = {int:not_deleted}' . $where,
  2308. array(
  2309. 'member_list' => $owner,
  2310. 'is_deleted' => 1,
  2311. 'not_deleted' => 0,
  2312. 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
  2313. )
  2314. );
  2315. }
  2316. // If sender and recipients all have deleted their message, it can be removed.
  2317. $request = $smcFunc['db_query']('', '
  2318. SELECT pm.id_pm AS sender, pmr.id_pm
  2319. FROM {db_prefix}personal_messages AS pm
  2320. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted})
  2321. WHERE pm.deleted_by_sender = {int:is_deleted}
  2322. ' . str_replace('id_pm', 'pm.id_pm', $where) . '
  2323. GROUP BY sender, pmr.id_pm
  2324. HAVING pmr.id_pm IS null',
  2325. array(
  2326. 'not_deleted' => 0,
  2327. 'is_deleted' => 1,
  2328. 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(),
  2329. )
  2330. );
  2331. $remove_pms = array();
  2332. while ($row = $smcFunc['db_fetch_assoc']($request))
  2333. $remove_pms[] = $row['sender'];
  2334. $smcFunc['db_free_result']($request);
  2335. if (!empty($remove_pms))
  2336. {
  2337. $smcFunc['db_query']('', '
  2338. DELETE FROM {db_prefix}personal_messages
  2339. WHERE id_pm IN ({array_int:pm_list})',
  2340. array(
  2341. 'pm_list' => $remove_pms,
  2342. )
  2343. );
  2344. $smcFunc['db_query']('', '
  2345. DELETE FROM {db_prefix}pm_recipients
  2346. WHERE id_pm IN ({array_int:pm_list})',
  2347. array(
  2348. 'pm_list' => $remove_pms,
  2349. )
  2350. );
  2351. }
  2352. // Any cached numbers may be wrong now.
  2353. cache_put_data('labelCounts:' . $user_info['id'], null, 720);
  2354. }
  2355. // Mark personal messages read.
  2356. function markMessages($personal_messages = null, $label = null, $owner = null)
  2357. {
  2358. global $user_info, $context, $smcFunc;
  2359. if ($owner === null)
  2360. $owner = $user_info['id'];
  2361. $smcFunc['db_query']('', '
  2362. UPDATE {db_prefix}pm_recipients
  2363. SET is_read = is_read | 1
  2364. WHERE id_member = {int:id_member}
  2365. AND NOT (is_read & 1 >= 1)' . ($label === null ? '' : '
  2366. AND FIND_IN_SET({string:label}, labels) != 0') . ($personal_messages !== null ? '
  2367. AND id_pm IN ({array_int:personal_messages})' : ''),
  2368. array(
  2369. 'personal_messages' => $personal_messages,
  2370. 'id_member' => $owner,
  2371. 'label' => $label,
  2372. )
  2373. );
  2374. // If something wasn't marked as read, get the number of unread messages remaining.
  2375. if ($smcFunc['db_affected_rows']() > 0)
  2376. {
  2377. if ($owner == $user_info['id'])
  2378. {
  2379. foreach ($context['labels'] as $label)
  2380. $context['labels'][(int) $label['id']]['unread_messages'] = 0;
  2381. }
  2382. $result = $smcFunc['db_query']('', '
  2383. SELECT labels, COUNT(*) AS num
  2384. FROM {db_prefix}pm_recipients
  2385. WHERE id_member = {int:id_member}
  2386. AND NOT (is_read & 1 >= 1)
  2387. AND deleted = {int:is_not_deleted}
  2388. GROUP BY labels',
  2389. array(
  2390. 'id_member' => $owner,
  2391. 'is_not_deleted' => 0,
  2392. )
  2393. );
  2394. $total_unread = 0;
  2395. while ($row = $smcFunc['db_fetch_assoc']($result))
  2396. {
  2397. $total_unread += $row['num'];
  2398. if ($owner != $user_info['id'])
  2399. continue;
  2400. $this_labels = explode(',', $row['labels']);
  2401. foreach ($this_labels as $this_label)
  2402. $context['labels'][(int) $this_label]['unread_messages'] += $row['num'];
  2403. }
  2404. $smcFunc['db_free_result']($result);
  2405. // Need to store all this.
  2406. cache_put_data('labelCounts:' . $owner, $context['labels'], 720);
  2407. updateMemberData($owner, array('unread_messages' => $total_unread));
  2408. // If it was for the current member, reflect this in the $user_info array too.
  2409. if ($owner == $user_info['id'])
  2410. $user_info['unread_messages'] = $total_unread;
  2411. }
  2412. }
  2413. // This function handles adding, deleting and editing labels on messages.
  2414. function ManageLabels()
  2415. {
  2416. global $txt, $context, $user_info, $scripturl, $smcFunc;
  2417. // Build the link tree elements...
  2418. $context['linktree'][] = array(
  2419. 'url' => $scripturl . '?action=pm;sa=manlabels',
  2420. 'name' => $txt['pm_manage_labels']
  2421. );
  2422. $context['page_title'] = $txt['pm_manage_labels'];
  2423. $context['sub_template'] = 'labels';
  2424. $the_labels = array();
  2425. // Add all existing labels to the array to save, slashing them as necessary...
  2426. foreach ($context['labels'] as $label)
  2427. {
  2428. if ($label['id'] != -1)
  2429. $the_labels[$label['id']] = $label['name'];
  2430. }
  2431. if (isset($_POST[$context['session_var']]))
  2432. {
  2433. checkSession('post');
  2434. // This will be for updating messages.
  2435. $message_changes = array();
  2436. $new_labels = array();
  2437. $rule_changes = array();
  2438. // Will most likely need this.
  2439. LoadRules();
  2440. // Adding a new label?
  2441. if (isset($_POST['add']))
  2442. {
  2443. $_POST['label'] = strtr($smcFunc['htmlspecialchars'](trim($_POST['label'])), array(',' => '&#044;'));
  2444. if ($smcFunc['strlen']($_POST['label']) > 30)
  2445. $_POST['label'] = $smcFunc['substr']($_POST['label'], 0, 30);
  2446. if ($_POST['label'] != '')
  2447. $the_labels[] = $_POST['label'];
  2448. }
  2449. // Deleting an existing label?
  2450. elseif (isset($_POST['delete'], $_POST['delete_label']))
  2451. {
  2452. $i = 0;
  2453. foreach ($the_labels as $id => $name)
  2454. {
  2455. if (isset($_POST['delete_label'][$id]))
  2456. {
  2457. unset($the_labels[$id]);
  2458. $message_changes[$id] = true;
  2459. }
  2460. else
  2461. $new_labels[$id] = $i++;
  2462. }
  2463. }
  2464. // The hardest one to deal with... changes.
  2465. elseif (isset($_POST['save']) && !empty($_POST['label_name']))
  2466. {
  2467. $i = 0;
  2468. foreach ($the_labels as $id => $name)
  2469. {
  2470. if ($id == -1)
  2471. continue;
  2472. elseif (isset($_POST['label_name'][$id]))
  2473. {
  2474. $_POST['label_name'][$id] = trim(strtr($smcFunc['htmlspecialchars']($_POST['label_name'][$id]), array(',' => '&#044;')));
  2475. if ($smcFunc['strlen']($_POST['label_name'][$id]) > 30)
  2476. $_POST['label_name'][$id] = $smcFunc['substr']($_POST['label_name'][$id], 0, 30);
  2477. if ($_POST['label_name'][$id] != '')
  2478. {
  2479. $the_labels[(int) $id] = $_POST['label_name'][$id];
  2480. $new_labels[$id] = $i++;
  2481. }
  2482. else
  2483. {
  2484. unset($the_labels[(int) $id]);
  2485. $message_changes[(int) $id] = true;
  2486. }
  2487. }
  2488. else
  2489. $new_labels[$id] = $i++;
  2490. }
  2491. }
  2492. // Save the label status.
  2493. updateMemberData($user_info['id'], array('message_labels' => implode(',', $the_labels)));
  2494. // Update all the messages currently with any label changes in them!
  2495. if (!empty($message_changes))
  2496. {
  2497. $searchArray = array_keys($message_changes);
  2498. if (!empty($new_labels))
  2499. {
  2500. for ($i = max($searchArray) + 1, $n = max(array_keys($new_labels)); $i <= $n; $i++)
  2501. $searchArray[] = $i;
  2502. }
  2503. // Now find the messages to change.
  2504. $request = $smcFunc['db_query']('', '
  2505. SELECT id_pm, labels
  2506. FROM {db_prefix}pm_recipients
  2507. WHERE FIND_IN_SET({raw:find_label_implode}, labels) != 0
  2508. AND id_member = {int:current_member}',
  2509. array(
  2510. 'current_member' => $user_info['id'],
  2511. 'find_label_implode' => '\'' . implode('\', labels) != 0 OR FIND_IN_SET(\'', $searchArray) . '\'',
  2512. )
  2513. );
  2514. while ($row = $smcFunc['db_fetch_assoc']($request))
  2515. {
  2516. // Do the long task of updating them...
  2517. $toChange = explode(',', $row['labels']);
  2518. foreach ($toChange as $key => $value)
  2519. if (in_array($value, $searchArray))
  2520. {
  2521. if (isset($new_labels[$value]))
  2522. $toChange[$key] = $new_labels[$value];
  2523. else
  2524. unset($toChange[$key]);
  2525. }
  2526. if (empty($toChange))
  2527. $toChange[] = '-1';
  2528. // Update the message.
  2529. $smcFunc['db_query']('', '
  2530. UPDATE {db_prefix}pm_recipients
  2531. SET labels = {string:new_labels}
  2532. WHERE id_pm = {int:id_pm}
  2533. AND id_member = {int:current_member}',
  2534. array(
  2535. 'current_member' => $user_info['id'],
  2536. 'id_pm' => $row['id_pm'],
  2537. 'new_labels' => implode(',', array_unique($toChange)),
  2538. )
  2539. );
  2540. }
  2541. $smcFunc['db_free_result']($request);
  2542. // Now do the same the rules - check through each rule.
  2543. foreach ($context['rules'] as $k => $rule)
  2544. {
  2545. // Each action...
  2546. foreach ($rule['actions'] as $k2 => $action)
  2547. {
  2548. if ($action['t'] != 'lab' || !in_array($action['v'], $searchArray))
  2549. continue;
  2550. $rule_changes[] = $rule['id'];
  2551. // If we're here we have a label which is either changed or gone...
  2552. if (isset($new_labels[$action['v']]))
  2553. $context['rules'][$k]['actions'][$k2]['v'] = $new_labels[$action['v']];
  2554. else
  2555. unset($context['rules'][$k]['actions'][$k2]);
  2556. }
  2557. }
  2558. }
  2559. // If we have rules to change do so now.
  2560. if (!empty($rule_changes))
  2561. {
  2562. $rule_changes = array_unique($rule_changes);
  2563. // Update/delete as appropriate.
  2564. foreach ($rule_changes as $k => $id)
  2565. if (!empty($context['rules'][$id]['actions']))
  2566. {
  2567. $smcFunc['db_query']('', '
  2568. UPDATE {db_prefix}pm_rules
  2569. SET actions = {string:actions}
  2570. WHERE id_rule = {int:id_rule}
  2571. AND id_member = {int:current_member}',
  2572. array(
  2573. 'current_member' => $user_info['id'],
  2574. 'id_rule' => $id,
  2575. 'actions' => serialize($context['rules'][$id]['actions']),
  2576. )
  2577. );
  2578. unset($rule_changes[$k]);
  2579. }
  2580. // Anything left here means it's lost all actions...
  2581. if (!empty($rule_changes))
  2582. $smcFunc['db_query']('', '
  2583. DELETE FROM {db_prefix}pm_rules
  2584. WHERE id_rule IN ({array_int:rule_list})
  2585. AND id_member = {int:current_member}',
  2586. array(
  2587. 'current_member' => $user_info['id'],
  2588. 'rule_list' => $rule_changes,
  2589. )
  2590. );
  2591. }
  2592. // Make sure we're not caching this!
  2593. cache_put_data('labelCounts:' . $user_info['id'], null, 720);
  2594. // To make the changes appear right away, redirect.
  2595. redirectexit('action=pm;sa=manlabels');
  2596. }
  2597. }
  2598. // Edit Personal Message Settings
  2599. function MessageSettings()
  2600. {
  2601. global $txt, $user_settings, $user_info, $context, $sourcedir, $smcFunc;
  2602. global $scripturl, $profile_vars, $cur_profile, $user_profile;
  2603. // Need this for the display.
  2604. require_once($sourcedir . '/Profile.php');
  2605. require_once($sourcedir . '/Profile-Modify.php');
  2606. // We want them to submit back to here.
  2607. $context['profile_custom_submit_url'] = $scripturl . '?action=pm;sa=settings;save';
  2608. loadMemberData($user_info['id'], false, 'profile');
  2609. $cur_profile = $user_profile[$user_info['id']];
  2610. loadLanguage('Profile');
  2611. loadTemplate('Profile');
  2612. $context['page_title'] = $txt['pm_settings'];
  2613. $context['user']['is_owner'] = true;
  2614. $context['id_member'] = $user_info['id'];
  2615. $context['require_password'] = false;
  2616. $context['menu_item_selected'] = 'settings';
  2617. $context['submit_button_text'] = $txt['pm_settings'];
  2618. $context['profile_header_text'] = $txt['personal_messages'];
  2619. // Add our position to the linktree.
  2620. $context['linktree'][] = array(
  2621. 'url' => $scripturl . '?action=pm;sa=settings',
  2622. 'name' => $txt['pm_settings']
  2623. );
  2624. // Are they saving?
  2625. if (isset($_REQUEST['save']))
  2626. {
  2627. checkSession('post');
  2628. // Mimic what profile would do.
  2629. $_POST = htmltrim__recursive($_POST);
  2630. $_POST = htmlspecialchars__recursive($_POST);
  2631. // Save the fields.
  2632. saveProfileFields();
  2633. if (!empty($profile_vars))
  2634. updateMemberData($user_info['id'], $profile_vars);
  2635. }
  2636. // Load up the fields.
  2637. pmprefs($user_info['id']);
  2638. }
  2639. // Allows a user to report a personal message they receive to the administrator.
  2640. function ReportMessage()
  2641. {
  2642. global $txt, $context, $scripturl, $sourcedir;
  2643. global $user_info, $language, $modSettings, $smcFunc;
  2644. // Check that this feature is even enabled!
  2645. if (empty($modSettings['enableReportPM']) || empty($_REQUEST['pmsg']))
  2646. fatal_lang_error('no_access', false);
  2647. $pmsg = (int) $_REQUEST['pmsg'];
  2648. if (!isAccessiblePM($pmsg, 'inbox'))
  2649. fatal_lang_error('no_access', false);
  2650. $context['pm_id'] = $pmsg;
  2651. $context['page_title'] = $txt['pm_report_title'];
  2652. // If we're here, just send the user to the template, with a few useful context bits.
  2653. if (!isset($_POST['report']))
  2654. {
  2655. $context['sub_template'] = 'report_message';
  2656. // !!! I don't like being able to pick who to send it to. Favoritism, etc. sucks.
  2657. // Now, get all the administrators.
  2658. $request = $smcFunc['db_query']('', '
  2659. SELECT id_member, real_name
  2660. FROM {db_prefix}members
  2661. WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0
  2662. ORDER BY real_name',
  2663. array(
  2664. 'admin_group' => 1,
  2665. )
  2666. );
  2667. $context['admins'] = array();
  2668. while ($row = $smcFunc['db_fetch_assoc']($request))
  2669. $context['admins'][$row['id_member']] = $row['real_name'];
  2670. $smcFunc['db_free_result']($request);
  2671. // How many admins in total?
  2672. $context['admin_count'] = count($context['admins']);
  2673. }
  2674. // Otherwise, let's get down to the sending stuff.
  2675. else
  2676. {
  2677. // Check the session before proceeding any further!
  2678. checkSession('post');
  2679. // First, pull out the message contents, and verify it actually went to them!
  2680. $request = $smcFunc['db_query']('', '
  2681. SELECT pm.subject, pm.body, pm.msgtime, pm.id_member_from, IFNULL(m.real_name, pm.from_name) AS sender_name
  2682. FROM {db_prefix}personal_messages AS pm
  2683. INNER JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm)
  2684. LEFT JOIN {db_prefix}members AS m ON (m.id_member = pm.id_member_from)
  2685. WHERE pm.id_pm = {int:id_pm}
  2686. AND pmr.id_member = {int:current_member}
  2687. AND pmr.deleted = {int:not_deleted}
  2688. LIMIT 1',
  2689. array(
  2690. 'current_member' => $user_info['id'],
  2691. 'id_pm' => $context['pm_id'],
  2692. 'not_deleted' => 0,
  2693. )
  2694. );
  2695. // Can only be a hacker here!
  2696. if ($smcFunc['db_num_rows']($request) == 0)
  2697. fatal_lang_error('no_access', false);
  2698. list ($subject, $body, $time, $memberFromID, $memberFromName) = $smcFunc['db_fetch_row']($request);
  2699. $smcFunc['db_free_result']($request);
  2700. // Remove the line breaks...
  2701. $body = preg_replace('~<br ?/?' . '>~i', "\n", $body);
  2702. // Get any other recipients of the email.
  2703. $request = $smcFunc['db_query']('', '
  2704. SELECT mem_to.id_member AS id_member_to, mem_to.real_name AS to_name, pmr.bcc
  2705. FROM {db_prefix}pm_recipients AS pmr
  2706. LEFT JOIN {db_prefix}members AS mem_to ON (mem_to.id_member = pmr.id_member)
  2707. WHERE pmr.id_pm = {int:id_pm}
  2708. AND pmr.id_member != {int:current_member}',
  2709. array(
  2710. 'current_member' => $user_info['id'],
  2711. 'id_pm' => $context['pm_id'],
  2712. )
  2713. );
  2714. $recipients = array();
  2715. $hidden_recipients = 0;
  2716. while ($row = $smcFunc['db_fetch_assoc']($request))
  2717. {
  2718. // If it's hidden still don't reveal their names - privacy after all ;)
  2719. if ($row['bcc'])
  2720. $hidden_recipients++;
  2721. else
  2722. $recipients[] = '[url=' . $scripturl . '?action=profile;u=' . $row['id_member_to'] . ']' . $row['to_name'] . '[/url]';
  2723. }
  2724. $smcFunc['db_free_result']($request);
  2725. if ($hidden_recipients)
  2726. $recipients[] = sprintf($txt['pm_report_pm_hidden'], $hidden_recipients);
  2727. // Now let's get out and loop through the admins.
  2728. $request = $smcFunc['db_query']('', '
  2729. SELECT id_member, real_name, lngfile
  2730. FROM {db_prefix}members
  2731. WHERE (id_group = {int:admin_id} OR FIND_IN_SET({int:admin_id}, additional_groups) != 0)
  2732. ' . (empty($_POST['ID_ADMIN']) ? '' : 'AND id_member = {int:specific_admin}') . '
  2733. ORDER BY lngfile',
  2734. array(
  2735. 'admin_id' => 1,
  2736. 'specific_admin' => isset($_POST['ID_ADMIN']) ? (int) $_POST['ID_ADMIN'] : 0,
  2737. )
  2738. );
  2739. // Maybe we shouldn't advertise this?
  2740. if ($smcFunc['db_num_rows']($request) == 0)
  2741. fatal_lang_error('no_access', false);
  2742. $memberFromName = un_htmlspecialchars($memberFromName);
  2743. // Prepare the message storage array.
  2744. $messagesToSend = array();
  2745. // Loop through each admin, and add them to the right language pile...
  2746. while ($row = $smcFunc['db_fetch_assoc']($request))
  2747. {
  2748. // Need to send in the correct language!
  2749. $cur_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
  2750. if (!isset($messagesToSend[$cur_language]))
  2751. {
  2752. loadLanguage('PersonalMessage', $cur_language, false);
  2753. // Make the body.
  2754. $report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($user_info['name']), $memberFromName), $txt['pm_report_pm_user_sent']);
  2755. $report_body .= "\n" . '[b]' . $_POST['reason'] . '[/b]' . "\n\n";
  2756. if (!empty($recipients))
  2757. $report_body .= $txt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n";
  2758. $report_body .= $txt['pm_report_pm_unedited_below'] . "\n" . '[quote author=' . (empty($memberFromID) ? '&quot;' . $memberFromName . '&quot;' : $memberFromName . ' link=action=profile;u=' . $memberFromID . ' date=' . $time) . ']' . "\n" . un_htmlspecialchars($body) . '[/quote]';
  2759. // Plonk it in the array ;)
  2760. $messagesToSend[$cur_language] = array(
  2761. 'subject' => ($smcFunc['strpos']($subject, $txt['pm_report_pm_subject']) === false ? $txt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject),
  2762. 'body' => $report_body,
  2763. 'recipients' => array(
  2764. 'to' => array(),
  2765. 'bcc' => array()
  2766. ),
  2767. );
  2768. }
  2769. // Add them to the list.
  2770. $messagesToSend[$cur_language]['recipients']['to'][$row['id_member']] = $row['id_member'];
  2771. }
  2772. $smcFunc['db_free_result']($request);
  2773. // Send a different email for each language.
  2774. foreach ($messagesToSend as $lang => $message)
  2775. sendpm($message['recipients'], $message['subject'], $message['body']);
  2776. // Give the user their own language back!
  2777. if (!empty($modSettings['userLanguage']))
  2778. loadLanguage('PersonalMessage', '', false);
  2779. // Leave them with a template.
  2780. $context['sub_template'] = 'report_message_complete';
  2781. }
  2782. }
  2783. // List all rules, and allow adding/entering etc....
  2784. function ManageRules()
  2785. {
  2786. global $txt, $context, $user_info, $scripturl, $smcFunc;
  2787. // The link tree - gotta have this :o
  2788. $context['linktree'][] = array(
  2789. 'url' => $scripturl . '?action=pm;sa=manrules',
  2790. 'name' => $txt['pm_manage_rules']
  2791. );
  2792. $context['page_title'] = $txt['pm_manage_rules'];
  2793. $context['sub_template'] = 'rules';
  2794. // Load them... load them!!
  2795. LoadRules();
  2796. // Likely to need all the groups!
  2797. $request = $smcFunc['db_query']('', '
  2798. SELECT mg.id_group, mg.group_name, IFNULL(gm.id_member, 0) AS can_moderate, mg.hidden
  2799. FROM {db_prefix}membergroups AS mg
  2800. LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
  2801. WHERE mg.min_posts = {int:min_posts}
  2802. AND mg.id_group != {int:moderator_group}
  2803. AND mg.hidden = {int:not_hidden}
  2804. ORDER BY mg.group_name',
  2805. array(
  2806. 'current_member' => $user_info['id'],
  2807. 'min_posts' => -1,
  2808. 'moderator_group' => 3,
  2809. 'not_hidden' => 0,
  2810. )
  2811. );
  2812. $context['groups'] = array();
  2813. while ($row = $smcFunc['db_fetch_assoc']($request))
  2814. {
  2815. // Hide hidden groups!
  2816. if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
  2817. continue;
  2818. $context['groups'][$row['id_group']] = $row['group_name'];
  2819. }
  2820. $smcFunc['db_free_result']($request);
  2821. // Applying all rules?
  2822. if (isset($_GET['apply']))
  2823. {
  2824. checkSession('get');
  2825. ApplyRules(true);
  2826. redirectexit('action=pm;sa=manrules');
  2827. }
  2828. // Editing a specific one?
  2829. if (isset($_GET['add']))
  2830. {
  2831. $context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']])? (int) $_GET['rid'] : 0;
  2832. $context['sub_template'] = 'add_rule';
  2833. // Current rule information...
  2834. if ($context['rid'])
  2835. {
  2836. $context['rule'] = $context['rules'][$context['rid']];
  2837. $members = array();
  2838. // Need to get member names!
  2839. foreach ($context['rule']['criteria'] as $k => $criteria)
  2840. if ($criteria['t'] == 'mid' && !empty($criteria['v']))
  2841. $members[(int) $criteria['v']] = $k;
  2842. if (!empty($members))
  2843. {
  2844. $request = $smcFunc['db_query']('', '
  2845. SELECT id_member, member_name
  2846. FROM {db_prefix}members
  2847. WHERE id_member IN ({array_int:member_list})',
  2848. array(
  2849. 'member_list' => array_keys($members),
  2850. )
  2851. );
  2852. while ($row = $smcFunc['db_fetch_assoc']($request))
  2853. $context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name'];
  2854. $smcFunc['db_free_result']($request);
  2855. }
  2856. }
  2857. else
  2858. $context['rule'] = array(
  2859. 'id' => '',
  2860. 'name' => '',
  2861. 'criteria' => array(),
  2862. 'actions' => array(),
  2863. 'logic' => 'and',
  2864. );
  2865. }
  2866. // Saving?
  2867. elseif (isset($_GET['save']))
  2868. {
  2869. checkSession('post');
  2870. $context['rid'] = isset($_GET['rid']) && isset($context['rules'][$_GET['rid']])? (int) $_GET['rid'] : 0;
  2871. // Name is easy!
  2872. $ruleName = $smcFunc['htmlspecialchars'](trim($_POST['rule_name']));
  2873. if (empty($ruleName))
  2874. fatal_lang_error('pm_rule_no_name', false);
  2875. // Sanity check...
  2876. if (empty($_POST['ruletype']) || empty($_POST['acttype']))
  2877. fatal_lang_error('pm_rule_no_criteria', false);
  2878. // Let's do the criteria first - it's also hardest!
  2879. $criteria = array();
  2880. foreach ($_POST['ruletype'] as $ind => $type)
  2881. {
  2882. // Check everything is here...
  2883. if ($type == 'gid' && (!isset($_POST['ruledefgroup'][$ind]) || !isset($context['groups'][$_POST['ruledefgroup'][$ind]])))
  2884. continue;
  2885. elseif ($type != 'bud' && !isset($_POST['ruledef'][$ind]))
  2886. continue;
  2887. // Members need to be found.
  2888. if ($type == 'mid')
  2889. {
  2890. $name = trim($_POST['ruledef'][$ind]);
  2891. $request = $smcFunc['db_query']('', '
  2892. SELECT id_member
  2893. FROM {db_prefix}members
  2894. WHERE real_name = {string:member_name}
  2895. OR member_name = {string:member_name}',
  2896. array(
  2897. 'member_name' => $name,
  2898. )
  2899. );
  2900. if ($smcFunc['db_num_rows']($request) == 0)
  2901. continue;
  2902. list ($memID) = $smcFunc['db_fetch_row']($request);
  2903. $smcFunc['db_free_result']($request);
  2904. $criteria[] = array('t' => 'mid', 'v' => $memID);
  2905. }
  2906. elseif ($type == 'bud')
  2907. $criteria[] = array('t' => 'bud', 'v' => 1);
  2908. elseif ($type == 'gid')
  2909. $criteria[] = array('t' => 'gid', 'v' => (int) $_POST['ruledefgroup'][$ind]);
  2910. elseif (in_array($type, array('sub', 'msg')) && trim($_POST['ruledef'][$ind]) != '')
  2911. $criteria[] = array('t' => $type, 'v' => $smcFunc['htmlspecialchars'](trim($_POST['ruledef'][$ind])));
  2912. }
  2913. // Also do the actions!
  2914. $actions = array();
  2915. $doDelete = 0;
  2916. $isOr = $_POST['rule_logic'] == 'or' ? 1 : 0;
  2917. foreach ($_POST['acttype'] as $ind => $type)
  2918. {
  2919. // Picking a valid label?
  2920. if ($type == 'lab' && (!isset($_POST['labdef'][$ind]) || !isset($context['labels'][$_POST['labdef'][$ind] - 1])))
  2921. continue;
  2922. // Record what we're doing.
  2923. if ($type == 'del')
  2924. $doDelete = 1;
  2925. elseif ($type == 'lab')
  2926. $actions[] = array('t' => 'lab', 'v' => (int) $_POST['labdef'][$ind] - 1);
  2927. }
  2928. if (empty($criteria) || (empty($actions) && !$doDelete))
  2929. fatal_lang_error('pm_rule_no_criteria', false);
  2930. // What are we storing?
  2931. $criteria = serialize($criteria);
  2932. $actions = serialize($actions);
  2933. // Create the rule?
  2934. if (empty($context['rid']))
  2935. $smcFunc['db_insert']('',
  2936. '{db_prefix}pm_rules',
  2937. array(
  2938. 'id_member' => 'int', 'rule_name' => 'string', 'criteria' => 'string', 'actions' => 'string',
  2939. 'delete_pm' => 'int', 'is_or' => 'int',
  2940. ),
  2941. array(
  2942. $user_info['id'], $ruleName, $criteria, $actions, $doDelete, $isOr,
  2943. ),
  2944. array('id_rule')
  2945. );
  2946. else
  2947. $smcFunc['db_query']('', '
  2948. UPDATE {db_prefix}pm_rules
  2949. SET rule_name = {string:rule_name}, criteria = {string:criteria}, actions = {string:actions},
  2950. delete_pm = {int:delete_pm}, is_or = {int:is_or}
  2951. WHERE id_rule = {int:id_rule}
  2952. AND id_member = {int:current_member}',
  2953. array(
  2954. 'current_member' => $user_info['id'],
  2955. 'delete_pm' => $doDelete,
  2956. 'is_or' => $isOr,
  2957. 'id_rule' => $context['rid'],
  2958. 'rule_name' => $ruleName,
  2959. 'criteria' => $criteria,
  2960. 'actions' => $actions,
  2961. )
  2962. );
  2963. redirectexit('action=pm;sa=manrules');
  2964. }
  2965. // Deleting?
  2966. elseif (isset($_POST['delselected']) && !empty($_POST['delrule']))
  2967. {
  2968. checkSession('post');
  2969. $toDelete = array();
  2970. foreach ($_POST['delrule'] as $k => $v)
  2971. $toDelete[] = (int) $k;
  2972. if (!empty($toDelete))
  2973. $smcFunc['db_query']('', '
  2974. DELETE FROM {db_prefix}pm_rules
  2975. WHERE id_rule IN ({array_int:delete_list})
  2976. AND id_member = {int:current_member}',
  2977. array(
  2978. 'current_member' => $user_info['id'],
  2979. 'delete_list' => $toDelete,
  2980. )
  2981. );
  2982. redirectexit('action=pm;sa=manrules');
  2983. }
  2984. }
  2985. // This will apply rules to all unread messages. If all_messages is set will, clearly, do it to all!
  2986. function ApplyRules($all_messages = false)
  2987. {
  2988. global $user_info, $smcFunc, $context, $options;
  2989. // Want this - duh!
  2990. loadRules();
  2991. // No rules?
  2992. if (empty($context['rules']))
  2993. return;
  2994. // Just unread ones?
  2995. $ruleQuery = $all_messages ? '' : ' AND pmr.is_new = 1';
  2996. //!!! Apply all should have timeout protection!
  2997. // Get all the messages that match this.
  2998. $request = $smcFunc['db_query']('', '
  2999. SELECT
  3000. pmr.id_pm, pm.id_member_from, pm.subject, pm.body, mem.id_group, pmr.labels
  3001. FROM {db_prefix}pm_recipients AS pmr
  3002. INNER JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
  3003. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
  3004. WHERE pmr.id_member = {int:current_member}
  3005. AND pmr.deleted = {int:not_deleted}
  3006. ' . $ruleQuery,
  3007. array(
  3008. 'current_member' => $user_info['id'],
  3009. 'not_deleted' => 0,
  3010. )
  3011. );
  3012. $actions = array();
  3013. while ($row = $smcFunc['db_fetch_assoc']($request))
  3014. {
  3015. foreach ($context['rules'] as $rule)
  3016. {
  3017. $match = false;
  3018. // Loop through all the criteria hoping to make a match.
  3019. foreach ($rule['criteria'] as $criterium)
  3020. {
  3021. if (($criterium['t'] == 'mid' && $criterium['v'] == $row['id_member_from']) || ($criterium['t'] == 'gid' && $criterium['v'] == $row['id_group']) || ($criterium['t'] == 'sub' && strpos($row['subject'], $criterium['v']) !== false) || ($criterium['t'] == 'msg' && strpos($row['body'], $criterium['v']) !== false))
  3022. $match = true;
  3023. // If we're adding and one criteria don't match then we stop!
  3024. elseif ($rule['logic'] == 'and')
  3025. {
  3026. $match = false;
  3027. break;
  3028. }
  3029. }
  3030. // If we have a match the rule must be true - act!
  3031. if ($match)
  3032. {
  3033. if ($rule['delete'])
  3034. $actions['deletes'][] = $row['id_pm'];
  3035. else
  3036. {
  3037. foreach ($rule['actions'] as $ruleAction)
  3038. {
  3039. if ($ruleAction['t'] == 'lab')
  3040. {
  3041. // Get a basic pot started!
  3042. if (!isset($actions['labels'][$row['id_pm']]))
  3043. $actions['labels'][$row['id_pm']] = empty($row['labels']) ? array() : explode(',', $row['labels']);
  3044. $actions['labels'][$row['id_pm']][] = $ruleAction['v'];
  3045. }
  3046. }
  3047. }
  3048. }
  3049. }
  3050. }
  3051. $smcFunc['db_free_result']($request);
  3052. // Deletes are easy!
  3053. if (!empty($actions['deletes']))
  3054. deleteMessages($actions['deletes']);
  3055. // Relabel?
  3056. if (!empty($actions['labels']))
  3057. {
  3058. foreach ($actions['labels'] as $pm => $labels)
  3059. {
  3060. // Quickly check each label is valid!
  3061. $realLabels = array();
  3062. foreach ($context['labels'] as $label)
  3063. if (in_array($label['id'], $labels) && ($label['id'] != -1 || empty($options['pm_remove_inbox_label'])))
  3064. $realLabels[] = $label['id'];
  3065. $smcFunc['db_query']('', '
  3066. UPDATE {db_prefix}pm_recipients
  3067. SET labels = {string:new_labels}
  3068. WHERE id_pm = {int:id_pm}
  3069. AND id_member = {int:current_member}',
  3070. array(
  3071. 'current_member' => $user_info['id'],
  3072. 'id_pm' => $pm,
  3073. 'new_labels' => empty($realLabels) ? '' : implode(',', $realLabels),
  3074. )
  3075. );
  3076. }
  3077. }
  3078. }
  3079. // Load up all the rules for the current user.
  3080. function LoadRules($reload = false)
  3081. {
  3082. global $user_info, $context, $smcFunc;
  3083. if (isset($context['rules']) && !$reload)
  3084. return;
  3085. $request = $smcFunc['db_query']('', '
  3086. SELECT
  3087. id_rule, rule_name, criteria, actions, delete_pm, is_or
  3088. FROM {db_prefix}pm_rules
  3089. WHERE id_member = {int:current_member}',
  3090. array(
  3091. 'current_member' => $user_info['id'],
  3092. )
  3093. );
  3094. $context['rules'] = array();
  3095. // Simply fill in the data!
  3096. while ($row = $smcFunc['db_fetch_assoc']($request))
  3097. {
  3098. $context['rules'][$row['id_rule']] = array(
  3099. 'id' => $row['id_rule'],
  3100. 'name' => $row['rule_name'],
  3101. 'criteria' => unserialize($row['criteria']),
  3102. 'actions' => unserialize($row['actions']),
  3103. 'delete' => $row['delete_pm'],
  3104. 'logic' => $row['is_or'] ? 'or' : 'and',
  3105. );
  3106. if ($row['delete_pm'])
  3107. $context['rules'][$row['id_rule']]['actions'][] = array('t' => 'del', 'v' => 1);
  3108. }
  3109. $smcFunc['db_free_result']($request);
  3110. }
  3111. // Check if the PM is available to the current user.
  3112. function isAccessiblePM($pmID, $validFor = 'in_or_outbox')
  3113. {
  3114. global $user_info, $smcFunc;
  3115. $request = $smcFunc['db_query']('', '
  3116. SELECT
  3117. pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted} AS valid_for_outbox,
  3118. pmr.id_pm IS NOT NULL AS valid_for_inbox
  3119. FROM {db_prefix}personal_messages AS pm
  3120. LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.id_member = {int:id_current_member} AND pmr.deleted = {int:not_deleted})
  3121. WHERE pm.id_pm = {int:id_pm}
  3122. AND ((pm.id_member_from = {int:id_current_member} AND pm.deleted_by_sender = {int:not_deleted}) OR pmr.id_pm IS NOT NULL)',
  3123. array(
  3124. 'id_pm' => $pmID,
  3125. 'id_current_member' => $user_info['id'],
  3126. 'not_deleted' => 0,
  3127. )
  3128. );
  3129. if ($smcFunc['db_num_rows']($request) === 0)
  3130. {
  3131. $smcFunc['db_free_result']($request);
  3132. return false;
  3133. }
  3134. $validationResult = $smcFunc['db_fetch_assoc']($request);
  3135. $smcFunc['db_free_result']($request);
  3136. switch ($validFor)
  3137. {
  3138. case 'inbox':
  3139. return !empty($validationResult['valid_for_inbox']);
  3140. break;
  3141. case 'outbox':
  3142. return !empty($validationResult['valid_for_outbox']);
  3143. break;
  3144. case 'in_or_outbox':
  3145. return !empty($validationResult['valid_for_inbox']) || !empty($validationResult['valid_for_outbox']);
  3146. break;
  3147. default:
  3148. trigger_error('Undefined validation type given', E_USER_ERROR);
  3149. break;
  3150. }
  3151. }
  3152. ?>