Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions_admin.php

Zuletzt modifiziert: 02.04.2025, 15:01 - Dateigröße: 91.50 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  /**
0015  * @ignore
0016  */
0017  if (!defined('IN_PHPBB'))
0018  {
0019      exit;
0020  }
0021   
0022  /**
0023  * Recalculate Nested Sets
0024  *
0025  * @param int    $new_id    first left_id (should start with 1)
0026  * @param string    $pkey    primary key-column (containing the id for the parent_id of the children)
0027  * @param string    $table    constant or fullname of the table
0028  * @param int    $parent_id parent_id of the current set (default = 0)
0029  * @param array    $where    contains strings to compare closer on the where statement (additional)
0030  */
0031  function recalc_nested_sets(&$new_id, $pkey, $table, $parent_id = 0, $where = array())
0032  {
0033      global $db;
0034   
0035      $sql = 'SELECT *
0036          FROM ' . $table . '
0037          WHERE parent_id = ' . (int) $parent_id .
0038          ((!empty($where)) ? ' AND ' . implode(' AND ', $where) : '') . '
0039          ORDER BY left_id ASC';
0040      $result = $db->sql_query($sql);
0041      while ($row = $db->sql_fetchrow($result))
0042      {
0043          // First we update the left_id for this module
0044          if ($row['left_id'] != $new_id)
0045          {
0046              $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('left_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}");
0047          }
0048          $new_id++;
0049   
0050          // Then we go through any children and update their left/right id's
0051          recalc_nested_sets($new_id, $pkey, $table, $row[$pkey], $where);
0052   
0053          // Then we come back and update the right_id for this module
0054          if ($row['right_id'] != $new_id)
0055          {
0056              $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('right_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}");
0057          }
0058          $new_id++;
0059      }
0060      $db->sql_freeresult($result);
0061  }
0062   
0063  /**
0064  * Simple version of jumpbox, just lists authed forums
0065  */
0066  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)
0067  {
0068      global $db, $auth, $phpbb_dispatcher;
0069   
0070      // This query is identical to the jumpbox one
0071      $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, forum_flags, forum_options, left_id, right_id
0072          FROM ' . FORUMS_TABLE . '
0073          ORDER BY left_id ASC';
0074      $result = $db->sql_query($sql, 600);
0075   
0076      $rowset = array();
0077      while ($row = $db->sql_fetchrow($result))
0078      {
0079          $rowset[(int) $row['forum_id']] = $row;
0080      }
0081      $db->sql_freeresult($result);
0082   
0083      $right = 0;
0084      $padding_store = array('0' => '');
0085      $padding = '';
0086      $forum_list = ($return_array) ? array() : '';
0087   
0088      /**
0089      * Modify the forum list data
0090      *
0091      * @event core.make_forum_select_modify_forum_list
0092      * @var    array    rowset    Array with the forums list data
0093      * @since 3.1.10-RC1
0094      */
0095      $vars = array('rowset');
0096      extract($phpbb_dispatcher->trigger_event('core.make_forum_select_modify_forum_list', compact($vars)));
0097   
0098      // Sometimes it could happen that forums will be displayed here not be displayed within the index page
0099      // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions.
0100      // If this happens, the padding could be "broken"
0101   
0102      foreach ($rowset as $row)
0103      {
0104          if ($row['left_id'] < $right)
0105          {
0106              $padding .= '&nbsp; &nbsp;';
0107              $padding_store[$row['parent_id']] = $padding;
0108          }
0109          else if ($row['left_id'] > $right + 1)
0110          {
0111              $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : '';
0112          }
0113   
0114          $right = $row['right_id'];
0115          $disabled = false;
0116   
0117          if (!$ignore_acl && $auth->acl_gets(array('f_list', 'a_forum', 'a_forumadd', 'a_forumdel'), $row['forum_id']))
0118          {
0119              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'])))
0120              {
0121                  $disabled = true;
0122              }
0123          }
0124          else if (!$ignore_acl)
0125          {
0126              continue;
0127          }
0128   
0129          if (
0130              ((is_array($ignore_id) && in_array($row['forum_id'], $ignore_id)) || $row['forum_id'] == $ignore_id)
0131              ||
0132              // Non-postable forum with no subforums, don't display
0133              ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']) && $ignore_emptycat)
0134              ||
0135              ($row['forum_type'] != FORUM_POST && $ignore_nonpost)
0136              )
0137          {
0138              $disabled = true;
0139          }
0140   
0141          if ($return_array)
0142          {
0143              // Include some more information...
0144              $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? true : false) : (($row['forum_id'] == $select_id) ? true : false);
0145              $forum_list[$row['forum_id']] = array_merge(array('padding' => $padding, 'selected' => ($selected && !$disabled), 'disabled' => $disabled), $row);
0146          }
0147          else
0148          {
0149              $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? ' selected="selected"' : '') : (($row['forum_id'] == $select_id) ? ' selected="selected"' : '');
0150              $forum_list .= '<option value="' . $row['forum_id'] . '"' . (($disabled) ? ' disabled="disabled" class="disabled-option"' : $selected) . '>' . $padding . $row['forum_name'] . '</option>';
0151          }
0152      }
0153      unset($padding_store, $rowset);
0154   
0155      return $forum_list;
0156  }
0157   
0158  /**
0159  * Generate size select options
0160  */
0161  function size_select_options($size_compare)
0162  {
0163      global $user;
0164   
0165      $size_types_text = array($user->lang['BYTES'], $user->lang['KIB'], $user->lang['MIB']);
0166      $size_types = array('b', 'kb', 'mb');
0167   
0168      $s_size_options = '';
0169   
0170      for ($i = 0, $size = count($size_types_text); $i < $size; $i++)
0171      {
0172          $selected = ($size_compare == $size_types[$i]) ? ' selected="selected"' : '';
0173          $s_size_options .= '<option value="' . $size_types[$i] . '"' . $selected . '>' . $size_types_text[$i] . '</option>';
0174      }
0175   
0176      return $s_size_options;
0177  }
0178   
0179  /**
0180  * Generate list of groups (option fields without select)
0181  *
0182  * @param int $group_id The default group id to mark as selected
0183  * @param array $exclude_ids The group ids to exclude from the list, false (default) if you whish to exclude no id
0184  * @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.
0185  *
0186  * @return string The list of options.
0187  */
0188  function group_select_options($group_id, $exclude_ids = false, $manage_founder = false)
0189  {
0190      global $db, $config, $phpbb_container;
0191   
0192      /** @var \phpbb\group\helper $group_helper */
0193      $group_helper = $phpbb_container->get('group_helper');
0194   
0195      $exclude_sql = ($exclude_ids !== false && count($exclude_ids)) ? 'WHERE ' . $db->sql_in_set('group_id', array_map('intval', $exclude_ids), true) : '';
0196      $sql_and = (!$config['coppa_enable']) ? (($exclude_sql) ? ' AND ' : ' WHERE ') . "group_name <> 'REGISTERED_COPPA'" : '';
0197      $sql_founder = ($manage_founder !== false) ? (($exclude_sql || $sql_and) ? ' AND ' : ' WHERE ') . 'group_founder_manage = ' . (int) $manage_founder : '';
0198   
0199      $sql = 'SELECT group_id, group_name, group_type
0200          FROM ' . GROUPS_TABLE . "
0201          $exclude_sql
0202          $sql_and
0203          $sql_founder
0204          ORDER BY group_type DESC, group_name ASC";
0205      $result = $db->sql_query($sql);
0206   
0207      $s_group_options = '';
0208      while ($row = $db->sql_fetchrow($result))
0209      {
0210          $selected = ($row['group_id'] == $group_id) ? ' selected="selected"' : '';
0211          $s_group_options .= '<option' . (($row['group_type'] == GROUP_SPECIAL) ? ' class="sep"' : '') . ' value="' . $row['group_id'] . '"' . $selected . '>' . $group_helper->get_name($row['group_name']) . '</option>';
0212      }
0213      $db->sql_freeresult($result);
0214   
0215      return $s_group_options;
0216  }
0217   
0218  /**
0219  * Obtain authed forums list
0220  */
0221  function get_forum_list($acl_list = 'f_list', $id_only = true, $postable_only = false, $no_cache = false)
0222  {
0223      global $db, $auth, $phpbb_dispatcher;
0224      static $forum_rows;
0225   
0226      if (!isset($forum_rows))
0227      {
0228          // This query is identical to the jumpbox one
0229          $expire_time = ($no_cache) ? 0 : 600;
0230   
0231          $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id
0232              FROM ' . FORUMS_TABLE . '
0233              ORDER BY left_id ASC';
0234          $result = $db->sql_query($sql, $expire_time);
0235   
0236          $forum_rows = array();
0237   
0238          $right = $padding = 0;
0239          $padding_store = array('0' => 0);
0240   
0241          while ($row = $db->sql_fetchrow($result))
0242          {
0243              if ($row['left_id'] < $right)
0244              {
0245                  $padding++;
0246                  $padding_store[$row['parent_id']] = $padding;
0247              }
0248              else if ($row['left_id'] > $right + 1)
0249              {
0250                  // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it.
0251                  // @todo digging deep to find out "how" this can happen.
0252                  $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding;
0253              }
0254   
0255              $right = $row['right_id'];
0256              $row['padding'] = $padding;
0257   
0258              $forum_rows[] = $row;
0259          }
0260          $db->sql_freeresult($result);
0261          unset($padding_store);
0262      }
0263   
0264      $rowset = array();
0265      foreach ($forum_rows as $row)
0266      {
0267          if ($postable_only && $row['forum_type'] != FORUM_POST)
0268          {
0269              continue;
0270          }
0271   
0272          if ($acl_list == '' || ($acl_list != '' && $auth->acl_gets($acl_list, $row['forum_id'])))
0273          {
0274              $rowset[] = ($id_only) ? (int) $row['forum_id'] : $row;
0275          }
0276      }
0277   
0278      /**
0279      * Modify the forum list data
0280      *
0281      * @event core.get_forum_list_modify_data
0282      * @var    array    rowset    Array with the forum list data
0283      * @since 3.1.10-RC1
0284      */
0285      $vars = array('rowset');
0286      extract($phpbb_dispatcher->trigger_event('core.get_forum_list_modify_data', compact($vars)));
0287   
0288      return $rowset;
0289  }
0290   
0291  /**
0292  * Get forum branch
0293  */
0294  function get_forum_branch($forum_id, $type = 'all', $order = 'descending', $include_forum = true)
0295  {
0296      global $db;
0297   
0298      switch ($type)
0299      {
0300          case 'parents':
0301              $condition = 'f1.left_id BETWEEN f2.left_id AND f2.right_id';
0302          break;
0303   
0304          case 'children':
0305              $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id';
0306          break;
0307   
0308          default:
0309              $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id OR f1.left_id BETWEEN f2.left_id AND f2.right_id';
0310          break;
0311      }
0312   
0313      $rows = array();
0314   
0315      $sql = 'SELECT f2.*
0316          FROM ' . FORUMS_TABLE . ' f1
0317          LEFT JOIN ' . FORUMS_TABLE . " f2 ON ($condition)
0318          WHERE f1.forum_id = $forum_id
0319          ORDER BY f2.left_id " . (($order == 'descending') ? 'ASC' : 'DESC');
0320      $result = $db->sql_query($sql);
0321   
0322      while ($row = $db->sql_fetchrow($result))
0323      {
0324          if (!$include_forum && $row['forum_id'] == $forum_id)
0325          {
0326              continue;
0327          }
0328   
0329          $rows[] = $row;
0330      }
0331      $db->sql_freeresult($result);
0332   
0333      return $rows;
0334  }
0335   
0336  /**
0337  * Copies permissions from one forum to others
0338  *
0339  * @param int    $src_forum_id        The source forum we want to copy permissions from
0340  * @param array    $dest_forum_ids        The destination forum(s) we want to copy to
0341  * @param bool    $clear_dest_perms    True if destination permissions should be deleted
0342  * @param bool    $add_log            True if log entry should be added
0343  *
0344  * @return bool                        False on error
0345  */
0346  function copy_forum_permissions($src_forum_id, $dest_forum_ids, $clear_dest_perms = true, $add_log = true)
0347  {
0348      global $db, $user, $phpbb_log;
0349   
0350      // Only one forum id specified
0351      if (!is_array($dest_forum_ids))
0352      {
0353          $dest_forum_ids = array($dest_forum_ids);
0354      }
0355   
0356      // Make sure forum ids are integers
0357      $src_forum_id = (int) $src_forum_id;
0358      $dest_forum_ids = array_map('intval', $dest_forum_ids);
0359   
0360      // No source forum or no destination forums specified
0361      if (empty($src_forum_id) || empty($dest_forum_ids))
0362      {
0363          return false;
0364      }
0365   
0366      // Check if source forum exists
0367      $sql = 'SELECT forum_name
0368          FROM ' . FORUMS_TABLE . '
0369          WHERE forum_id = ' . $src_forum_id;
0370      $result = $db->sql_query($sql);
0371      $src_forum_name = $db->sql_fetchfield('forum_name');
0372      $db->sql_freeresult($result);
0373   
0374      // Source forum doesn't exist
0375      if (empty($src_forum_name))
0376      {
0377          return false;
0378      }
0379   
0380      // Check if destination forums exists
0381      $sql = 'SELECT forum_id, forum_name
0382          FROM ' . FORUMS_TABLE . '
0383          WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
0384      $result = $db->sql_query($sql);
0385   
0386      $dest_forum_ids = $dest_forum_names = array();
0387      while ($row = $db->sql_fetchrow($result))
0388      {
0389          $dest_forum_ids[]    = (int) $row['forum_id'];
0390          $dest_forum_names[]    = $row['forum_name'];
0391      }
0392      $db->sql_freeresult($result);
0393   
0394      // No destination forum exists
0395      if (empty($dest_forum_ids))
0396      {
0397          return false;
0398      }
0399   
0400      // From the mysql documentation:
0401      // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear
0402      // in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14.
0403      // Due to this we stay on the safe side if we do the insertion "the manual way"
0404   
0405      // Rowsets we're going to insert
0406      $users_sql_ary = $groups_sql_ary = array();
0407   
0408      // Query acl users table for source forum data
0409      $sql = 'SELECT user_id, auth_option_id, auth_role_id, auth_setting
0410          FROM ' . ACL_USERS_TABLE . '
0411          WHERE forum_id = ' . $src_forum_id;
0412      $result = $db->sql_query($sql);
0413   
0414      while ($row = $db->sql_fetchrow($result))
0415      {
0416          $row = array(
0417              'user_id'            => (int) $row['user_id'],
0418              'auth_option_id'    => (int) $row['auth_option_id'],
0419              'auth_role_id'        => (int) $row['auth_role_id'],
0420              'auth_setting'        => (int) $row['auth_setting'],
0421          );
0422   
0423          foreach ($dest_forum_ids as $dest_forum_id)
0424          {
0425              $users_sql_ary[] = $row + array('forum_id' => $dest_forum_id);
0426          }
0427      }
0428      $db->sql_freeresult($result);
0429   
0430      // Query acl groups table for source forum data
0431      $sql = 'SELECT group_id, auth_option_id, auth_role_id, auth_setting
0432          FROM ' . ACL_GROUPS_TABLE . '
0433          WHERE forum_id = ' . $src_forum_id;
0434      $result = $db->sql_query($sql);
0435   
0436      while ($row = $db->sql_fetchrow($result))
0437      {
0438          $row = array(
0439              'group_id'            => (int) $row['group_id'],
0440              'auth_option_id'    => (int) $row['auth_option_id'],
0441              'auth_role_id'        => (int) $row['auth_role_id'],
0442              'auth_setting'        => (int) $row['auth_setting'],
0443          );
0444   
0445          foreach ($dest_forum_ids as $dest_forum_id)
0446          {
0447              $groups_sql_ary[] = $row + array('forum_id' => $dest_forum_id);
0448          }
0449      }
0450      $db->sql_freeresult($result);
0451   
0452      $db->sql_transaction('begin');
0453   
0454      // Clear current permissions of destination forums
0455      if ($clear_dest_perms)
0456      {
0457          $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
0458              WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
0459          $db->sql_query($sql);
0460   
0461          $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
0462              WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids);
0463          $db->sql_query($sql);
0464      }
0465   
0466      $db->sql_multi_insert(ACL_USERS_TABLE, $users_sql_ary);
0467      $db->sql_multi_insert(ACL_GROUPS_TABLE, $groups_sql_ary);
0468   
0469      if ($add_log)
0470      {
0471          $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_COPIED_PERMISSIONS', false, array($src_forum_name, implode(', ', $dest_forum_names)));
0472      }
0473   
0474      $db->sql_transaction('commit');
0475   
0476      return true;
0477  }
0478   
0479  /**
0480  * Get physical file listing
0481  */
0482  function filelist($rootdir, $dir = '', $type = 'gif|jpg|jpeg|png|svg|webp')
0483  {
0484      $matches = array($dir => array());
0485   
0486      // Remove initial / if present
0487      $rootdir = (substr($rootdir, 0, 1) == '/') ? substr($rootdir, 1) : $rootdir;
0488      // Add closing / if not present
0489      $rootdir = ($rootdir && substr($rootdir, -1) != '/') ? $rootdir . '/' : $rootdir;
0490   
0491      // Remove initial / if present
0492      $dir = (substr($dir, 0, 1) == '/') ? substr($dir, 1) : $dir;
0493      // Add closing / if not present
0494      $dir = ($dir && substr($dir, -1) != '/') ? $dir . '/' : $dir;
0495   
0496      if (!is_dir($rootdir . $dir))
0497      {
0498          return $matches;
0499      }
0500   
0501      $dh = @opendir($rootdir . $dir);
0502   
0503      if (!$dh)
0504      {
0505          return $matches;
0506      }
0507   
0508      while (($fname = readdir($dh)) !== false)
0509      {
0510          if (is_file("$rootdir$dir$fname"))
0511          {
0512              if (filesize("$rootdir$dir$fname") && preg_match('#\.' . $type . '$#i', $fname))
0513              {
0514                  $matches[$dir][] = $fname;
0515              }
0516          }
0517          else if ($fname[0] != '.' && is_dir("$rootdir$dir$fname"))
0518          {
0519              $matches += filelist($rootdir, "$dir$fname", $type);
0520          }
0521      }
0522      closedir($dh);
0523   
0524      return $matches;
0525  }
0526   
0527  /**
0528  * Move topic(s)
0529  */
0530  function move_topics($topic_ids, $forum_id, $auto_sync = true)
0531  {
0532      global $db, $phpbb_dispatcher;
0533   
0534      if (empty($topic_ids))
0535      {
0536          return;
0537      }
0538   
0539      $forum_ids = array($forum_id);
0540   
0541      if (!is_array($topic_ids))
0542      {
0543          $topic_ids = array($topic_ids);
0544      }
0545   
0546      /**
0547       * Perform additional actions before topics move
0548       *
0549       * @event core.move_topics_before
0550       * @var    array    topic_ids    Array of the moved topic ids
0551       * @var    string    forum_id    The forum id from where the topics are moved
0552       * @since 3.2.9-RC1
0553       */
0554      $vars = array(
0555          'topic_ids',
0556          'forum_id',
0557      );
0558      extract($phpbb_dispatcher->trigger_event('core.move_topics_before', compact($vars)));
0559   
0560      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
0561          WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids) . '
0562              AND forum_id = ' . $forum_id;
0563      $db->sql_query($sql);
0564   
0565      if ($auto_sync)
0566      {
0567          $sql = 'SELECT DISTINCT forum_id
0568              FROM ' . TOPICS_TABLE . '
0569              WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
0570          $result = $db->sql_query($sql);
0571   
0572          while ($row = $db->sql_fetchrow($result))
0573          {
0574              $forum_ids[] = $row['forum_id'];
0575          }
0576          $db->sql_freeresult($result);
0577      }
0578   
0579      $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE);
0580   
0581      /**
0582       * Perform additional actions before topics move
0583       *
0584       * @event core.move_topics_before_query
0585       * @var    array    table_ary    Array of tables from which forum_id will be updated for all rows that hold the moved topics
0586       * @var    array    topic_ids    Array of the moved topic ids
0587       * @var    string    forum_id    The forum id from where the topics are moved
0588       * @var    array    forum_ids    Array of the forums where the topics are moving (includes also forum_id)
0589       * @var bool    auto_sync    Whether or not to perform auto sync
0590       * @since 3.1.5-RC1
0591       */
0592      $vars = array(
0593              'table_ary',
0594              'topic_ids',
0595              'forum_id',
0596              'forum_ids',
0597              'auto_sync',
0598      );
0599      extract($phpbb_dispatcher->trigger_event('core.move_topics_before_query', compact($vars)));
0600   
0601      foreach ($table_ary as $table)
0602      {
0603          $sql = "UPDATE $table
0604              SET forum_id = $forum_id
0605              WHERE " . $db->sql_in_set('topic_id', $topic_ids);
0606          $db->sql_query($sql);
0607      }
0608      unset($table_ary);
0609   
0610      /**
0611       * Perform additional actions after topics move
0612       *
0613       * @event core.move_topics_after
0614       * @var    array    topic_ids    Array of the moved topic ids
0615       * @var    string    forum_id    The forum id from where the topics were moved
0616       * @var    array    forum_ids    Array of the forums where the topics were moved (includes also forum_id)
0617       * @since 3.2.9-RC1
0618       */
0619      $vars = array(
0620          'topic_ids',
0621          'forum_id',
0622          'forum_ids',
0623      );
0624      extract($phpbb_dispatcher->trigger_event('core.move_topics_after', compact($vars)));
0625   
0626      if ($auto_sync)
0627      {
0628          sync('forum', 'forum_id', $forum_ids, true, true);
0629          unset($forum_ids);
0630      }
0631  }
0632   
0633  /**
0634  * Move post(s)
0635  */
0636  function move_posts($post_ids, $topic_id, $auto_sync = true)
0637  {
0638      global $db, $phpbb_dispatcher;
0639   
0640      if (!is_array($post_ids))
0641      {
0642          $post_ids = array($post_ids);
0643      }
0644   
0645      $forum_ids = array();
0646      $topic_ids = array($topic_id);
0647   
0648      $sql = 'SELECT DISTINCT topic_id, forum_id
0649          FROM ' . POSTS_TABLE . '
0650          WHERE ' . $db->sql_in_set('post_id', $post_ids);
0651      $result = $db->sql_query($sql);
0652   
0653      while ($row = $db->sql_fetchrow($result))
0654      {
0655          $forum_ids[] = (int) $row['forum_id'];
0656          $topic_ids[] = (int) $row['topic_id'];
0657      }
0658      $db->sql_freeresult($result);
0659   
0660      $sql = 'SELECT forum_id
0661          FROM ' . TOPICS_TABLE . '
0662          WHERE topic_id = ' . $topic_id;
0663      $result = $db->sql_query($sql);
0664      $forum_row = $db->sql_fetchrow($result);
0665      $db->sql_freeresult($result);
0666   
0667      if (!$forum_row)
0668      {
0669          trigger_error('NO_TOPIC');
0670      }
0671   
0672      /**
0673       * Perform additional actions before moving posts
0674       *
0675       * @event core.move_posts_before
0676       * @var    array    post_ids    Array of post ids to move
0677       * @var    int        topic_id    The topic id the posts are moved to
0678       * @var    bool    auto_sync    Whether or not to perform auto sync
0679       * @var    array    forum_ids    Array of the forum ids the posts are moved from
0680       * @var    array    topic_ids    Array of the topic ids the posts are moved from
0681       * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0682       * @since 3.1.7-RC1
0683       */
0684      $vars = array(
0685              'post_ids',
0686              'topic_id',
0687              'auto_sync',
0688              'forum_ids',
0689              'topic_ids',
0690              'forum_row',
0691      );
0692      extract($phpbb_dispatcher->trigger_event('core.move_posts_before', compact($vars)));
0693   
0694      $sql = 'UPDATE ' . POSTS_TABLE . '
0695          SET forum_id = ' . (int) $forum_row['forum_id'] . ", topic_id = $topic_id
0696          WHERE " . $db->sql_in_set('post_id', $post_ids);
0697      $db->sql_query($sql);
0698   
0699      $sql = 'UPDATE ' . ATTACHMENTS_TABLE . "
0700          SET topic_id = $topic_id, in_message = 0
0701          WHERE " . $db->sql_in_set('post_msg_id', $post_ids);
0702      $db->sql_query($sql);
0703   
0704      /**
0705       * Perform additional actions after moving posts
0706       *
0707       * @event core.move_posts_after
0708       * @var    array    post_ids    Array of the moved post ids
0709       * @var    int        topic_id    The topic id the posts are moved to
0710       * @var    bool    auto_sync    Whether or not to perform auto sync
0711       * @var    array    forum_ids    Array of the forum ids the posts are moved from
0712       * @var    array    topic_ids    Array of the topic ids the posts are moved from
0713       * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0714       * @since 3.1.7-RC1
0715       */
0716      $vars = array(
0717              'post_ids',
0718              'topic_id',
0719              'auto_sync',
0720              'forum_ids',
0721              'topic_ids',
0722              'forum_row',
0723      );
0724      extract($phpbb_dispatcher->trigger_event('core.move_posts_after', compact($vars)));
0725   
0726      if ($auto_sync)
0727      {
0728          $forum_ids[] = (int) $forum_row['forum_id'];
0729   
0730          sync('topic_reported', 'topic_id', $topic_ids);
0731          sync('topic_attachment', 'topic_id', $topic_ids);
0732          sync('topic', 'topic_id', $topic_ids, true);
0733          sync('forum', 'forum_id', $forum_ids, true, true);
0734   
0735          /**
0736           * Perform additional actions after move post sync
0737           *
0738           * @event core.move_posts_sync_after
0739           * @var    array    post_ids    Array of the moved post ids
0740           * @var    int        topic_id    The topic id the posts are moved to
0741           * @var    bool    auto_sync    Whether or not to perform auto sync
0742           * @var    array    forum_ids    Array of the forum ids the posts are moved from
0743           * @var    array    topic_ids    Array of the topic ids the posts are moved from
0744           * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0745           * @since 3.1.11-RC1
0746           */
0747          $vars = array(
0748              'post_ids',
0749              'topic_id',
0750              'auto_sync',
0751              'forum_ids',
0752              'topic_ids',
0753              'forum_row',
0754          );
0755          extract($phpbb_dispatcher->trigger_event('core.move_posts_sync_after', compact($vars)));
0756      }
0757   
0758      // Update posted information
0759      update_posted_info($topic_ids);
0760  }
0761   
0762  /**
0763  * Remove topic(s)
0764  */
0765  function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true)
0766  {
0767      global $db, $config, $phpbb_container, $phpbb_dispatcher;
0768   
0769      $approved_topics = 0;
0770      $forum_ids = $topic_ids = array();
0771   
0772      if ($where_type === 'range')
0773      {
0774          $where_clause = $where_ids;
0775      }
0776      else
0777      {
0778          $where_ids = (is_array($where_ids)) ? array_unique($where_ids) : array($where_ids);
0779   
0780          if (!count($where_ids))
0781          {
0782              return array('topics' => 0, 'posts' => 0);
0783          }
0784   
0785          $where_clause = $db->sql_in_set($where_type, $where_ids);
0786      }
0787   
0788      // Making sure that delete_posts does not call delete_topics again...
0789      $return = array(
0790          'posts' => ($call_delete_posts) ? delete_posts($where_type, $where_ids, false, true, $post_count_sync, false) : 0,
0791      );
0792   
0793      $sql = 'SELECT topic_id, forum_id, topic_visibility, topic_moved_id
0794          FROM ' . TOPICS_TABLE . '
0795          WHERE ' . $where_clause;
0796      $result = $db->sql_query($sql);
0797   
0798      while ($row = $db->sql_fetchrow($result))
0799      {
0800          $forum_ids[] = $row['forum_id'];
0801          $topic_ids[] = $row['topic_id'];
0802   
0803          if ($row['topic_visibility'] == ITEM_APPROVED && !$row['topic_moved_id'])
0804          {
0805              $approved_topics++;
0806          }
0807      }
0808      $db->sql_freeresult($result);
0809   
0810      $return['topics'] = count($topic_ids);
0811   
0812      if (!count($topic_ids))
0813      {
0814          return $return;
0815      }
0816   
0817      $db->sql_transaction('begin');
0818   
0819      $table_ary = array(BOOKMARKS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, POLL_VOTES_TABLE, POLL_OPTIONS_TABLE, TOPICS_WATCH_TABLE, TOPICS_TABLE);
0820   
0821      /**
0822       * Perform additional actions before topic(s) deletion
0823       *
0824       * @event core.delete_topics_before_query
0825       * @var    array    table_ary    Array of tables from which all rows will be deleted that hold a topic_id occuring in topic_ids
0826       * @var    array    topic_ids    Array of topic ids to delete
0827       * @since 3.1.4-RC1
0828       */
0829      $vars = array(
0830              'table_ary',
0831              'topic_ids',
0832      );
0833      extract($phpbb_dispatcher->trigger_event('core.delete_topics_before_query', compact($vars)));
0834   
0835      foreach ($table_ary as $table)
0836      {
0837          $sql = "DELETE FROM $table
0838              WHERE " . $db->sql_in_set('topic_id', $topic_ids);
0839          $db->sql_query($sql);
0840      }
0841      unset($table_ary);
0842   
0843      /**
0844       * Perform additional actions after topic(s) deletion
0845       *
0846       * @event core.delete_topics_after_query
0847       * @var    array    topic_ids    Array of topic ids that were deleted
0848       * @since 3.1.4-RC1
0849       */
0850      $vars = array(
0851              'topic_ids',
0852      );
0853      extract($phpbb_dispatcher->trigger_event('core.delete_topics_after_query', compact($vars)));
0854   
0855      $moved_topic_ids = array();
0856   
0857      // update the other forums
0858      $sql = 'SELECT topic_id, forum_id
0859          FROM ' . TOPICS_TABLE . '
0860          WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids);
0861      $result = $db->sql_query($sql);
0862   
0863      while ($row = $db->sql_fetchrow($result))
0864      {
0865          $forum_ids[] = $row['forum_id'];
0866          $moved_topic_ids[] = $row['topic_id'];
0867      }
0868      $db->sql_freeresult($result);
0869   
0870      if (count($moved_topic_ids))
0871      {
0872          $sql = 'DELETE FROM ' . TOPICS_TABLE . '
0873              WHERE ' . $db->sql_in_set('topic_id', $moved_topic_ids);
0874          $db->sql_query($sql);
0875      }
0876   
0877      $db->sql_transaction('commit');
0878   
0879      if ($auto_sync)
0880      {
0881          sync('forum', 'forum_id', array_unique($forum_ids), true, true);
0882          sync('topic_reported', $where_type, $where_ids);
0883      }
0884   
0885      if ($approved_topics)
0886      {
0887          $config->increment('num_topics', $approved_topics * (-1), false);
0888      }
0889   
0890      /* @var $phpbb_notifications \phpbb\notification\manager */
0891      $phpbb_notifications = $phpbb_container->get('notification_manager');
0892   
0893      $phpbb_notifications->delete_notifications(array(
0894          'notification.type.topic',
0895          'notification.type.approve_topic',
0896          'notification.type.topic_in_queue',
0897      ), $topic_ids);
0898   
0899      return $return;
0900  }
0901   
0902  /**
0903  * Remove post(s)
0904  */
0905  function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true)
0906  {
0907      global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container, $phpbb_dispatcher;
0908   
0909      // Notifications types to delete
0910      $delete_notifications_types = array(
0911          'notification.type.quote',
0912          'notification.type.approve_post',
0913          'notification.type.post_in_queue',
0914          'notification.type.report_post',
0915      );
0916   
0917      /**
0918      * Perform additional actions before post(s) deletion
0919      *
0920      * @event core.delete_posts_before
0921      * @var    string    where_type                    Variable containing posts deletion mode
0922      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
0923      * @var    bool    auto_sync                    Flag indicating if topics/forums should be synchronized
0924      * @var    bool    posted_sync                    Flag indicating if topics_posted table should be resynchronized
0925      * @var    bool    post_count_sync                Flag indicating if posts count should be resynchronized
0926      * @var    bool    call_delete_topics            Flag indicating if topics having no posts should be deleted
0927      * @var    array    delete_notifications_types    Array with notifications types to delete
0928      * @since 3.1.0-a4
0929      */
0930      $vars = array(
0931          'where_type',
0932          'where_ids',
0933          'auto_sync',
0934          'posted_sync',
0935          'post_count_sync',
0936          'call_delete_topics',
0937          'delete_notifications_types',
0938      );
0939      extract($phpbb_dispatcher->trigger_event('core.delete_posts_before', compact($vars)));
0940   
0941      if ($where_type === 'range')
0942      {
0943          $where_clause = $where_ids;
0944      }
0945      else
0946      {
0947          if (is_array($where_ids))
0948          {
0949              $where_ids = array_unique($where_ids);
0950          }
0951          else
0952          {
0953              $where_ids = array($where_ids);
0954          }
0955   
0956          if (!count($where_ids))
0957          {
0958              return false;
0959          }
0960   
0961          $where_ids = array_map('intval', $where_ids);
0962   
0963  /*        Possible code for splitting post deletion
0964          if (count($where_ids) >= 1001)
0965          {
0966              // Split into chunks of 1000
0967              $chunks = array_chunk($where_ids, 1000);
0968   
0969              foreach ($chunks as $_where_ids)
0970              {
0971                  delete_posts($where_type, $_where_ids, $auto_sync, $posted_sync, $post_count_sync, $call_delete_topics);
0972              }
0973   
0974              return;
0975          }*/
0976   
0977          $where_clause = $db->sql_in_set($where_type, $where_ids);
0978      }
0979   
0980      $approved_posts = 0;
0981      $post_ids = $topic_ids = $forum_ids = $post_counts = $remove_topics = array();
0982   
0983      $sql = 'SELECT post_id, poster_id, post_visibility, post_postcount, topic_id, forum_id
0984          FROM ' . POSTS_TABLE . '
0985          WHERE ' . $where_clause;
0986      $result = $db->sql_query($sql);
0987   
0988      while ($row = $db->sql_fetchrow($result))
0989      {
0990          $post_ids[] = (int) $row['post_id'];
0991          $poster_ids[] = (int) $row['poster_id'];
0992          $topic_ids[] = (int) $row['topic_id'];
0993          $forum_ids[] = (int) $row['forum_id'];
0994   
0995          if ($row['post_postcount'] && $post_count_sync && $row['post_visibility'] == ITEM_APPROVED)
0996          {
0997              $post_counts[$row['poster_id']] = (!empty($post_counts[$row['poster_id']])) ? $post_counts[$row['poster_id']] + 1 : 1;
0998          }
0999   
1000          if ($row['post_visibility'] == ITEM_APPROVED)
1001          {
1002              $approved_posts++;
1003          }
1004      }
1005      $db->sql_freeresult($result);
1006   
1007      if (!count($post_ids))
1008      {
1009          return false;
1010      }
1011   
1012      $db->sql_transaction('begin');
1013   
1014      $table_ary = array(POSTS_TABLE, REPORTS_TABLE);
1015   
1016      /**
1017      * Perform additional actions during post(s) deletion before running the queries
1018      *
1019      * @event core.delete_posts_in_transaction_before
1020      * @var    array    post_ids                    Array with deleted posts' ids
1021      * @var    array    poster_ids                    Array with deleted posts' author ids
1022      * @var    array    topic_ids                    Array with deleted posts' topic ids
1023      * @var    array    forum_ids                    Array with deleted posts' forum ids
1024      * @var    string    where_type                    Variable containing posts deletion mode
1025      * @var    mixed    where_ids                    Array or comma separated list of post ids to delete
1026      * @var    array    delete_notifications_types    Array with notifications types to delete
1027      * @var    array    table_ary                    Array with table names to delete data from
1028      * @since 3.1.7-RC1
1029      */
1030      $vars = array(
1031          'post_ids',
1032          'poster_ids',
1033          'topic_ids',
1034          'forum_ids',
1035          'where_type',
1036          'where_ids',
1037          'delete_notifications_types',
1038          'table_ary',
1039      );
1040      extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction_before', compact($vars)));
1041   
1042      foreach ($table_ary as $table)
1043      {
1044          $sql = "DELETE FROM $table
1045              WHERE " . $db->sql_in_set('post_id', $post_ids);
1046          $db->sql_query($sql);
1047      }
1048      unset($table_ary);
1049   
1050      // Adjust users post counts
1051      if (count($post_counts) && $post_count_sync)
1052      {
1053          foreach ($post_counts as $poster_id => $substract)
1054          {
1055              $sql = 'UPDATE ' . USERS_TABLE . '
1056                  SET user_posts = 0
1057                  WHERE user_id = ' . $poster_id . '
1058                  AND user_posts < ' . $substract;
1059              $db->sql_query($sql);
1060   
1061              $sql = 'UPDATE ' . USERS_TABLE . '
1062                  SET user_posts = user_posts - ' . $substract . '
1063                  WHERE user_id = ' . $poster_id . '
1064                  AND user_posts >= ' . $substract;
1065              $db->sql_query($sql);
1066          }
1067      }
1068   
1069      // Remove topics now having no posts?
1070      if (count($topic_ids))
1071      {
1072          $sql = 'SELECT topic_id
1073              FROM ' . POSTS_TABLE . '
1074              WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
1075              GROUP BY topic_id';
1076          $result = $db->sql_query($sql);
1077   
1078          while ($row = $db->sql_fetchrow($result))
1079          {
1080              $remove_topics[] = $row['topic_id'];
1081          }
1082          $db->sql_freeresult($result);
1083   
1084          // Actually, those not within remove_topics should be removed. ;)
1085          $remove_topics = array_diff($topic_ids, $remove_topics);
1086      }
1087   
1088      // Remove the message from the search index
1089      $search_type = $config['search_type'];
1090   
1091      if (!class_exists($search_type))
1092      {
1093          trigger_error('NO_SUCH_SEARCH_MODULE');
1094      }
1095   
1096      $error = false;
1097      $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher);
1098   
1099      if ($error)
1100      {
1101          trigger_error($error);
1102      }
1103   
1104      $search->index_remove($post_ids, $poster_ids, $forum_ids);
1105   
1106      /** @var \phpbb\attachment\manager $attachment_manager */
1107      $attachment_manager = $phpbb_container->get('attachment.manager');
1108      $attachment_manager->delete('post', $post_ids, false);
1109      unset($attachment_manager);
1110   
1111      /**
1112      * Perform additional actions during post(s) deletion
1113      *
1114      * @event core.delete_posts_in_transaction
1115      * @var    array    post_ids                    Array with deleted posts' ids
1116      * @var    array    poster_ids                    Array with deleted posts' author ids
1117      * @var    array    topic_ids                    Array with deleted posts' topic ids
1118      * @var    array    forum_ids                    Array with deleted posts' forum ids
1119      * @var    string    where_type                    Variable containing posts deletion mode
1120      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
1121      * @var    array    delete_notifications_types    Array with notifications types to delete
1122      * @since 3.1.0-a4
1123      */
1124      $vars = array(
1125          'post_ids',
1126          'poster_ids',
1127          'topic_ids',
1128          'forum_ids',
1129          'where_type',
1130          'where_ids',
1131          'delete_notifications_types',
1132      );
1133      extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction', compact($vars)));
1134   
1135      $db->sql_transaction('commit');
1136   
1137      /**
1138      * Perform additional actions after post(s) deletion
1139      *
1140      * @event core.delete_posts_after
1141      * @var    array    post_ids                    Array with deleted posts' ids
1142      * @var    array    poster_ids                    Array with deleted posts' author ids
1143      * @var    array    topic_ids                    Array with deleted posts' topic ids
1144      * @var    array    forum_ids                    Array with deleted posts' forum ids
1145      * @var    string    where_type                    Variable containing posts deletion mode
1146      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
1147      * @var    array    delete_notifications_types    Array with notifications types to delete
1148      * @since 3.1.0-a4
1149      */
1150      $vars = array(
1151          'post_ids',
1152          'poster_ids',
1153          'topic_ids',
1154          'forum_ids',
1155          'where_type',
1156          'where_ids',
1157          'delete_notifications_types',
1158      );
1159      extract($phpbb_dispatcher->trigger_event('core.delete_posts_after', compact($vars)));
1160   
1161      // Resync topics_posted table
1162      if ($posted_sync)
1163      {
1164          update_posted_info($topic_ids);
1165      }
1166   
1167      if ($auto_sync)
1168      {
1169          sync('topic_reported', 'topic_id', $topic_ids);
1170          sync('topic', 'topic_id', $topic_ids, true);
1171          sync('forum', 'forum_id', $forum_ids, true, true);
1172      }
1173   
1174      if ($approved_posts && $post_count_sync)
1175      {
1176          $config->increment('num_posts', $approved_posts * (-1), false);
1177      }
1178   
1179      // We actually remove topics now to not be inconsistent (the delete_topics function calls this function too)
1180      if (count($remove_topics) && $call_delete_topics)
1181      {
1182          delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false);
1183      }
1184   
1185      /* @var $phpbb_notifications \phpbb\notification\manager */
1186      $phpbb_notifications = $phpbb_container->get('notification_manager');
1187   
1188      $phpbb_notifications->delete_notifications($delete_notifications_types, $post_ids);
1189   
1190      return count($post_ids);
1191  }
1192   
1193  /**
1194  * Deletes shadow topics pointing to a specified forum.
1195  *
1196  * @param int        $forum_id        The forum id
1197  * @param string        $sql_more        Additional WHERE statement, e.g. t.topic_time < (time() - 1234)
1198  * @param bool        $auto_sync        Will call sync() if this is true
1199  *
1200  * @return array        Array with affected forums
1201  */
1202  function delete_topic_shadows($forum_id, $sql_more = '', $auto_sync = true)
1203  {
1204      global $db;
1205   
1206      if (!$forum_id)
1207      {
1208          // Nothing to do.
1209          return;
1210      }
1211   
1212      // Set of affected forums we have to resync
1213      $sync_forum_ids = array();
1214   
1215      // Amount of topics we select and delete at once.
1216      $batch_size = 500;
1217   
1218      do
1219      {
1220          $sql = 'SELECT t2.forum_id, t2.topic_id
1221              FROM ' . TOPICS_TABLE . ' t2, ' . TOPICS_TABLE . ' t
1222              WHERE t2.topic_moved_id = t.topic_id
1223                  AND t.forum_id = ' . (int) $forum_id . '
1224                  ' . (($sql_more) ? 'AND ' . $sql_more : '');
1225          $result = $db->sql_query_limit($sql, $batch_size);
1226   
1227          $topic_ids = array();
1228          while ($row = $db->sql_fetchrow($result))
1229          {
1230              $topic_ids[] = (int) $row['topic_id'];
1231   
1232              $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id'];
1233          }
1234          $db->sql_freeresult($result);
1235   
1236          if (!empty($topic_ids))
1237          {
1238              $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1239                  WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1240              $db->sql_query($sql);
1241          }
1242      }
1243      while (count($topic_ids) == $batch_size);
1244   
1245      if ($auto_sync)
1246      {
1247          sync('forum', 'forum_id', $sync_forum_ids, true, true);
1248      }
1249   
1250      return $sync_forum_ids;
1251  }
1252   
1253  /**
1254  * Update/Sync posted information for topics
1255  */
1256  function update_posted_info(&$topic_ids)
1257  {
1258      global $db, $config;
1259   
1260      if (empty($topic_ids) || !$config['load_db_track'])
1261      {
1262          return;
1263      }
1264   
1265      // First of all, let us remove any posted information for these topics
1266      $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . '
1267          WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1268      $db->sql_query($sql);
1269   
1270      // Now, let us collect the user/topic combos for rebuilding the information
1271      $sql = 'SELECT poster_id, topic_id
1272          FROM ' . POSTS_TABLE . '
1273          WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
1274              AND poster_id <> ' . ANONYMOUS . '
1275          GROUP BY poster_id, topic_id';
1276      $result = $db->sql_query($sql);
1277   
1278      $posted = array();
1279      while ($row = $db->sql_fetchrow($result))
1280      {
1281          // Add as key to make them unique (grouping by) and circumvent empty keys on array_unique
1282          $posted[$row['poster_id']][] = $row['topic_id'];
1283      }
1284      $db->sql_freeresult($result);
1285   
1286      // Now add the information...
1287      $sql_ary = array();
1288      foreach ($posted as $user_id => $topic_row)
1289      {
1290          foreach ($topic_row as $topic_id)
1291          {
1292              $sql_ary[] = array(
1293                  'user_id'        => (int) $user_id,
1294                  'topic_id'        => (int) $topic_id,
1295                  'topic_posted'    => 1,
1296              );
1297          }
1298      }
1299      unset($posted);
1300   
1301      $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary);
1302  }
1303   
1304  /**
1305  * All-encompasing sync function
1306  *
1307  * Exaples:
1308  * <code>
1309  * sync('topic', 'topic_id', 123);            // resync topic #123
1310  * sync('topic', 'forum_id', array(2, 3));    // resync topics from forum #2 and #3
1311  * sync('topic');                            // resync all topics
1312  * sync('topic', 'range', 'topic_id BETWEEN 1 AND 60');    // resync a range of topics/forums (only available for 'topic' and 'forum' modes)
1313  * </code>
1314  *
1315  * Modes:
1316  * - forum                Resync complete forum
1317  * - topic                Resync topics
1318  * - topic_moved            Removes topic shadows that would be in the same forum as the topic they link to
1319  * - topic_visibility    Resyncs the topic_visibility flag according to the status of the first post
1320  * - post_reported        Resyncs the post_reported flag, relying on actual reports
1321  * - topic_reported        Resyncs the topic_reported flag, relying on post_reported flags
1322  * - post_attachement    Same as post_reported, but with attachment flags
1323  * - topic_attachement    Same as topic_reported, but with attachment flags
1324  */
1325  function sync($mode, $where_type = '', $where_ids = '', $resync_parents = false, $sync_extra = false)
1326  {
1327      global $db, $phpbb_dispatcher;
1328   
1329      if (is_array($where_ids))
1330      {
1331          $where_ids = array_unique($where_ids);
1332          $where_ids = array_map('intval', $where_ids);
1333      }
1334      else if ($where_type != 'range')
1335      {
1336          $where_ids = ($where_ids) ? array((int) $where_ids) : array();
1337      }
1338   
1339      if ($mode == 'forum' || $mode == 'topic' || $mode == 'topic_visibility' || $mode == 'topic_reported' || $mode == 'post_reported')
1340      {
1341          if (!$where_type)
1342          {
1343              $where_sql = '';
1344              $where_sql_and = 'WHERE';
1345          }
1346          else if ($where_type == 'range')
1347          {
1348              // Only check a range of topics/forums. For instance: 'topic_id BETWEEN 1 AND 60'
1349              $where_sql = 'WHERE (' . $mode[0] . ".$where_ids)";
1350              $where_sql_and = $where_sql . "\n\tAND";
1351          }
1352          else
1353          {
1354              // Do not sync the "global forum"
1355              $where_ids = array_diff($where_ids, array(0));
1356   
1357              if (!count($where_ids))
1358              {
1359                  // Empty array with IDs. This means that we don't have any work to do. Just return.
1360                  return;
1361              }
1362   
1363              // Limit the topics/forums we are syncing, use specific topic/forum IDs.
1364              // $where_type contains the field for the where clause (forum_id, topic_id)
1365              $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
1366              $where_sql_and = $where_sql . "\n\tAND";
1367          }
1368      }
1369      else
1370      {
1371          if (!count($where_ids))
1372          {
1373              return;
1374          }
1375   
1376          // $where_type contains the field for the where clause (forum_id, topic_id)
1377          $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
1378          $where_sql_and = $where_sql . "\n\tAND";
1379      }
1380   
1381      switch ($mode)
1382      {
1383          case 'topic_moved':
1384              $db->sql_transaction('begin');
1385              switch ($db->get_sql_layer())
1386              {
1387                  case 'mysqli':
1388                      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1389                          USING ' . TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
1390                          WHERE t1.topic_moved_id = t2.topic_id
1391                              AND t1.forum_id = t2.forum_id";
1392                      $db->sql_query($sql);
1393                  break;
1394   
1395                  default:
1396                      $sql = 'SELECT t1.topic_id
1397                          FROM ' .TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
1398                          WHERE t1.topic_moved_id = t2.topic_id
1399                              AND t1.forum_id = t2.forum_id";
1400                      $result = $db->sql_query($sql);
1401   
1402                      $topic_id_ary = array();
1403                      while ($row = $db->sql_fetchrow($result))
1404                      {
1405                          $topic_id_ary[] = $row['topic_id'];
1406                      }
1407                      $db->sql_freeresult($result);
1408   
1409                      if (!count($topic_id_ary))
1410                      {
1411                          return;
1412                      }
1413   
1414                      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1415                          WHERE ' . $db->sql_in_set('topic_id', $topic_id_ary);
1416                      $db->sql_query($sql);
1417   
1418                  break;
1419              }
1420   
1421              $db->sql_transaction('commit');
1422              break;
1423   
1424          case 'topic_visibility':
1425   
1426              $db->sql_transaction('begin');
1427   
1428              $sql = 'SELECT t.topic_id, p.post_visibility
1429                  FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
1430                  $where_sql_and p.topic_id = t.topic_id
1431                      AND p.post_visibility = " . ITEM_APPROVED;
1432              $result = $db->sql_query($sql);
1433   
1434              $topics_approved = array();
1435              while ($row = $db->sql_fetchrow($result))
1436              {
1437                  $topics_approved[] = (int) $row['topic_id'];
1438              }
1439              $db->sql_freeresult($result);
1440   
1441              $sql = 'SELECT t.topic_id, p.post_visibility
1442                  FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
1443                  $where_sql_and " . $db->sql_in_set('t.topic_id', $topics_approved, true, true) . '
1444                      AND p.topic_id = t.topic_id
1445                      AND p.post_visibility = ' . ITEM_DELETED;
1446              $result = $db->sql_query($sql);
1447   
1448              $topics_softdeleted = array();
1449              while ($row = $db->sql_fetchrow($result))
1450              {
1451                  $topics_softdeleted[] = (int) $row['topic_id'];
1452              }
1453              $db->sql_freeresult($result);
1454   
1455              $topics_softdeleted = array_diff($topics_softdeleted, $topics_approved);
1456              $topics_not_unapproved = array_merge($topics_softdeleted, $topics_approved);
1457   
1458              $update_ary = array(
1459                  ITEM_UNAPPROVED    => (!empty($topics_not_unapproved)) ? $where_sql_and . ' ' . $db->sql_in_set('topic_id', $topics_not_unapproved, true) : '',
1460                  ITEM_APPROVED    => (!empty($topics_approved)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_approved) : '',
1461                  ITEM_DELETED    => (!empty($topics_softdeleted)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_softdeleted) : '',
1462              );
1463   
1464              foreach ($update_ary as $visibility => $sql_where)
1465              {
1466                  if ($sql_where)
1467                  {
1468                      $sql = 'UPDATE ' . TOPICS_TABLE . '
1469                          SET topic_visibility = ' . $visibility . '
1470                          ' . $sql_where;
1471                      $db->sql_query($sql);
1472                  }
1473              }
1474   
1475              $db->sql_transaction('commit');
1476              break;
1477   
1478          case 'post_reported':
1479              $post_ids = $post_reported = array();
1480   
1481              $db->sql_transaction('begin');
1482   
1483              $sql = 'SELECT p.post_id, p.post_reported
1484                  FROM ' . POSTS_TABLE . " p
1485                  $where_sql
1486                  GROUP BY p.post_id, p.post_reported";
1487              $result = $db->sql_query($sql);
1488   
1489              while ($row = $db->sql_fetchrow($result))
1490              {
1491                  $post_ids[$row['post_id']] = $row['post_id'];
1492                  if ($row['post_reported'])
1493                  {
1494                      $post_reported[$row['post_id']] = 1;
1495                  }
1496              }
1497              $db->sql_freeresult($result);
1498   
1499              $sql = 'SELECT DISTINCT(post_id)
1500                  FROM ' . REPORTS_TABLE . '
1501                  WHERE ' . $db->sql_in_set('post_id', $post_ids) . '
1502                      AND report_closed = 0';
1503              $result = $db->sql_query($sql);
1504   
1505              $post_ids = array();
1506              while ($row = $db->sql_fetchrow($result))
1507              {
1508                  if (!isset($post_reported[$row['post_id']]))
1509                  {
1510                      $post_ids[] = $row['post_id'];
1511                  }
1512                  else
1513                  {
1514                      unset($post_reported[$row['post_id']]);
1515                  }
1516              }
1517              $db->sql_freeresult($result);
1518   
1519              // $post_reported should be empty by now, if it's not it contains
1520              // posts that are falsely flagged as reported
1521              foreach ($post_reported as $post_id => $void)
1522              {
1523                  $post_ids[] = $post_id;
1524              }
1525   
1526              if (count($post_ids))
1527              {
1528                  $sql = 'UPDATE ' . POSTS_TABLE . '
1529                      SET post_reported = 1 - post_reported
1530                      WHERE ' . $db->sql_in_set('post_id', $post_ids);
1531                  $db->sql_query($sql);
1532              }
1533   
1534              $db->sql_transaction('commit');
1535              break;
1536   
1537          case 'topic_reported':
1538              if ($sync_extra)
1539              {
1540                  sync('post_reported', $where_type, $where_ids);
1541              }
1542   
1543              $topic_ids = $topic_reported = array();
1544   
1545              $db->sql_transaction('begin');
1546   
1547              $sql = 'SELECT DISTINCT(t.topic_id)
1548                  FROM ' . POSTS_TABLE . " t
1549                  $where_sql_and t.post_reported = 1";
1550              $result = $db->sql_query($sql);
1551   
1552              while ($row = $db->sql_fetchrow($result))
1553              {
1554                  $topic_reported[$row['topic_id']] = 1;
1555              }
1556              $db->sql_freeresult($result);
1557   
1558              $sql = 'SELECT t.topic_id, t.topic_reported
1559                  FROM ' . TOPICS_TABLE . " t
1560                  $where_sql";
1561              $result = $db->sql_query($sql);
1562   
1563              while ($row = $db->sql_fetchrow($result))
1564              {
1565                  if ($row['topic_reported'] ^ isset($topic_reported[$row['topic_id']]))
1566                  {
1567                      $topic_ids[] = $row['topic_id'];
1568                  }
1569              }
1570              $db->sql_freeresult($result);
1571   
1572              if (count($topic_ids))
1573              {
1574                  $sql = 'UPDATE ' . TOPICS_TABLE . '
1575                      SET topic_reported = 1 - topic_reported
1576                      WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1577                  $db->sql_query($sql);
1578              }
1579   
1580              $db->sql_transaction('commit');
1581              break;
1582   
1583          case 'post_attachment':
1584              $post_ids = $post_attachment = array();
1585   
1586              $db->sql_transaction('begin');
1587   
1588              $sql = 'SELECT p.post_id, p.post_attachment
1589                  FROM ' . POSTS_TABLE . " p
1590                  $where_sql
1591                  GROUP BY p.post_id, p.post_attachment";
1592              $result = $db->sql_query($sql);
1593   
1594              while ($row = $db->sql_fetchrow($result))
1595              {
1596                  $post_ids[$row['post_id']] = $row['post_id'];
1597                  if ($row['post_attachment'])
1598                  {
1599                      $post_attachment[$row['post_id']] = 1;
1600                  }
1601              }
1602              $db->sql_freeresult($result);
1603   
1604              $sql = 'SELECT DISTINCT(post_msg_id)
1605                  FROM ' . ATTACHMENTS_TABLE . '
1606                  WHERE ' . $db->sql_in_set('post_msg_id', $post_ids) . '
1607                      AND in_message = 0';
1608              $result = $db->sql_query($sql);
1609   
1610              $post_ids = array();
1611              while ($row = $db->sql_fetchrow($result))
1612              {
1613                  if (!isset($post_attachment[$row['post_msg_id']]))
1614                  {
1615                      $post_ids[] = $row['post_msg_id'];
1616                  }
1617                  else
1618                  {
1619                      unset($post_attachment[$row['post_msg_id']]);
1620                  }
1621              }
1622              $db->sql_freeresult($result);
1623   
1624              // $post_attachment should be empty by now, if it's not it contains
1625              // posts that are falsely flagged as having attachments
1626              foreach ($post_attachment as $post_id => $void)
1627              {
1628                  $post_ids[] = $post_id;
1629              }
1630   
1631              if (count($post_ids))
1632              {
1633                  $sql = 'UPDATE ' . POSTS_TABLE . '
1634                      SET post_attachment = 1 - post_attachment
1635                      WHERE ' . $db->sql_in_set('post_id', $post_ids);
1636                  $db->sql_query($sql);
1637              }
1638   
1639              $db->sql_transaction('commit');
1640              break;
1641   
1642          case 'topic_attachment':
1643              if ($sync_extra)
1644              {
1645                  sync('post_attachment', $where_type, $where_ids);
1646              }
1647   
1648              $topic_ids = $topic_attachment = array();
1649   
1650              $db->sql_transaction('begin');
1651   
1652              $sql = 'SELECT DISTINCT(t.topic_id)
1653                  FROM ' . POSTS_TABLE . " t
1654                  $where_sql_and t.post_attachment = 1";
1655              $result = $db->sql_query($sql);
1656   
1657              while ($row = $db->sql_fetchrow($result))
1658              {
1659                  $topic_attachment[$row['topic_id']] = 1;
1660              }
1661              $db->sql_freeresult($result);
1662   
1663              $sql = 'SELECT t.topic_id, t.topic_attachment
1664                  FROM ' . TOPICS_TABLE . " t
1665                  $where_sql";
1666              $result = $db->sql_query($sql);
1667   
1668              while ($row = $db->sql_fetchrow($result))
1669              {
1670                  if ($row['topic_attachment'] ^ isset($topic_attachment[$row['topic_id']]))
1671                  {
1672                      $topic_ids[] = $row['topic_id'];
1673                  }
1674              }
1675              $db->sql_freeresult($result);
1676   
1677              if (count($topic_ids))
1678              {
1679                  $sql = 'UPDATE ' . TOPICS_TABLE . '
1680                      SET topic_attachment = 1 - topic_attachment
1681                      WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1682                  $db->sql_query($sql);
1683              }
1684   
1685              $db->sql_transaction('commit');
1686   
1687              break;
1688   
1689          case 'forum':
1690   
1691              $db->sql_transaction('begin');
1692   
1693              // 1: Get the list of all forums
1694              $sql = 'SELECT f.*
1695                  FROM ' . FORUMS_TABLE . " f
1696                  $where_sql";
1697              $result = $db->sql_query($sql);
1698   
1699              $forum_data = $forum_ids = $post_ids = $last_post_id = $post_info = array();
1700              while ($row = $db->sql_fetchrow($result))
1701              {
1702                  if ($row['forum_type'] == FORUM_LINK)
1703                  {
1704                      continue;
1705                  }
1706   
1707                  $forum_id = (int) $row['forum_id'];
1708                  $forum_ids[$forum_id] = $forum_id;
1709   
1710                  $forum_data[$forum_id] = $row;
1711                  if ($sync_extra)
1712                  {
1713                      $forum_data[$forum_id]['posts_approved'] = 0;
1714                      $forum_data[$forum_id]['posts_unapproved'] = 0;
1715                      $forum_data[$forum_id]['posts_softdeleted'] = 0;
1716                      $forum_data[$forum_id]['topics_approved'] = 0;
1717                      $forum_data[$forum_id]['topics_unapproved'] = 0;
1718                      $forum_data[$forum_id]['topics_softdeleted'] = 0;
1719                  }
1720                  $forum_data[$forum_id]['last_post_id'] = 0;
1721                  $forum_data[$forum_id]['last_post_subject'] = '';
1722                  $forum_data[$forum_id]['last_post_time'] = 0;
1723                  $forum_data[$forum_id]['last_poster_id'] = 0;
1724                  $forum_data[$forum_id]['last_poster_name'] = '';
1725                  $forum_data[$forum_id]['last_poster_colour'] = '';
1726              }
1727              $db->sql_freeresult($result);
1728   
1729              if (!count($forum_ids))
1730              {
1731                  break;
1732              }
1733   
1734              $forum_ids = array_values($forum_ids);
1735   
1736              // 2: Get topic counts for each forum (optional)
1737              if ($sync_extra)
1738              {
1739                  $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS total_topics
1740                      FROM ' . TOPICS_TABLE . '
1741                      WHERE ' . $db->sql_in_set('forum_id', $forum_ids) . '
1742                      GROUP BY forum_id, topic_visibility';
1743                  $result = $db->sql_query($sql);
1744   
1745                  while ($row = $db->sql_fetchrow($result))
1746                  {
1747                      $forum_id = (int) $row['forum_id'];
1748   
1749                      if ($row['topic_visibility'] == ITEM_APPROVED)
1750                      {
1751                          $forum_data[$forum_id]['topics_approved'] = $row['total_topics'];
1752                      }
1753                      else if ($row['topic_visibility'] == ITEM_UNAPPROVED || $row['topic_visibility'] == ITEM_REAPPROVE)
1754                      {
1755                          $forum_data[$forum_id]['topics_unapproved'] = $row['total_topics'];
1756                      }
1757                      else if ($row['topic_visibility'] == ITEM_DELETED)
1758                      {
1759                          $forum_data[$forum_id]['topics_softdeleted'] = $row['total_topics'];
1760                      }
1761                  }
1762                  $db->sql_freeresult($result);
1763              }
1764   
1765              // 3: Get post count for each forum (optional)
1766              if ($sync_extra)
1767              {
1768                  if (count($forum_ids) == 1)
1769                  {
1770                      $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
1771                          FROM ' . TOPICS_TABLE . ' t
1772                          WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1773                              AND t.topic_status <> ' . ITEM_MOVED;
1774                  }
1775                  else
1776                  {
1777                      $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
1778                          FROM ' . TOPICS_TABLE . ' t
1779                          WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1780                              AND t.topic_status <> ' . ITEM_MOVED . '
1781                          GROUP BY t.forum_id';
1782                  }
1783   
1784                  $result = $db->sql_query($sql);
1785   
1786                  while ($row = $db->sql_fetchrow($result))
1787                  {
1788                      $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
1789   
1790                      $forum_data[$forum_id]['posts_approved'] = (int) $row['forum_posts_approved'];
1791                      $forum_data[$forum_id]['posts_unapproved'] = (int) $row['forum_posts_unapproved'];
1792                      $forum_data[$forum_id]['posts_softdeleted'] = (int) $row['forum_posts_softdeleted'];
1793                  }
1794                  $db->sql_freeresult($result);
1795              }
1796   
1797              // 4: Get last_post_id for each forum
1798              if (count($forum_ids) == 1)
1799              {
1800                  $sql = 'SELECT MAX(t.topic_last_post_id) as last_post_id
1801                      FROM ' . TOPICS_TABLE . ' t
1802                      WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1803                          AND t.topic_visibility = ' . ITEM_APPROVED;
1804              }
1805              else
1806              {
1807                  $sql = 'SELECT t.forum_id, MAX(t.topic_last_post_id) as last_post_id
1808                      FROM ' . TOPICS_TABLE . ' t
1809                      WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1810                          AND t.topic_visibility = ' . ITEM_APPROVED . '
1811                      GROUP BY t.forum_id';
1812              }
1813   
1814              $result = $db->sql_query($sql);
1815   
1816              while ($row = $db->sql_fetchrow($result))
1817              {
1818                  $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
1819   
1820                  $forum_data[$forum_id]['last_post_id'] = (int) $row['last_post_id'];
1821   
1822                  $post_ids[] = $row['last_post_id'];
1823              }
1824              $db->sql_freeresult($result);
1825   
1826              // 5: Retrieve last_post infos
1827              if (count($post_ids))
1828              {
1829                  $sql_ary = array(
1830                      'SELECT'    => 'p.post_id, p.poster_id, p.post_subject, p.post_time, p.post_username, u.username, u.user_colour',
1831                      'FROM'        => array(
1832                          POSTS_TABLE    => 'p',
1833                          USERS_TABLE => 'u',
1834                      ),
1835                      'WHERE'        => $db->sql_in_set('p.post_id', $post_ids) . '
1836                          AND p.poster_id = u.user_id',
1837                  );
1838   
1839                  /**
1840                  * Event to modify the SQL array to get the post and user data from all forums' last posts
1841                  *
1842                  * @event core.sync_forum_last_post_info_sql
1843                  * @var    array    sql_ary        SQL array with some post and user data from the last posts list
1844                  * @since 3.3.5-RC1
1845                  */
1846                  $vars = ['sql_ary'];
1847                  extract($phpbb_dispatcher->trigger_event('core.sync_forum_last_post_info_sql', compact($vars)));
1848                  $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
1849   
1850                  while ($row = $db->sql_fetchrow($result))
1851                  {
1852                      $post_info[$row['post_id']] = $row;
1853                  }
1854                  $db->sql_freeresult($result);
1855   
1856                  foreach ($forum_data as $forum_id => $data)
1857                  {
1858                      if ($data['last_post_id'])
1859                      {
1860                          if (isset($post_info[$data['last_post_id']]))
1861                          {
1862                              $forum_data[$forum_id]['last_post_subject'] = $post_info[$data['last_post_id']]['post_subject'];
1863                              $forum_data[$forum_id]['last_post_time'] = $post_info[$data['last_post_id']]['post_time'];
1864                              $forum_data[$forum_id]['last_poster_id'] = $post_info[$data['last_post_id']]['poster_id'];
1865                              $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'];
1866                              $forum_data[$forum_id]['last_poster_colour'] = $post_info[$data['last_post_id']]['user_colour'];
1867                          }
1868                          else
1869                          {
1870                              // For some reason we did not find the post in the db
1871                              $forum_data[$forum_id]['last_post_id'] = 0;
1872                              $forum_data[$forum_id]['last_post_subject'] = '';
1873                              $forum_data[$forum_id]['last_post_time'] = 0;
1874                              $forum_data[$forum_id]['last_poster_id'] = 0;
1875                              $forum_data[$forum_id]['last_poster_name'] = '';
1876                              $forum_data[$forum_id]['last_poster_colour'] = '';
1877                          }
1878                      }
1879                  }
1880              }
1881   
1882              // 6: Now do that thing
1883              $fieldnames = array('last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour');
1884   
1885              if ($sync_extra)
1886              {
1887                  array_push($fieldnames, 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'topics_approved', 'topics_unapproved', 'topics_softdeleted');
1888              }
1889   
1890              /**
1891              * Event to modify the SQL array to get the post and user data from all forums' last posts
1892              *
1893              * @event core.sync_modify_forum_data
1894              * @var    array    forum_data        Array with data to update for all forum ids
1895              * @var    array    post_info        Array with some post and user data from the last posts list
1896              * @var    array    fieldnames        Array with the partial column names that are being updated
1897              * @since 3.3.5-RC1
1898              */
1899              $vars = [
1900                  'forum_data',
1901                  'post_info',
1902                  'fieldnames',
1903              ];
1904              extract($phpbb_dispatcher->trigger_event('core.sync_modify_forum_data', compact($vars)));
1905              unset($post_info);
1906   
1907              foreach ($forum_data as $forum_id => $row)
1908              {
1909                  $sql_ary = array();
1910   
1911                  foreach ($fieldnames as $fieldname)
1912                  {
1913                      if ($row['forum_' . $fieldname] != $row[$fieldname])
1914                      {
1915                          if (preg_match('#(name|colour|subject)$#', $fieldname))
1916                          {
1917                              $sql_ary['forum_' . $fieldname] = (string) $row[$fieldname];
1918                          }
1919                          else
1920                          {
1921                              $sql_ary['forum_' . $fieldname] = (int) $row[$fieldname];
1922                          }
1923                      }
1924                  }
1925   
1926                  if (count($sql_ary))
1927                  {
1928                      $sql = 'UPDATE ' . FORUMS_TABLE . '
1929                          SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
1930                          WHERE forum_id = ' . $forum_id;
1931                      $db->sql_query($sql);
1932                  }
1933              }
1934   
1935              $db->sql_transaction('commit');
1936              break;
1937   
1938          case 'topic':
1939              $topic_data = $post_ids = $resync_forums = $delete_topics = $delete_posts = $moved_topics = array();
1940   
1941              $db->sql_transaction('begin');
1942   
1943              $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
1944                  FROM ' . TOPICS_TABLE . " t
1945                  $where_sql";
1946              $result = $db->sql_query($sql);
1947   
1948              while ($row = $db->sql_fetchrow($result))
1949              {
1950                  if ($row['topic_moved_id'])
1951                  {
1952                      $moved_topics[] = $row['topic_id'];
1953                      continue;
1954                  }
1955   
1956                  $topic_id = (int) $row['topic_id'];
1957                  $topic_data[$topic_id] = $row;
1958                  $topic_data[$topic_id]['visibility'] = ITEM_UNAPPROVED;
1959                  $topic_data[$topic_id]['posts_approved'] = 0;
1960                  $topic_data[$topic_id]['posts_unapproved'] = 0;
1961                  $topic_data[$topic_id]['posts_softdeleted'] = 0;
1962                  $topic_data[$topic_id]['first_post_id'] = 0;
1963                  $topic_data[$topic_id]['last_post_id'] = 0;
1964                  unset($topic_data[$topic_id]['topic_id']);
1965   
1966                  // This array holds all topic_ids
1967                  $delete_topics[$topic_id] = '';
1968   
1969                  if ($sync_extra)
1970                  {
1971                      $topic_data[$topic_id]['reported'] = 0;
1972                      $topic_data[$topic_id]['attachment'] = 0;
1973                  }
1974              }
1975              $db->sql_freeresult($result);
1976   
1977              // Use "t" as table alias because of the $where_sql clause
1978              // NOTE: 't.post_visibility' in the GROUP BY is causing a major slowdown.
1979              $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
1980                  FROM ' . POSTS_TABLE . " t
1981                  $where_sql
1982                  GROUP BY t.topic_id, t.post_visibility";
1983              $result = $db->sql_query($sql);
1984   
1985              while ($row = $db->sql_fetchrow($result))
1986              {
1987                  $topic_id = (int) $row['topic_id'];
1988   
1989                  $row['first_post_id'] = (int) $row['first_post_id'];
1990                  $row['last_post_id'] = (int) $row['last_post_id'];
1991   
1992                  if (!isset($topic_data[$topic_id]))
1993                  {
1994                      // Hey, these posts come from a topic that does not exist
1995                      $delete_posts[$topic_id] = '';
1996                  }
1997                  else
1998                  {
1999                      // Unset the corresponding entry in $delete_topics
2000                      // When we'll be done, only topics with no posts will remain
2001                      unset($delete_topics[$topic_id]);
2002   
2003                      if ($row['post_visibility'] == ITEM_APPROVED)
2004                      {
2005                          $topic_data[$topic_id]['posts_approved'] = $row['total_posts'];
2006                      }
2007                      else if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE)
2008                      {
2009                          $topic_data[$topic_id]['posts_unapproved'] = $row['total_posts'];
2010                      }
2011                      else if ($row['post_visibility'] == ITEM_DELETED)
2012                      {
2013                          $topic_data[$topic_id]['posts_softdeleted'] = $row['total_posts'];
2014                      }
2015   
2016                      if ($row['post_visibility'] == ITEM_APPROVED)
2017                      {
2018                          $topic_data[$topic_id]['visibility'] = ITEM_APPROVED;
2019                          $topic_data[$topic_id]['first_post_id'] = $row['first_post_id'];
2020                          $topic_data[$topic_id]['last_post_id'] = $row['last_post_id'];
2021                      }
2022                      else if ($topic_data[$topic_id]['visibility'] != ITEM_APPROVED)
2023                      {
2024                          // If there is no approved post, we take the min/max of the other visibilities
2025                          // for the last and first post info, because it is only visible to moderators anyway
2026                          $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'];
2027                          $topic_data[$topic_id]['last_post_id'] = max($topic_data[$topic_id]['last_post_id'], $row['last_post_id']);
2028   
2029                          if ($topic_data[$topic_id]['visibility'] == ITEM_UNAPPROVED || $topic_data[$topic_id]['visibility'] == ITEM_REAPPROVE)
2030                          {
2031                              // Soft delete status is stronger than unapproved.
2032                              $topic_data[$topic_id]['visibility'] = $row['post_visibility'];
2033                          }
2034                      }
2035                  }
2036              }
2037              $db->sql_freeresult($result);
2038   
2039              foreach ($topic_data as $topic_id => $row)
2040              {
2041                  $post_ids[] = $row['first_post_id'];
2042                  if ($row['first_post_id'] != $row['last_post_id'])
2043                  {
2044                      $post_ids[] = $row['last_post_id'];
2045                  }
2046              }
2047   
2048              // Now we delete empty topics and orphan posts
2049              if (count($delete_posts))
2050              {
2051                  delete_posts('topic_id', array_keys($delete_posts), false);
2052                  unset($delete_posts);
2053              }
2054   
2055              if (!count($topic_data))
2056              {
2057                  // If we get there, topic ids were invalid or topics did not contain any posts
2058                  delete_topics($where_type, $where_ids, true);
2059                  return;
2060              }
2061   
2062              if (count($delete_topics))
2063              {
2064                  $delete_topic_ids = array();
2065                  foreach ($delete_topics as $topic_id => $void)
2066                  {
2067                      unset($topic_data[$topic_id]);
2068                      $delete_topic_ids[] = $topic_id;
2069                  }
2070   
2071                  delete_topics('topic_id', $delete_topic_ids, false);
2072                  unset($delete_topics, $delete_topic_ids);
2073              }
2074   
2075              $sql_ary = array(
2076                  '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',
2077                  'FROM'        => array(
2078                      POSTS_TABLE    => 'p',
2079                      USERS_TABLE => 'u',
2080                  ),
2081                  'WHERE'        => $db->sql_in_set('p.post_id', $post_ids) . '
2082                      AND u.user_id = p.poster_id',
2083              );
2084   
2085              $custom_fieldnames = [];
2086              /**
2087              * Event to modify the SQL array to get the post and user data from all topics' last posts
2088              *
2089              * @event core.sync_topic_last_post_info_sql
2090              * @var    array    sql_ary                    SQL array with some post and user data from the last posts list
2091              * @var    array    custom_fieldnames        Empty array for custom fieldnames to update the topics_table with
2092              * @since 3.3.5-RC1
2093              */
2094              $vars = [
2095                  'sql_ary',
2096                  'custom_fieldnames',
2097              ];
2098              extract($phpbb_dispatcher->trigger_event('core.sync_topic_last_post_info_sql', compact($vars)));
2099              $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
2100   
2101              while ($row = $db->sql_fetchrow($result))
2102              {
2103                  $topic_id = intval($row['topic_id']);
2104   
2105                  if ($row['post_id'] == $topic_data[$topic_id]['first_post_id'])
2106                  {
2107                      $topic_data[$topic_id]['time'] = $row['post_time'];
2108                      $topic_data[$topic_id]['poster'] = $row['poster_id'];
2109                      $topic_data[$topic_id]['first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2110                      $topic_data[$topic_id]['first_poster_colour'] = $row['user_colour'];
2111                  }
2112   
2113                  if ($row['post_id'] == $topic_data[$topic_id]['last_post_id'])
2114                  {
2115                      $topic_data[$topic_id]['last_poster_id'] = $row['poster_id'];
2116                      $topic_data[$topic_id]['last_post_subject'] = $row['post_subject'];
2117                      $topic_data[$topic_id]['last_post_time'] = $row['post_time'];
2118                      $topic_data[$topic_id]['last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2119                      $topic_data[$topic_id]['last_poster_colour'] = $row['user_colour'];
2120                  }
2121   
2122                  /**
2123                  * Event to modify the topic_data when syncing topics
2124                  *
2125                  * @event core.sync_modify_topic_data
2126                  * @var    array    topic_data        Array with the topics' data we are syncing
2127                  * @var    array    row                Array with some of the current user and post data
2128                  * @var    int        topic_id        The current topic_id of $row
2129                  * @since 3.3.5-RC1
2130                  */
2131                  $vars = [
2132                      'topic_data',
2133                      'row',
2134                      'topic_id',
2135                  ];
2136                  extract($phpbb_dispatcher->trigger_event('core.sync_modify_topic_data', compact($vars)));
2137              }
2138              $db->sql_freeresult($result);
2139   
2140              // Make sure shadow topics do link to existing topics
2141              if (count($moved_topics))
2142              {
2143                  $delete_topics = array();
2144   
2145                  $sql = 'SELECT t1.topic_id, t1.topic_moved_id
2146                      FROM ' . TOPICS_TABLE . ' t1
2147                      LEFT JOIN ' . TOPICS_TABLE . ' t2 ON (t2.topic_id = t1.topic_moved_id)
2148                      WHERE ' . $db->sql_in_set('t1.topic_id', $moved_topics) . '
2149                          AND t2.topic_id IS NULL';
2150                  $result = $db->sql_query($sql);
2151   
2152                  while ($row = $db->sql_fetchrow($result))
2153                  {
2154                      $delete_topics[] = $row['topic_id'];
2155                  }
2156                  $db->sql_freeresult($result);
2157   
2158                  if (count($delete_topics))
2159                  {
2160                      delete_topics('topic_id', $delete_topics, false);
2161                  }
2162                  unset($delete_topics);
2163   
2164                  // Make sure shadow topics having no last post data being updated (this only rarely happens...)
2165                  $sql = 'SELECT topic_id, topic_moved_id, topic_last_post_id, topic_first_post_id
2166                      FROM ' . TOPICS_TABLE . '
2167                      WHERE ' . $db->sql_in_set('topic_id', $moved_topics) . '
2168                          AND topic_last_post_time = 0';
2169                  $result = $db->sql_query($sql);
2170   
2171                  $shadow_topic_data = $post_ids = array();
2172                  while ($row = $db->sql_fetchrow($result))
2173                  {
2174                      $shadow_topic_data[$row['topic_moved_id']] = $row;
2175                      $post_ids[] = $row['topic_last_post_id'];
2176                      $post_ids[] = $row['topic_first_post_id'];
2177                  }
2178                  $db->sql_freeresult($result);
2179   
2180                  $sync_shadow_topics = array();
2181                  if (count($post_ids))
2182                  {
2183                      $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
2184                          FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
2185                          WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
2186                              AND u.user_id = p.poster_id';
2187                      $result = $db->sql_query($sql);
2188   
2189                      while ($row = $db->sql_fetchrow($result))
2190                      {
2191                          $topic_id = (int) $row['topic_id'];
2192   
2193                          // Ok, there should be a shadow topic. If there isn't, then there's something wrong with the db.
2194                          // However, there's not much we can do about it.
2195                          if (!empty($shadow_topic_data[$topic_id]))
2196                          {
2197                              if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_first_post_id'])
2198                              {
2199                                  $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
2200   
2201                                  if (!isset($sync_shadow_topics[$orig_topic_id]))
2202                                  {
2203                                      $sync_shadow_topics[$orig_topic_id] = array();
2204                                  }
2205   
2206                                  $sync_shadow_topics[$orig_topic_id]['topic_time'] = $row['post_time'];
2207                                  $sync_shadow_topics[$orig_topic_id]['topic_poster'] = $row['poster_id'];
2208                                  $sync_shadow_topics[$orig_topic_id]['topic_first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2209                                  $sync_shadow_topics[$orig_topic_id]['topic_first_poster_colour'] = $row['user_colour'];
2210                              }
2211   
2212                              if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_last_post_id'])
2213                              {
2214                                  $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
2215   
2216                                  if (!isset($sync_shadow_topics[$orig_topic_id]))
2217                                  {
2218                                      $sync_shadow_topics[$orig_topic_id] = array();
2219                                  }
2220   
2221                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_id'] = $row['poster_id'];
2222                                  $sync_shadow_topics[$orig_topic_id]['topic_last_post_subject'] = $row['post_subject'];
2223                                  $sync_shadow_topics[$orig_topic_id]['topic_last_post_time'] = $row['post_time'];
2224                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2225                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_colour'] = $row['user_colour'];
2226                              }
2227                          }
2228                      }
2229                      $db->sql_freeresult($result);
2230   
2231                      $shadow_topic_data = array();
2232   
2233                      // Update the information we collected
2234                      if (count($sync_shadow_topics))
2235                      {
2236                          foreach ($sync_shadow_topics as $sync_topic_id => $sql_ary)
2237                          {
2238                              $sql = 'UPDATE ' . TOPICS_TABLE . '
2239                                  SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2240                                  WHERE topic_id = ' . $sync_topic_id;
2241                              $db->sql_query($sql);
2242                          }
2243                      }
2244                  }
2245   
2246                  unset($sync_shadow_topics, $shadow_topic_data);
2247              }
2248   
2249              // These are fields that will be synchronised
2250              $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');
2251   
2252              // Add custom fieldnames
2253              $fieldnames = array_merge($fieldnames, $custom_fieldnames);
2254              unset($custom_fieldnames);
2255   
2256              if ($sync_extra)
2257              {
2258                  // This routine assumes that post_reported values are correct
2259                  // if they are not, use sync('post_reported') first
2260                  $sql = 'SELECT t.topic_id, p.post_id
2261                      FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
2262                      $where_sql_and p.topic_id = t.topic_id
2263                          AND p.post_reported = 1
2264                      GROUP BY t.topic_id, p.post_id";
2265                  $result = $db->sql_query($sql);
2266   
2267                  $fieldnames[] = 'reported';
2268                  while ($row = $db->sql_fetchrow($result))
2269                  {
2270                      $topic_data[intval($row['topic_id'])]['reported'] = 1;
2271                  }
2272                  $db->sql_freeresult($result);
2273   
2274                  // This routine assumes that post_attachment values are correct
2275                  // if they are not, use sync('post_attachment') first
2276                  $sql = 'SELECT t.topic_id, p.post_id
2277                      FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
2278                      $where_sql_and p.topic_id = t.topic_id
2279                          AND p.post_attachment = 1
2280                      GROUP BY t.topic_id, p.post_id";
2281                  $result = $db->sql_query($sql);
2282   
2283                  $fieldnames[] = 'attachment';
2284                  while ($row = $db->sql_fetchrow($result))
2285                  {
2286                      $topic_data[intval($row['topic_id'])]['attachment'] = 1;
2287                  }
2288                  $db->sql_freeresult($result);
2289              }
2290   
2291              foreach ($topic_data as $topic_id => $row)
2292              {
2293                  $sql_ary = array();
2294   
2295                  foreach ($fieldnames as $fieldname)
2296                  {
2297                      if (isset($row[$fieldname]) && isset($row['topic_' . $fieldname]) && $row['topic_' . $fieldname] != $row[$fieldname])
2298                      {
2299                          $sql_ary['topic_' . $fieldname] = $row[$fieldname];
2300                      }
2301                  }
2302   
2303                  if (count($sql_ary))
2304                  {
2305                      $sql = 'UPDATE ' . TOPICS_TABLE . '
2306                          SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2307                          WHERE topic_id = ' . $topic_id;
2308                      $db->sql_query($sql);
2309   
2310                      $resync_forums[$row['forum_id']] = $row['forum_id'];
2311                  }
2312              }
2313              unset($topic_data);
2314   
2315              $db->sql_transaction('commit');
2316   
2317              // if some topics have been resync'ed then resync parent forums
2318              // except when we're only syncing a range, we don't want to sync forums during
2319              // batch processing.
2320              if ($resync_parents && count($resync_forums) && $where_type != 'range')
2321              {
2322                  sync('forum', 'forum_id', array_values($resync_forums), true, true);
2323              }
2324              break;
2325      }
2326   
2327      return;
2328  }
2329   
2330  /**
2331  * Prune function
2332  */
2333  function prune($forum_id, $prune_mode, $prune_date, $prune_flags = 0, $auto_sync = true, $prune_limit = 0)
2334  {
2335      global $db, $phpbb_dispatcher;
2336   
2337      if (!is_array($forum_id))
2338      {
2339          $forum_id = array($forum_id);
2340      }
2341   
2342      if (!count($forum_id))
2343      {
2344          return;
2345      }
2346   
2347      $sql_and = '';
2348   
2349      if (!($prune_flags & FORUM_FLAG_PRUNE_ANNOUNCE))
2350      {
2351          $sql_and .= ' AND topic_type <> ' . POST_ANNOUNCE;
2352          $sql_and .= ' AND topic_type <> ' . POST_GLOBAL;
2353      }
2354   
2355      if (!($prune_flags & FORUM_FLAG_PRUNE_STICKY))
2356      {
2357          $sql_and .= ' AND topic_type <> ' . POST_STICKY;
2358      }
2359   
2360      if ($prune_mode == 'posted')
2361      {
2362          $sql_and .= " AND topic_last_post_time < $prune_date";
2363      }
2364   
2365      if ($prune_mode == 'viewed')
2366      {
2367          $sql_and .= " AND topic_last_view_time < $prune_date";
2368      }
2369   
2370      if ($prune_mode == 'shadow')
2371      {
2372          $sql_and .= ' AND topic_status = ' . ITEM_MOVED . " AND topic_last_post_time < $prune_date";
2373      }
2374   
2375      /**
2376      * Use this event to modify the SQL that selects topics to be pruned
2377      *
2378      * @event core.prune_sql
2379      * @var string    forum_id        The forum id
2380      * @var string    prune_mode        The prune mode
2381      * @var string    prune_date        The prune date
2382      * @var int        prune_flags        The prune flags
2383      * @var bool        auto_sync        Whether or not to perform auto sync
2384      * @var string    sql_and            SQL text appended to where clause
2385      * @var int        prune_limit        The prune limit
2386      * @since 3.1.3-RC1
2387      * @changed 3.1.10-RC1            Added prune_limit
2388      */
2389      $vars = array(
2390          'forum_id',
2391          'prune_mode',
2392          'prune_date',
2393          'prune_flags',
2394          'auto_sync',
2395          'sql_and',
2396          'prune_limit',
2397      );
2398      extract($phpbb_dispatcher->trigger_event('core.prune_sql', compact($vars)));
2399   
2400      $sql = 'SELECT topic_id
2401          FROM ' . TOPICS_TABLE . '
2402          WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
2403              AND poll_start = 0
2404              $sql_and";
2405      $result = $db->sql_query_limit($sql, $prune_limit);
2406   
2407      $topic_list = array();
2408      while ($row = $db->sql_fetchrow($result))
2409      {
2410          $topic_list[] = $row['topic_id'];
2411      }
2412      $db->sql_freeresult($result);
2413   
2414      if ($prune_flags & FORUM_FLAG_PRUNE_POLL)
2415      {
2416          $sql = 'SELECT topic_id
2417              FROM ' . TOPICS_TABLE . '
2418              WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
2419                  AND poll_start > 0
2420                  AND poll_last_vote < $prune_date
2421                  $sql_and";
2422          $result = $db->sql_query_limit($sql, $prune_limit);
2423   
2424          while ($row = $db->sql_fetchrow($result))
2425          {
2426              $topic_list[] = $row['topic_id'];
2427          }
2428          $db->sql_freeresult($result);
2429   
2430          $topic_list = array_unique($topic_list);
2431      }
2432   
2433      /**
2434       * Perform additional actions before topic deletion via pruning
2435       *
2436       * @event core.prune_delete_before
2437       * @var int[]    topic_list        The IDs of the topics to be deleted
2438       * @since 3.2.2-RC1
2439       */
2440      $vars = array('topic_list');
2441      extract($phpbb_dispatcher->trigger_event('core.prune_delete_before', compact($vars)));
2442   
2443      return delete_topics('topic_id', $topic_list, $auto_sync, false);
2444  }
2445   
2446  /**
2447  * Function auto_prune(), this function now relies on passed vars
2448  */
2449  function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_freq, $log_prune = true)
2450  {
2451      global $db, $user, $phpbb_log;
2452   
2453      $sql = 'SELECT forum_name
2454          FROM ' . FORUMS_TABLE . "
2455          WHERE forum_id = $forum_id";
2456      $result = $db->sql_query($sql, 3600);
2457      $row = $db->sql_fetchrow($result);
2458      $db->sql_freeresult($result);
2459   
2460      if ($row)
2461      {
2462          $prune_date = time() - ($prune_days * 86400);
2463          $next_prune = time() + ($prune_freq * 86400);
2464   
2465          $result = prune($forum_id, $prune_mode, $prune_date, $prune_flags, true, 300);
2466   
2467          if ($result['topics'] == 0 && $result['posts'] == 0)
2468          {
2469              $column = $prune_mode === 'shadow' ? 'prune_shadow_next' : 'prune_next';
2470   
2471              $sql = 'UPDATE ' . FORUMS_TABLE . "
2472                  SET $column = $next_prune
2473                  WHERE forum_id = $forum_id";
2474              $db->sql_query($sql);
2475          }
2476   
2477          if ($log_prune)
2478          {
2479              $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_AUTO_PRUNE', false, [$row['forum_name']]);
2480          }
2481      }
2482   
2483      return;
2484  }
2485   
2486  /**
2487  * Cache moderators. Called whenever permissions are changed
2488  * via admin_permissions. Changes of usernames and group names
2489  * must be carried through for the moderators table.
2490  *
2491  * @param \phpbb\db\driver\driver_interface $db Database connection
2492  * @param \phpbb\cache\driver\driver_interface $cache Cache driver
2493  * @param \phpbb\auth\auth $auth Authentication object
2494  * @return null
2495  */
2496  function phpbb_cache_moderators($db, $cache, $auth)
2497  {
2498      // Remove cached sql results
2499      $cache->destroy('sql', MODERATOR_CACHE_TABLE);
2500   
2501      // Clear table
2502      switch ($db->get_sql_layer())
2503      {
2504          case 'sqlite3':
2505              $db->sql_query('DELETE FROM ' . MODERATOR_CACHE_TABLE);
2506          break;
2507   
2508          default:
2509              $db->sql_query('TRUNCATE TABLE ' . MODERATOR_CACHE_TABLE);
2510          break;
2511      }
2512   
2513      // We add moderators who have forum moderator permissions without an explicit ACL_NEVER setting
2514      $sql_ary = array();
2515   
2516      // Grab all users having moderative options...
2517      $hold_ary = $auth->acl_user_raw_data(false, 'm_%', false);
2518   
2519      // Add users?
2520      if (!empty($hold_ary))
2521      {
2522          // At least one moderative option warrants a display
2523          $ug_id_ary = array_keys($hold_ary);
2524   
2525          // Remove users who have group memberships with DENY moderator permissions
2526          $sql_ary_deny = array(
2527              'SELECT'    => 'a.forum_id, ug.user_id, g.group_id',
2528   
2529              'FROM'        => array(
2530                  ACL_OPTIONS_TABLE    => 'o',
2531                  USER_GROUP_TABLE    => 'ug',
2532                  GROUPS_TABLE        => 'g',
2533                  ACL_GROUPS_TABLE    => 'a',
2534              ),
2535   
2536              'LEFT_JOIN'    => array(
2537                  array(
2538                      'FROM'    => array(ACL_ROLES_DATA_TABLE => 'r'),
2539                      'ON'    => 'a.auth_role_id = r.role_id',
2540                  ),
2541              ),
2542   
2543              'WHERE'        => '(o.auth_option_id = a.auth_option_id OR o.auth_option_id = r.auth_option_id)
2544                  AND ((a.auth_setting = ' . ACL_NEVER . ' AND r.auth_setting IS NULL)
2545                      OR r.auth_setting = ' . ACL_NEVER . ')
2546                  AND a.group_id = ug.group_id
2547                  AND g.group_id = ug.group_id
2548                  AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
2549                  AND ' . $db->sql_in_set('ug.user_id', $ug_id_ary) . "
2550                  AND ug.user_pending = 0
2551                  AND o.auth_option " . $db->sql_like_expression('m_' . $db->get_any_char()),
2552          );
2553          $sql = $db->sql_build_query('SELECT', $sql_ary_deny);
2554          $result = $db->sql_query($sql);
2555   
2556          while ($row = $db->sql_fetchrow($result))
2557          {
2558              if (isset($hold_ary[$row['user_id']][$row['forum_id']]))
2559              {
2560                  unset($hold_ary[$row['user_id']][$row['forum_id']]);
2561              }
2562          }
2563          $db->sql_freeresult($result);
2564   
2565          if (count($hold_ary))
2566          {
2567              // Get usernames...
2568              $sql = 'SELECT user_id, username
2569                  FROM ' . USERS_TABLE . '
2570                  WHERE ' . $db->sql_in_set('user_id', array_keys($hold_ary));
2571              $result = $db->sql_query($sql);
2572   
2573              $usernames_ary = array();
2574              while ($row = $db->sql_fetchrow($result))
2575              {
2576                  $usernames_ary[$row['user_id']] = $row['username'];
2577              }
2578              $db->sql_freeresult($result);
2579   
2580              foreach ($hold_ary as $user_id => $forum_id_ary)
2581              {
2582                  // Do not continue if user does not exist
2583                  if (!isset($usernames_ary[$user_id]))
2584                  {
2585                      continue;
2586                  }
2587   
2588                  foreach ($forum_id_ary as $forum_id => $auth_ary)
2589                  {
2590                      $sql_ary[] = array(
2591                          'forum_id'        => (int) $forum_id,
2592                          'user_id'        => (int) $user_id,
2593                          'username'        => (string) $usernames_ary[$user_id],
2594                          'group_id'        => 0,
2595                          'group_name'    => ''
2596                      );
2597                  }
2598              }
2599          }
2600      }
2601   
2602      // Now to the groups...
2603      $hold_ary = $auth->acl_group_raw_data(false, 'm_%', false);
2604   
2605      if (!empty($hold_ary))
2606      {
2607          $ug_id_ary = array_keys($hold_ary);
2608   
2609          // Make sure not hidden or special groups are involved...
2610          $sql = 'SELECT group_name, group_id, group_type
2611              FROM ' . GROUPS_TABLE . '
2612              WHERE ' . $db->sql_in_set('group_id', $ug_id_ary);
2613          $result = $db->sql_query($sql);
2614   
2615          $groupnames_ary = array();
2616          while ($row = $db->sql_fetchrow($result))
2617          {
2618              if ($row['group_type'] == GROUP_HIDDEN || $row['group_type'] == GROUP_SPECIAL)
2619              {
2620                  unset($hold_ary[$row['group_id']]);
2621              }
2622   
2623              $groupnames_ary[$row['group_id']] = $row['group_name'];
2624          }
2625          $db->sql_freeresult($result);
2626   
2627          foreach ($hold_ary as $group_id => $forum_id_ary)
2628          {
2629              // If there is no group, we do not assign it...
2630              if (!isset($groupnames_ary[$group_id]))
2631              {
2632                  continue;
2633              }
2634   
2635              foreach ($forum_id_ary as $forum_id => $auth_ary)
2636              {
2637                  $flag = false;
2638                  foreach ($auth_ary as $auth_option => $setting)
2639                  {
2640                      // Make sure at least one ACL_YES option is set...
2641                      if ($setting == ACL_YES)
2642                      {
2643                          $flag = true;
2644                          break;
2645                      }
2646                  }
2647   
2648                  if (!$flag)
2649                  {
2650                      continue;
2651                  }
2652   
2653                  $sql_ary[] = array(
2654                      'forum_id'        => (int) $forum_id,
2655                      'user_id'        => 0,
2656                      'username'        => '',
2657                      'group_id'        => (int) $group_id,
2658                      'group_name'    => (string) $groupnames_ary[$group_id]
2659                  );
2660              }
2661          }
2662      }
2663   
2664      $db->sql_multi_insert(MODERATOR_CACHE_TABLE, $sql_ary);
2665  }
2666   
2667  /**
2668  * View log
2669  *
2670  * @param    string    $mode            The mode defines which log_type is used and from which log the entry is retrieved
2671  * @param    array    &$log            The result array with the logs
2672  * @param    mixed    &$log_count        If $log_count is set to false, we will skip counting all entries in the database.
2673  *                                    Otherwise an integer with the number of total matching entries is returned.
2674  * @param    int        $limit            Limit the number of entries that are returned
2675  * @param    int        $offset            Offset when fetching the log entries, f.e. when paginating
2676  * @param    mixed    $forum_id        Restrict the log entries to the given forum_id (can also be an array of forum_ids)
2677  * @param    int        $topic_id        Restrict the log entries to the given topic_id
2678  * @param    int        $user_id        Restrict the log entries to the given user_id
2679  * @param    int        $limit_days        Only get log entries newer than the given timestamp
2680  * @param    string    $sort_by        SQL order option, e.g. 'l.log_time DESC'
2681  * @param    string    $keywords        Will only return log entries that have the keywords in log_operation or log_data
2682  *
2683  * @return    int                Returns the offset of the last valid page, if the specified offset was invalid (too high)
2684  */
2685  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 = '')
2686  {
2687      global $phpbb_log;
2688   
2689      $count_logs = ($log_count !== false);
2690   
2691      $log = $phpbb_log->get_logs($mode, $count_logs, $limit, $offset, $forum_id, $topic_id, $user_id, $limit_days, $sort_by, $keywords);
2692      $log_count = $phpbb_log->get_log_count();
2693   
2694      return $phpbb_log->get_valid_offset();
2695  }
2696   
2697  /**
2698  * Removes moderators and administrators from foe lists.
2699  *
2700  * @param \phpbb\db\driver\driver_interface $db Database connection
2701  * @param \phpbb\auth\auth $auth Authentication object
2702  * @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore
2703  * @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore
2704  * @return null
2705  */
2706  function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false)
2707  {
2708      // update foes for some user
2709      if (is_array($user_id) && count($user_id))
2710      {
2711          $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2712              WHERE ' . $db->sql_in_set('zebra_id', $user_id) . '
2713                  AND foe = 1';
2714          $db->sql_query($sql);
2715          return;
2716      }
2717   
2718      // update foes for some group
2719      if (is_array($group_id) && count($group_id))
2720      {
2721          // Grab group settings...
2722          $sql_ary = array(
2723              'SELECT'    => 'a.group_id',
2724   
2725              'FROM'        => array(
2726                  ACL_OPTIONS_TABLE    => 'ao',
2727                  ACL_GROUPS_TABLE    => 'a',
2728              ),
2729   
2730              'LEFT_JOIN'    => array(
2731                  array(
2732                      'FROM'    => array(ACL_ROLES_DATA_TABLE => 'r'),
2733                      'ON'    => 'a.auth_role_id = r.role_id',
2734                  ),
2735              ),
2736   
2737              'WHERE'        => '(ao.auth_option_id = a.auth_option_id OR ao.auth_option_id = r.auth_option_id)
2738                  AND ' . $db->sql_in_set('a.group_id', $group_id) . "
2739                  AND ao.auth_option IN ('a_', 'm_')",
2740   
2741              'GROUP_BY'    => 'a.group_id',
2742          );
2743          $sql = $db->sql_build_query('SELECT', $sql_ary);
2744          $result = $db->sql_query($sql);
2745   
2746          $groups = array();
2747          while ($row = $db->sql_fetchrow($result))
2748          {
2749              $groups[] = (int) $row['group_id'];
2750          }
2751          $db->sql_freeresult($result);
2752   
2753          if (!count($groups))
2754          {
2755              return;
2756          }
2757   
2758          switch ($db->get_sql_layer())
2759          {
2760              case 'mysqli':
2761                  $sql = 'DELETE z.*
2762                      FROM ' . ZEBRA_TABLE . ' z, ' . USER_GROUP_TABLE . ' ug
2763                      WHERE z.zebra_id = ug.user_id
2764                          AND z.foe = 1
2765                          AND ' . $db->sql_in_set('ug.group_id', $groups);
2766                  $db->sql_query($sql);
2767              break;
2768   
2769              default:
2770                  $sql = 'SELECT user_id
2771                      FROM ' . USER_GROUP_TABLE . '
2772                      WHERE ' . $db->sql_in_set('group_id', $groups);
2773                  $result = $db->sql_query($sql);
2774   
2775                  $users = array();
2776                  while ($row = $db->sql_fetchrow($result))
2777                  {
2778                      $users[] = (int) $row['user_id'];
2779                  }
2780                  $db->sql_freeresult($result);
2781   
2782                  if (count($users))
2783                  {
2784                      $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2785                          WHERE ' . $db->sql_in_set('zebra_id', $users) . '
2786                              AND foe = 1';
2787                      $db->sql_query($sql);
2788                  }
2789              break;
2790          }
2791   
2792          return;
2793      }
2794   
2795      // update foes for everyone
2796      $perms = array();
2797      foreach ($auth->acl_get_list(false, array('a_', 'm_'), false) as $forum_id => $forum_ary)
2798      {
2799          foreach ($forum_ary as $auth_option => $user_ary)
2800          {
2801              $perms = array_merge($perms, $user_ary);
2802          }
2803      }
2804   
2805      if (count($perms))
2806      {
2807          $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2808              WHERE ' . $db->sql_in_set('zebra_id', array_unique($perms)) . '
2809                  AND foe = 1';
2810          $db->sql_query($sql);
2811      }
2812      unset($perms);
2813  }
2814   
2815  /**
2816  * Lists inactive users
2817  */
2818  function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC')
2819  {
2820      global $db, $user;
2821   
2822      $sql = 'SELECT COUNT(user_id) AS user_count
2823          FROM ' . USERS_TABLE . '
2824          WHERE user_type = ' . USER_INACTIVE .
2825          (($limit_days) ? " AND user_inactive_time >= $limit_days" : '');
2826      $result = $db->sql_query($sql);
2827      $user_count = (int) $db->sql_fetchfield('user_count');
2828      $db->sql_freeresult($result);
2829   
2830      if ($user_count == 0)
2831      {
2832          // Save the queries, because there are no users to display
2833          return 0;
2834      }
2835   
2836      if ($offset >= $user_count)
2837      {
2838          $offset = ($offset - $limit < 0) ? 0 : $offset - $limit;
2839      }
2840   
2841      $sql = 'SELECT *
2842          FROM ' . USERS_TABLE . '
2843          WHERE user_type = ' . USER_INACTIVE .
2844          (($limit_days) ? " AND user_inactive_time >= $limit_days" : '') . "
2845          ORDER BY $sort_by";
2846      $result = $db->sql_query_limit($sql, $limit, $offset);
2847   
2848      while ($row = $db->sql_fetchrow($result))
2849      {
2850          $row['inactive_reason'] = $user->lang['INACTIVE_REASON_UNKNOWN'];
2851          switch ($row['user_inactive_reason'])
2852          {
2853              case INACTIVE_REGISTER:
2854                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REGISTER'];
2855              break;
2856   
2857              case INACTIVE_PROFILE:
2858                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_PROFILE'];
2859              break;
2860   
2861              case INACTIVE_MANUAL:
2862                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_MANUAL'];
2863              break;
2864   
2865              case INACTIVE_REMIND:
2866                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REMIND'];
2867              break;
2868          }
2869   
2870          $users[] = $row;
2871      }
2872      $db->sql_freeresult($result);
2873   
2874      return $offset;
2875  }
2876   
2877  /**
2878  * Lists warned users
2879  */
2880  function view_warned_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_warnings DESC')
2881  {
2882      global $db;
2883   
2884      $sql = 'SELECT user_id, username, user_colour, user_warnings, user_last_warning
2885          FROM ' . USERS_TABLE . '
2886          WHERE user_warnings > 0
2887          ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '') . "
2888          ORDER BY $sort_by";
2889      $result = $db->sql_query_limit($sql, $limit, $offset);
2890      $users = $db->sql_fetchrowset($result);
2891      $db->sql_freeresult($result);
2892   
2893      $sql = 'SELECT count(user_id) AS user_count
2894          FROM ' . USERS_TABLE . '
2895          WHERE user_warnings > 0
2896          ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '');
2897      $result = $db->sql_query($sql);
2898      $user_count = (int) $db->sql_fetchfield('user_count');
2899      $db->sql_freeresult($result);
2900   
2901      return;
2902  }
2903   
2904  /**
2905  * Get database size
2906  */
2907  function get_database_size()
2908  {
2909      global $db, $user;
2910   
2911      $database_size = false;
2912   
2913      switch ($db->get_sql_layer())
2914      {
2915          case 'mysqli':
2916              $mysql_engine    = ['MyISAM', 'InnoDB', 'Aria'];
2917              $db_name        = $db->get_db_name();
2918              $database_size    = 0;
2919   
2920              $sql = 'SHOW TABLE STATUS
2921                  FROM ' . $db->sql_quote($db_name);
2922              $result = $db->sql_query($sql, 7200);
2923   
2924              while ($row = $db->sql_fetchrow($result))
2925              {
2926                  if (isset($row['Engine']) && in_array($row['Engine'], $mysql_engine))
2927                  {
2928                      $database_size += $row['Data_length'] + $row['Index_length'];
2929                  }
2930              }
2931   
2932              $db->sql_freeresult($result);
2933   
2934              $database_size = $database_size ? $database_size : false;
2935   
2936          break;
2937   
2938          case 'sqlite3':
2939              global $dbhost;
2940   
2941              if (file_exists($dbhost))
2942              {
2943                  $database_size = filesize($dbhost);
2944              }
2945   
2946          break;
2947   
2948          case 'mssql_odbc':
2949          case 'mssqlnative':
2950              $sql = 'SELECT @@VERSION AS mssql_version';
2951              $result = $db->sql_query($sql);
2952              $row = $db->sql_fetchrow($result);
2953              $db->sql_freeresult($result);
2954   
2955              $sql = 'SELECT ((SUM(size) * 8.0) * 1024.0) as dbsize
2956                  FROM sysfiles';
2957   
2958              if ($row)
2959              {
2960                  // Azure stats are stored elsewhere
2961                  if (strpos($row['mssql_version'], 'SQL Azure') !== false)
2962                  {
2963                      $sql = 'SELECT ((SUM(reserved_page_count) * 8.0) * 1024.0) as dbsize
2964                      FROM sys.dm_db_partition_stats';
2965                  }
2966              }
2967   
2968              $result = $db->sql_query($sql, 7200);
2969              $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
2970              $db->sql_freeresult($result);
2971          break;
2972   
2973          case 'postgres':
2974              $database = $db->get_db_name();
2975   
2976              if (strpos($database, '.') !== false)
2977              {
2978                  $database = explode('.', $database)[0];
2979              }
2980   
2981              $sql = "SELECT pg_database_size('" . $database . "') AS dbsize";
2982              $result = $db->sql_query($sql, 7200);
2983              $row = $db->sql_fetchrow($result);
2984              $database_size = !empty($row['dbsize']) ? $row['dbsize'] : false;
2985              $db->sql_freeresult($result);
2986          break;
2987   
2988          case 'oracle':
2989              $sql = 'SELECT SUM(bytes) as dbsize
2990                  FROM user_segments';
2991              $result = $db->sql_query($sql, 7200);
2992              $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
2993              $db->sql_freeresult($result);
2994          break;
2995      }
2996   
2997      $database_size = ($database_size !== false) ? get_formatted_filesize($database_size) : $user->lang['NOT_AVAILABLE'];
2998   
2999      return $database_size;
3000  }
3001   
3002  /*
3003  * Tidy Warnings
3004  * Remove all warnings which have now expired from the database
3005  * The duration of a warning can be defined by the administrator
3006  * This only removes the warning and reduces the associated count,
3007  * it does not remove the user note recording the contents of the warning
3008  */
3009  function tidy_warnings()
3010  {
3011      global $db, $config;
3012   
3013      $expire_date = time() - ($config['warnings_expire_days'] * 86400);
3014      $warning_list = $user_list = array();
3015   
3016      $sql = 'SELECT * FROM ' . WARNINGS_TABLE . "
3017          WHERE warning_time < $expire_date";
3018      $result = $db->sql_query($sql);
3019   
3020      while ($row = $db->sql_fetchrow($result))
3021      {
3022          $warning_list[] = $row['warning_id'];
3023          $user_list[$row['user_id']] = isset($user_list[$row['user_id']]) ? ++$user_list[$row['user_id']] : 1;
3024      }
3025      $db->sql_freeresult($result);
3026   
3027      if (count($warning_list))
3028      {
3029          $db->sql_transaction('begin');
3030   
3031          $sql = 'DELETE FROM ' . WARNINGS_TABLE . '
3032              WHERE ' . $db->sql_in_set('warning_id', $warning_list);
3033          $db->sql_query($sql);
3034   
3035          foreach ($user_list as $user_id => $value)
3036          {
3037              $sql = 'UPDATE ' . USERS_TABLE . " SET user_warnings = user_warnings - $value
3038                  WHERE user_id = $user_id";
3039              $db->sql_query($sql);
3040          }
3041   
3042          $db->sql_transaction('commit');
3043      }
3044   
3045      $config->set('warnings_last_gc', time(), false);
3046  }
3047   
3048  /**
3049  * Tidy database, doing some maintanance tasks
3050  */
3051  function tidy_database()
3052  {
3053      global $config, $db;
3054   
3055      // Here we check permission consistency
3056   
3057      // Sometimes, it can happen permission tables having forums listed which do not exist
3058      $sql = 'SELECT forum_id
3059          FROM ' . FORUMS_TABLE;
3060      $result = $db->sql_query($sql);
3061   
3062      $forum_ids = array(0);
3063      while ($row = $db->sql_fetchrow($result))
3064      {
3065          $forum_ids[] = $row['forum_id'];
3066      }
3067      $db->sql_freeresult($result);
3068   
3069      $db->sql_transaction('begin');
3070   
3071      // Delete those rows from the acl tables not having listed the forums above
3072      $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
3073          WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
3074      $db->sql_query($sql);
3075   
3076      $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
3077          WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
3078      $db->sql_query($sql);
3079   
3080      $db->sql_transaction('commit');
3081   
3082      $config->set('database_last_gc', time(), false);
3083  }
3084   
3085  /**
3086  * Add permission language - this will make sure custom files will be included
3087  */
3088  function add_permission_language()
3089  {
3090      global $user, $phpEx, $phpbb_extension_manager;
3091   
3092      // add permission language files from extensions
3093      $finder = $phpbb_extension_manager->get_finder();
3094   
3095      $lang_files = $finder
3096          ->prefix('permissions_')
3097          ->suffix(".$phpEx")
3098          ->core_path('language/')
3099          ->extension_directory('/language')
3100          ->find();
3101   
3102      foreach ($lang_files as $lang_file => $ext_name)
3103      {
3104          if ($ext_name === '/')
3105          {
3106              $user->add_lang($lang_file);
3107          }
3108          else
3109          {
3110              $user->add_lang_ext($ext_name, $lang_file);
3111          }
3112      }
3113  }
3114   
3115  /**
3116   * Enables a particular flag in a bitfield column of a given table.
3117   *
3118   * @param string    $table_name        The table to update
3119   * @param string    $column_name    The column containing a bitfield to update
3120   * @param int        $flag            The binary flag which is OR-ed with the current column value
3121   * @param string    $sql_more        This string is attached to the sql query generated to update the table.
3122   *
3123   * @return null
3124   */
3125  function enable_bitfield_column_flag($table_name, $column_name, $flag, $sql_more = '')
3126  {
3127      global $db;
3128   
3129      $sql = 'UPDATE ' . $table_name . '
3130          SET ' . $column_name . ' = ' . $db->sql_bit_or($column_name, $flag) . '
3131          ' . $sql_more;
3132      $db->sql_query($sql);
3133  }
3134   
3135  function display_ban_end_options()
3136  {
3137      global $user, $template;
3138   
3139      // Ban length options
3140      $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; ');
3141   
3142      $ban_end_options = '';
3143      foreach ($ban_end_text as $length => $text)
3144      {
3145          $ban_end_options .= '<option value="' . $length . '">' . $text . '</option>';
3146      }
3147   
3148      $template->assign_vars(array(
3149          'S_BAN_END_OPTIONS'    => $ban_end_options
3150      ));
3151  }
3152   
3153  /**
3154  * Display ban options
3155  */
3156  function display_ban_options($mode)
3157  {
3158      global $user, $db, $template;
3159   
3160      switch ($mode)
3161      {
3162          case 'user':
3163   
3164              $field = 'username';
3165   
3166              $sql = 'SELECT b.*, u.user_id, u.username, u.username_clean
3167                  FROM ' . BANLIST_TABLE . ' b, ' . USERS_TABLE . ' u
3168                  WHERE (b.ban_end >= ' . time() . '
3169                          OR b.ban_end = 0)
3170                      AND u.user_id = b.ban_userid
3171                  ORDER BY u.username_clean ASC';
3172          break;
3173   
3174          case 'ip':
3175   
3176              $field = 'ban_ip';
3177   
3178              $sql = 'SELECT *
3179                  FROM ' . BANLIST_TABLE . '
3180                  WHERE (ban_end >= ' . time() . "
3181                          OR ban_end = 0)
3182                      AND ban_ip <> ''
3183                  ORDER BY ban_ip";
3184          break;
3185   
3186          case 'email':
3187   
3188              $field = 'ban_email';
3189   
3190              $sql = 'SELECT *
3191                  FROM ' . BANLIST_TABLE . '
3192                  WHERE (ban_end >= ' . time() . "
3193                          OR ban_end = 0)
3194                      AND ban_email <> ''
3195                  ORDER BY ban_email";
3196          break;
3197      }
3198      $result = $db->sql_query($sql);
3199   
3200      $banned_options = $excluded_options = array();
3201      while ($row = $db->sql_fetchrow($result))
3202      {
3203          $option = '<option value="' . $row['ban_id'] . '">' . $row[$field] . '</option>';
3204   
3205          if ($row['ban_exclude'])
3206          {
3207              $excluded_options[] = $option;
3208          }
3209          else
3210          {
3211              $banned_options[] = $option;
3212          }
3213   
3214          $time_length = ($row['ban_end']) ? ($row['ban_end'] - $row['ban_start']) / 60 : 0;
3215   
3216          if ($time_length == 0)
3217          {
3218              // Banned permanently
3219              $ban_length = $user->lang['PERMANENT'];
3220          }
3221          else if (isset($ban_end_text[$time_length]))
3222          {
3223              // Banned for a given duration
3224              $ban_length = $user->lang('BANNED_UNTIL_DURATION', $ban_end_text[$time_length], $user->format_date($row['ban_end'], false, true));
3225          }
3226          else
3227          {
3228              // Banned until given date
3229              $ban_length = $user->lang('BANNED_UNTIL_DATE', $user->format_date($row['ban_end'], false, true));
3230          }
3231   
3232          $template->assign_block_vars('bans', array(
3233              'BAN_ID'        => (int) $row['ban_id'],
3234              'LENGTH'        => $ban_length,
3235              'A_LENGTH'        => addslashes($ban_length),
3236              'REASON'        => $row['ban_reason'],
3237              'A_REASON'        => addslashes($row['ban_reason']),
3238              'GIVE_REASON'    => $row['ban_give_reason'],
3239              'A_GIVE_REASON'    => addslashes($row['ban_give_reason']),
3240          ));
3241      }
3242      $db->sql_freeresult($result);
3243   
3244      $options = '';
3245      if ($excluded_options)
3246      {
3247          $options .= '<optgroup label="' . $user->lang['OPTIONS_EXCLUDED'] . '">';
3248          $options .= implode('', $excluded_options);
3249          $options .= '</optgroup>';
3250      }
3251   
3252      if ($banned_options)
3253      {
3254          $options .= '<optgroup label="' . $user->lang['OPTIONS_BANNED'] . '">';
3255          $options .= implode('', $banned_options);
3256          $options .= '</optgroup>';
3257      }
3258   
3259      $template->assign_vars(array(
3260          'S_BANNED_OPTIONS'    => ($banned_options || $excluded_options) ? true : false,
3261          'BANNED_OPTIONS'    => $options,
3262      ));
3263  }
3264