PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/subs/Post.subs.php

https://github.com/Arantor/Elkarte
PHP | 2020 lines | 1448 code | 233 blank | 339 comment | 268 complexity | 922cd132992ae98fbed7f02290fcd10c MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file contains those functions pertaining to posting, and other such
  16. * operations, including sending emails, ims, blocking spam, preparsing posts,
  17. * spell checking, and the post box.
  18. *
  19. */
  20. if (!defined('ELKARTE'))
  21. die('No access...');
  22. /**
  23. * Takes a message and parses it, returning nothing.
  24. * Cleans up links (javascript, etc.) and code/quote sections.
  25. * Won't convert \n's and a few other things if previewing is true.
  26. *
  27. * @param $message
  28. * @param $previewing
  29. */
  30. function preparsecode(&$message, $previewing = false)
  31. {
  32. global $user_info, $modSettings, $smcFunc, $context;
  33. // This line makes all languages *theoretically* work even with the wrong charset ;).
  34. $message = preg_replace('~&amp;#(\d{4,5}|[2-9]\d{2,4}|1[2-9]\d);~', '&#$1;', $message);
  35. // Clean up after nobbc ;).
  36. $message = preg_replace('~\[nobbc\](.+?)\[/nobbc\]~ie', '\'[nobbc]\' . strtr(\'$1\', array(\'[\' => \'&#91;\', \']\' => \'&#93;\', \':\' => \'&#58;\', \'@\' => \'&#64;\')) . \'[/nobbc]\'', $message);
  37. // Remove \r's... they're evil!
  38. $message = strtr($message, array("\r" => ''));
  39. // You won't believe this - but too many periods upsets apache it seems!
  40. $message = preg_replace('~\.{100,}~', '...', $message);
  41. // Trim off trailing quotes - these often happen by accident.
  42. while (substr($message, -7) == '[quote]')
  43. $message = substr($message, 0, -7);
  44. while (substr($message, 0, 8) == '[/quote]')
  45. $message = substr($message, 8);
  46. // Find all code blocks, work out whether we'd be parsing them, then ensure they are all closed.
  47. $in_tag = false;
  48. $had_tag = false;
  49. $codeopen = 0;
  50. if (preg_match_all('~(\[(/)*code(?:=[^\]]+)?\])~is', $message, $matches))
  51. foreach ($matches[0] as $index => $dummy)
  52. {
  53. // Closing?
  54. if (!empty($matches[2][$index]))
  55. {
  56. // If it's closing and we're not in a tag we need to open it...
  57. if (!$in_tag)
  58. $codeopen = true;
  59. // Either way we ain't in one any more.
  60. $in_tag = false;
  61. }
  62. // Opening tag...
  63. else
  64. {
  65. $had_tag = true;
  66. // If we're in a tag don't do nought!
  67. if (!$in_tag)
  68. $in_tag = true;
  69. }
  70. }
  71. // If we have an open tag, close it.
  72. if ($in_tag)
  73. $message .= '[/code]';
  74. // Open any ones that need to be open, only if we've never had a tag.
  75. if ($codeopen && !$had_tag)
  76. $message = '[code]' . $message;
  77. // Now that we've fixed all the code tags, let's fix the img and url tags...
  78. $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
  79. // The regular expression non breaking space.
  80. $non_breaking_space = '\x{A0}';
  81. // Only mess with stuff outside [code] tags.
  82. for ($i = 0, $n = count($parts); $i < $n; $i++)
  83. {
  84. // It goes 0 = outside, 1 = begin tag, 2 = inside, 3 = close tag, repeat.
  85. if ($i % 4 == 0)
  86. {
  87. fixTags($parts[$i]);
  88. // Replace /me.+?\n with [me=name]dsf[/me]\n.
  89. if (strpos($user_info['name'], '[') !== false || strpos($user_info['name'], ']') !== false || strpos($user_info['name'], '\'') !== false || strpos($user_info['name'], '"') !== false)
  90. $parts[$i] = preg_replace('~(\A|\n)/me(?: |&nbsp;)([^\n]*)(?:\z)?~i', '$1[me=&quot;' . $user_info['name'] . '&quot;]$2[/me]', $parts[$i]);
  91. else
  92. $parts[$i] = preg_replace('~(\A|\n)/me(?: |&nbsp;)([^\n]*)(?:\z)?~i', '$1[me=' . $user_info['name'] . ']$2[/me]', $parts[$i]);
  93. if (!$previewing && strpos($parts[$i], '[html]') !== false)
  94. {
  95. if (allowedTo('admin_forum'))
  96. $parts[$i] = preg_replace('~\[html\](.+?)\[/html\]~ise', '\'[html]\' . strtr(un_htmlspecialchars(\'$1\'), array("\n" => \'&#13;\', \' \' => \' &#32;\', \'[\' => \'&#91;\', \']\' => \'&#93;\')) . \'[/html]\'', $parts[$i]);
  97. // We should edit them out, or else if an admin edits the message they will get shown...
  98. else
  99. {
  100. while (strpos($parts[$i], '[html]') !== false)
  101. $parts[$i] = preg_replace('~\[[/]?html\]~i', '', $parts[$i]);
  102. }
  103. }
  104. // Let's look at the time tags...
  105. $parts[$i] = preg_replace('~\[time(?:=(absolute))*\](.+?)\[/time\]~ie', '\'[time]\' . (is_numeric(\'$2\') || @strtotime(\'$2\') == 0 ? \'$2\' : strtotime(\'$2\') - (\'$1\' == \'absolute\' ? 0 : (($modSettings[\'time_offset\'] + $user_info[\'time_offset\']) * 3600))) . \'[/time]\'', $parts[$i]);
  106. // Change the color specific tags to [color=the color].
  107. $parts[$i] = preg_replace('~\[(black|blue|green|red|white)\]~', '[color=$1]', $parts[$i]); // First do the opening tags.
  108. $parts[$i] = preg_replace('~\[/(black|blue|green|red|white)\]~', '[/color]', $parts[$i]); // And now do the closing tags
  109. // Make sure all tags are lowercase.
  110. $parts[$i] = preg_replace('~\[([/]?)(list|li|table|tr|td)((\s[^\]]+)*)\]~ie', '\'[$1\' . strtolower(\'$2\') . \'$3]\'', $parts[$i]);
  111. $list_open = substr_count($parts[$i], '[list]') + substr_count($parts[$i], '[list ');
  112. $list_close = substr_count($parts[$i], '[/list]');
  113. if ($list_close - $list_open > 0)
  114. $parts[$i] = str_repeat('[list]', $list_close - $list_open) . $parts[$i];
  115. if ($list_open - $list_close > 0)
  116. $parts[$i] = $parts[$i] . str_repeat('[/list]', $list_open - $list_close);
  117. $mistake_fixes = array(
  118. // Find [table]s not followed by [tr].
  119. '~\[table\](?![\s' . $non_breaking_space . ']*\[tr\])~su' => '[table][tr]',
  120. // Find [tr]s not followed by [td].
  121. '~\[tr\](?![\s' . $non_breaking_space . ']*\[td\])~su' => '[tr][td]',
  122. // Find [/td]s not followed by something valid.
  123. '~\[/td\](?![\s' . $non_breaking_space . ']*(?:\[td\]|\[/tr\]|\[/table\]))~su' => '[/td][/tr]',
  124. // Find [/tr]s not followed by something valid.
  125. '~\[/tr\](?![\s' . $non_breaking_space . ']*(?:\[tr\]|\[/table\]))~su' => '[/tr][/table]',
  126. // Find [/td]s incorrectly followed by [/table].
  127. '~\[/td\][\s' . $non_breaking_space . ']*\[/table\]~su' => '[/td][/tr][/table]',
  128. // Find [table]s, [tr]s, and [/td]s (possibly correctly) followed by [td].
  129. '~\[(table|tr|/td)\]([\s' . $non_breaking_space . ']*)\[td\]~su' => '[$1]$2[_td_]',
  130. // Now, any [td]s left should have a [tr] before them.
  131. '~\[td\]~s' => '[tr][td]',
  132. // Look for [tr]s which are correctly placed.
  133. '~\[(table|/tr)\]([\s' . $non_breaking_space . ']*)\[tr\]~su' => '[$1]$2[_tr_]',
  134. // Any remaining [tr]s should have a [table] before them.
  135. '~\[tr\]~s' => '[table][tr]',
  136. // Look for [/td]s followed by [/tr].
  137. '~\[/td\]([\s' . $non_breaking_space . ']*)\[/tr\]~su' => '[/td]$1[_/tr_]',
  138. // Any remaining [/tr]s should have a [/td].
  139. '~\[/tr\]~s' => '[/td][/tr]',
  140. // Look for properly opened [li]s which aren't closed.
  141. '~\[li\]([^\[\]]+?)\[li\]~s' => '[li]$1[_/li_][_li_]',
  142. '~\[li\]([^\[\]]+?)\[/list\]~s' => '[_li_]$1[_/li_][/list]',
  143. '~\[li\]([^\[\]]+?)$~s' => '[li]$1[/li]',
  144. // Lists - find correctly closed items/lists.
  145. '~\[/li\]([\s' . $non_breaking_space . ']*)\[/list\]~su' => '[_/li_]$1[/list]',
  146. // Find list items closed and then opened.
  147. '~\[/li\]([\s' . $non_breaking_space . ']*)\[li\]~su' => '[_/li_]$1[_li_]',
  148. // Now, find any [list]s or [/li]s followed by [li].
  149. '~\[(list(?: [^\]]*?)?|/li)\]([\s' . $non_breaking_space . ']*)\[li\]~su' => '[$1]$2[_li_]',
  150. // Allow for sub lists.
  151. '~\[/li\]([\s' . $non_breaking_space . ']*)\[list\]~u' => '[_/li_]$1[list]',
  152. '~\[/list\]([\s' . $non_breaking_space . ']*)\[li\]~u' => '[/list]$1[_li_]',
  153. // Any remaining [li]s weren't inside a [list].
  154. '~\[li\]~' => '[list][li]',
  155. // Any remaining [/li]s weren't before a [/list].
  156. '~\[/li\]~' => '[/li][/list]',
  157. // Put the correct ones back how we found them.
  158. '~\[_(li|/li|td|tr|/tr)_\]~' => '[$1]',
  159. // Images with no real url.
  160. '~\[img\]https?://.{0,7}\[/img\]~' => '',
  161. );
  162. // Fix up some use of tables without [tr]s, etc. (it has to be done more than once to catch it all.)
  163. for ($j = 0; $j < 3; $j++)
  164. $parts[$i] = preg_replace(array_keys($mistake_fixes), $mistake_fixes, $parts[$i]);
  165. // Now we're going to do full scale table checking...
  166. $table_check = $parts[$i];
  167. $table_offset = 0;
  168. $table_array = array();
  169. $table_order = array(
  170. 'table' => 'td',
  171. 'tr' => 'table',
  172. 'td' => 'tr',
  173. );
  174. while (preg_match('~\[(/)*(table|tr|td)\]~', $table_check, $matches) != false)
  175. {
  176. // Keep track of where this is.
  177. $offset = strpos($table_check, $matches[0]);
  178. $remove_tag = false;
  179. // Is it opening?
  180. if ($matches[1] != '/')
  181. {
  182. // If the previous table tag isn't correct simply remove it.
  183. if ((!empty($table_array) && $table_array[0] != $table_order[$matches[2]]) || (empty($table_array) && $matches[2] != 'table'))
  184. $remove_tag = true;
  185. // Record this was the last tag.
  186. else
  187. array_unshift($table_array, $matches[2]);
  188. }
  189. // Otherwise is closed!
  190. else
  191. {
  192. // Only keep the tag if it's closing the right thing.
  193. if (empty($table_array) || ($table_array[0] != $matches[2]))
  194. $remove_tag = true;
  195. else
  196. array_shift($table_array);
  197. }
  198. // Removing?
  199. if ($remove_tag)
  200. {
  201. $parts[$i] = substr($parts[$i], 0, $table_offset + $offset) . substr($parts[$i], $table_offset + strlen($matches[0]) + $offset);
  202. // We've lost some data.
  203. $table_offset -= strlen($matches[0]);
  204. }
  205. // Remove everything up to here.
  206. $table_offset += $offset + strlen($matches[0]);
  207. $table_check = substr($table_check, $offset + strlen($matches[0]));
  208. }
  209. // Close any remaining table tags.
  210. foreach ($table_array as $tag)
  211. $parts[$i] .= '[/' . $tag . ']';
  212. // Remove empty bbc from the sections outside the code tags
  213. $parts[$i] = preg_replace('~\[[bisu]\]\s*\[/[bisu]\]~', '', $parts[$i]);
  214. $parts[$i] = preg_replace('~\[quote\]\s*\[/quote\]~', '', $parts[$i]);
  215. $parts[$i] = preg_replace('~\[color=(?:#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]\s*\[/color\]~', '', $parts[$i]);
  216. }
  217. }
  218. // Put it back together!
  219. if (!$previewing)
  220. $message = strtr(implode('', $parts), array(' ' => '&nbsp; ', "\n" => '<br />', "\xC2\xA0" => '&nbsp;'));
  221. else
  222. $message = strtr(implode('', $parts), array(' ' => '&nbsp; ', "\xC2\xA0" => '&nbsp;'));
  223. // Now let's quickly clean up things that will slow our parser (which are common in posted code.)
  224. $message = strtr($message, array('[]' => '&#91;]', '[&#039;' => '&#91;&#039;'));
  225. }
  226. /**
  227. * This is very simple, and just removes things done by preparsecode.
  228. *
  229. * @param $message
  230. */
  231. function un_preparsecode($message)
  232. {
  233. global $smcFunc;
  234. $parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
  235. // We're going to unparse only the stuff outside [code]...
  236. for ($i = 0, $n = count($parts); $i < $n; $i++)
  237. {
  238. // If $i is a multiple of four (0, 4, 8, ...) then it's not a code section...
  239. if ($i % 4 == 0)
  240. {
  241. $parts[$i] = preg_replace('~\[html\](.+?)\[/html\]~ie', '\'[html]\' . strtr(htmlspecialchars(\'$1\', ENT_QUOTES), array(\'\\&quot;\' => \'&quot;\', \'&amp;#13;\' => \'<br />\', \'&amp;#32;\' => \' \', \'&amp;#91;\' => \'[\', \'&amp;#93;\' => \']\')) . \'[/html]\'', $parts[$i]);
  242. // $parts[$i] = preg_replace('~\[html\](.+?)\[/html\]~ie', '\'[html]\' . strtr(htmlspecialchars(\'$1\', ENT_QUOTES), array(\'\\&quot;\' => \'&quot;\', \'&amp;#13;\' => \'<br />\', \'&amp;#32;\' => \' \', \'&amp;#38;\' => \'&#38;\', \'&amp;#91;\' => \'[\', \'&amp;#93;\' => \']\')) . \'[/html]\'', $parts[$i]);
  243. // Attempt to un-parse the time to something less awful.
  244. $parts[$i] = preg_replace('~\[time\](\d{0,10})\[/time\]~ie', '\'[time]\' . timeformat(\'$1\', false) . \'[/time]\'', $parts[$i]);
  245. }
  246. }
  247. // Change breaks back to \n's and &nsbp; back to spaces.
  248. return preg_replace('~<br( /)?' . '>~', "\n", str_replace('&nbsp;', ' ', implode('', $parts)));
  249. }
  250. /**
  251. * Fix any URLs posted - ie. remove 'javascript:'.
  252. * Used by preparsecode, fixes links in message and returns nothing.
  253. *
  254. * @param string $message
  255. */
  256. function fixTags(&$message)
  257. {
  258. global $modSettings;
  259. // WARNING: Editing the below can cause large security holes in your forum.
  260. // Edit only if you are sure you know what you are doing.
  261. $fixArray = array(
  262. // [img]http://...[/img] or [img width=1]http://...[/img]
  263. array(
  264. 'tag' => 'img',
  265. 'protocols' => array('http', 'https'),
  266. 'embeddedUrl' => false,
  267. 'hasEqualSign' => false,
  268. 'hasExtra' => true,
  269. ),
  270. // [url]http://...[/url]
  271. array(
  272. 'tag' => 'url',
  273. 'protocols' => array('http', 'https'),
  274. 'embeddedUrl' => true,
  275. 'hasEqualSign' => false,
  276. ),
  277. // [url=http://...]name[/url]
  278. array(
  279. 'tag' => 'url',
  280. 'protocols' => array('http', 'https'),
  281. 'embeddedUrl' => true,
  282. 'hasEqualSign' => true,
  283. ),
  284. // [iurl]http://...[/iurl]
  285. array(
  286. 'tag' => 'iurl',
  287. 'protocols' => array('http', 'https'),
  288. 'embeddedUrl' => true,
  289. 'hasEqualSign' => false,
  290. ),
  291. // [iurl=http://...]name[/iurl]
  292. array(
  293. 'tag' => 'iurl',
  294. 'protocols' => array('http', 'https'),
  295. 'embeddedUrl' => true,
  296. 'hasEqualSign' => true,
  297. ),
  298. // [ftp]ftp://...[/ftp]
  299. array(
  300. 'tag' => 'ftp',
  301. 'protocols' => array('ftp', 'ftps'),
  302. 'embeddedUrl' => true,
  303. 'hasEqualSign' => false,
  304. ),
  305. // [ftp=ftp://...]name[/ftp]
  306. array(
  307. 'tag' => 'ftp',
  308. 'protocols' => array('ftp', 'ftps'),
  309. 'embeddedUrl' => true,
  310. 'hasEqualSign' => true,
  311. ),
  312. // [flash]http://...[/flash]
  313. array(
  314. 'tag' => 'flash',
  315. 'protocols' => array('http', 'https'),
  316. 'embeddedUrl' => false,
  317. 'hasEqualSign' => false,
  318. 'hasExtra' => true,
  319. ),
  320. );
  321. // Fix each type of tag.
  322. foreach ($fixArray as $param)
  323. fixTag($message, $param['tag'], $param['protocols'], $param['embeddedUrl'], $param['hasEqualSign'], !empty($param['hasExtra']));
  324. // Now fix possible security problems with images loading links automatically...
  325. $message = preg_replace('~(\[img.*?\])(.+?)\[/img\]~eis', '\'$1\' . preg_replace(\'~action(=|%3d)(?!dlattach)~i\', \'action-\', \'$2\') . \'[/img]\'', $message);
  326. // Limit the size of images posted?
  327. if (!empty($modSettings['max_image_width']) || !empty($modSettings['max_image_height']))
  328. {
  329. // We'll need this for image processing
  330. require_once(SUBSDIR . '/Attachments.subs.php');
  331. // Find all the img tags - with or without width and height.
  332. preg_match_all('~\[img(\s+width=\d+)?(\s+height=\d+)?(\s+width=\d+)?\](.+?)\[/img\]~is', $message, $matches, PREG_PATTERN_ORDER);
  333. $replaces = array();
  334. foreach ($matches[0] as $match => $dummy)
  335. {
  336. // If the width was after the height, handle it.
  337. $matches[1][$match] = !empty($matches[3][$match]) ? $matches[3][$match] : $matches[1][$match];
  338. // Now figure out if they had a desired height or width...
  339. $desired_width = !empty($matches[1][$match]) ? (int) substr(trim($matches[1][$match]), 6) : 0;
  340. $desired_height = !empty($matches[2][$match]) ? (int) substr(trim($matches[2][$match]), 7) : 0;
  341. // One was omitted, or both. We'll have to find its real size...
  342. if (empty($desired_width) || empty($desired_height))
  343. {
  344. list ($width, $height) = url_image_size(un_htmlspecialchars($matches[4][$match]));
  345. // They don't have any desired width or height!
  346. if (empty($desired_width) && empty($desired_height))
  347. {
  348. $desired_width = $width;
  349. $desired_height = $height;
  350. }
  351. // Scale it to the width...
  352. elseif (empty($desired_width) && !empty($height))
  353. $desired_width = (int) (($desired_height * $width) / $height);
  354. // Scale if to the height.
  355. elseif (!empty($width))
  356. $desired_height = (int) (($desired_width * $height) / $width);
  357. }
  358. // If the width and height are fine, just continue along...
  359. if ($desired_width <= $modSettings['max_image_width'] && $desired_height <= $modSettings['max_image_height'])
  360. continue;
  361. // Too bad, it's too wide. Make it as wide as the maximum.
  362. if ($desired_width > $modSettings['max_image_width'] && !empty($modSettings['max_image_width']))
  363. {
  364. $desired_height = (int) (($modSettings['max_image_width'] * $desired_height) / $desired_width);
  365. $desired_width = $modSettings['max_image_width'];
  366. }
  367. // Now check the height, as well. Might have to scale twice, even...
  368. if ($desired_height > $modSettings['max_image_height'] && !empty($modSettings['max_image_height']))
  369. {
  370. $desired_width = (int) (($modSettings['max_image_height'] * $desired_width) / $desired_height);
  371. $desired_height = $modSettings['max_image_height'];
  372. }
  373. $replaces[$matches[0][$match]] = '[img' . (!empty($desired_width) ? ' width=' . $desired_width : '') . (!empty($desired_height) ? ' height=' . $desired_height : '') . ']' . $matches[4][$match] . '[/img]';
  374. }
  375. // If any img tags were actually changed...
  376. if (!empty($replaces))
  377. $message = strtr($message, $replaces);
  378. }
  379. }
  380. /**
  381. * Fix a specific class of tag - ie. url with =.
  382. * Used by fixTags, fixes a specific tag's links.
  383. *
  384. * @param string $message
  385. * @param string $myTag - the tag
  386. * @param string $protocols - http or ftp
  387. * @param bool $embeddedUrl = false - whether it *can* be set to something
  388. * @param bool $hasEqualSign = false, whether it *is* set to something
  389. * @param bool $hasExtra = false - whether it can have extra cruft after the begin tag.
  390. */
  391. function fixTag(&$message, $myTag, $protocols, $embeddedUrl = false, $hasEqualSign = false, $hasExtra = false)
  392. {
  393. global $boardurl, $scripturl;
  394. if (preg_match('~^([^:]+://[^/]+)~', $boardurl, $match) != 0)
  395. $domain_url = $match[1];
  396. else
  397. $domain_url = $boardurl . '/';
  398. $replaces = array();
  399. if ($hasEqualSign)
  400. preg_match_all('~\[(' . $myTag . ')=([^\]]*?)\](?:(.+?)\[/(' . $myTag . ')\])?~is', $message, $matches);
  401. else
  402. preg_match_all('~\[(' . $myTag . ($hasExtra ? '(?:[^\]]*?)' : '') . ')\](.+?)\[/(' . $myTag . ')\]~is', $message, $matches);
  403. foreach ($matches[0] as $k => $dummy)
  404. {
  405. // Remove all leading and trailing whitespace.
  406. $replace = trim($matches[2][$k]);
  407. $this_tag = $matches[1][$k];
  408. $this_close = $hasEqualSign ? (empty($matches[4][$k]) ? '' : $matches[4][$k]) : $matches[3][$k];
  409. $found = false;
  410. foreach ($protocols as $protocol)
  411. {
  412. $found = strncasecmp($replace, $protocol . '://', strlen($protocol) + 3) === 0;
  413. if ($found)
  414. break;
  415. }
  416. if (!$found && $protocols[0] == 'http')
  417. {
  418. if (substr($replace, 0, 1) == '/')
  419. $replace = $domain_url . $replace;
  420. elseif (substr($replace, 0, 1) == '?')
  421. $replace = $scripturl . $replace;
  422. elseif (substr($replace, 0, 1) == '#' && $embeddedUrl)
  423. {
  424. $replace = '#' . preg_replace('~[^A-Za-z0-9_\-#]~', '', substr($replace, 1));
  425. $this_tag = 'iurl';
  426. $this_close = 'iurl';
  427. }
  428. else
  429. $replace = $protocols[0] . '://' . $replace;
  430. }
  431. elseif (!$found && $protocols[0] == 'ftp')
  432. $replace = $protocols[0] . '://' . preg_replace('~^(?!ftps?)[^:]+://~', '', $replace);
  433. elseif (!$found)
  434. $replace = $protocols[0] . '://' . $replace;
  435. if ($hasEqualSign && $embeddedUrl)
  436. $replaces[$matches[0][$k]] = '[' . $this_tag . '=' . $replace . ']' . (empty($matches[4][$k]) ? '' : $matches[3][$k] . '[/' . $this_close . ']');
  437. elseif ($hasEqualSign)
  438. $replaces['[' . $matches[1][$k] . '=' . $matches[2][$k] . ']'] = '[' . $this_tag . '=' . $replace . ']';
  439. elseif ($embeddedUrl)
  440. $replaces['[' . $matches[1][$k] . ']' . $matches[2][$k] . '[/' . $matches[3][$k] . ']'] = '[' . $this_tag . '=' . $replace . ']' . $matches[2][$k] . '[/' . $this_close . ']';
  441. else
  442. $replaces['[' . $matches[1][$k] . ']' . $matches[2][$k] . '[/' . $matches[3][$k] . ']'] = '[' . $this_tag . ']' . $replace . '[/' . $this_close . ']';
  443. }
  444. foreach ($replaces as $k => $v)
  445. {
  446. if ($k == $v)
  447. unset($replaces[$k]);
  448. }
  449. if (!empty($replaces))
  450. $message = strtr($message, $replaces);
  451. }
  452. /**
  453. * Sends a notification to members who have elected to receive emails
  454. * when things happen to a topic, such as replies are posted.
  455. * The function automatically finds the subject and its board, and
  456. * checks permissions for each member who is "signed up" for notifications.
  457. * It will not send 'reply' notifications more than once in a row.
  458. *
  459. * @param array $topics - represents the topics the action is happening to.
  460. * @param string $type - can be any of reply, sticky, lock, unlock, remove,
  461. * move, merge, and split. An appropriate message will be sent for each.
  462. * @param array $exclude = array() - members in the exclude array will not be
  463. * processed for the topic with the same key.
  464. * @param array $members_only = array() - are the only ones that will be sent the notification if they have it on.
  465. * @uses Post language file
  466. */
  467. function sendNotifications($topics, $type, $exclude = array(), $members_only = array())
  468. {
  469. global $txt, $scripturl, $language, $user_info;
  470. global $modSettings, $smcFunc;
  471. // Can't do it if there's no topics.
  472. if (empty($topics))
  473. return;
  474. // It must be an array - it must!
  475. if (!is_array($topics))
  476. $topics = array($topics);
  477. // Email functions will be helpful here
  478. require_once(SUBSDIR . '/Mail.subs.php');
  479. // Get the subject and body...
  480. $result = $smcFunc['db_query']('', '
  481. SELECT mf.subject, ml.body, ml.id_member, t.id_last_msg, t.id_topic,
  482. IFNULL(mem.real_name, ml.poster_name) AS poster_name
  483. FROM {db_prefix}topics AS t
  484. INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
  485. INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
  486. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ml.id_member)
  487. WHERE t.id_topic IN ({array_int:topic_list})
  488. LIMIT 1',
  489. array(
  490. 'topic_list' => $topics,
  491. )
  492. );
  493. $topicData = array();
  494. while ($row = $smcFunc['db_fetch_assoc']($result))
  495. {
  496. // Clean it up.
  497. censorText($row['subject']);
  498. censorText($row['body']);
  499. $row['subject'] = un_htmlspecialchars($row['subject']);
  500. $row['body'] = trim(un_htmlspecialchars(strip_tags(strtr(parse_bbc($row['body'], false, $row['id_last_msg']), array('<br />' => "\n", '</div>' => "\n", '</li>' => "\n", '&#91;' => '[', '&#93;' => ']')))));
  501. $topicData[$row['id_topic']] = array(
  502. 'subject' => $row['subject'],
  503. 'body' => $row['body'],
  504. 'last_id' => $row['id_last_msg'],
  505. 'topic' => $row['id_topic'],
  506. 'name' => $user_info['name'],
  507. 'exclude' => '',
  508. );
  509. }
  510. $smcFunc['db_free_result']($result);
  511. // Work out any exclusions...
  512. foreach ($topics as $key => $id)
  513. if (isset($topicData[$id]) && !empty($exclude[$key]))
  514. $topicData[$id]['exclude'] = (int) $exclude[$key];
  515. // Nada?
  516. if (empty($topicData))
  517. trigger_error('sendNotifications(): topics not found', E_USER_NOTICE);
  518. $topics = array_keys($topicData);
  519. // Just in case they've gone walkies.
  520. if (empty($topics))
  521. return;
  522. // Insert all of these items into the digest log for those who want notifications later.
  523. $digest_insert = array();
  524. foreach ($topicData as $id => $data)
  525. $digest_insert[] = array($data['topic'], $data['last_id'], $type, (int) $data['exclude']);
  526. $smcFunc['db_insert']('',
  527. '{db_prefix}log_digest',
  528. array(
  529. 'id_topic' => 'int', 'id_msg' => 'int', 'note_type' => 'string', 'exclude' => 'int',
  530. ),
  531. $digest_insert,
  532. array()
  533. );
  534. // Find the members with notification on for this topic.
  535. $members = $smcFunc['db_query']('', '
  536. SELECT
  537. mem.id_member, mem.email_address, mem.notify_regularity, mem.notify_types, mem.notify_send_body, mem.lngfile,
  538. ln.sent, mem.id_group, mem.additional_groups, b.member_groups, mem.id_post_group, t.id_member_started,
  539. ln.id_topic
  540. FROM {db_prefix}log_notify AS ln
  541. INNER JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
  542. INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)
  543. INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
  544. WHERE ln.id_topic IN ({array_int:topic_list})
  545. AND mem.notify_types < {int:notify_types}
  546. AND mem.notify_regularity < {int:notify_regularity}
  547. AND mem.is_activated = {int:is_activated}
  548. AND ln.id_member != {int:current_member}' .
  549. (empty($members_only) ? '' : ' AND ln.id_member IN ({array_int:members_only})') . '
  550. ORDER BY mem.lngfile',
  551. array(
  552. 'current_member' => $user_info['id'],
  553. 'topic_list' => $topics,
  554. 'notify_types' => $type == 'reply' ? '4' : '3',
  555. 'notify_regularity' => 2,
  556. 'is_activated' => 1,
  557. 'members_only' => is_array($members_only) ? $members_only : array($members_only),
  558. )
  559. );
  560. $sent = 0;
  561. $current_language = '';
  562. while ($row = $smcFunc['db_fetch_assoc']($members))
  563. {
  564. // Don't do the excluded...
  565. if ($topicData[$row['id_topic']]['exclude'] == $row['id_member'])
  566. continue;
  567. // Easier to check this here... if they aren't the topic poster do they really want to know?
  568. if ($type != 'reply' && $row['notify_types'] == 2 && $row['id_member'] != $row['id_member_started'])
  569. continue;
  570. if ($row['id_group'] != 1)
  571. {
  572. $allowed = explode(',', $row['member_groups']);
  573. $row['additional_groups'] = explode(',', $row['additional_groups']);
  574. $row['additional_groups'][] = $row['id_group'];
  575. $row['additional_groups'][] = $row['id_post_group'];
  576. if (count(array_intersect($allowed, $row['additional_groups'])) == 0)
  577. continue;
  578. }
  579. $needed_language = empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile'];
  580. if (empty($current_language) || $current_language != $needed_language)
  581. $current_language = loadLanguage('Post', $needed_language, false);
  582. $message_type = 'notification_' . $type;
  583. $replacements = array(
  584. 'TOPICSUBJECT' => $topicData[$row['id_topic']]['subject'],
  585. 'POSTERNAME' => un_htmlspecialchars($topicData[$row['id_topic']]['name']),
  586. 'TOPICLINK' => $scripturl . '?topic=' . $row['id_topic'] . '.new;topicseen#new',
  587. 'UNSUBSCRIBELINK' => $scripturl . '?action=notify;topic=' . $row['id_topic'] . '.0',
  588. );
  589. if ($type == 'remove')
  590. unset($replacements['TOPICLINK'], $replacements['UNSUBSCRIBELINK']);
  591. // Do they want the body of the message sent too?
  592. if (!empty($row['notify_send_body']) && $type == 'reply' && empty($modSettings['disallow_sendBody']))
  593. {
  594. $message_type .= '_body';
  595. $replacements['MESSAGE'] = $topicData[$row['id_topic']]['body'];
  596. }
  597. if (!empty($row['notify_regularity']) && $type == 'reply')
  598. $message_type .= '_once';
  599. // Send only if once is off or it's on and it hasn't been sent.
  600. if ($type != 'reply' || empty($row['notify_regularity']) || empty($row['sent']))
  601. {
  602. $emaildata = loadEmailTemplate($message_type, $replacements, $needed_language);
  603. sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, 'm' . $topicData[$row['id_topic']]['last_id']);
  604. $sent++;
  605. }
  606. }
  607. $smcFunc['db_free_result']($members);
  608. if (isset($current_language) && $current_language != $user_info['language'])
  609. loadLanguage('Post');
  610. // Sent!
  611. if ($type == 'reply' && !empty($sent))
  612. $smcFunc['db_query']('', '
  613. UPDATE {db_prefix}log_notify
  614. SET sent = {int:is_sent}
  615. WHERE id_topic IN ({array_int:topic_list})
  616. AND id_member != {int:current_member}',
  617. array(
  618. 'current_member' => $user_info['id'],
  619. 'topic_list' => $topics,
  620. 'is_sent' => 1,
  621. )
  622. );
  623. // For approvals we need to unsend the exclusions (This *is* the quickest way!)
  624. if (!empty($sent) && !empty($exclude))
  625. {
  626. foreach ($topicData as $id => $data)
  627. if ($data['exclude'])
  628. $smcFunc['db_query']('', '
  629. UPDATE {db_prefix}log_notify
  630. SET sent = {int:not_sent}
  631. WHERE id_topic = {int:id_topic}
  632. AND id_member = {int:id_member}',
  633. array(
  634. 'not_sent' => 0,
  635. 'id_topic' => $id,
  636. 'id_member' => $data['exclude'],
  637. )
  638. );
  639. }
  640. }
  641. /**
  642. * Create a post, either as new topic (id_topic = 0) or in an existing one.
  643. * The input parameters of this function assume:
  644. * - Strings have been escaped.
  645. * - Integers have been cast to integer.
  646. * - Mandatory parameters are set.
  647. *
  648. * @param array $msgOptions
  649. * @param array $topicOptions
  650. * @param array $posterOptions
  651. */
  652. function createPost(&$msgOptions, &$topicOptions, &$posterOptions)
  653. {
  654. global $user_info, $txt, $modSettings, $smcFunc;
  655. // Set optional parameters to the default value.
  656. $msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon'];
  657. $msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']);
  658. $msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments'];
  659. $msgOptions['approved'] = isset($msgOptions['approved']) ? (int) $msgOptions['approved'] : 1;
  660. $topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id'];
  661. $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
  662. $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
  663. $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
  664. $topicOptions['redirect_expires'] = isset($topicOptions['redirect_expires']) ? $topicOptions['redirect_expires'] : null;
  665. $topicOptions['redirect_topic'] = isset($topicOptions['redirect_topic']) ? $topicOptions['redirect_topic'] : null;
  666. $posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id'];
  667. $posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip'] : $posterOptions['ip'];
  668. // We need to know if the topic is approved. If we're told that's great - if not find out.
  669. if (!$modSettings['postmod_active'])
  670. $topicOptions['is_approved'] = true;
  671. elseif (!empty($topicOptions['id']) && !isset($topicOptions['is_approved']))
  672. {
  673. $request = $smcFunc['db_query']('', '
  674. SELECT approved
  675. FROM {db_prefix}topics
  676. WHERE id_topic = {int:id_topic}
  677. LIMIT 1',
  678. array(
  679. 'id_topic' => $topicOptions['id'],
  680. )
  681. );
  682. list ($topicOptions['is_approved']) = $smcFunc['db_fetch_row']($request);
  683. $smcFunc['db_free_result']($request);
  684. }
  685. // If nothing was filled in as name/e-mail address, try the member table.
  686. if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || (empty($posterOptions['email']) && !empty($posterOptions['id'])))
  687. {
  688. if (empty($posterOptions['id']))
  689. {
  690. $posterOptions['id'] = 0;
  691. $posterOptions['name'] = $txt['guest_title'];
  692. $posterOptions['email'] = '';
  693. }
  694. elseif ($posterOptions['id'] != $user_info['id'])
  695. {
  696. $request = $smcFunc['db_query']('', '
  697. SELECT member_name, email_address
  698. FROM {db_prefix}members
  699. WHERE id_member = {int:id_member}
  700. LIMIT 1',
  701. array(
  702. 'id_member' => $posterOptions['id'],
  703. )
  704. );
  705. // Couldn't find the current poster?
  706. if ($smcFunc['db_num_rows']($request) == 0)
  707. {
  708. trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE);
  709. $posterOptions['id'] = 0;
  710. $posterOptions['name'] = $txt['guest_title'];
  711. $posterOptions['email'] = '';
  712. }
  713. else
  714. list ($posterOptions['name'], $posterOptions['email']) = $smcFunc['db_fetch_row']($request);
  715. $smcFunc['db_free_result']($request);
  716. }
  717. else
  718. {
  719. $posterOptions['name'] = $user_info['name'];
  720. $posterOptions['email'] = $user_info['email'];
  721. }
  722. }
  723. // It's do or die time: forget any user aborts!
  724. $previous_ignore_user_abort = ignore_user_abort(true);
  725. $new_topic = empty($topicOptions['id']);
  726. $message_columns = array(
  727. 'id_board' => 'int', 'id_topic' => 'int', 'id_member' => 'int', 'subject' => 'string-255', 'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : (empty($modSettings['max_messageLength']) ? 'string' : 'string-65534')),
  728. 'poster_name' => 'string-255', 'poster_email' => 'string-255', 'poster_time' => 'int', 'poster_ip' => 'string-255',
  729. 'smileys_enabled' => 'int', 'modified_name' => 'string', 'icon' => 'string-16', 'approved' => 'int',
  730. );
  731. $message_parameters = array(
  732. $topicOptions['board'], $topicOptions['id'], $posterOptions['id'], $msgOptions['subject'], $msgOptions['body'],
  733. $posterOptions['name'], $posterOptions['email'], time(), $posterOptions['ip'],
  734. $msgOptions['smileys_enabled'] ? 1 : 0, '', $msgOptions['icon'], $msgOptions['approved'],
  735. );
  736. // What if we want to do anything with posts?
  737. call_integration_hook('integrate_create_post', array($msgOptions, $topicOptions, $posterOptions, $message_columns, $message_parameters));
  738. // Insert the post.
  739. $smcFunc['db_insert']('',
  740. '{db_prefix}messages',
  741. $message_columns,
  742. $message_parameters,
  743. array('id_msg')
  744. );
  745. $msgOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}messages', 'id_msg');
  746. // Something went wrong creating the message...
  747. if (empty($msgOptions['id']))
  748. return false;
  749. // Fix the attachments.
  750. if (!empty($msgOptions['attachments']))
  751. $smcFunc['db_query']('', '
  752. UPDATE {db_prefix}attachments
  753. SET id_msg = {int:id_msg}
  754. WHERE id_attach IN ({array_int:attachment_list})',
  755. array(
  756. 'attachment_list' => $msgOptions['attachments'],
  757. 'id_msg' => $msgOptions['id'],
  758. )
  759. );
  760. // Insert a new topic (if the topicID was left empty.)
  761. if ($new_topic)
  762. {
  763. $topic_columns = array(
  764. 'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
  765. 'id_last_msg' => 'int', 'locked' => 'int', 'is_sticky' => 'int', 'num_views' => 'int',
  766. 'id_poll' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int',
  767. 'redirect_expires' => 'int', 'id_redirect_topic' => 'int',
  768. );
  769. $topic_parameters = array(
  770. $topicOptions['board'], $posterOptions['id'], $posterOptions['id'], $msgOptions['id'],
  771. $msgOptions['id'], $topicOptions['lock_mode'] === null ? 0 : $topicOptions['lock_mode'], $topicOptions['sticky_mode'] === null ? 0 : $topicOptions['sticky_mode'], 0,
  772. $topicOptions['poll'] === null ? 0 : $topicOptions['poll'], $msgOptions['approved'] ? 0 : 1, $msgOptions['approved'],
  773. $topicOptions['redirect_expires'] === null ? 0 : $topicOptions['redirect_expires'], $topicOptions['redirect_topic'] === null ? 0 : $topicOptions['redirect_topic'],
  774. );
  775. call_integration_hook('integrate_before_create_topic', array($msgOptions, $topicOptions, $posterOptions, $topic_columns, $topic_parameters));
  776. $smcFunc['db_insert']('',
  777. '{db_prefix}topics',
  778. $topic_columns,
  779. $topic_parameters,
  780. array('id_topic')
  781. );
  782. $topicOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic');
  783. // The topic couldn't be created for some reason.
  784. if (empty($topicOptions['id']))
  785. {
  786. // We should delete the post that did work, though...
  787. $smcFunc['db_query']('', '
  788. DELETE FROM {db_prefix}messages
  789. WHERE id_msg = {int:id_msg}',
  790. array(
  791. 'id_msg' => $msgOptions['id'],
  792. )
  793. );
  794. return false;
  795. }
  796. // Fix the message with the topic.
  797. $smcFunc['db_query']('', '
  798. UPDATE {db_prefix}messages
  799. SET id_topic = {int:id_topic}
  800. WHERE id_msg = {int:id_msg}',
  801. array(
  802. 'id_topic' => $topicOptions['id'],
  803. 'id_msg' => $msgOptions['id'],
  804. )
  805. );
  806. // There's been a new topic AND a new post today.
  807. trackStats(array('topics' => '+', 'posts' => '+'));
  808. updateStats('topic', true);
  809. updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
  810. // What if we want to export new topics out to a CMS?
  811. call_integration_hook('integrate_create_topic', array($msgOptions, $topicOptions, $posterOptions));
  812. }
  813. // The topic already exists, it only needs a little updating.
  814. else
  815. {
  816. $update_parameters = array(
  817. 'poster_id' => $posterOptions['id'],
  818. 'id_msg' => $msgOptions['id'],
  819. 'locked' => $topicOptions['lock_mode'],
  820. 'is_sticky' => $topicOptions['sticky_mode'],
  821. 'id_topic' => $topicOptions['id'],
  822. 'counter_increment' => 1,
  823. );
  824. $topics_columns = array();
  825. if ($msgOptions['approved'])
  826. $topics_columns = array(
  827. 'id_member_updated = {int:poster_id}',
  828. 'id_last_msg = {int:id_msg}',
  829. 'num_replies = num_replies + {int:counter_increment}',
  830. );
  831. else
  832. $topics_columns = array(
  833. 'unapproved_posts = unapproved_posts + {int:counter_increment}',
  834. );
  835. if ($topicOptions['lock_mode'] !== null)
  836. $topics_columns = array(
  837. 'locked = {int:locked}',
  838. );
  839. if ($topicOptions['sticky_mode'] !== null)
  840. $topics_columns = array(
  841. 'is_sticky = {int:is_sticky}',
  842. );
  843. call_integration_hook('integrate_modify_topic', array($topics_columns, $update_parameters, $msgOptions, $topicOptions, $posterOptions));
  844. // Update the number of replies and the lock/sticky status.
  845. $smcFunc['db_query']('', '
  846. UPDATE {db_prefix}topics
  847. SET
  848. ' . implode(', ', $topics_columns) . '
  849. WHERE id_topic = {int:id_topic}',
  850. $update_parameters
  851. );
  852. // One new post has been added today.
  853. trackStats(array('posts' => '+'));
  854. }
  855. // Creating is modifying...in a way.
  856. // @todo Why not set id_msg_modified on the insert?
  857. $smcFunc['db_query']('', '
  858. UPDATE {db_prefix}messages
  859. SET id_msg_modified = {int:id_msg}
  860. WHERE id_msg = {int:id_msg}',
  861. array(
  862. 'id_msg' => $msgOptions['id'],
  863. )
  864. );
  865. // Increase the number of posts and topics on the board.
  866. if ($msgOptions['approved'])
  867. $smcFunc['db_query']('', '
  868. UPDATE {db_prefix}boards
  869. SET num_posts = num_posts + 1' . ($new_topic ? ', num_topics = num_topics + 1' : '') . '
  870. WHERE id_board = {int:id_board}',
  871. array(
  872. 'id_board' => $topicOptions['board'],
  873. )
  874. );
  875. else
  876. {
  877. $smcFunc['db_query']('', '
  878. UPDATE {db_prefix}boards
  879. SET unapproved_posts = unapproved_posts + 1' . ($new_topic ? ', unapproved_topics = unapproved_topics + 1' : '') . '
  880. WHERE id_board = {int:id_board}',
  881. array(
  882. 'id_board' => $topicOptions['board'],
  883. )
  884. );
  885. // Add to the approval queue too.
  886. $smcFunc['db_insert']('',
  887. '{db_prefix}approval_queue',
  888. array(
  889. 'id_msg' => 'int',
  890. ),
  891. array(
  892. $msgOptions['id'],
  893. ),
  894. array()
  895. );
  896. }
  897. // Mark inserted topic as read (only for the user calling this function).
  898. if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest'])
  899. {
  900. // Since it's likely they *read* it before replying, let's try an UPDATE first.
  901. if (!$new_topic)
  902. {
  903. $smcFunc['db_query']('', '
  904. UPDATE {db_prefix}log_topics
  905. SET id_msg = {int:id_msg}
  906. WHERE id_member = {int:current_member}
  907. AND id_topic = {int:id_topic}',
  908. array(
  909. 'current_member' => $posterOptions['id'],
  910. 'id_msg' => $msgOptions['id'],
  911. 'id_topic' => $topicOptions['id'],
  912. )
  913. );
  914. $flag = $smcFunc['db_affected_rows']() != 0;
  915. }
  916. if (empty($flag))
  917. {
  918. require_once(SUBSDIR . '/Topic.subs.php');
  919. markTopicsRead(array($posterOptions['id'], $topicOptions['id'], $msgOptions['id'], 0), false);
  920. }
  921. }
  922. // If there's a custom search index, it may need updating...
  923. require_once(SUBSDIR . '/Search.subs.php');
  924. $searchAPI = findSearchAPI();
  925. if (is_callable(array($searchAPI, 'postCreated')))
  926. $searchAPI->postCreated($msgOptions, $topicOptions, $posterOptions);
  927. // Increase the post counter for the user that created the post.
  928. if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id']) && $msgOptions['approved'])
  929. {
  930. // Are you the one that happened to create this post?
  931. if ($user_info['id'] == $posterOptions['id'])
  932. $user_info['posts']++;
  933. updateMemberData($posterOptions['id'], array('posts' => '+'));
  934. }
  935. // They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...)
  936. $_SESSION['last_read_topic'] = 0;
  937. // Better safe than sorry.
  938. if (isset($_SESSION['topicseen_cache'][$topicOptions['board']]))
  939. $_SESSION['topicseen_cache'][$topicOptions['board']]--;
  940. // Update all the stats so everyone knows about this new topic and message.
  941. updateStats('message', true, $msgOptions['id']);
  942. // Update the last message on the board assuming it's approved AND the topic is.
  943. if ($msgOptions['approved'])
  944. updateLastMessages($topicOptions['board'], $new_topic || !empty($topicOptions['is_approved']) ? $msgOptions['id'] : 0);
  945. // Alright, done now... we can abort now, I guess... at least this much is done.
  946. ignore_user_abort($previous_ignore_user_abort);
  947. // Success.
  948. return true;
  949. }
  950. /**
  951. * Modifying a post...
  952. *
  953. * @param array &$msgOptions
  954. * @param array &$topicOptions
  955. * @param array &$posterOptions
  956. */
  957. function modifyPost(&$msgOptions, &$topicOptions, &$posterOptions)
  958. {
  959. global $user_info, $modSettings, $smcFunc;
  960. $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
  961. $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
  962. $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
  963. // This is longer than it has to be, but makes it so we only set/change what we have to.
  964. $messages_columns = array();
  965. if (isset($posterOptions['name']))
  966. $messages_columns['poster_name'] = $posterOptions['name'];
  967. if (isset($posterOptions['email']))
  968. $messages_columns['poster_email'] = $posterOptions['email'];
  969. if (isset($msgOptions['icon']))
  970. $messages_columns['icon'] = $msgOptions['icon'];
  971. if (isset($msgOptions['subject']))
  972. $messages_columns['subject'] = $msgOptions['subject'];
  973. if (isset($msgOptions['body']))
  974. {
  975. $messages_columns['body'] = $msgOptions['body'];
  976. // using a custom search index, then lets get the old message so we can update our index as needed
  977. if (!empty($modSettings['search_custom_index_config']))
  978. {
  979. $request = $smcFunc['db_query']('', '
  980. SELECT body
  981. FROM {db_prefix}messages
  982. WHERE id_msg = {int:id_msg}',
  983. array(
  984. 'id_msg' => $msgOptions['id'],
  985. )
  986. );
  987. list ($msgOptions['old_body']) = $smcFunc['db_fetch_row']($request);
  988. $smcFunc['db_free_result']($request);
  989. }
  990. }
  991. if (!empty($msgOptions['modify_time']))
  992. {
  993. $messages_columns['modified_time'] = $msgOptions['modify_time'];
  994. $messages_columns['modified_name'] = $msgOptions['modify_name'];
  995. $messages_columns['id_msg_modified'] = $modSettings['maxMsgID'];
  996. }
  997. if (isset($msgOptions['smileys_enabled']))
  998. $messages_columns['smileys_enabled'] = empty($msgOptions['smileys_enabled']) ? 0 : 1;
  999. // Which columns need to be ints?
  1000. $messageInts = array('modified_time', 'id_msg_modified', 'smileys_enabled');
  1001. $update_parameters = array(
  1002. 'id_msg' => $msgOptions['id'],
  1003. );
  1004. call_integration_hook('integrate_modify_post', array($messages_columns, $update_parameters, $msgOptions, $topicOptions, $posterOptions, $messageInts));
  1005. foreach ($messages_columns as $var => $val)
  1006. {
  1007. $messages_columns[$var] = $var . ' = {' . (in_array($var, $messageInts) ? 'int' : 'string') . ':var_' . $var . '}';
  1008. $update_parameters['var_' . $var] = $val;
  1009. }
  1010. // Nothing to do?
  1011. if (empty($messages_columns))
  1012. return true;
  1013. // Change the post.
  1014. $smcFunc['db_query']('', '
  1015. UPDATE {db_prefix}messages
  1016. SET ' . implode(', ', $messages_columns) . '
  1017. WHERE id_msg = {int:id_msg}',
  1018. $update_parameters
  1019. );
  1020. // Lock and or sticky the post.
  1021. if ($topicOptions['sticky_mode'] !== null || $topicOptions['lock_mode'] !== null || $topicOptions['poll'] !== null)
  1022. {
  1023. $smcFunc['db_query']('', '
  1024. UPDATE {db_prefix}topics
  1025. SET
  1026. is_sticky = {raw:is_sticky},
  1027. locked = {raw:locked},
  1028. id_poll = {raw:id_poll}
  1029. WHERE id_topic = {int:id_topic}',
  1030. array(
  1031. 'is_sticky' => $topicOptions['sticky_mode'] === null ? 'is_sticky' : (int) $topicOptions['sticky_mode'],
  1032. 'locked' => $topicOptions['lock_mode'] === null ? 'locked' : (int) $topicOptions['lock_mode'],
  1033. 'id_poll' => $topicOptions['poll'] === null ? 'id_poll' : (int) $topicOptions['poll'],
  1034. 'id_topic' => $topicOptions['id'],
  1035. )
  1036. );
  1037. }
  1038. // Mark the edited post as read.
  1039. if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest'])
  1040. {
  1041. // Since it's likely they *read* it before editing, let's try an UPDATE first.
  1042. $smcFunc['db_query']('', '
  1043. UPDATE {db_prefix}log_topics
  1044. SET id_msg = {int:id_msg}
  1045. WHERE id_member = {int:current_member}
  1046. AND id_topic = {int:id_topic}',
  1047. array(
  1048. 'current_member' => $user_info['id'],
  1049. 'id_msg' => $modSettings['maxMsgID'],
  1050. 'id_topic' => $topicOptions['id'],
  1051. )
  1052. );
  1053. $flag = $smcFunc['db_affected_rows']() != 0;
  1054. if (empty($flag))
  1055. {
  1056. require_once(SUBSDIR . '/Topic.subs.php');
  1057. markTopicsRead(array($user_info['id'], $topicOptions['id'], $modSettings['maxMsgID'], 0), false);
  1058. }
  1059. }
  1060. // If there's a custom search index, it needs to be modified...
  1061. require_once(SUBSDIR . '/Search.subs.php');
  1062. $searchAPI = findSearchAPI();
  1063. if (is_callable(array($searchAPI, 'postModified')))
  1064. $searchAPI->postModified($msgOptions, $topicOptions, $posterOptions);
  1065. if (isset($msgOptions['subject']))
  1066. {
  1067. // Only update the subject if this was the first message in the topic.
  1068. $request = $smcFunc['db_query']('', '
  1069. SELECT id_topic
  1070. FROM {db_prefix}topics
  1071. WHERE id_first_msg = {int:id_first_msg}
  1072. LIMIT 1',
  1073. array(
  1074. 'id_first_msg' => $msgOptions['id'],
  1075. )
  1076. );
  1077. if ($smcFunc['db_num_rows']($request) == 1)
  1078. updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
  1079. $smcFunc['db_free_result']($request);
  1080. }
  1081. // Finally, if we are setting the approved state we need to do much more work :(
  1082. if ($modSettings['postmod_active'] && isset($msgOptions['approved']))
  1083. approvePosts($msgOptions['id'], $msgOptions['approved']);
  1084. return true;
  1085. }
  1086. /**
  1087. * Approve (or not) some posts... without permission checks...
  1088. *
  1089. * @param array $msgs - array of message ids
  1090. * @param bool $approve = true
  1091. */
  1092. function approvePosts($msgs, $approve = true)
  1093. {
  1094. global $smcFunc;
  1095. if (!is_array($msgs))
  1096. $msgs = array($msgs);
  1097. if (empty($msgs))
  1098. return false;
  1099. // May as well start at the beginning, working out *what* we need to change.
  1100. $request = $smcFunc['db_query']('', '
  1101. SELECT m.id_msg, m.approved, m.id_topic, m.id_board, t.id_first_msg, t.id_last_msg,
  1102. m.body, m.subject, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.id_member,
  1103. t.approved AS topic_approved, b.count_posts
  1104. FROM {db_prefix}messages AS m
  1105. INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
  1106. INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
  1107. LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
  1108. WHERE m.id_msg IN ({array_int:message_list})
  1109. AND m.approved = {int:approved_state}',
  1110. array(
  1111. 'message_list' => $msgs,
  1112. 'approved_state' => $approve ? 0 : 1,
  1113. )
  1114. );
  1115. $msgs = array();
  1116. $topics = array();
  1117. $topic_changes = array();
  1118. $board_changes = array();
  1119. $notification_topics = array();
  1120. $notification_posts = array();
  1121. $member_post_changes = array();
  1122. while ($row = $smcFunc['db_fetch_assoc']($request))
  1123. {
  1124. // Easy...
  1125. $msgs[] = $row['id_msg'];
  1126. $topics[] = $row['id_topic'];
  1127. // Ensure our change array exists already.
  1128. if (!isset($topic_changes[$row['id_topic']]))
  1129. $topic_changes[$row['id_topic']] = array(
  1130. 'id_last_msg' => $row['id_last_msg'],
  1131. 'approved' => $row['topic_approved'],
  1132. 'replies' => 0,
  1133. 'unapproved_posts' => 0,
  1134. );
  1135. if (!isset($board_changes[$row['id_board']]))
  1136. $board_changes[$row['id_board']] = array(
  1137. 'posts' => 0,
  1138. 'topics' => 0,
  1139. 'unapproved_posts' => 0,
  1140. 'unapproved_topics' => 0,
  1141. );
  1142. // If it's the first message then the topic state changes!
  1143. if ($row['id_msg'] == $row['id_first_msg'])
  1144. {
  1145. $topic_changes[$row['id_topic']]['approved'] = $approve ? 1 : 0;
  1146. $board_changes[$row['id_board']]['unapproved_topics'] += $approve ? -1 : 1;
  1147. $board_changes[$row['id_board']]['topics'] += $approve ? 1 : -1;
  1148. // Note we need to ensure we announce this topic!
  1149. $notification_topics[] = array(
  1150. 'body' => $row['body'],
  1151. 'subject' => $row['subject'],
  1152. 'name' => $row['poster_name'],
  1153. 'board' => $row['id_board'],
  1154. 'topic' => $row['id_topic'],
  1155. 'msg' => $row['id_first_msg'],
  1156. 'poster' => $row['id_member'],
  1157. );
  1158. }
  1159. else
  1160. {
  1161. $topic_changes[$row['id_topic']]['replies'] += $approve ? 1 : -1;
  1162. // This will be a post... but don't notify unless it's not followed by approved ones.
  1163. if ($row['id_msg'] > $row['id_last_msg'])
  1164. $notification_posts[$row['id_topic']][] = array(
  1165. 'id' => $row['id_msg'],
  1166. 'body' => $row['body'],
  1167. 'subject' => $row['subject'],
  1168. 'name' => $row['poster_name'],
  1169. 'topic' => $row['id_topic'],
  1170. );
  1171. }
  1172. // If this is being approved and id_msg is higher than the current id_last_msg then it changes.
  1173. if ($approve && $row['id_msg'] > $topic_changes[$row['id_topic']]['id_last_msg'])
  1174. $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_msg'];
  1175. // If this is being unapproved, and it's equal to the id_last_msg we need to find a new one!
  1176. elseif (!$approve)
  1177. // Default to the first message and then we'll override in a bit ;)
  1178. $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_first_msg'];
  1179. $topic_changes[$row['id_topic']]['unapproved_posts'] += $approve ? -1 : 1;
  1180. $board_changes[$row['id_board']]['unapproved_posts'] += $approve ? -1 : 1;
  1181. $board_changes[$row['id_board']]['posts'] += $approve ? 1 : -1;
  1182. // Post count for the user?
  1183. if ($row['id_member'] && empty($row['count_posts']))
  1184. $member_post_changes[$row['id_member']] = isset($member_post_changes[$row['id_member']]) ? $member_post_changes[$row['id_member']] + 1 : 1;
  1185. }
  1186. $smcFunc['db_free_result']($request);
  1187. if (empty($msgs))
  1188. return;
  1189. // Now we have the differences make the changes, first the easy one.
  1190. $smcFunc['db_query']('', '
  1191. UPDATE {db_prefix}messages
  1192. SET approved = {int:approved_state}
  1193. WHERE id_msg IN ({array_int:message_list})',
  1194. array(
  1195. 'message_list' => $msgs,
  1196. 'approved_state' => $approve ? 1 : 0,
  1197. )
  1198. );
  1199. // If we were unapproving find the last msg in the topics...
  1200. if (!$approve)
  1201. {
  1202. $request = $smcFunc['db_query']('', '
  1203. SELECT id_topic, MAX(id_msg) AS id_last_msg
  1204. FROM {db_prefix}messages
  1205. WHERE id_topic IN ({array_int:topic_list})
  1206. AND approved = {int:approved}
  1207. GROUP BY id_topic',
  1208. array(
  1209. 'topic_list' => $topics,
  1210. 'approved' => 1,
  1211. )
  1212. );
  1213. while ($row = $smcFunc['db_fetch_assoc']($request))
  1214. $topic_changes[$row['id_topic']]['id_…

Large files files are truncated, but you can click here to view the full file