PageRenderTime 38ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/functions_admin.php

http://github.com/phpbb/phpbb3
PHP | 3231 lines | 2264 code | 485 blank | 482 comment | 371 complexity | 51c14e5d35764e3bc063a166d32489c6 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. /**
  14. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. /**
  21. * Recalculate Nested Sets
  22. *
  23. * @param int $new_id first left_id (should start with 1)
  24. * @param string $pkey primary key-column (containing the id for the parent_id of the children)
  25. * @param string $table constant or fullname of the table
  26. * @param int $parent_id parent_id of the current set (default = 0)
  27. * @param array $where contains strings to compare closer on the where statement (additional)
  28. */
  29. function recalc_nested_sets(&$new_id, $pkey, $table, $parent_id = 0, $where = array())
  30. {
  31. global $db;
  32. $sql = 'SELECT *
  33. FROM ' . $table . '
  34. WHERE parent_id = ' . (int) $parent_id .
  35. ((!empty($where)) ? ' AND ' . implode(' AND ', $where) : '') . '
  36. ORDER BY left_id ASC';
  37. $result = $db->sql_query($sql);
  38. while ($row = $db->sql_fetchrow($result))
  39. {
  40. // First we update the left_id for this module
  41. if ($row['left_id'] != $new_id)
  42. {
  43. $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('left_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}");
  44. }
  45. $new_id++;
  46. // Then we go through any children and update their left/right id's
  47. recalc_nested_sets($new_id, $pkey, $table, $row[$pkey], $where);
  48. // Then we come back and update the right_id for this module
  49. if ($row['right_id'] != $new_id)
  50. {
  51. $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('right_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}");
  52. }
  53. $new_id++;
  54. }
  55. $db->sql_freeresult($result);
  56. }
  57. /**
  58. * Simple version of jumpbox, just lists authed forums
  59. */
  60. function make_forum_select($select_id = false, $ignore_id = false, $ignore_acl = false, $ignore_nonpost = false, $ignore_emptycat = true, $only_acl_post = false, $return_array = false)
  61. {
  62. global $db, $auth, $phpbb_dispatcher;
  63. // This query is identical to the jumpbox one
  64. $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, forum_flags, forum_options, left_id, right_id
  65. FROM ' . FORUMS_TABLE . '
  66. ORDER BY left_id ASC';
  67. $result = $db->sql_query($sql, 600);
  68. $rowset = array();
  69. while ($row = $db->sql_fetchrow($result))
  70. {
  71. $rowset[(int) $row['forum_id']] = $row;
  72. }
  73. $db->sql_freeresult($result);
  74. $right = 0;
  75. $padding_store = array('0' => '');
  76. $padding = '';
  77. $forum_list = ($return_array) ? array() : '';
  78. /**
  79. * Modify the forum list data
  80. *
  81. * @event core.make_forum_select_modify_forum_list
  82. * @var array rowset Array with the forums list data
  83. * @since 3.1.10-RC1
  84. */
  85. $vars = array('rowset');
  86. extract($phpbb_dispatcher->trigger_event('core.make_forum_select_modify_forum_list', compact($vars)));
  87. // Sometimes it could happen that forums will be displayed here not be displayed within the index page
  88. // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
  89. // If this happens, the padding could be "broken"
  90. foreach ($rowset as $row)
  91. {
  92. if ($row['left_id'] < $right)
  93. {
  94. $padding .= '&nbsp; &nbsp;';
  95. $padding_store[$row['parent_id']] = $padding;
  96. }
  97. else if ($row['left_id'] > $right + 1)
  98. {
  99. $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : '';
  100. }
  101. $right = $row['right_id'];
  102. $disabled = false;
  103. if (!$ignore_acl && $auth->acl_gets(array('f_list', 'a_forum', 'a_forumadd', 'a_forumdel'), $row['forum_id']))
  104. {
  105. if ($only_acl_post && !$auth->acl_get('f_post', $row['forum_id']) || (!$auth->acl_get('m_approve', $row['forum_id']) && !$auth->acl_get('f_noapprove', $row['forum_id'])))
  106. {
  107. $disabled = true;
  108. }
  109. }
  110. else if (!$ignore_acl)
  111. {
  112. continue;
  113. }
  114. if (
  115. ((is_array($ignore_id) && in_array($row['forum_id'], $ignore_id)) || $row['forum_id'] == $ignore_id)
  116. ||
  117. // Non-postable forum with no subforums, don't display
  118. ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']) && $ignore_emptycat)
  119. ||
  120. ($row['forum_type'] != FORUM_POST && $ignore_nonpost)
  121. )
  122. {
  123. $disabled = true;
  124. }
  125. if ($return_array)
  126. {
  127. // Include some more information...
  128. $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? true : false) : (($row['forum_id'] == $select_id) ? true : false);
  129. $forum_list[$row['forum_id']] = array_merge(array('padding' => $padding, 'selected' => ($selected && !$disabled), 'disabled' => $disabled), $row);
  130. }
  131. else
  132. {
  133. $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? ' selected="selected"' : '') : (($row['forum_id'] == $select_id) ? ' selected="selected"' : '');
  134. $forum_list .= '<option value="' . $row['forum_id'] . '"' . (($disabled) ? ' disabled="disabled" class="disabled-option"' : $selected) . '>' . $padding . $row['forum_name'] . '</option>';
  135. }
  136. }
  137. unset($padding_store, $rowset);
  138. return $forum_list;
  139. }
  140. /**
  141. * Generate size select options
  142. */
  143. function size_select_options($size_compare)
  144. {
  145. global $user;
  146. $size_types_text = array($user->lang['BYTES'], $user->lang['KIB'], $user->lang['MIB']);
  147. $size_types = array('b', 'kb', 'mb');
  148. $s_size_options = '';
  149. for ($i = 0, $size = count($size_types_text); $i < $size; $i++)
  150. {
  151. $selected = ($size_compare == $size_types[$i]) ? ' selected="selected"' : '';
  152. $s_size_options .= '<option value="' . $size_types[$i] . '"' . $selected . '>' . $size_types_text[$i] . '</option>';
  153. }
  154. return $s_size_options;
  155. }
  156. /**
  157. * Generate list of groups (option fields without select)
  158. *
  159. * @param int $group_id The default group id to mark as selected
  160. * @param array $exclude_ids The group ids to exclude from the list, false (default) if you whish to exclude no id
  161. * @param int $manage_founder If set to false (default) all groups are returned, if 0 only those groups returned not being managed by founders only, if 1 only those groups returned managed by founders only.
  162. *
  163. * @return string The list of options.
  164. */
  165. function group_select_options($group_id, $exclude_ids = false, $manage_founder = false)
  166. {
  167. global $db, $config, $phpbb_container;
  168. /** @var \phpbb\group\helper $group_helper */
  169. $group_helper = $phpbb_container->get('group_helper');
  170. $exclude_sql = ($exclude_ids !== false && count($exclude_ids)) ? 'WHERE ' . $db->sql_in_set('group_id', array_map('intval', $exclude_ids), true) : '';
  171. $sql_and = (!$config['coppa_enable']) ? (($exclude_sql) ? ' AND ' : ' WHERE ') . "group_name <> 'REGISTERED_COPPA'" : '';
  172. $sql_founder = ($manage_founder !== false) ? (($exclude_sql || $sql_and) ? ' AND ' : ' WHERE ') . 'group_founder_manage = ' . (int) $manage_founder : '';
  173. $sql = 'SELECT group_id, group_name, group_type
  174. FROM ' . GROUPS_TABLE . "
  175. $exclude_sql
  176. $sql_and
  177. $sql_founder
  178. ORDER BY group_type DESC, group_name ASC";
  179. $result = $db->sql_query($sql);
  180. $s_group_options = '';
  181. while ($row = $db->sql_fetchrow($result))
  182. {
  183. $selected = ($row['group_id'] == $group_id) ? ' selected="selected"' : '';
  184. $s_group_options .= '<option' . (($row['group_type'] == GROUP_SPECIAL) ? ' class="sep"' : '') . ' value="' . $row['group_id'] . '"' . $selected . '>' . $group_helper->get_name($row['group_name']) . '</option>';
  185. }
  186. $db->sql_freeresult($result);
  187. return $s_group_options;
  188. }
  189. /**
  190. * Obtain authed forums list
  191. */
  192. function get_forum_list($acl_list = 'f_list', $id_only = true, $postable_only = false, $no_cache = false)
  193. {
  194. global $db, $auth, $phpbb_dispatcher;
  195. static $forum_rows;
  196. if (!isset($forum_rows))
  197. {
  198. // This query is identical to the jumpbox one
  199. $expire_time = ($no_cache) ? 0 : 600;
  200. $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
  201. FROM ' . FORUMS_TABLE . '
  202. ORDER BY left_id ASC';
  203. $result = $db->sql_query($sql, $expire_time);
  204. $forum_rows = array();
  205. $right = $padding = 0;
  206. $padding_store = array('0' => 0);
  207. while ($row = $db->sql_fetchrow($result))
  208. {
  209. if ($row['left_id'] < $right)
  210. {
  211. $padding++;
  212. $padding_store[$row['parent_id']] = $padding;
  213. }
  214. else if ($row['left_id'] > $right + 1)
  215. {
  216. // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
  217. // @todo digging deep to find out "how" this can happen.
  218. $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
  219. }
  220. $right = $row['right_id'];
  221. $row['padding'] = $padding;
  222. $forum_rows[] = $row;
  223. }
  224. $db->sql_freeresult($result);
  225. unset($padding_store);
  226. }
  227. $rowset = array();
  228. foreach ($forum_rows as $row)
  229. {
  230. if ($postable_only && $row['forum_type'] != FORUM_POST)
  231. {
  232. continue;
  233. }
  234. if ($acl_list == '' || ($acl_list != '' && $auth->acl_gets($acl_list, $row['forum_id'])))
  235. {
  236. $rowset[] = ($id_only) ? (int) $row['forum_id'] : $row;
  237. }
  238. }
  239. /**
  240. * Modify the forum list data
  241. *
  242. * @event core.get_forum_list_modify_data
  243. * @var array rowset Array with the forum list data
  244. * @since 3.1.10-RC1
  245. */
  246. $vars = array('rowset');
  247. extract($phpbb_dispatcher->trigger_event('core.get_forum_list_modify_data', compact($vars)));
  248. return $rowset;
  249. }
  250. /**
  251. * Get forum branch
  252. */
  253. function get_forum_branch($forum_id, $type = 'all', $order = 'descending', $include_forum = true)
  254. {
  255. global $db;
  256. switch ($type)
  257. {
  258. case 'parents':
  259. $condition = 'f1.left_id BETWEEN f2.left_id AND f2.right_id';
  260. break;
  261. case 'children':
  262. $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id';
  263. break;
  264. default:
  265. $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id OR f1.left_id BETWEEN f2.left_id AND f2.right_id';
  266. break;
  267. }
  268. $rows = array();
  269. $sql = 'SELECT f2.*
  270. FROM ' . FORUMS_TABLE . ' f1
  271. LEFT JOIN ' . FORUMS_TABLE . " f2 ON ($condition)
  272. WHERE f1.forum_id = $forum_id
  273. ORDER BY f2.left_id " . (($order == 'descending') ? 'ASC' : 'DESC');
  274. $result = $db->sql_query($sql);
  275. while ($row = $db->sql_fetchrow($result))
  276. {
  277. if (!$include_forum && $row['forum_id'] == $forum_id)
  278. {
  279. continue;
  280. }
  281. $rows[] = $row;
  282. }
  283. $db->sql_freeresult($result);
  284. return $rows;
  285. }
  286. /**
  287. * Copies permissions from one forum to others
  288. *
  289. * @param int $src_forum_id The source forum we want to copy permissions from
  290. * @param array $dest_forum_ids The destination forum(s) we want to copy to
  291. * @param bool $clear_dest_perms True if destination permissions should be deleted
  292. * @param bool $add_log True if log entry should be added
  293. *
  294. * @return bool False on error
  295. */
  296. function copy_forum_permissions($src_forum_id, $dest_forum_ids, $clear_dest_perms = true, $add_log = true)
  297. {
  298. global $db, $user, $phpbb_log;
  299. // Only one forum id specified
  300. if (!is_array($dest_forum_ids))
  301. {
  302. $dest_forum_ids = array($dest_forum_ids);
  303. }
  304. // Make sure forum ids are integers
  305. $src_forum_id = (int) $src_forum_id;
  306. $dest_forum_ids = array_map('intval', $dest_forum_ids);
  307. // No source forum or no destination forums specified
  308. if (empty($src_forum_id) || empty($dest_forum_ids))
  309. {
  310. return false;
  311. }
  312. // Check if source forum exists
  313. $sql = 'SELECT forum_name
  314. FROM ' . FORUMS_TABLE . '
  315. WHERE forum_id = ' . $src_forum_id;
  316. $result = $db->sql_query($sql);
  317. $src_forum_name = $db->sql_fetchfield('forum_name');
  318. $db->sql_freeresult($result);
  319. // Source forum doesn't exist
  320. if (empty($src_forum_name))
  321. {
  322. return false;
  323. }
  324. // Check if destination forums exists
  325. $sql = 'SELECT forum_id, forum_name
  326. FROM ' . FORUMS_TABLE . '
  327. WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
  328. $result = $db->sql_query($sql);
  329. $dest_forum_ids = $dest_forum_names = array();
  330. while ($row = $db->sql_fetchrow($result))
  331. {
  332. $dest_forum_ids[] = (int) $row['forum_id'];
  333. $dest_forum_names[] = $row['forum_name'];
  334. }
  335. $db->sql_freeresult($result);
  336. // No destination forum exists
  337. if (empty($dest_forum_ids))
  338. {
  339. return false;
  340. }
  341. // From the mysql documentation:
  342. // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear
  343. // in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14.
  344. // Due to this we stay on the safe side if we do the insertion "the manual way"
  345. // Rowsets we're going to insert
  346. $users_sql_ary = $groups_sql_ary = array();
  347. // Query acl users table for source forum data
  348. $sql = 'SELECT user_id, auth_option_id, auth_role_id, auth_setting
  349. FROM ' . ACL_USERS_TABLE . '
  350. WHERE forum_id = ' . $src_forum_id;
  351. $result = $db->sql_query($sql);
  352. while ($row = $db->sql_fetchrow($result))
  353. {
  354. $row = array(
  355. 'user_id' => (int) $row['user_id'],
  356. 'auth_option_id' => (int) $row['auth_option_id'],
  357. 'auth_role_id' => (int) $row['auth_role_id'],
  358. 'auth_setting' => (int) $row['auth_setting'],
  359. );
  360. foreach ($dest_forum_ids as $dest_forum_id)
  361. {
  362. $users_sql_ary[] = $row + array('forum_id' => $dest_forum_id);
  363. }
  364. }
  365. $db->sql_freeresult($result);
  366. // Query acl groups table for source forum data
  367. $sql = 'SELECT group_id, auth_option_id, auth_role_id, auth_setting
  368. FROM ' . ACL_GROUPS_TABLE . '
  369. WHERE forum_id = ' . $src_forum_id;
  370. $result = $db->sql_query($sql);
  371. while ($row = $db->sql_fetchrow($result))
  372. {
  373. $row = array(
  374. 'group_id' => (int) $row['group_id'],
  375. 'auth_option_id' => (int) $row['auth_option_id'],
  376. 'auth_role_id' => (int) $row['auth_role_id'],
  377. 'auth_setting' => (int) $row['auth_setting'],
  378. );
  379. foreach ($dest_forum_ids as $dest_forum_id)
  380. {
  381. $groups_sql_ary[] = $row + array('forum_id' => $dest_forum_id);
  382. }
  383. }
  384. $db->sql_freeresult($result);
  385. $db->sql_transaction('begin');
  386. // Clear current permissions of destination forums
  387. if ($clear_dest_perms)
  388. {
  389. $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
  390. WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
  391. $db->sql_query($sql);
  392. $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
  393. WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
  394. $db->sql_query($sql);
  395. }
  396. $db->sql_multi_insert(ACL_USERS_TABLE, $users_sql_ary);
  397. $db->sql_multi_insert(ACL_GROUPS_TABLE, $groups_sql_ary);
  398. if ($add_log)
  399. {
  400. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_COPIED_PERMISSIONS', false, array($src_forum_name, implode(', ', $dest_forum_names)));
  401. }
  402. $db->sql_transaction('commit');
  403. return true;
  404. }
  405. /**
  406. * Get physical file listing
  407. */
  408. function filelist($rootdir, $dir = '', $type = 'gif|jpg|jpeg|png')
  409. {
  410. $matches = array($dir => array());
  411. // Remove initial / if present
  412. $rootdir = (substr($rootdir, 0, 1) == '/') ? substr($rootdir, 1) : $rootdir;
  413. // Add closing / if not present
  414. $rootdir = ($rootdir && substr($rootdir, -1) != '/') ? $rootdir . '/' : $rootdir;
  415. // Remove initial / if present
  416. $dir = (substr($dir, 0, 1) == '/') ? substr($dir, 1) : $dir;
  417. // Add closing / if not present
  418. $dir = ($dir && substr($dir, -1) != '/') ? $dir . '/' : $dir;
  419. if (!is_dir($rootdir . $dir))
  420. {
  421. return $matches;
  422. }
  423. $dh = @opendir($rootdir . $dir);
  424. if (!$dh)
  425. {
  426. return $matches;
  427. }
  428. while (($fname = readdir($dh)) !== false)
  429. {
  430. if (is_file("$rootdir$dir$fname"))
  431. {
  432. if (filesize("$rootdir$dir$fname") && preg_match('#\.' . $type . '$#i', $fname))
  433. {
  434. $matches[$dir][] = $fname;
  435. }
  436. }
  437. else if ($fname[0] != '.' && is_dir("$rootdir$dir$fname"))
  438. {
  439. $matches += filelist($rootdir, "$dir$fname", $type);
  440. }
  441. }
  442. closedir($dh);
  443. return $matches;
  444. }
  445. /**
  446. * Move topic(s)
  447. */
  448. function move_topics($topic_ids, $forum_id, $auto_sync = true)
  449. {
  450. global $db, $phpbb_dispatcher;
  451. if (empty($topic_ids))
  452. {
  453. return;
  454. }
  455. $forum_ids = array($forum_id);
  456. if (!is_array($topic_ids))
  457. {
  458. $topic_ids = array($topic_ids);
  459. }
  460. /**
  461. * Perform additional actions before topics move
  462. *
  463. * @event core.move_topics_before
  464. * @var array topic_ids Array of the moved topic ids
  465. * @var string forum_id The forum id from where the topics are moved
  466. * @since 3.2.9-RC1
  467. */
  468. $vars = array(
  469. 'topic_ids',
  470. 'forum_id',
  471. );
  472. extract($phpbb_dispatcher->trigger_event('core.move_topics_before', compact($vars)));
  473. $sql = 'DELETE FROM ' . TOPICS_TABLE . '
  474. WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids) . '
  475. AND forum_id = ' . $forum_id;
  476. $db->sql_query($sql);
  477. if ($auto_sync)
  478. {
  479. $sql = 'SELECT DISTINCT forum_id
  480. FROM ' . TOPICS_TABLE . '
  481. WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
  482. $result = $db->sql_query($sql);
  483. while ($row = $db->sql_fetchrow($result))
  484. {
  485. $forum_ids[] = $row['forum_id'];
  486. }
  487. $db->sql_freeresult($result);
  488. }
  489. $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE);
  490. /**
  491. * Perform additional actions before topics move
  492. *
  493. * @event core.move_topics_before_query
  494. * @var array table_ary Array of tables from which forum_id will be updated for all rows that hold the moved topics
  495. * @var array topic_ids Array of the moved topic ids
  496. * @var string forum_id The forum id from where the topics are moved
  497. * @var array forum_ids Array of the forums where the topics are moving (includes also forum_id)
  498. * @var bool auto_sync Whether or not to perform auto sync
  499. * @since 3.1.5-RC1
  500. */
  501. $vars = array(
  502. 'table_ary',
  503. 'topic_ids',
  504. 'forum_id',
  505. 'forum_ids',
  506. 'auto_sync',
  507. );
  508. extract($phpbb_dispatcher->trigger_event('core.move_topics_before_query', compact($vars)));
  509. foreach ($table_ary as $table)
  510. {
  511. $sql = "UPDATE $table
  512. SET forum_id = $forum_id
  513. WHERE " . $db->sql_in_set('topic_id', $topic_ids);
  514. $db->sql_query($sql);
  515. }
  516. unset($table_ary);
  517. /**
  518. * Perform additional actions after topics move
  519. *
  520. * @event core.move_topics_after
  521. * @var array topic_ids Array of the moved topic ids
  522. * @var string forum_id The forum id from where the topics were moved
  523. * @var array forum_ids Array of the forums where the topics were moved (includes also forum_id)
  524. * @since 3.2.9-RC1
  525. */
  526. $vars = array(
  527. 'topic_ids',
  528. 'forum_id',
  529. 'forum_ids',
  530. );
  531. extract($phpbb_dispatcher->trigger_event('core.move_topics_after', compact($vars)));
  532. if ($auto_sync)
  533. {
  534. sync('forum', 'forum_id', $forum_ids, true, true);
  535. unset($forum_ids);
  536. }
  537. }
  538. /**
  539. * Move post(s)
  540. */
  541. function move_posts($post_ids, $topic_id, $auto_sync = true)
  542. {
  543. global $db, $phpbb_dispatcher;
  544. if (!is_array($post_ids))
  545. {
  546. $post_ids = array($post_ids);
  547. }
  548. $forum_ids = array();
  549. $topic_ids = array($topic_id);
  550. $sql = 'SELECT DISTINCT topic_id, forum_id
  551. FROM ' . POSTS_TABLE . '
  552. WHERE ' . $db->sql_in_set('post_id', $post_ids);
  553. $result = $db->sql_query($sql);
  554. while ($row = $db->sql_fetchrow($result))
  555. {
  556. $forum_ids[] = (int) $row['forum_id'];
  557. $topic_ids[] = (int) $row['topic_id'];
  558. }
  559. $db->sql_freeresult($result);
  560. $sql = 'SELECT forum_id
  561. FROM ' . TOPICS_TABLE . '
  562. WHERE topic_id = ' . $topic_id;
  563. $result = $db->sql_query($sql);
  564. $forum_row = $db->sql_fetchrow($result);
  565. $db->sql_freeresult($result);
  566. if (!$forum_row)
  567. {
  568. trigger_error('NO_TOPIC');
  569. }
  570. /**
  571. * Perform additional actions before moving posts
  572. *
  573. * @event core.move_posts_before
  574. * @var array post_ids Array of post ids to move
  575. * @var int topic_id The topic id the posts are moved to
  576. * @var bool auto_sync Whether or not to perform auto sync
  577. * @var array forum_ids Array of the forum ids the posts are moved from
  578. * @var array topic_ids Array of the topic ids the posts are moved from
  579. * @var array forum_row Array with the forum id of the topic the posts are moved to
  580. * @since 3.1.7-RC1
  581. */
  582. $vars = array(
  583. 'post_ids',
  584. 'topic_id',
  585. 'auto_sync',
  586. 'forum_ids',
  587. 'topic_ids',
  588. 'forum_row',
  589. );
  590. extract($phpbb_dispatcher->trigger_event('core.move_posts_before', compact($vars)));
  591. $sql = 'UPDATE ' . POSTS_TABLE . '
  592. SET forum_id = ' . (int) $forum_row['forum_id'] . ", topic_id = $topic_id
  593. WHERE " . $db->sql_in_set('post_id', $post_ids);
  594. $db->sql_query($sql);
  595. $sql = 'UPDATE ' . ATTACHMENTS_TABLE . "
  596. SET topic_id = $topic_id, in_message = 0
  597. WHERE " . $db->sql_in_set('post_msg_id', $post_ids);
  598. $db->sql_query($sql);
  599. /**
  600. * Perform additional actions after moving posts
  601. *
  602. * @event core.move_posts_after
  603. * @var array post_ids Array of the moved post ids
  604. * @var int topic_id The topic id the posts are moved to
  605. * @var bool auto_sync Whether or not to perform auto sync
  606. * @var array forum_ids Array of the forum ids the posts are moved from
  607. * @var array topic_ids Array of the topic ids the posts are moved from
  608. * @var array forum_row Array with the forum id of the topic the posts are moved to
  609. * @since 3.1.7-RC1
  610. */
  611. $vars = array(
  612. 'post_ids',
  613. 'topic_id',
  614. 'auto_sync',
  615. 'forum_ids',
  616. 'topic_ids',
  617. 'forum_row',
  618. );
  619. extract($phpbb_dispatcher->trigger_event('core.move_posts_after', compact($vars)));
  620. if ($auto_sync)
  621. {
  622. $forum_ids[] = (int) $forum_row['forum_id'];
  623. sync('topic_reported', 'topic_id', $topic_ids);
  624. sync('topic_attachment', 'topic_id', $topic_ids);
  625. sync('topic', 'topic_id', $topic_ids, true);
  626. sync('forum', 'forum_id', $forum_ids, true, true);
  627. /**
  628. * Perform additional actions after move post sync
  629. *
  630. * @event core.move_posts_sync_after
  631. * @var array post_ids Array of the moved post ids
  632. * @var int topic_id The topic id the posts are moved to
  633. * @var bool auto_sync Whether or not to perform auto sync
  634. * @var array forum_ids Array of the forum ids the posts are moved from
  635. * @var array topic_ids Array of the topic ids the posts are moved from
  636. * @var array forum_row Array with the forum id of the topic the posts are moved to
  637. * @since 3.1.11-RC1
  638. */
  639. $vars = array(
  640. 'post_ids',
  641. 'topic_id',
  642. 'auto_sync',
  643. 'forum_ids',
  644. 'topic_ids',
  645. 'forum_row',
  646. );
  647. extract($phpbb_dispatcher->trigger_event('core.move_posts_sync_after', compact($vars)));
  648. }
  649. // Update posted information
  650. update_posted_info($topic_ids);
  651. }
  652. /**
  653. * Remove topic(s)
  654. */
  655. function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true)
  656. {
  657. global $db, $config, $phpbb_container, $phpbb_dispatcher;
  658. $approved_topics = 0;
  659. $forum_ids = $topic_ids = array();
  660. if ($where_type === 'range')
  661. {
  662. $where_clause = $where_ids;
  663. }
  664. else
  665. {
  666. $where_ids = (is_array($where_ids)) ? array_unique($where_ids) : array($where_ids);
  667. if (!count($where_ids))
  668. {
  669. return array('topics' => 0, 'posts' => 0);
  670. }
  671. $where_clause = $db->sql_in_set($where_type, $where_ids);
  672. }
  673. // Making sure that delete_posts does not call delete_topics again...
  674. $return = array(
  675. 'posts' => ($call_delete_posts) ? delete_posts($where_type, $where_ids, false, true, $post_count_sync, false) : 0,
  676. );
  677. $sql = 'SELECT topic_id, forum_id, topic_visibility, topic_moved_id
  678. FROM ' . TOPICS_TABLE . '
  679. WHERE ' . $where_clause;
  680. $result = $db->sql_query($sql);
  681. while ($row = $db->sql_fetchrow($result))
  682. {
  683. $forum_ids[] = $row['forum_id'];
  684. $topic_ids[] = $row['topic_id'];
  685. if ($row['topic_visibility'] == ITEM_APPROVED && !$row['topic_moved_id'])
  686. {
  687. $approved_topics++;
  688. }
  689. }
  690. $db->sql_freeresult($result);
  691. $return['topics'] = count($topic_ids);
  692. if (!count($topic_ids))
  693. {
  694. return $return;
  695. }
  696. $db->sql_transaction('begin');
  697. $table_ary = array(BOOKMARKS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, POLL_VOTES_TABLE, POLL_OPTIONS_TABLE, TOPICS_WATCH_TABLE, TOPICS_TABLE);
  698. /**
  699. * Perform additional actions before topic(s) deletion
  700. *
  701. * @event core.delete_topics_before_query
  702. * @var array table_ary Array of tables from which all rows will be deleted that hold a topic_id occuring in topic_ids
  703. * @var array topic_ids Array of topic ids to delete
  704. * @since 3.1.4-RC1
  705. */
  706. $vars = array(
  707. 'table_ary',
  708. 'topic_ids',
  709. );
  710. extract($phpbb_dispatcher->trigger_event('core.delete_topics_before_query', compact($vars)));
  711. foreach ($table_ary as $table)
  712. {
  713. $sql = "DELETE FROM $table
  714. WHERE " . $db->sql_in_set('topic_id', $topic_ids);
  715. $db->sql_query($sql);
  716. }
  717. unset($table_ary);
  718. /**
  719. * Perform additional actions after topic(s) deletion
  720. *
  721. * @event core.delete_topics_after_query
  722. * @var array topic_ids Array of topic ids that were deleted
  723. * @since 3.1.4-RC1
  724. */
  725. $vars = array(
  726. 'topic_ids',
  727. );
  728. extract($phpbb_dispatcher->trigger_event('core.delete_topics_after_query', compact($vars)));
  729. $moved_topic_ids = array();
  730. // update the other forums
  731. $sql = 'SELECT topic_id, forum_id
  732. FROM ' . TOPICS_TABLE . '
  733. WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids);
  734. $result = $db->sql_query($sql);
  735. while ($row = $db->sql_fetchrow($result))
  736. {
  737. $forum_ids[] = $row['forum_id'];
  738. $moved_topic_ids[] = $row['topic_id'];
  739. }
  740. $db->sql_freeresult($result);
  741. if (count($moved_topic_ids))
  742. {
  743. $sql = 'DELETE FROM ' . TOPICS_TABLE . '
  744. WHERE ' . $db->sql_in_set('topic_id', $moved_topic_ids);
  745. $db->sql_query($sql);
  746. }
  747. $db->sql_transaction('commit');
  748. if ($auto_sync)
  749. {
  750. sync('forum', 'forum_id', array_unique($forum_ids), true, true);
  751. sync('topic_reported', $where_type, $where_ids);
  752. }
  753. if ($approved_topics)
  754. {
  755. $config->increment('num_topics', $approved_topics * (-1), false);
  756. }
  757. /* @var $phpbb_notifications \phpbb\notification\manager */
  758. $phpbb_notifications = $phpbb_container->get('notification_manager');
  759. $phpbb_notifications->delete_notifications(array(
  760. 'notification.type.topic',
  761. 'notification.type.approve_topic',
  762. 'notification.type.topic_in_queue',
  763. ), $topic_ids);
  764. return $return;
  765. }
  766. /**
  767. * Remove post(s)
  768. */
  769. function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true)
  770. {
  771. global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container, $phpbb_dispatcher;
  772. // Notifications types to delete
  773. $delete_notifications_types = array(
  774. 'notification.type.quote',
  775. 'notification.type.approve_post',
  776. 'notification.type.post_in_queue',
  777. 'notification.type.report_post',
  778. );
  779. /**
  780. * Perform additional actions before post(s) deletion
  781. *
  782. * @event core.delete_posts_before
  783. * @var string where_type Variable containing posts deletion mode
  784. * @var mixed where_ids Array or comma separated list of posts ids to delete
  785. * @var bool auto_sync Flag indicating if topics/forums should be synchronized
  786. * @var bool posted_sync Flag indicating if topics_posted table should be resynchronized
  787. * @var bool post_count_sync Flag indicating if posts count should be resynchronized
  788. * @var bool call_delete_topics Flag indicating if topics having no posts should be deleted
  789. * @var array delete_notifications_types Array with notifications types to delete
  790. * @since 3.1.0-a4
  791. */
  792. $vars = array(
  793. 'where_type',
  794. 'where_ids',
  795. 'auto_sync',
  796. 'posted_sync',
  797. 'post_count_sync',
  798. 'call_delete_topics',
  799. 'delete_notifications_types',
  800. );
  801. extract($phpbb_dispatcher->trigger_event('core.delete_posts_before', compact($vars)));
  802. if ($where_type === 'range')
  803. {
  804. $where_clause = $where_ids;
  805. }
  806. else
  807. {
  808. if (is_array($where_ids))
  809. {
  810. $where_ids = array_unique($where_ids);
  811. }
  812. else
  813. {
  814. $where_ids = array($where_ids);
  815. }
  816. if (!count($where_ids))
  817. {
  818. return false;
  819. }
  820. $where_ids = array_map('intval', $where_ids);
  821. /* Possible code for splitting post deletion
  822. if (count($where_ids) >= 1001)
  823. {
  824. // Split into chunks of 1000
  825. $chunks = array_chunk($where_ids, 1000);
  826. foreach ($chunks as $_where_ids)
  827. {
  828. delete_posts($where_type, $_where_ids, $auto_sync, $posted_sync, $post_count_sync, $call_delete_topics);
  829. }
  830. return;
  831. }*/
  832. $where_clause = $db->sql_in_set($where_type, $where_ids);
  833. }
  834. $approved_posts = 0;
  835. $post_ids = $topic_ids = $forum_ids = $post_counts = $remove_topics = array();
  836. $sql = 'SELECT post_id, poster_id, post_visibility, post_postcount, topic_id, forum_id
  837. FROM ' . POSTS_TABLE . '
  838. WHERE ' . $where_clause;
  839. $result = $db->sql_query($sql);
  840. while ($row = $db->sql_fetchrow($result))
  841. {
  842. $post_ids[] = (int) $row['post_id'];
  843. $poster_ids[] = (int) $row['poster_id'];
  844. $topic_ids[] = (int) $row['topic_id'];
  845. $forum_ids[] = (int) $row['forum_id'];
  846. if ($row['post_postcount'] && $post_count_sync && $row['post_visibility'] == ITEM_APPROVED)
  847. {
  848. $post_counts[$row['poster_id']] = (!empty($post_counts[$row['poster_id']])) ? $post_counts[$row['poster_id']] + 1 : 1;
  849. }
  850. if ($row['post_visibility'] == ITEM_APPROVED)
  851. {
  852. $approved_posts++;
  853. }
  854. }
  855. $db->sql_freeresult($result);
  856. if (!count($post_ids))
  857. {
  858. return false;
  859. }
  860. $db->sql_transaction('begin');
  861. $table_ary = array(POSTS_TABLE, REPORTS_TABLE);
  862. /**
  863. * Perform additional actions during post(s) deletion before running the queries
  864. *
  865. * @event core.delete_posts_in_transaction_before
  866. * @var array post_ids Array with deleted posts' ids
  867. * @var array poster_ids Array with deleted posts' author ids
  868. * @var array topic_ids Array with deleted posts' topic ids
  869. * @var array forum_ids Array with deleted posts' forum ids
  870. * @var string where_type Variable containing posts deletion mode
  871. * @var mixed where_ids Array or comma separated list of post ids to delete
  872. * @var array delete_notifications_types Array with notifications types to delete
  873. * @var array table_ary Array with table names to delete data from
  874. * @since 3.1.7-RC1
  875. */
  876. $vars = array(
  877. 'post_ids',
  878. 'poster_ids',
  879. 'topic_ids',
  880. 'forum_ids',
  881. 'where_type',
  882. 'where_ids',
  883. 'delete_notifications_types',
  884. 'table_ary',
  885. );
  886. extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction_before', compact($vars)));
  887. foreach ($table_ary as $table)
  888. {
  889. $sql = "DELETE FROM $table
  890. WHERE " . $db->sql_in_set('post_id', $post_ids);
  891. $db->sql_query($sql);
  892. }
  893. unset($table_ary);
  894. // Adjust users post counts
  895. if (count($post_counts) && $post_count_sync)
  896. {
  897. foreach ($post_counts as $poster_id => $substract)
  898. {
  899. $sql = 'UPDATE ' . USERS_TABLE . '
  900. SET user_posts = 0
  901. WHERE user_id = ' . $poster_id . '
  902. AND user_posts < ' . $substract;
  903. $db->sql_query($sql);
  904. $sql = 'UPDATE ' . USERS_TABLE . '
  905. SET user_posts = user_posts - ' . $substract . '
  906. WHERE user_id = ' . $poster_id . '
  907. AND user_posts >= ' . $substract;
  908. $db->sql_query($sql);
  909. }
  910. }
  911. // Remove topics now having no posts?
  912. if (count($topic_ids))
  913. {
  914. $sql = 'SELECT topic_id
  915. FROM ' . POSTS_TABLE . '
  916. WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
  917. GROUP BY topic_id';
  918. $result = $db->sql_query($sql);
  919. while ($row = $db->sql_fetchrow($result))
  920. {
  921. $remove_topics[] = $row['topic_id'];
  922. }
  923. $db->sql_freeresult($result);
  924. // Actually, those not within remove_topics should be removed. ;)
  925. $remove_topics = array_diff($topic_ids, $remove_topics);
  926. }
  927. // Remove the message from the search index
  928. $search_type = $config['search_type'];
  929. if (!class_exists($search_type))
  930. {
  931. trigger_error('NO_SUCH_SEARCH_MODULE');
  932. }
  933. $error = false;
  934. $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher);
  935. if ($error)
  936. {
  937. trigger_error($error);
  938. }
  939. $search->index_remove($post_ids, $poster_ids, $forum_ids);
  940. /** @var \phpbb\attachment\manager $attachment_manager */
  941. $attachment_manager = $phpbb_container->get('attachment.manager');
  942. $attachment_manager->delete('post', $post_ids, false);
  943. unset($attachment_manager);
  944. /**
  945. * Perform additional actions during post(s) deletion
  946. *
  947. * @event core.delete_posts_in_transaction
  948. * @var array post_ids Array with deleted posts' ids
  949. * @var array poster_ids Array with deleted posts' author ids
  950. * @var array topic_ids Array with deleted posts' topic ids
  951. * @var array forum_ids Array with deleted posts' forum ids
  952. * @var string where_type Variable containing posts deletion mode
  953. * @var mixed where_ids Array or comma separated list of posts ids to delete
  954. * @var array delete_notifications_types Array with notifications types to delete
  955. * @since 3.1.0-a4
  956. */
  957. $vars = array(
  958. 'post_ids',
  959. 'poster_ids',
  960. 'topic_ids',
  961. 'forum_ids',
  962. 'where_type',
  963. 'where_ids',
  964. 'delete_notifications_types',
  965. );
  966. extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction', compact($vars)));
  967. $db->sql_transaction('commit');
  968. /**
  969. * Perform additional actions after post(s) deletion
  970. *
  971. * @event core.delete_posts_after
  972. * @var array post_ids Array with deleted posts' ids
  973. * @var array poster_ids Array with deleted posts' author ids
  974. * @var array topic_ids Array with deleted posts' topic ids
  975. * @var array forum_ids Array with deleted posts' forum ids
  976. * @var string where_type Variable containing posts deletion mode
  977. * @var mixed where_ids Array or comma separated list of posts ids to delete
  978. * @var array delete_notifications_types Array with notifications types to delete
  979. * @since 3.1.0-a4
  980. */
  981. $vars = array(
  982. 'post_ids',
  983. 'poster_ids',
  984. 'topic_ids',
  985. 'forum_ids',
  986. 'where_type',
  987. 'where_ids',
  988. 'delete_notifications_types',
  989. );
  990. extract($phpbb_dispatcher->trigger_event('core.delete_posts_after', compact($vars)));
  991. // Resync topics_posted table
  992. if ($posted_sync)
  993. {
  994. update_posted_info($topic_ids);
  995. }
  996. if ($auto_sync)
  997. {
  998. sync('topic_reported', 'topic_id', $topic_ids);
  999. sync('topic', 'topic_id', $topic_ids, true);
  1000. sync('forum', 'forum_id', $forum_ids, true, true);
  1001. }
  1002. if ($approved_posts && $post_count_sync)
  1003. {
  1004. $config->increment('num_posts', $approved_posts * (-1), false);
  1005. }
  1006. // We actually remove topics now to not be inconsistent (the delete_topics function calls this function too)
  1007. if (count($remove_topics) && $call_delete_topics)
  1008. {
  1009. delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false);
  1010. }
  1011. /* @var $phpbb_notifications \phpbb\notification\manager */
  1012. $phpbb_notifications = $phpbb_container->get('notification_manager');
  1013. $phpbb_notifications->delete_notifications($delete_notifications_types, $post_ids);
  1014. return count($post_ids);
  1015. }
  1016. /**
  1017. * Deletes shadow topics pointing to a specified forum.
  1018. *
  1019. * @param int $forum_id The forum id
  1020. * @param string $sql_more Additional WHERE statement, e.g. t.topic_time < (time() - 1234)
  1021. * @param bool $auto_sync Will call sync() if this is true
  1022. *
  1023. * @return array Array with affected forums
  1024. */
  1025. function delete_topic_shadows($forum_id, $sql_more = '', $auto_sync = true)
  1026. {
  1027. global $db;
  1028. if (!$forum_id)
  1029. {
  1030. // Nothing to do.
  1031. return;
  1032. }
  1033. // Set of affected forums we have to resync
  1034. $sync_forum_ids = array();
  1035. // Amount of topics we select and delete at once.
  1036. $batch_size = 500;
  1037. do
  1038. {
  1039. $sql = 'SELECT t2.forum_id, t2.topic_id
  1040. FROM ' . TOPICS_TABLE . ' t2, ' . TOPICS_TABLE . ' t
  1041. WHERE t2.topic_moved_id = t.topic_id
  1042. AND t.forum_id = ' . (int) $forum_id . '
  1043. ' . (($sql_more) ? 'AND ' . $sql_more : '');
  1044. $result = $db->sql_query_limit($sql, $batch_size);
  1045. $topic_ids = array();
  1046. while ($row = $db->sql_fetchrow($result))
  1047. {
  1048. $topic_ids[] = (int) $row['topic_id'];
  1049. $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id'];
  1050. }
  1051. $db->sql_freeresult($result);
  1052. if (!empty($topic_ids))
  1053. {
  1054. $sql = 'DELETE FROM ' . TOPICS_TABLE . '
  1055. WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
  1056. $db->sql_query($sql);
  1057. }
  1058. }
  1059. while (count($topic_ids) == $batch_size);
  1060. if ($auto_sync)
  1061. {
  1062. sync('forum', 'forum_id', $sync_forum_ids, true, true);
  1063. }
  1064. return $sync_forum_ids;
  1065. }
  1066. /**
  1067. * Update/Sync posted information for topics
  1068. */
  1069. function update_posted_info(&$topic_ids)
  1070. {
  1071. global $db, $config;
  1072. if (empty($topic_ids) || !$config['load_db_track'])
  1073. {
  1074. return;
  1075. }
  1076. // First of all, let us remove any posted information for these topics
  1077. $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . '
  1078. WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
  1079. $db->sql_query($sql);
  1080. // Now, let us collect the user/topic combos for rebuilding the information
  1081. $sql = 'SELECT poster_id, topic_id
  1082. FROM ' . POSTS_TABLE . '
  1083. WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
  1084. AND poster_id <> ' . ANONYMOUS . '
  1085. GROUP BY poster_id, topic_id';
  1086. $result = $db->sql_query($sql);
  1087. $posted = array();
  1088. while ($row = $db->sql_fetchrow($result))
  1089. {
  1090. // Add as key to make them unique (grouping by) and circumvent empty keys on array_unique
  1091. $posted[$row['poster_id']][] = $row['topic_id'];
  1092. }
  1093. $db->sql_freeresult($result);
  1094. // Now add the information...
  1095. $sql_ary = array();
  1096. foreach ($posted as $user_id => $topic_row)
  1097. {
  1098. foreach ($topic_row as $topic_id)
  1099. {
  1100. $sql_ary[] = array(
  1101. 'user_id' => (int) $user_id,
  1102. 'topic_id' => (int) $topic_id,
  1103. 'topic_posted' => 1,
  1104. );
  1105. }
  1106. }
  1107. unset($posted);
  1108. $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary);
  1109. }
  1110. /**
  1111. * All-encompasing sync function
  1112. *
  1113. * Exaples:
  1114. * <code>
  1115. * sync('topic', 'topic_id', 123); // resync topic #123
  1116. * sync('topic', 'forum_id', array(2, 3)); // resync topics from forum #2 and #3
  1117. * sync('topic'); // resync all topics
  1118. * sync('topic', 'range', 'topic_id BETWEEN 1 AND 60'); // resync a range of topics/forums (only available for 'topic' and 'forum' modes)
  1119. * </code>
  1120. *
  1121. * Modes:
  1122. * - forum Resync complete forum
  1123. * - topic Resync topics
  1124. * - topic_moved Removes topic shadows that would be in the same forum as the topic they link to
  1125. * - topic_visibility Resyncs the topic_visibility flag according to the status of the first post
  1126. * - post_reported Resyncs the post_reported flag, relying on actual reports
  1127. * - topic_reported Resyncs the topic_reported flag, relying on post_reported flags
  1128. * - post_attachement Same as post_reported, but with attachment flags
  1129. * - topic_attachement Same as topic_reported, but with attachment flags
  1130. */
  1131. function sync($mode, $where_type = '', $where_ids = '', $resync_parents = false, $sync_extra = false)
  1132. {
  1133. global $db;
  1134. if (is_array($where_ids))
  1135. {
  1136. $where_ids = array_unique($where_ids);
  1137. $where_ids = array_map('intval', $where_ids);
  1138. }
  1139. else if ($where_type != 'range')
  1140. {
  1141. $where_ids = ($where_ids) ? array((int) $where_ids) : array();
  1142. }
  1143. if ($mode == 'forum' || $mode == 'topic' || $mode == 'topic_visibility' || $mode == 'topic_reported' || $mode == 'post_reported')
  1144. {
  1145. if (!$where_type)
  1146. {
  1147. $where_sql = '';
  1148. $where_sql_and = 'WHERE';
  1149. }
  1150. else if ($where_type == 'range')
  1151. {
  1152. // Only check a range of topics/forums. For instance: 'topic_id BETWEEN 1 AND 60'
  1153. $where_sql = 'WHERE (' . $mode[0] . ".$where_ids)";
  1154. $where_sql_and = $where_sql . "\n\tAND";
  1155. }
  1156. else
  1157. {
  1158. // Do not sync the "global forum"
  1159. $where_ids = array_diff($where_ids, array(0));
  1160. if (!count($where_ids))
  1161. {
  1162. // Empty array with IDs. This means that we don't have any work to do. Just return.
  1163. return;
  1164. }
  1165. // Limit the topics/forums we are syncing, use specific topic/forum IDs.
  1166. // $where_type contains the field for the where clause (forum_id, topic_id)
  1167. $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
  1168. $where_sql_and = $where_sql . "\n\tAND";
  1169. }
  1170. }
  1171. else
  1172. {
  1173. if (!count($where_ids))
  1174. {
  1175. return;
  1176. }
  1177. // $where_type contains the field for the where clause (forum_id, topic_id)
  1178. $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
  1179. $where_sql_and = $where_sql . "\n\tAND";
  1180. }
  1181. switch ($mode)
  1182. {
  1183. case 'topic_moved':
  1184. $db->sql_transaction('begin');
  1185. switch ($db->get_sql_layer())
  1186. {
  1187. case 'mysqli':
  1188. $sql = 'DELETE FROM ' . TOPICS_TABLE . '
  1189. USING ' . TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
  1190. WHERE t1.topic_moved_id = t2.topic_id
  1191. AND t1.forum_id = t2.forum_id";
  1192. $db->sql_query($sql);
  1193. break;
  1194. default:
  1195. $sql = 'SELECT t1.topic_id
  1196. FROM ' .TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
  1197. WHERE t1.topic_moved_id = t2.topic_id
  1198. AND t1.forum_id = t2.forum_id";
  1199. $result = $db->sql_query($sql);
  1200. $topic_id_ary = array();
  1201. while ($row = $db->sql_fetchrow($result))
  1202. {
  1203. $topic_id_ary[] = $row['topic_id'];
  1204. }
  1205. $db->sql_freeresult($result);
  1206. if (!count($topic_id_ary))
  1207. {
  1208. return;
  1209. }
  1210. $sql = 'DELETE FROM ' . TOPICS_TABLE . '
  1211. WHERE ' . $db->sql_in_set('topic_id', $topic_id_ary);
  1212. $db->sql_query($sql);
  1213. break;
  1214. }
  1215. $db->sql_transaction('commit');
  1216. break;
  1217. case 'topic_visibility':
  1218. $db->sql_transaction('begin');
  1219. $sql = 'SELECT t.topic_id, p.post_visibility
  1220. FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
  1221. $where_sql_and p.topic_id = t.topic_id
  1222. AND p.post_visibility = " . ITEM_APPROVED;
  1223. $result = $db->sql_query($sql);
  1224. $topics_approved = array();
  1225. while ($row = $db->sql_fetchrow($result))
  1226. {
  1227. $topics_approved[] = (int) $row['topic_id'];
  1228. }
  1229. $db->sql_freeresult($result);
  1230. $sql = 'SELECT t.topic_id, p.post_visibility
  1231. FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
  1232. $where_sql_and " . $db->sql_in_set('t.topic_id', $topics_approved, true, true) . '
  1233. AND p.topic_id = t.topic_id
  1234. AND p.post_visibility = ' . ITEM_DELETED;
  1235. $result = $db->sql_query($sql);
  1236. $topics_softdeleted = array();
  1237. while ($row = $db->sql_fetchrow($result))
  1238. {
  1239. $topics_softdeleted[] = (int) $row['topic_id'];
  1240. }
  1241. $db->sql_freeresult($result);
  1242. $topics_softdeleted = array_diff($topics_softdeleted, $topics_approved);
  1243. $topics_not_unapproved = array_merge($topics_softdeleted, $topics_approved);
  1244. $update_ary = array(
  1245. ITEM_UNAPPROVED => (!empty($topics_not_unapproved)) ? $where_sql_and . ' ' . $db->sql_in_set('topic_id', $topics_not_unapproved, true) : '',
  1246. ITEM_APPROVED => (!empty($topics_approved)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_approved) : '',
  1247. ITEM_DELETED => (!empty($topics_softdeleted)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_softdeleted) : '',
  1248. );
  1249. foreach ($update_ary as $visibility => $sql_where)
  1250. {
  1251. if ($sql_where)
  1252. {
  1253. $sql = 'UPDATE ' . TOPICS_TABLE . '
  1254. SET topic_visibility = ' . $visibility . '
  1255. ' . $sql_where;
  1256. $db->sql_query($sql);
  1257. }
  1258. }
  1259. $db->sql_transaction('commit');
  1260. break;
  1261. case 'post_reported':
  1262. $post_ids = $post_reported = array();
  1263. $db->sql_transaction('begin');
  1264. $sql = 'SELECT p.post_id, p.post_reported
  1265. FROM ' . POSTS_TABLE . " p
  1266. $where_sql
  1267. GROUP BY p.post_id, p.post_reported";
  1268. $result = $db->sql_query($sql);
  1269. while ($row = $db->sql_fetchrow($result))
  1270. {
  1271. $post_ids[$row['post_id']] = $row['post_id'];
  1272. if ($row['post_reported'])
  1273. {
  1274. $post_reported[$row['post_id']] = 1;
  1275. }
  1276. }
  1277. $db->sql_freeresult($result);
  1278. $sql = 'SELECT DISTINCT(post_id)
  1279. FROM ' . REPORTS_TABLE . '
  1280. WHERE ' . $db->sql_in_set('post_id', $post_ids) . '
  1281. AND report_closed = 0';
  1282. $result = $db->sql_query($sql);
  1283. $post_ids = array();
  1284. while ($row = $db->sql_fetchrow($result))
  1285. {
  1286. if (!isset($post_reported[$row['post_id']]))
  1287. {
  1288. $post_ids[] = $row['post_id'];
  1289. }
  1290. else
  1291. {
  1292. unset($post_reported[$row['post_id']]);
  1293. }
  1294. }
  1295. $db->sql_freeresult($result);
  1296. // $post_reported should be empty by now, if it's not it contains
  1297. // posts that are falsely flagged as reported
  1298. foreach ($post_reported as $post_id => $void)
  1299. {
  1300. $post_ids[] = $post_id;
  1301. }
  1302. if (count($post_ids))
  1303. {
  1304. $sql = 'UPDATE ' . POSTS_TABLE . '
  1305. SET post_reported = 1 - post_reported
  1306. WHERE ' . $db->sql_in_set('post_id', $post_ids);
  1307. $db->sql_query($sql);
  1308. }
  1309. $db->sql_transaction('commit');
  1310. break;
  1311. case 'topic_reported':
  1312. if ($sync_extra)
  1313. {
  1314. sync('post_reported', $where_type, $where_ids);
  1315. }
  1316. $topic_ids = $topic_reported = array();
  1317. $db->sql_transaction('begin');
  1318. $sql = 'SELECT DISTINCT(t.topic_id)
  1319. FROM ' . POSTS_TABLE . " t
  1320. $where_sql_and t.post_reported = 1";
  1321. $result = $db->sql_query($sql);
  1322. while ($row = $db->sql_fetchrow($result))
  1323. {
  1324. $topic_reported[$row['topic_id']] = 1;
  1325. }
  1326. $db->sql_freeresult($result);
  1327. $sql = 'SELECT t.topic_id, t.topic_reported
  1328. FROM ' . TOPICS_TABLE . " t
  1329. $where_sql";
  1330. $result = $db->sql_query($sql);
  1331. while ($row = $db->sql_fetchrow($result))
  1332. {
  1333. if ($row['topic_reported'] ^ isset($topic_reported[$row['topic_id']]))
  1334. {
  1335. $topic_ids[] = $row['topic_id'];
  1336. }
  1337. }
  1338. $db->sql_freeresult($result);
  1339. if (count($topic_ids))
  1340. {
  1341. $sql = 'UPDATE ' . TOPICS_TABLE . '
  1342. SET topic_reported = 1 - topic_reported
  1343. WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
  1344. $db->sql_query($sql);
  1345. }
  1346. $db->sql_transaction('commit');
  1347. break;
  1348. case 'post_attachment':
  1349. $post_ids = $post_attachment = array();
  1350. $db->sql_transaction('begin');
  1351. $sql = 'SELECT p.post_id, p.post_attachment
  1352. FROM ' . POSTS_TABLE . " p
  1353. $where_sql
  1354. GROUP BY p.post_id, p.post_attachment";
  1355. $result = $db->sql_query($sql);
  1356. while ($row = $db->sql_fetchrow($result))
  1357. {
  1358. $post_ids[$row['post_id']] = $row['post_id'];
  1359. if ($row['post_attachment'])
  1360. {
  1361. $post_attachment[$row['post_id']] = 1;
  1362. }
  1363. }
  1364. $db->sql_freeresult($result);
  1365. $sql = 'SELECT DISTINCT(post_msg_id)
  1366. FROM ' . ATTACHMENTS_TABLE . '
  1367. WHERE ' . $db->sql_in_set('post_msg_id', $post_ids) . '
  1368. AND in_message = 0';
  1369. $result = $db->sql_query($sql);
  1370. $post_ids = array();
  1371. while ($row = $db->sql_fetchrow($result))
  1372. {
  1373. if (!isset($post_attachment[$row['post_msg_id']]))
  1374. {
  1375. $post_ids[] = $row['post_msg_id'];
  1376. }
  1377. else
  1378. {
  1379. unset($post_attachment[$row['post_msg_id']]);
  1380. }
  1381. }
  1382. $db->sql_freeresult($result);
  1383. // $post_attachment should be empty by now, if it's not it contains
  1384. // posts that are falsely flagged as having attachments
  1385. foreach ($post_attachment as $post_id => $void)
  1386. {
  1387. $post_ids[] = $post_id;
  1388. }
  1389. if (count($post_ids))
  1390. {
  1391. $sql = 'UPDATE ' . POSTS_TABLE . '
  1392. SET post_attachment = 1 - post_attachment
  1393. WHERE ' . $db->sql_in_set('post_id', $post_ids);
  1394. $db->sql_query($sql);
  1395. }
  1396. $db->sql_transaction('commit');
  1397. break;
  1398. case 'topic_attachment':
  1399. if ($sync_extra)
  1400. {
  1401. sync('post_attachment', $where_type, $where_ids);
  1402. }
  1403. $topic_ids = $topic_attachment = array();
  1404. $db->sql_transaction('begin');
  1405. $sql = 'SELECT DISTINCT(t.topic_id)
  1406. FROM ' . POSTS_TABLE . " t
  1407. $where_sql_and t.post_attachment = 1";
  1408. $result = $db->sql_query($sql);
  1409. while ($row = $db->sql_fetchrow($result))
  1410. {
  1411. $topic_attachment[$row['topic_id']] = 1;
  1412. }
  1413. $db->sql_freeresult($result);
  1414. $sql = 'SELECT t.topic_id, t.topic_attachment
  1415. FROM ' . TOPICS_TABLE . " t
  1416. $where_sql";
  1417. $result = $db->sql_query($sql);
  1418. while ($row = $db->sql_fetchrow($result))
  1419. {
  1420. if ($row['topic_attachment'] ^ isset($topic_attachment[$row['topic_id']]))
  1421. {
  1422. $topic_ids[] = $row['topic_id'];
  1423. }
  1424. }
  1425. $db->sql_freeresult($result);
  1426. if (count($topic_ids))
  1427. {
  1428. $sql = 'UPDATE ' . TOPICS_TABLE . '
  1429. SET topic_attachment = 1 - topic_attachment
  1430. WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
  1431. $db->sql_query($sql);
  1432. }
  1433. $db->sql_transaction('commit');
  1434. break;
  1435. case 'forum':
  1436. $db->sql_transaction('begin');
  1437. // 1: Get the list of all forums
  1438. $sql = 'SELECT f.*
  1439. FROM ' . FORUMS_TABLE . " f
  1440. $where_sql";
  1441. $result = $db->sql_query($sql);
  1442. $forum_data = $forum_ids = $post_ids = $last_post_id = $post_info = array();
  1443. while ($row = $db->sql_fetchrow($result))
  1444. {
  1445. if ($row['forum_type'] == FORUM_LINK)
  1446. {
  1447. continue;
  1448. }
  1449. $forum_id = (int) $row['forum_id'];
  1450. $forum_ids[$forum_id] = $forum_id;
  1451. $forum_data[$forum_id] = $row;
  1452. if ($sync_extra)
  1453. {
  1454. $forum_data[$forum_id]['posts_approved'] = 0;
  1455. $forum_data[$forum_id]['posts_unapproved'] = 0;
  1456. $forum_data[$forum_id]['posts_softdeleted'] = 0;
  1457. $forum_data[$forum_id]['topics_approved'] = 0;
  1458. $forum_data[$forum_id]['topics_unapproved'] = 0;
  1459. $forum_data[$forum_id]['topics_softdeleted'] = 0;
  1460. }
  1461. $forum_data[$forum_id]['last_post_id'] = 0;
  1462. $forum_data[$forum_id]['last_post_subject'] = '';
  1463. $forum_data[$forum_id]['last_post_time'] = 0;
  1464. $forum_data[$forum_id]['last_poster_id'] = 0;
  1465. $forum_data[$forum_id]['last_poster_name'] = '';
  1466. $forum_data[$forum_id]['last_poster_colour'] = '';
  1467. }
  1468. $db->sql_freeresult($result);
  1469. if (!count($forum_ids))
  1470. {
  1471. break;
  1472. }
  1473. $forum_ids = array_values($forum_ids);
  1474. // 2: Get topic counts for each forum (optional)
  1475. if ($sync_extra)
  1476. {
  1477. $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS total_topics
  1478. FROM ' . TOPICS_TABLE . '
  1479. WHERE ' . $db->sql_in_set('forum_id', $forum_ids) . '
  1480. GROUP BY forum_id, topic_visibility';
  1481. $result = $db->sql_query($sql);
  1482. while ($row = $db->sql_fetchrow($result))
  1483. {
  1484. $forum_id = (int) $row['forum_id'];
  1485. if ($row['topic_visibility'] == ITEM_APPROVED)
  1486. {
  1487. $forum_data[$forum_id]['topics_approved'] = $row['total_topics'];
  1488. }
  1489. else if ($row['topic_visibility'] == ITEM_UNAPPROVED || $row['topic_visibility'] == ITEM_REAPPROVE)
  1490. {
  1491. $forum_data[$forum_id]['topics_unapproved'] = $row['total_topics'];
  1492. }
  1493. else if ($row['topic_visibility'] == ITEM_DELETED)
  1494. {
  1495. $forum_data[$forum_id]['topics_softdeleted'] = $row['total_topics'];
  1496. }
  1497. }
  1498. $db->sql_freeresult($result);
  1499. }
  1500. // 3: Get post count for each forum (optional)
  1501. if ($sync_extra)
  1502. {
  1503. if (count($forum_ids) == 1)
  1504. {
  1505. $sql = 'SELECT SUM(t.topic_posts_approved) AS forum_posts_approved, SUM(t.topic_posts_unapproved) AS forum_posts_unapproved, SUM(t.topic_posts_softdeleted) AS forum_posts_softdeleted
  1506. FROM ' . TOPICS_TABLE . ' t
  1507. WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
  1508. AND t.topic_status <> ' . ITEM_MOVED;
  1509. }
  1510. else
  1511. {
  1512. $sql = 'SELECT t.forum_id, SUM(t.topic_posts_approved) AS forum_posts_approved, SUM(t.topic_posts_unapproved) AS forum_posts_unapproved, SUM(t.topic_posts_softdeleted) AS forum_posts_softdeleted
  1513. FROM ' . TOPICS_TABLE . ' t
  1514. WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
  1515. AND t.topic_status <> ' . ITEM_MOVED . '
  1516. GROUP BY t.forum_id';
  1517. }
  1518. $result = $db->sql_query($sql);
  1519. while ($row = $db->sql_fetchrow($result))
  1520. {
  1521. $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
  1522. $forum_data[$forum_id]['posts_approved'] = (int) $row['forum_posts_approved'];
  1523. $forum_data[$forum_id]['posts_unapproved'] = (int) $row['forum_posts_unapproved'];
  1524. $forum_data[$forum_id]['posts_softdeleted'] = (int) $row['forum_posts_softdeleted'];
  1525. }
  1526. $db->sql_freeresult($result);
  1527. }
  1528. // 4: Get last_post_id for each forum
  1529. if (count($forum_ids) == 1)
  1530. {
  1531. $sql = 'SELECT MAX(t.topic_last_post_id) as last_post_id
  1532. FROM ' . TOPICS_TABLE . ' t
  1533. WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
  1534. AND t.topic_visibility = ' . ITEM_APPROVED;
  1535. }
  1536. else
  1537. {
  1538. $sql = 'SELECT t.forum_id, MAX(t.topic_last_post_id) as last_post_id
  1539. FROM ' . TOPICS_TABLE . ' t
  1540. WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
  1541. AND t.topic_visibility = ' . ITEM_APPROVED . '
  1542. GROUP BY t.forum_id';
  1543. }
  1544. $result = $db->sql_query($sql);
  1545. while ($row = $db->sql_fetchrow($result))
  1546. {
  1547. $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
  1548. $forum_data[$forum_id]['last_post_id'] = (int) $row['last_post_id'];
  1549. $post_ids[] = $row['last_post_id'];
  1550. }
  1551. $db->sql_freeresult($result);
  1552. // 5: Retrieve last_post infos
  1553. if (count($post_ids))
  1554. {
  1555. $sql = 'SELECT p.post_id, p.poster_id, p.post_subject, p.post_time, p.post_username, u.username, u.user_colour
  1556. FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
  1557. WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
  1558. AND p.poster_id = u.user_id';
  1559. $result = $db->sql_query($sql);
  1560. while ($row = $db->sql_fetchrow($result))
  1561. {
  1562. $post_info[$row['post_id']] = $row;
  1563. }
  1564. $db->sql_freeresult($result);
  1565. foreach ($forum_data as $forum_id => $data)
  1566. {
  1567. if ($data['last_post_id'])
  1568. {
  1569. if (isset($post_info[$data['last_post_id']]))
  1570. {
  1571. $forum_data[$forum_id]['last_post_subject'] = $post_info[$data['last_post_id']]['post_subject'];
  1572. $forum_data[$forum_id]['last_post_time'] = $post_info[$data['last_post_id']]['post_time'];
  1573. $forum_data[$forum_id]['last_poster_id'] = $post_info[$data['last_post_id']]['poster_id'];
  1574. $forum_data[$forum_id]['last_poster_name'] = ($post_info[$data['last_post_id']]['poster_id'] != ANONYMOUS) ? $post_info[$data['last_post_id']]['username'] : $post_info[$data['last_post_id']]['post_username'];
  1575. $forum_data[$forum_id]['last_poster_colour'] = $post_info[$data['last_post_id']]['user_colour'];
  1576. }
  1577. else
  1578. {
  1579. // For some reason we did not find the post in the db
  1580. $forum_data[$forum_id]['last_post_id'] = 0;
  1581. $forum_data[$forum_id]['last_post_subject'] = '';
  1582. $forum_data[$forum_id]['last_post_time'] = 0;
  1583. $forum_data[$forum_id]['last_poster_id'] = 0;
  1584. $forum_data[$forum_id]['last_poster_name'] = '';
  1585. $forum_data[$forum_id]['last_poster_colour'] = '';
  1586. }
  1587. }
  1588. }
  1589. unset($post_info);
  1590. }
  1591. // 6: Now do that thing
  1592. $fieldnames = array('last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour');
  1593. if ($sync_extra)
  1594. {
  1595. array_push($fieldnames, 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'topics_approved', 'topics_unapproved', 'topics_softdeleted');
  1596. }
  1597. foreach ($forum_data as $forum_id => $row)
  1598. {
  1599. $sql_ary = array();
  1600. foreach ($fieldnames as $fieldname)
  1601. {
  1602. if ($row['forum_' . $fieldname] != $row[$fieldname])
  1603. {
  1604. if (preg_match('#(name|colour|subject)$#', $fieldname))
  1605. {
  1606. $sql_ary['forum_' . $fieldname] = (string) $row[$fieldname];
  1607. }
  1608. else
  1609. {
  1610. $sql_ary['forum_' . $fieldname] = (int) $row[$fieldname];
  1611. }
  1612. }
  1613. }
  1614. if (count($sql_ary))
  1615. {
  1616. $sql = 'UPDATE ' . FORUMS_TABLE . '
  1617. SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
  1618. WHERE forum_id = ' . $forum_id;
  1619. $db->sql_query($sql);
  1620. }
  1621. }
  1622. $db->sql_transaction('commit');
  1623. break;
  1624. case 'topic':
  1625. $topic_data = $post_ids = $resync_forums = $delete_topics = $delete_posts = $moved_topics = array();
  1626. $db->sql_transaction('begin');
  1627. $sql = 'SELECT t.topic_id, t.forum_id, t.topic_moved_id, t.topic_visibility, ' . (($sync_extra) ? 't.topic_attachment, t.topic_reported, ' : '') . 't.topic_poster, t.topic_time, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_first_post_id, t.topic_first_poster_name, t.topic_first_poster_colour, t.topic_last_post_id, t.topic_last_post_subject, t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_poster_colour, t.topic_last_post_time
  1628. FROM ' . TOPICS_TABLE . " t
  1629. $where_sql";
  1630. $result = $db->sql_query($sql);
  1631. while ($row = $db->sql_fetchrow($result))
  1632. {
  1633. if ($row['topic_moved_id'])
  1634. {
  1635. $moved_topics[] = $row['topic_id'];
  1636. continue;
  1637. }
  1638. $topic_id = (int) $row['topic_id'];
  1639. $topic_data[$topic_id] = $row;
  1640. $topic_data[$topic_id]['visibility'] = ITEM_UNAPPROVED;
  1641. $topic_data[$topic_id]['posts_approved'] = 0;
  1642. $topic_data[$topic_id]['posts_unapproved'] = 0;
  1643. $topic_data[$topic_id]['posts_softdeleted'] = 0;
  1644. $topic_data[$topic_id]['first_post_id'] = 0;
  1645. $topic_data[$topic_id]['last_post_id'] = 0;
  1646. unset($topic_data[$topic_id]['topic_id']);
  1647. // This array holds all topic_ids
  1648. $delete_topics[$topic_id] = '';
  1649. if ($sync_extra)
  1650. {
  1651. $topic_data[$topic_id]['reported'] = 0;
  1652. $topic_data[$topic_id]['attachment'] = 0;
  1653. }
  1654. }
  1655. $db->sql_freeresult($result);
  1656. // Use "t" as table alias because of the $where_sql clause
  1657. // NOTE: 't.post_visibility' in the GROUP BY is causing a major slowdown.
  1658. $sql = 'SELECT t.topic_id, t.post_visibility, COUNT(t.post_id) AS total_posts, MIN(t.post_id) AS first_post_id, MAX(t.post_id) AS last_post_id
  1659. FROM ' . POSTS_TABLE . " t
  1660. $where_sql
  1661. GROUP BY t.topic_id, t.post_visibility";
  1662. $result = $db->sql_query($sql);
  1663. while ($row = $db->sql_fetchrow($result))
  1664. {
  1665. $topic_id = (int) $row['topic_id'];
  1666. $row['first_post_id'] = (int) $row['first_post_id'];
  1667. $row['last_post_id'] = (int) $row['last_post_id'];
  1668. if (!isset($topic_data[$topic_id]))
  1669. {
  1670. // Hey, these posts come from a topic that does not exist
  1671. $delete_posts[$topic_id] = '';
  1672. }
  1673. else
  1674. {
  1675. // Unset the corresponding entry in $delete_topics
  1676. // When we'll be done, only topics with no posts will remain
  1677. unset($delete_topics[$topic_id]);
  1678. if ($row['post_visibility'] == ITEM_APPROVED)
  1679. {
  1680. $topic_data[$topic_id]['posts_approved'] = $row['total_posts'];
  1681. }
  1682. else if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE)
  1683. {
  1684. $topic_data[$topic_id]['posts_unapproved'] = $row['total_posts'];
  1685. }
  1686. else if ($row['post_visibility'] == ITEM_DELETED)
  1687. {
  1688. $topic_data[$topic_id]['posts_softdeleted'] = $row['total_posts'];
  1689. }
  1690. if ($row['post_visibility'] == ITEM_APPROVED)
  1691. {
  1692. $topic_data[$topic_id]['visibility'] = ITEM_APPROVED;
  1693. $topic_data[$topic_id]['first_post_id'] = $row['first_post_id'];
  1694. $topic_data[$topic_id]['last_post_id'] = $row['last_post_id'];
  1695. }
  1696. else if ($topic_data[$topic_id]['visibility'] != ITEM_APPROVED)
  1697. {
  1698. // If there is no approved post, we take the min/max of the other visibilities
  1699. // for the last and first post info, because it is only visible to moderators anyway
  1700. $topic_data[$topic_id]['first_post_id'] = (!empty($topic_data[$topic_id]['first_post_id'])) ? min($topic_data[$topic_id]['first_post_id'], $row['first_post_id']) : $row['first_post_id'];
  1701. $topic_data[$topic_id]['last_post_id'] = max($topic_data[$topic_id]['last_post_id'], $row['last_post_id']);
  1702. if ($topic_data[$topic_id]['visibility'] == ITEM_UNAPPROVED || $topic_data[$topic_id]['visibility'] == ITEM_REAPPROVE)
  1703. {
  1704. // Soft delete status is stronger than unapproved.
  1705. $topic_data[$topic_id]['visibility'] = $row['post_visibility'];
  1706. }
  1707. }
  1708. }
  1709. }
  1710. $db->sql_freeresult($result);
  1711. foreach ($topic_data as $topic_id => $row)
  1712. {
  1713. $post_ids[] = $row['first_post_id'];
  1714. if ($row['first_post_id'] != $row['last_post_id'])
  1715. {
  1716. $post_ids[] = $row['last_post_id'];
  1717. }
  1718. }
  1719. // Now we delete empty topics and orphan posts
  1720. if (count($delete_posts))
  1721. {
  1722. delete_posts('topic_id', array_keys($delete_posts), false);
  1723. unset($delete_posts);
  1724. }
  1725. if (!count($topic_data))
  1726. {
  1727. // If we get there, topic ids were invalid or topics did not contain any posts
  1728. delete_topics($where_type, $where_ids, true);
  1729. return;
  1730. }
  1731. if (count($delete_topics))
  1732. {
  1733. $delete_topic_ids = array();
  1734. foreach ($delete_topics as $topic_id => $void)
  1735. {
  1736. unset($topic_data[$topic_id]);
  1737. $delete_topic_ids[] = $topic_id;
  1738. }
  1739. delete_topics('topic_id', $delete_topic_ids, false);
  1740. unset($delete_topics, $delete_topic_ids);
  1741. }
  1742. $sql = 'SELECT p.post_id, p.topic_id, p.post_visibility, p.poster_id, p.post_subject, p.post_username, p.post_time, u.username, u.user_colour
  1743. FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
  1744. WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
  1745. AND u.user_id = p.poster_id';
  1746. $result = $db->sql_query($sql);
  1747. while ($row = $db->sql_fetchrow($result))
  1748. {
  1749. $topic_id = intval($row['topic_id']);
  1750. if ($row['post_id'] == $topic_data[$topic_id]['first_post_id'])
  1751. {
  1752. $topic_data[$topic_id]['time'] = $row['post_time'];
  1753. $topic_data[$topic_id]['poster'] = $row['poster_id'];
  1754. $topic_data[$topic_id]['first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
  1755. $topic_data[$topic_id]['first_poster_colour'] = $row['user_colour'];
  1756. }
  1757. if ($row['post_id'] == $topic_data[$topic_id]['last_post_id'])
  1758. {
  1759. $topic_data[$topic_id]['last_poster_id'] = $row['poster_id'];
  1760. $topic_data[$topic_id]['last_post_subject'] = $row['post_subject'];
  1761. $topic_data[$topic_id]['last_post_time'] = $row['post_time'];
  1762. $topic_data[$topic_id]['last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
  1763. $topic_data[$topic_id]['last_poster_colour'] = $row['user_colour'];
  1764. }
  1765. }
  1766. $db->sql_freeresult($result);
  1767. // Make sure shadow topics do link to existing topics
  1768. if (count($moved_topics))
  1769. {
  1770. $delete_topics = array();
  1771. $sql = 'SELECT t1.topic_id, t1.topic_moved_id
  1772. FROM ' . TOPICS_TABLE . ' t1
  1773. LEFT JOIN ' . TOPICS_TABLE . ' t2 ON (t2.topic_id = t1.topic_moved_id)
  1774. WHERE ' . $db->sql_in_set('t1.topic_id', $moved_topics) . '
  1775. AND t2.topic_id IS NULL';
  1776. $result = $db->sql_query($sql);
  1777. while ($row = $db->sql_fetchrow($result))
  1778. {
  1779. $delete_topics[] = $row['topic_id'];
  1780. }
  1781. $db->sql_freeresult($result);
  1782. if (count($delete_topics))
  1783. {
  1784. delete_topics('topic_id', $delete_topics, false);
  1785. }
  1786. unset($delete_topics);
  1787. // Make sure shadow topics having no last post data being updated (this only rarely happens...)
  1788. $sql = 'SELECT topic_id, topic_moved_id, topic_last_post_id, topic_first_post_id
  1789. FROM ' . TOPICS_TABLE . '
  1790. WHERE ' . $db->sql_in_set('topic_id', $moved_topics) . '
  1791. AND topic_last_post_time = 0';
  1792. $result = $db->sql_query($sql);
  1793. $shadow_topic_data = $post_ids = array();
  1794. while ($row = $db->sql_fetchrow($result))
  1795. {
  1796. $shadow_topic_data[$row['topic_moved_id']] = $row;
  1797. $post_ids[] = $row['topic_last_post_id'];
  1798. $post_ids[] = $row['topic_first_post_id'];
  1799. }
  1800. $db->sql_freeresult($result);
  1801. $sync_shadow_topics = array();
  1802. if (count($post_ids))
  1803. {
  1804. $sql = 'SELECT p.post_id, p.topic_id, p.post_visibility, p.poster_id, p.post_subject, p.post_username, p.post_time, u.username, u.user_colour
  1805. FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
  1806. WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
  1807. AND u.user_id = p.poster_id';
  1808. $result = $db->sql_query($sql);
  1809. while ($row = $db->sql_fetchrow($result))
  1810. {
  1811. $topic_id = (int) $row['topic_id'];
  1812. // Ok, there should be a shadow topic. If there isn't, then there's something wrong with the db.
  1813. // However, there's not much we can do about it.
  1814. if (!empty($shadow_topic_data[$topic_id]))
  1815. {
  1816. if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_first_post_id'])
  1817. {
  1818. $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
  1819. if (!isset($sync_shadow_topics[$orig_topic_id]))
  1820. {
  1821. $sync_shadow_topics[$orig_topic_id] = array();
  1822. }
  1823. $sync_shadow_topics[$orig_topic_id]['topic_time'] = $row['post_time'];
  1824. $sync_shadow_topics[$orig_topic_id]['topic_poster'] = $row['poster_id'];
  1825. $sync_shadow_topics[$orig_topic_id]['topic_first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
  1826. $sync_shadow_topics[$orig_topic_id]['topic_first_poster_colour'] = $row['user_colour'];
  1827. }
  1828. if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_last_post_id'])
  1829. {
  1830. $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
  1831. if (!isset($sync_shadow_topics[$orig_topic_id]))
  1832. {
  1833. $sync_shadow_topics[$orig_topic_id] = array();
  1834. }
  1835. $sync_shadow_topics[$orig_topic_id]['topic_last_poster_id'] = $row['poster_id'];
  1836. $sync_shadow_topics[$orig_topic_id]['topic_last_post_subject'] = $row['post_subject'];
  1837. $sync_shadow_topics[$orig_topic_id]['topic_last_post_time'] = $row['post_time'];
  1838. $sync_shadow_topics[$orig_topic_id]['topic_last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
  1839. $sync_shadow_topics[$orig_topic_id]['topic_last_poster_colour'] = $row['user_colour'];
  1840. }
  1841. }
  1842. }
  1843. $db->sql_freeresult($result);
  1844. $shadow_topic_data = array();
  1845. // Update the information we collected
  1846. if (count($sync_shadow_topics))
  1847. {
  1848. foreach ($sync_shadow_topics as $sync_topic_id => $sql_ary)
  1849. {
  1850. $sql = 'UPDATE ' . TOPICS_TABLE . '
  1851. SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
  1852. WHERE topic_id = ' . $sync_topic_id;
  1853. $db->sql_query($sql);
  1854. }
  1855. }
  1856. }
  1857. unset($sync_shadow_topics, $shadow_topic_data);
  1858. }
  1859. // These are fields that will be synchronised
  1860. $fieldnames = array('time', 'visibility', 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'poster', 'first_post_id', 'first_poster_name', 'first_poster_colour', 'last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour');
  1861. if ($sync_extra)
  1862. {
  1863. // This routine assumes that post_reported values are correct
  1864. // if they are not, use sync('post_reported') first
  1865. $sql = 'SELECT t.topic_id, p.post_id
  1866. FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
  1867. $where_sql_and p.topic_id = t.topic_id
  1868. AND p.post_reported = 1
  1869. GROUP BY t.topic_id, p.post_id";
  1870. $result = $db->sql_query($sql);
  1871. $fieldnames[] = 'reported';
  1872. while ($row = $db->sql_fetchrow($result))
  1873. {
  1874. $topic_data[intval($row['topic_id'])]['reported'] = 1;
  1875. }
  1876. $db->sql_freeresult($result);
  1877. // This routine assumes that post_attachment values are correct
  1878. // if they are not, use sync('post_attachment') first
  1879. $sql = 'SELECT t.topic_id, p.post_id
  1880. FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
  1881. $where_sql_and p.topic_id = t.topic_id
  1882. AND p.post_attachment = 1
  1883. GROUP BY t.topic_id, p.post_id";
  1884. $result = $db->sql_query($sql);
  1885. $fieldnames[] = 'attachment';
  1886. while ($row = $db->sql_fetchrow($result))
  1887. {
  1888. $topic_data[intval($row['topic_id'])]['attachment'] = 1;
  1889. }
  1890. $db->sql_freeresult($result);
  1891. }
  1892. foreach ($topic_data as $topic_id => $row)
  1893. {
  1894. $sql_ary = array();
  1895. foreach ($fieldnames as $fieldname)
  1896. {
  1897. if (isset($row[$fieldname]) && isset($row['topic_' . $fieldname]) && $row['topic_' . $fieldname] != $row[$fieldname])
  1898. {
  1899. $sql_ary['topic_' . $fieldname] = $row[$fieldname];
  1900. }
  1901. }
  1902. if (count($sql_ary))
  1903. {
  1904. $sql = 'UPDATE ' . TOPICS_TABLE . '
  1905. SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
  1906. WHERE topic_id = ' . $topic_id;
  1907. $db->sql_query($sql);
  1908. $resync_forums[$row['forum_id']] = $row['forum_id'];
  1909. }
  1910. }
  1911. unset($topic_data);
  1912. $db->sql_transaction('commit');
  1913. // if some topics have been resync'ed then resync parent forums
  1914. // except when we're only syncing a range, we don't want to sync forums during
  1915. // batch processing.
  1916. if ($resync_parents && count($resync_forums) && $where_type != 'range')
  1917. {
  1918. sync('forum', 'forum_id', array_values($resync_forums), true, true);
  1919. }
  1920. break;
  1921. }
  1922. return;
  1923. }
  1924. /**
  1925. * Prune function
  1926. */
  1927. function prune($forum_id, $prune_mode, $prune_date, $prune_flags = 0, $auto_sync = true, $prune_limit = 0)
  1928. {
  1929. global $db, $phpbb_dispatcher;
  1930. if (!is_array($forum_id))
  1931. {
  1932. $forum_id = array($forum_id);
  1933. }
  1934. if (!count($forum_id))
  1935. {
  1936. return;
  1937. }
  1938. $sql_and = '';
  1939. if (!($prune_flags & FORUM_FLAG_PRUNE_ANNOUNCE))
  1940. {
  1941. $sql_and .= ' AND topic_type <> ' . POST_ANNOUNCE;
  1942. $sql_and .= ' AND topic_type <> ' . POST_GLOBAL;
  1943. }
  1944. if (!($prune_flags & FORUM_FLAG_PRUNE_STICKY))
  1945. {
  1946. $sql_and .= ' AND topic_type <> ' . POST_STICKY;
  1947. }
  1948. if ($prune_mode == 'posted')
  1949. {
  1950. $sql_and .= " AND topic_last_post_time < $prune_date";
  1951. }
  1952. if ($prune_mode == 'viewed')
  1953. {
  1954. $sql_and .= " AND topic_last_view_time < $prune_date";
  1955. }
  1956. if ($prune_mode == 'shadow')
  1957. {
  1958. $sql_and .= ' AND topic_status = ' . ITEM_MOVED . " AND topic_last_post_time < $prune_date";
  1959. }
  1960. /**
  1961. * Use this event to modify the SQL that selects topics to be pruned
  1962. *
  1963. * @event core.prune_sql
  1964. * @var string forum_id The forum id
  1965. * @var string prune_mode The prune mode
  1966. * @var string prune_date The prune date
  1967. * @var int prune_flags The prune flags
  1968. * @var bool auto_sync Whether or not to perform auto sync
  1969. * @var string sql_and SQL text appended to where clause
  1970. * @var int prune_limit The prune limit
  1971. * @since 3.1.3-RC1
  1972. * @changed 3.1.10-RC1 Added prune_limit
  1973. */
  1974. $vars = array(
  1975. 'forum_id',
  1976. 'prune_mode',
  1977. 'prune_date',
  1978. 'prune_flags',
  1979. 'auto_sync',
  1980. 'sql_and',
  1981. 'prune_limit',
  1982. );
  1983. extract($phpbb_dispatcher->trigger_event('core.prune_sql', compact($vars)));
  1984. $sql = 'SELECT topic_id
  1985. FROM ' . TOPICS_TABLE . '
  1986. WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
  1987. AND poll_start = 0
  1988. $sql_and";
  1989. $result = $db->sql_query_limit($sql, $prune_limit);
  1990. $topic_list = array();
  1991. while ($row = $db->sql_fetchrow($result))
  1992. {
  1993. $topic_list[] = $row['topic_id'];
  1994. }
  1995. $db->sql_freeresult($result);
  1996. if ($prune_flags & FORUM_FLAG_PRUNE_POLL)
  1997. {
  1998. $sql = 'SELECT topic_id
  1999. FROM ' . TOPICS_TABLE . '
  2000. WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
  2001. AND poll_start > 0
  2002. AND poll_last_vote < $prune_date
  2003. $sql_and";
  2004. $result = $db->sql_query_limit($sql, $prune_limit);
  2005. while ($row = $db->sql_fetchrow($result))
  2006. {
  2007. $topic_list[] = $row['topic_id'];
  2008. }
  2009. $db->sql_freeresult($result);
  2010. $topic_list = array_unique($topic_list);
  2011. }
  2012. /**
  2013. * Perform additional actions before topic deletion via pruning
  2014. *
  2015. * @event core.prune_delete_before
  2016. * @var int[] topic_list The IDs of the topics to be deleted
  2017. * @since 3.2.2-RC1
  2018. */
  2019. $vars = array('topic_list');
  2020. extract($phpbb_dispatcher->trigger_event('core.prune_delete_before', compact($vars)));
  2021. return delete_topics('topic_id', $topic_list, $auto_sync, false);
  2022. }
  2023. /**
  2024. * Function auto_prune(), this function now relies on passed vars
  2025. */
  2026. function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_freq, $log_prune = true)
  2027. {
  2028. global $db, $user, $phpbb_log;
  2029. $sql = 'SELECT forum_name
  2030. FROM ' . FORUMS_TABLE . "
  2031. WHERE forum_id = $forum_id";
  2032. $result = $db->sql_query($sql, 3600);
  2033. $row = $db->sql_fetchrow($result);
  2034. $db->sql_freeresult($result);
  2035. if ($row)
  2036. {
  2037. $prune_date = time() - ($prune_days * 86400);
  2038. $next_prune = time() + ($prune_freq * 86400);
  2039. $result = prune($forum_id, $prune_mode, $prune_date, $prune_flags, true, 300);
  2040. if ($result['topics'] == 0 && $result['posts'] == 0)
  2041. {
  2042. $column = $prune_mode === 'shadow' ? 'prune_shadow_next' : 'prune_next';
  2043. $sql = 'UPDATE ' . FORUMS_TABLE . "
  2044. SET $column = $next_prune
  2045. WHERE forum_id = $forum_id";
  2046. $db->sql_query($sql);
  2047. }
  2048. if ($log_prune)
  2049. {
  2050. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_AUTO_PRUNE', false, [$row['forum_name']]);
  2051. }
  2052. }
  2053. return;
  2054. }
  2055. /**
  2056. * Cache moderators. Called whenever permissions are changed
  2057. * via admin_permissions. Changes of usernames and group names
  2058. * must be carried through for the moderators table.
  2059. *
  2060. * @param \phpbb\db\driver\driver_interface $db Database connection
  2061. * @param \phpbb\cache\driver\driver_interface Cache driver
  2062. * @param \phpbb\auth\auth $auth Authentication object
  2063. * @return null
  2064. */
  2065. function phpbb_cache_moderators($db, $cache, $auth)
  2066. {
  2067. // Remove cached sql results
  2068. $cache->destroy('sql', MODERATOR_CACHE_TABLE);
  2069. // Clear table
  2070. switch ($db->get_sql_layer())
  2071. {
  2072. case 'sqlite3':
  2073. $db->sql_query('DELETE FROM ' . MODERATOR_CACHE_TABLE);
  2074. break;
  2075. default:
  2076. $db->sql_query('TRUNCATE TABLE ' . MODERATOR_CACHE_TABLE);
  2077. break;
  2078. }
  2079. // We add moderators who have forum moderator permissions without an explicit ACL_NEVER setting
  2080. $sql_ary = array();
  2081. // Grab all users having moderative options...
  2082. $hold_ary = $auth->acl_user_raw_data(false, 'm_%', false);
  2083. // Add users?
  2084. if (!empty($hold_ary))
  2085. {
  2086. // At least one moderative option warrants a display
  2087. $ug_id_ary = array_keys($hold_ary);
  2088. // Remove users who have group memberships with DENY moderator permissions
  2089. $sql_ary_deny = array(
  2090. 'SELECT' => 'a.forum_id, ug.user_id, g.group_id',
  2091. 'FROM' => array(
  2092. ACL_OPTIONS_TABLE => 'o',
  2093. USER_GROUP_TABLE => 'ug',
  2094. GROUPS_TABLE => 'g',
  2095. ACL_GROUPS_TABLE => 'a',
  2096. ),
  2097. 'LEFT_JOIN' => array(
  2098. array(
  2099. 'FROM' => array(ACL_ROLES_DATA_TABLE => 'r'),
  2100. 'ON' => 'a.auth_role_id = r.role_id',
  2101. ),
  2102. ),
  2103. 'WHERE' => '(o.auth_option_id = a.auth_option_id OR o.auth_option_id = r.auth_option_id)
  2104. AND ((a.auth_setting = ' . ACL_NEVER . ' AND r.auth_setting IS NULL)
  2105. OR r.auth_setting = ' . ACL_NEVER . ')
  2106. AND a.group_id = ug.group_id
  2107. AND g.group_id = ug.group_id
  2108. AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
  2109. AND ' . $db->sql_in_set('ug.user_id', $ug_id_ary) . "
  2110. AND ug.user_pending = 0
  2111. AND o.auth_option " . $db->sql_like_expression('m_' . $db->get_any_char()),
  2112. );
  2113. $sql = $db->sql_build_query('SELECT', $sql_ary_deny);
  2114. $result = $db->sql_query($sql);
  2115. while ($row = $db->sql_fetchrow($result))
  2116. {
  2117. if (isset($hold_ary[$row['user_id']][$row['forum_id']]))
  2118. {
  2119. unset($hold_ary[$row['user_id']][$row['forum_id']]);
  2120. }
  2121. }
  2122. $db->sql_freeresult($result);
  2123. if (count($hold_ary))
  2124. {
  2125. // Get usernames...
  2126. $sql = 'SELECT user_id, username
  2127. FROM ' . USERS_TABLE . '
  2128. WHERE ' . $db->sql_in_set('user_id', array_keys($hold_ary));
  2129. $result = $db->sql_query($sql);
  2130. $usernames_ary = array();
  2131. while ($row = $db->sql_fetchrow($result))
  2132. {
  2133. $usernames_ary[$row['user_id']] = $row['username'];
  2134. }
  2135. $db->sql_freeresult($result);
  2136. foreach ($hold_ary as $user_id => $forum_id_ary)
  2137. {
  2138. // Do not continue if user does not exist
  2139. if (!isset($usernames_ary[$user_id]))
  2140. {
  2141. continue;
  2142. }
  2143. foreach ($forum_id_ary as $forum_id => $auth_ary)
  2144. {
  2145. $sql_ary[] = array(
  2146. 'forum_id' => (int) $forum_id,
  2147. 'user_id' => (int) $user_id,
  2148. 'username' => (string) $usernames_ary[$user_id],
  2149. 'group_id' => 0,
  2150. 'group_name' => ''
  2151. );
  2152. }
  2153. }
  2154. }
  2155. }
  2156. // Now to the groups...
  2157. $hold_ary = $auth->acl_group_raw_data(false, 'm_%', false);
  2158. if (!empty($hold_ary))
  2159. {
  2160. $ug_id_ary = array_keys($hold_ary);
  2161. // Make sure not hidden or special groups are involved...
  2162. $sql = 'SELECT group_name, group_id, group_type
  2163. FROM ' . GROUPS_TABLE . '
  2164. WHERE ' . $db->sql_in_set('group_id', $ug_id_ary);
  2165. $result = $db->sql_query($sql);
  2166. $groupnames_ary = array();
  2167. while ($row = $db->sql_fetchrow($result))
  2168. {
  2169. if ($row['group_type'] == GROUP_HIDDEN || $row['group_type'] == GROUP_SPECIAL)
  2170. {
  2171. unset($hold_ary[$row['group_id']]);
  2172. }
  2173. $groupnames_ary[$row['group_id']] = $row['group_name'];
  2174. }
  2175. $db->sql_freeresult($result);
  2176. foreach ($hold_ary as $group_id => $forum_id_ary)
  2177. {
  2178. // If there is no group, we do not assign it...
  2179. if (!isset($groupnames_ary[$group_id]))
  2180. {
  2181. continue;
  2182. }
  2183. foreach ($forum_id_ary as $forum_id => $auth_ary)
  2184. {
  2185. $flag = false;
  2186. foreach ($auth_ary as $auth_option => $setting)
  2187. {
  2188. // Make sure at least one ACL_YES option is set...
  2189. if ($setting == ACL_YES)
  2190. {
  2191. $flag = true;
  2192. break;
  2193. }
  2194. }
  2195. if (!$flag)
  2196. {
  2197. continue;
  2198. }
  2199. $sql_ary[] = array(
  2200. 'forum_id' => (int) $forum_id,
  2201. 'user_id' => 0,
  2202. 'username' => '',
  2203. 'group_id' => (int) $group_id,
  2204. 'group_name' => (string) $groupnames_ary[$group_id]
  2205. );
  2206. }
  2207. }
  2208. }
  2209. $db->sql_multi_insert(MODERATOR_CACHE_TABLE, $sql_ary);
  2210. }
  2211. /**
  2212. * View log
  2213. *
  2214. * @param string $mode The mode defines which log_type is used and from which log the entry is retrieved
  2215. * @param array &$log The result array with the logs
  2216. * @param mixed &$log_count If $log_count is set to false, we will skip counting all entries in the database.
  2217. * Otherwise an integer with the number of total matching entries is returned.
  2218. * @param int $limit Limit the number of entries that are returned
  2219. * @param int $offset Offset when fetching the log entries, f.e. when paginating
  2220. * @param mixed $forum_id Restrict the log entries to the given forum_id (can also be an array of forum_ids)
  2221. * @param int $topic_id Restrict the log entries to the given topic_id
  2222. * @param int $user_id Restrict the log entries to the given user_id
  2223. * @param int $log_time Only get log entries newer than the given timestamp
  2224. * @param string $sort_by SQL order option, e.g. 'l.log_time DESC'
  2225. * @param string $keywords Will only return log entries that have the keywords in log_operation or log_data
  2226. *
  2227. * @return int Returns the offset of the last valid page, if the specified offset was invalid (too high)
  2228. */
  2229. function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $limit_days = 0, $sort_by = 'l.log_time DESC', $keywords = '')
  2230. {
  2231. global $phpbb_log;
  2232. $count_logs = ($log_count !== false);
  2233. $log = $phpbb_log->get_logs($mode, $count_logs, $limit, $offset, $forum_id, $topic_id, $user_id, $limit_days, $sort_by, $keywords);
  2234. $log_count = $phpbb_log->get_log_count();
  2235. return $phpbb_log->get_valid_offset();
  2236. }
  2237. /**
  2238. * Removes moderators and administrators from foe lists.
  2239. *
  2240. * @param \phpbb\db\driver\driver_interface $db Database connection
  2241. * @param \phpbb\auth\auth $auth Authentication object
  2242. * @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore
  2243. * @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore
  2244. * @return null
  2245. */
  2246. function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false)
  2247. {
  2248. // update foes for some user
  2249. if (is_array($user_id) && count($user_id))
  2250. {
  2251. $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
  2252. WHERE ' . $db->sql_in_set('zebra_id', $user_id) . '
  2253. AND foe = 1';
  2254. $db->sql_query($sql);
  2255. return;
  2256. }
  2257. // update foes for some group
  2258. if (is_array($group_id) && count($group_id))
  2259. {
  2260. // Grab group settings...
  2261. $sql_ary = array(
  2262. 'SELECT' => 'a.group_id',
  2263. 'FROM' => array(
  2264. ACL_OPTIONS_TABLE => 'ao',
  2265. ACL_GROUPS_TABLE => 'a',
  2266. ),
  2267. 'LEFT_JOIN' => array(
  2268. array(
  2269. 'FROM' => array(ACL_ROLES_DATA_TABLE => 'r'),
  2270. 'ON' => 'a.auth_role_id = r.role_id',
  2271. ),
  2272. ),
  2273. 'WHERE' => '(ao.auth_option_id = a.auth_option_id OR ao.auth_option_id = r.auth_option_id)
  2274. AND ' . $db->sql_in_set('a.group_id', $group_id) . "
  2275. AND ao.auth_option IN ('a_', 'm_')",
  2276. 'GROUP_BY' => 'a.group_id',
  2277. );
  2278. $sql = $db->sql_build_query('SELECT', $sql_ary);
  2279. $result = $db->sql_query($sql);
  2280. $groups = array();
  2281. while ($row = $db->sql_fetchrow($result))
  2282. {
  2283. $groups[] = (int) $row['group_id'];
  2284. }
  2285. $db->sql_freeresult($result);
  2286. if (!count($groups))
  2287. {
  2288. return;
  2289. }
  2290. switch ($db->get_sql_layer())
  2291. {
  2292. case 'mysqli':
  2293. $sql = 'DELETE z.*
  2294. FROM ' . ZEBRA_TABLE . ' z, ' . USER_GROUP_TABLE . ' ug
  2295. WHERE z.zebra_id = ug.user_id
  2296. AND z.foe = 1
  2297. AND ' . $db->sql_in_set('ug.group_id', $groups);
  2298. $db->sql_query($sql);
  2299. break;
  2300. default:
  2301. $sql = 'SELECT user_id
  2302. FROM ' . USER_GROUP_TABLE . '
  2303. WHERE ' . $db->sql_in_set('group_id', $groups);
  2304. $result = $db->sql_query($sql);
  2305. $users = array();
  2306. while ($row = $db->sql_fetchrow($result))
  2307. {
  2308. $users[] = (int) $row['user_id'];
  2309. }
  2310. $db->sql_freeresult($result);
  2311. if (count($users))
  2312. {
  2313. $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
  2314. WHERE ' . $db->sql_in_set('zebra_id', $users) . '
  2315. AND foe = 1';
  2316. $db->sql_query($sql);
  2317. }
  2318. break;
  2319. }
  2320. return;
  2321. }
  2322. // update foes for everyone
  2323. $perms = array();
  2324. foreach ($auth->acl_get_list(false, array('a_', 'm_'), false) as $forum_id => $forum_ary)
  2325. {
  2326. foreach ($forum_ary as $auth_option => $user_ary)
  2327. {
  2328. $perms = array_merge($perms, $user_ary);
  2329. }
  2330. }
  2331. if (count($perms))
  2332. {
  2333. $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
  2334. WHERE ' . $db->sql_in_set('zebra_id', array_unique($perms)) . '
  2335. AND foe = 1';
  2336. $db->sql_query($sql);
  2337. }
  2338. unset($perms);
  2339. }
  2340. /**
  2341. * Lists inactive users
  2342. */
  2343. function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC')
  2344. {
  2345. global $db, $user;
  2346. $sql = 'SELECT COUNT(user_id) AS user_count
  2347. FROM ' . USERS_TABLE . '
  2348. WHERE user_type = ' . USER_INACTIVE .
  2349. (($limit_days) ? " AND user_inactive_time >= $limit_days" : '');
  2350. $result = $db->sql_query($sql);
  2351. $user_count = (int) $db->sql_fetchfield('user_count');
  2352. $db->sql_freeresult($result);
  2353. if ($user_count == 0)
  2354. {
  2355. // Save the queries, because there are no users to display
  2356. return 0;
  2357. }
  2358. if ($offset >= $user_count)
  2359. {
  2360. $offset = ($offset - $limit < 0) ? 0 : $offset - $limit;
  2361. }
  2362. $sql = 'SELECT *
  2363. FROM ' . USERS_TABLE . '
  2364. WHERE user_type = ' . USER_INACTIVE .
  2365. (($limit_days) ? " AND user_inactive_time >= $limit_days" : '') . "
  2366. ORDER BY $sort_by";
  2367. $result = $db->sql_query_limit($sql, $limit, $offset);
  2368. while ($row = $db->sql_fetchrow($result))
  2369. {
  2370. $row['inactive_reason'] = $user->lang['INACTIVE_REASON_UNKNOWN'];
  2371. switch ($row['user_inactive_reason'])
  2372. {
  2373. case INACTIVE_REGISTER:
  2374. $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REGISTER'];
  2375. break;
  2376. case INACTIVE_PROFILE:
  2377. $row['inactive_reason'] = $user->lang['INACTIVE_REASON_PROFILE'];
  2378. break;
  2379. case INACTIVE_MANUAL:
  2380. $row['inactive_reason'] = $user->lang['INACTIVE_REASON_MANUAL'];
  2381. break;
  2382. case INACTIVE_REMIND:
  2383. $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REMIND'];
  2384. break;
  2385. }
  2386. $users[] = $row;
  2387. }
  2388. $db->sql_freeresult($result);
  2389. return $offset;
  2390. }
  2391. /**
  2392. * Lists warned users
  2393. */
  2394. function view_warned_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_warnings DESC')
  2395. {
  2396. global $db;
  2397. $sql = 'SELECT user_id, username, user_colour, user_warnings, user_last_warning
  2398. FROM ' . USERS_TABLE . '
  2399. WHERE user_warnings > 0
  2400. ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '') . "
  2401. ORDER BY $sort_by";
  2402. $result = $db->sql_query_limit($sql, $limit, $offset);
  2403. $users = $db->sql_fetchrowset($result);
  2404. $db->sql_freeresult($result);
  2405. $sql = 'SELECT count(user_id) AS user_count
  2406. FROM ' . USERS_TABLE . '
  2407. WHERE user_warnings > 0
  2408. ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '');
  2409. $result = $db->sql_query($sql);
  2410. $user_count = (int) $db->sql_fetchfield('user_count');
  2411. $db->sql_freeresult($result);
  2412. return;
  2413. }
  2414. /**
  2415. * Get database size
  2416. * Currently only mysql and mssql are supported
  2417. */
  2418. function get_database_size()
  2419. {
  2420. global $db, $user, $table_prefix;
  2421. $database_size = false;
  2422. // This code is heavily influenced by a similar routine in phpMyAdmin 2.2.0
  2423. switch ($db->get_sql_layer())
  2424. {
  2425. case 'mysqli':
  2426. $sql = 'SELECT VERSION() AS mysql_version';
  2427. $result = $db->sql_query($sql);
  2428. $row = $db->sql_fetchrow($result);
  2429. $db->sql_freeresult($result);
  2430. if ($row)
  2431. {
  2432. $version = $row['mysql_version'];
  2433. if (preg_match('#(3\.23|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria)#', $version))
  2434. {
  2435. $db_name = (preg_match('#^(?:3\.23\.(?:[6-9]|[1-9]{2}))|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria#', $version)) ? "`{$db->get_db_name()}`" : $db->get_db_name();
  2436. $sql = 'SHOW TABLE STATUS
  2437. FROM ' . $db_name;
  2438. $result = $db->sql_query($sql, 7200);
  2439. $database_size = 0;
  2440. while ($row = $db->sql_fetchrow($result))
  2441. {
  2442. if ((isset($row['Type']) && $row['Type'] != 'MRG_MyISAM') || (isset($row['Engine']) && ($row['Engine'] == 'MyISAM' || $row['Engine'] == 'InnoDB' || $row['Engine'] == 'Aria')))
  2443. {
  2444. if ($table_prefix != '')
  2445. {
  2446. if (strpos($row['Name'], $table_prefix) !== false)
  2447. {
  2448. $database_size += $row['Data_length'] + $row['Index_length'];
  2449. }
  2450. }
  2451. else
  2452. {
  2453. $database_size += $row['Data_length'] + $row['Index_length'];
  2454. }
  2455. }
  2456. }
  2457. $db->sql_freeresult($result);
  2458. }
  2459. }
  2460. break;
  2461. case 'sqlite3':
  2462. global $dbhost;
  2463. if (file_exists($dbhost))
  2464. {
  2465. $database_size = filesize($dbhost);
  2466. }
  2467. break;
  2468. case 'mssql_odbc':
  2469. case 'mssqlnative':
  2470. $sql = 'SELECT @@VERSION AS mssql_version';
  2471. $result = $db->sql_query($sql);
  2472. $row = $db->sql_fetchrow($result);
  2473. $db->sql_freeresult($result);
  2474. $sql = 'SELECT ((SUM(size) * 8.0) * 1024.0) as dbsize
  2475. FROM sysfiles';
  2476. if ($row)
  2477. {
  2478. // Azure stats are stored elsewhere
  2479. if (strpos($row['mssql_version'], 'SQL Azure') !== false)
  2480. {
  2481. $sql = 'SELECT ((SUM(reserved_page_count) * 8.0) * 1024.0) as dbsize
  2482. FROM sys.dm_db_partition_stats';
  2483. }
  2484. }
  2485. $result = $db->sql_query($sql, 7200);
  2486. $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
  2487. $db->sql_freeresult($result);
  2488. break;
  2489. case 'postgres':
  2490. $sql = "SELECT proname
  2491. FROM pg_proc
  2492. WHERE proname = 'pg_database_size'";
  2493. $result = $db->sql_query($sql);
  2494. $row = $db->sql_fetchrow($result);
  2495. $db->sql_freeresult($result);
  2496. if ($row['proname'] == 'pg_database_size')
  2497. {
  2498. $database = $db->get_db_name();
  2499. if (strpos($database, '.') !== false)
  2500. {
  2501. list($database, ) = explode('.', $database);
  2502. }
  2503. $sql = "SELECT oid
  2504. FROM pg_database
  2505. WHERE datname = '$database'";
  2506. $result = $db->sql_query($sql);
  2507. $row = $db->sql_fetchrow($result);
  2508. $db->sql_freeresult($result);
  2509. $oid = $row['oid'];
  2510. $sql = 'SELECT pg_database_size(' . $oid . ') as size';
  2511. $result = $db->sql_query($sql);
  2512. $row = $db->sql_fetchrow($result);
  2513. $db->sql_freeresult($result);
  2514. $database_size = $row['size'];
  2515. }
  2516. break;
  2517. case 'oracle':
  2518. $sql = 'SELECT SUM(bytes) as dbsize
  2519. FROM user_segments';
  2520. $result = $db->sql_query($sql, 7200);
  2521. $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
  2522. $db->sql_freeresult($result);
  2523. break;
  2524. }
  2525. $database_size = ($database_size !== false) ? get_formatted_filesize($database_size) : $user->lang['NOT_AVAILABLE'];
  2526. return $database_size;
  2527. }
  2528. /*
  2529. * Tidy Warnings
  2530. * Remove all warnings which have now expired from the database
  2531. * The duration of a warning can be defined by the administrator
  2532. * This only removes the warning and reduces the associated count,
  2533. * it does not remove the user note recording the contents of the warning
  2534. */
  2535. function tidy_warnings()
  2536. {
  2537. global $db, $config;
  2538. $expire_date = time() - ($config['warnings_expire_days'] * 86400);
  2539. $warning_list = $user_list = array();
  2540. $sql = 'SELECT * FROM ' . WARNINGS_TABLE . "
  2541. WHERE warning_time < $expire_date";
  2542. $result = $db->sql_query($sql);
  2543. while ($row = $db->sql_fetchrow($result))
  2544. {
  2545. $warning_list[] = $row['warning_id'];
  2546. $user_list[$row['user_id']] = isset($user_list[$row['user_id']]) ? ++$user_list[$row['user_id']] : 1;
  2547. }
  2548. $db->sql_freeresult($result);
  2549. if (count($warning_list))
  2550. {
  2551. $db->sql_transaction('begin');
  2552. $sql = 'DELETE FROM ' . WARNINGS_TABLE . '
  2553. WHERE ' . $db->sql_in_set('warning_id', $warning_list);
  2554. $db->sql_query($sql);
  2555. foreach ($user_list as $user_id => $value)
  2556. {
  2557. $sql = 'UPDATE ' . USERS_TABLE . " SET user_warnings = user_warnings - $value
  2558. WHERE user_id = $user_id";
  2559. $db->sql_query($sql);
  2560. }
  2561. $db->sql_transaction('commit');
  2562. }
  2563. $config->set('warnings_last_gc', time(), false);
  2564. }
  2565. /**
  2566. * Tidy database, doing some maintanance tasks
  2567. */
  2568. function tidy_database()
  2569. {
  2570. global $config, $db;
  2571. // Here we check permission consistency
  2572. // Sometimes, it can happen permission tables having forums listed which do not exist
  2573. $sql = 'SELECT forum_id
  2574. FROM ' . FORUMS_TABLE;
  2575. $result = $db->sql_query($sql);
  2576. $forum_ids = array(0);
  2577. while ($row = $db->sql_fetchrow($result))
  2578. {
  2579. $forum_ids[] = $row['forum_id'];
  2580. }
  2581. $db->sql_freeresult($result);
  2582. $db->sql_transaction('begin');
  2583. // Delete those rows from the acl tables not having listed the forums above
  2584. $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
  2585. WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
  2586. $db->sql_query($sql);
  2587. $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
  2588. WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
  2589. $db->sql_query($sql);
  2590. $db->sql_transaction('commit');
  2591. $config->set('database_last_gc', time(), false);
  2592. }
  2593. /**
  2594. * Add permission language - this will make sure custom files will be included
  2595. */
  2596. function add_permission_language()
  2597. {
  2598. global $user, $phpEx, $phpbb_extension_manager;
  2599. // add permission language files from extensions
  2600. $finder = $phpbb_extension_manager->get_finder();
  2601. $lang_files = $finder
  2602. ->prefix('permissions_')
  2603. ->suffix(".$phpEx")
  2604. ->core_path('language/')
  2605. ->extension_directory('/language')
  2606. ->find();
  2607. foreach ($lang_files as $lang_file => $ext_name)
  2608. {
  2609. if ($ext_name === '/')
  2610. {
  2611. $user->add_lang($lang_file);
  2612. }
  2613. else
  2614. {
  2615. $user->add_lang_ext($ext_name, $lang_file);
  2616. }
  2617. }
  2618. }
  2619. /**
  2620. * Enables a particular flag in a bitfield column of a given table.
  2621. *
  2622. * @param string $table_name The table to update
  2623. * @param string $column_name The column containing a bitfield to update
  2624. * @param int $flag The binary flag which is OR-ed with the current column value
  2625. * @param string $sql_more This string is attached to the sql query generated to update the table.
  2626. *
  2627. * @return null
  2628. */
  2629. function enable_bitfield_column_flag($table_name, $column_name, $flag, $sql_more = '')
  2630. {
  2631. global $db;
  2632. $sql = 'UPDATE ' . $table_name . '
  2633. SET ' . $column_name . ' = ' . $db->sql_bit_or($column_name, $flag) . '
  2634. ' . $sql_more;
  2635. $db->sql_query($sql);
  2636. }
  2637. function display_ban_end_options()
  2638. {
  2639. global $user, $template;
  2640. // Ban length options
  2641. $ban_end_text = array(0 => $user->lang['PERMANENT'], 30 => $user->lang['30_MINS'], 60 => $user->lang['1_HOUR'], 360 => $user->lang['6_HOURS'], 1440 => $user->lang['1_DAY'], 10080 => $user->lang['7_DAYS'], 20160 => $user->lang['2_WEEKS'], 40320 => $user->lang['1_MONTH'], -1 => $user->lang['UNTIL'] . ' -&gt; ');
  2642. $ban_end_options = '';
  2643. foreach ($ban_end_text as $length => $text)
  2644. {
  2645. $ban_end_options .= '<option value="' . $length . '">' . $text . '</option>';
  2646. }
  2647. $template->assign_vars(array(
  2648. 'S_BAN_END_OPTIONS' => $ban_end_options
  2649. ));
  2650. }
  2651. /**
  2652. * Display ban options
  2653. */
  2654. function display_ban_options($mode)
  2655. {
  2656. global $user, $db, $template;
  2657. switch ($mode)
  2658. {
  2659. case 'user':
  2660. $field = 'username';
  2661. $sql = 'SELECT b.*, u.user_id, u.username, u.username_clean
  2662. FROM ' . BANLIST_TABLE . ' b, ' . USERS_TABLE . ' u
  2663. WHERE (b.ban_end >= ' . time() . '
  2664. OR b.ban_end = 0)
  2665. AND u.user_id = b.ban_userid
  2666. ORDER BY u.username_clean ASC';
  2667. break;
  2668. case 'ip':
  2669. $field = 'ban_ip';
  2670. $sql = 'SELECT *
  2671. FROM ' . BANLIST_TABLE . '
  2672. WHERE (ban_end >= ' . time() . "
  2673. OR ban_end = 0)
  2674. AND ban_ip <> ''
  2675. ORDER BY ban_ip";
  2676. break;
  2677. case 'email':
  2678. $field = 'ban_email';
  2679. $sql = 'SELECT *
  2680. FROM ' . BANLIST_TABLE . '
  2681. WHERE (ban_end >= ' . time() . "
  2682. OR ban_end = 0)
  2683. AND ban_email <> ''
  2684. ORDER BY ban_email";
  2685. break;
  2686. }
  2687. $result = $db->sql_query($sql);
  2688. $banned_options = $excluded_options = array();
  2689. while ($row = $db->sql_fetchrow($result))
  2690. {
  2691. $option = '<option value="' . $row['ban_id'] . '">' . $row[$field] . '</option>';
  2692. if ($row['ban_exclude'])
  2693. {
  2694. $excluded_options[] = $option;
  2695. }
  2696. else
  2697. {
  2698. $banned_options[] = $option;
  2699. }
  2700. $time_length = ($row['ban_end']) ? ($row['ban_end'] - $row['ban_start']) / 60 : 0;
  2701. if ($time_length == 0)
  2702. {
  2703. // Banned permanently
  2704. $ban_length = $user->lang['PERMANENT'];
  2705. }
  2706. else if (isset($ban_end_text[$time_length]))
  2707. {
  2708. // Banned for a given duration
  2709. $ban_length = $user->lang('BANNED_UNTIL_DURATION', $ban_end_text[$time_length], $user->format_date($row['ban_end'], false, true));
  2710. }
  2711. else
  2712. {
  2713. // Banned until given date
  2714. $ban_length = $user->lang('BANNED_UNTIL_DATE', $user->format_date($row['ban_end'], false, true));
  2715. }
  2716. $template->assign_block_vars('bans', array(
  2717. 'BAN_ID' => (int) $row['ban_id'],
  2718. 'LENGTH' => $ban_length,
  2719. 'A_LENGTH' => addslashes($ban_length),
  2720. 'REASON' => $row['ban_reason'],
  2721. 'A_REASON' => addslashes($row['ban_reason']),
  2722. 'GIVE_REASON' => $row['ban_give_reason'],
  2723. 'A_GIVE_REASON' => addslashes($row['ban_give_reason']),
  2724. ));
  2725. }
  2726. $db->sql_freeresult($result);
  2727. $options = '';
  2728. if ($excluded_options)
  2729. {
  2730. $options .= '<optgroup label="' . $user->lang['OPTIONS_EXCLUDED'] . '">';
  2731. $options .= implode('', $excluded_options);
  2732. $options .= '</optgroup>';
  2733. }
  2734. if ($banned_options)
  2735. {
  2736. $options .= '<optgroup label="' . $user->lang['OPTIONS_BANNED'] . '">';
  2737. $options .= implode('', $banned_options);
  2738. $options .= '</optgroup>';
  2739. }
  2740. $template->assign_vars(array(
  2741. 'S_BANNED_OPTIONS' => ($banned_options || $excluded_options) ? true : false,
  2742. 'BANNED_OPTIONS' => $options,
  2743. ));
  2744. }