Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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: 09.10.2024, 12:51 - Dateigröße: 87.09 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 = sizeof($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 && sizeof($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')
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      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
0547          WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids) . '
0548              AND forum_id = ' . $forum_id;
0549      $db->sql_query($sql);
0550   
0551      if ($auto_sync)
0552      {
0553          $sql = 'SELECT DISTINCT forum_id
0554              FROM ' . TOPICS_TABLE . '
0555              WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
0556          $result = $db->sql_query($sql);
0557   
0558          while ($row = $db->sql_fetchrow($result))
0559          {
0560              $forum_ids[] = $row['forum_id'];
0561          }
0562          $db->sql_freeresult($result);
0563      }
0564   
0565      $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE);
0566   
0567      /**
0568       * Perform additional actions before topics move
0569       *
0570       * @event core.move_topics_before_query
0571       * @var    array    table_ary    Array of tables from which forum_id will be updated for all rows that hold the moved topics
0572       * @var    array    topic_ids    Array of the moved topic ids
0573       * @var    string    forum_id    The forum id from where the topics are moved
0574       * @var    array    forum_ids    Array of the forums where the topics are moving (includes also forum_id)
0575       * @var bool    auto_sync    Whether or not to perform auto sync
0576       * @since 3.1.5-RC1
0577       */
0578      $vars = array(
0579              'table_ary',
0580              'topic_ids',
0581              'forum_id',
0582              'forum_ids',
0583              'auto_sync',
0584      );
0585      extract($phpbb_dispatcher->trigger_event('core.move_topics_before_query', compact($vars)));
0586   
0587      foreach ($table_ary as $table)
0588      {
0589          $sql = "UPDATE $table
0590              SET forum_id = $forum_id
0591              WHERE " . $db->sql_in_set('topic_id', $topic_ids);
0592          $db->sql_query($sql);
0593      }
0594      unset($table_ary);
0595   
0596      if ($auto_sync)
0597      {
0598          sync('forum', 'forum_id', $forum_ids, true, true);
0599          unset($forum_ids);
0600      }
0601  }
0602   
0603  /**
0604  * Move post(s)
0605  */
0606  function move_posts($post_ids, $topic_id, $auto_sync = true)
0607  {
0608      global $db, $phpbb_dispatcher;
0609   
0610      if (!is_array($post_ids))
0611      {
0612          $post_ids = array($post_ids);
0613      }
0614   
0615      $forum_ids = array();
0616      $topic_ids = array($topic_id);
0617   
0618      $sql = 'SELECT DISTINCT topic_id, forum_id
0619          FROM ' . POSTS_TABLE . '
0620          WHERE ' . $db->sql_in_set('post_id', $post_ids);
0621      $result = $db->sql_query($sql);
0622   
0623      while ($row = $db->sql_fetchrow($result))
0624      {
0625          $forum_ids[] = (int) $row['forum_id'];
0626          $topic_ids[] = (int) $row['topic_id'];
0627      }
0628      $db->sql_freeresult($result);
0629   
0630      $sql = 'SELECT forum_id
0631          FROM ' . TOPICS_TABLE . '
0632          WHERE topic_id = ' . $topic_id;
0633      $result = $db->sql_query($sql);
0634      $forum_row = $db->sql_fetchrow($result);
0635      $db->sql_freeresult($result);
0636   
0637      if (!$forum_row)
0638      {
0639          trigger_error('NO_TOPIC');
0640      }
0641   
0642      /**
0643       * Perform additional actions before moving posts
0644       *
0645       * @event core.move_posts_before
0646       * @var    array    post_ids    Array of post ids to move
0647       * @var    int        topic_id    The topic id the posts are moved to
0648       * @var    bool    auto_sync    Whether or not to perform auto sync
0649       * @var    array    forum_ids    Array of the forum ids the posts are moved from
0650       * @var    array    topic_ids    Array of the topic ids the posts are moved from
0651       * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0652       * @since 3.1.7-RC1
0653       */
0654      $vars = array(
0655              'post_ids',
0656              'topic_id',
0657              'auto_sync',
0658              'forum_ids',
0659              'topic_ids',
0660              'forum_row',
0661      );
0662      extract($phpbb_dispatcher->trigger_event('core.move_posts_before', compact($vars)));
0663   
0664      $sql = 'UPDATE ' . POSTS_TABLE . '
0665          SET forum_id = ' . (int) $forum_row['forum_id'] . ", topic_id = $topic_id
0666          WHERE " . $db->sql_in_set('post_id', $post_ids);
0667      $db->sql_query($sql);
0668   
0669      $sql = 'UPDATE ' . ATTACHMENTS_TABLE . "
0670          SET topic_id = $topic_id, in_message = 0
0671          WHERE " . $db->sql_in_set('post_msg_id', $post_ids);
0672      $db->sql_query($sql);
0673   
0674      /**
0675       * Perform additional actions after moving posts
0676       *
0677       * @event core.move_posts_after
0678       * @var    array    post_ids    Array of the moved post ids
0679       * @var    int        topic_id    The topic id the posts are moved to
0680       * @var    bool    auto_sync    Whether or not to perform auto sync
0681       * @var    array    forum_ids    Array of the forum ids the posts are moved from
0682       * @var    array    topic_ids    Array of the topic ids the posts are moved from
0683       * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0684       * @since 3.1.7-RC1
0685       */
0686      $vars = array(
0687              'post_ids',
0688              'topic_id',
0689              'auto_sync',
0690              'forum_ids',
0691              'topic_ids',
0692              'forum_row',
0693      );
0694      extract($phpbb_dispatcher->trigger_event('core.move_posts_after', compact($vars)));
0695   
0696      if ($auto_sync)
0697      {
0698          $forum_ids[] = (int) $forum_row['forum_id'];
0699   
0700          sync('topic_reported', 'topic_id', $topic_ids);
0701          sync('topic_attachment', 'topic_id', $topic_ids);
0702          sync('topic', 'topic_id', $topic_ids, true);
0703          sync('forum', 'forum_id', $forum_ids, true, true);
0704   
0705          /**
0706           * Perform additional actions after move post sync
0707           *
0708           * @event core.move_posts_sync_after
0709           * @var    array    post_ids    Array of the moved post ids
0710           * @var    int        topic_id    The topic id the posts are moved to
0711           * @var    bool    auto_sync    Whether or not to perform auto sync
0712           * @var    array    forum_ids    Array of the forum ids the posts are moved from
0713           * @var    array    topic_ids    Array of the topic ids the posts are moved from
0714           * @var    array    forum_row    Array with the forum id of the topic the posts are moved to
0715           * @since 3.1.11-RC1
0716           */
0717          $vars = array(
0718              'post_ids',
0719              'topic_id',
0720              'auto_sync',
0721              'forum_ids',
0722              'topic_ids',
0723              'forum_row',
0724          );
0725          extract($phpbb_dispatcher->trigger_event('core.move_posts_sync_after', compact($vars)));
0726      }
0727   
0728      // Update posted information
0729      update_posted_info($topic_ids);
0730  }
0731   
0732  /**
0733  * Remove topic(s)
0734  */
0735  function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true)
0736  {
0737      global $db, $config, $phpbb_container, $phpbb_dispatcher;
0738   
0739      $approved_topics = 0;
0740      $forum_ids = $topic_ids = array();
0741   
0742      if ($where_type === 'range')
0743      {
0744          $where_clause = $where_ids;
0745      }
0746      else
0747      {
0748          $where_ids = (is_array($where_ids)) ? array_unique($where_ids) : array($where_ids);
0749   
0750          if (!sizeof($where_ids))
0751          {
0752              return array('topics' => 0, 'posts' => 0);
0753          }
0754   
0755          $where_clause = $db->sql_in_set($where_type, $where_ids);
0756      }
0757   
0758      // Making sure that delete_posts does not call delete_topics again...
0759      $return = array(
0760          'posts' => ($call_delete_posts) ? delete_posts($where_type, $where_ids, false, true, $post_count_sync, false) : 0,
0761      );
0762   
0763      $sql = 'SELECT topic_id, forum_id, topic_visibility, topic_moved_id
0764          FROM ' . TOPICS_TABLE . '
0765          WHERE ' . $where_clause;
0766      $result = $db->sql_query($sql);
0767   
0768      while ($row = $db->sql_fetchrow($result))
0769      {
0770          $forum_ids[] = $row['forum_id'];
0771          $topic_ids[] = $row['topic_id'];
0772   
0773          if ($row['topic_visibility'] == ITEM_APPROVED && !$row['topic_moved_id'])
0774          {
0775              $approved_topics++;
0776          }
0777      }
0778      $db->sql_freeresult($result);
0779   
0780      $return['topics'] = sizeof($topic_ids);
0781   
0782      if (!sizeof($topic_ids))
0783      {
0784          return $return;
0785      }
0786   
0787      $db->sql_transaction('begin');
0788   
0789      $table_ary = array(BOOKMARKS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, POLL_VOTES_TABLE, POLL_OPTIONS_TABLE, TOPICS_WATCH_TABLE, TOPICS_TABLE);
0790   
0791      /**
0792       * Perform additional actions before topic(s) deletion
0793       *
0794       * @event core.delete_topics_before_query
0795       * @var    array    table_ary    Array of tables from which all rows will be deleted that hold a topic_id occuring in topic_ids
0796       * @var    array    topic_ids    Array of topic ids to delete
0797       * @since 3.1.4-RC1
0798       */
0799      $vars = array(
0800              'table_ary',
0801              'topic_ids',
0802      );
0803      extract($phpbb_dispatcher->trigger_event('core.delete_topics_before_query', compact($vars)));
0804   
0805      foreach ($table_ary as $table)
0806      {
0807          $sql = "DELETE FROM $table
0808              WHERE " . $db->sql_in_set('topic_id', $topic_ids);
0809          $db->sql_query($sql);
0810      }
0811      unset($table_ary);
0812   
0813      /**
0814       * Perform additional actions after topic(s) deletion
0815       *
0816       * @event core.delete_topics_after_query
0817       * @var    array    topic_ids    Array of topic ids that were deleted
0818       * @since 3.1.4-RC1
0819       */
0820      $vars = array(
0821              'topic_ids',
0822      );
0823      extract($phpbb_dispatcher->trigger_event('core.delete_topics_after_query', compact($vars)));
0824   
0825      $moved_topic_ids = array();
0826   
0827      // update the other forums
0828      $sql = 'SELECT topic_id, forum_id
0829          FROM ' . TOPICS_TABLE . '
0830          WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids);
0831      $result = $db->sql_query($sql);
0832   
0833      while ($row = $db->sql_fetchrow($result))
0834      {
0835          $forum_ids[] = $row['forum_id'];
0836          $moved_topic_ids[] = $row['topic_id'];
0837      }
0838      $db->sql_freeresult($result);
0839   
0840      if (sizeof($moved_topic_ids))
0841      {
0842          $sql = 'DELETE FROM ' . TOPICS_TABLE . '
0843              WHERE ' . $db->sql_in_set('topic_id', $moved_topic_ids);
0844          $db->sql_query($sql);
0845      }
0846   
0847      $db->sql_transaction('commit');
0848   
0849      if ($auto_sync)
0850      {
0851          sync('forum', 'forum_id', array_unique($forum_ids), true, true);
0852          sync('topic_reported', $where_type, $where_ids);
0853      }
0854   
0855      if ($approved_topics)
0856      {
0857          $config->increment('num_topics', $approved_topics * (-1), false);
0858      }
0859   
0860      /* @var $phpbb_notifications \phpbb\notification\manager */
0861      $phpbb_notifications = $phpbb_container->get('notification_manager');
0862   
0863      $phpbb_notifications->delete_notifications(array(
0864          'notification.type.topic',
0865          'notification.type.approve_topic',
0866          'notification.type.topic_in_queue',
0867      ), $topic_ids);
0868   
0869      return $return;
0870  }
0871   
0872  /**
0873  * Remove post(s)
0874  */
0875  function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true)
0876  {
0877      global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container, $phpbb_dispatcher;
0878   
0879      // Notifications types to delete
0880      $delete_notifications_types = array(
0881          'notification.type.quote',
0882          'notification.type.approve_post',
0883          'notification.type.post_in_queue',
0884          'notification.type.report_post',
0885      );
0886   
0887      /**
0888      * Perform additional actions before post(s) deletion
0889      *
0890      * @event core.delete_posts_before
0891      * @var    string    where_type                    Variable containing posts deletion mode
0892      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
0893      * @var    bool    auto_sync                    Flag indicating if topics/forums should be synchronized
0894      * @var    bool    posted_sync                    Flag indicating if topics_posted table should be resynchronized
0895      * @var    bool    post_count_sync                Flag indicating if posts count should be resynchronized
0896      * @var    bool    call_delete_topics            Flag indicating if topics having no posts should be deleted
0897      * @var    array    delete_notifications_types    Array with notifications types to delete
0898      * @since 3.1.0-a4
0899      */
0900      $vars = array(
0901          'where_type',
0902          'where_ids',
0903          'auto_sync',
0904          'posted_sync',
0905          'post_count_sync',
0906          'call_delete_topics',
0907          'delete_notifications_types',
0908      );
0909      extract($phpbb_dispatcher->trigger_event('core.delete_posts_before', compact($vars)));
0910   
0911      if ($where_type === 'range')
0912      {
0913          $where_clause = $where_ids;
0914      }
0915      else
0916      {
0917          if (is_array($where_ids))
0918          {
0919              $where_ids = array_unique($where_ids);
0920          }
0921          else
0922          {
0923              $where_ids = array($where_ids);
0924          }
0925   
0926          if (!sizeof($where_ids))
0927          {
0928              return false;
0929          }
0930   
0931          $where_ids = array_map('intval', $where_ids);
0932   
0933  /*        Possible code for splitting post deletion
0934          if (sizeof($where_ids) >= 1001)
0935          {
0936              // Split into chunks of 1000
0937              $chunks = array_chunk($where_ids, 1000);
0938   
0939              foreach ($chunks as $_where_ids)
0940              {
0941                  delete_posts($where_type, $_where_ids, $auto_sync, $posted_sync, $post_count_sync, $call_delete_topics);
0942              }
0943   
0944              return;
0945          }*/
0946   
0947          $where_clause = $db->sql_in_set($where_type, $where_ids);
0948      }
0949   
0950      $approved_posts = 0;
0951      $post_ids = $topic_ids = $forum_ids = $post_counts = $remove_topics = array();
0952   
0953      $sql = 'SELECT post_id, poster_id, post_visibility, post_postcount, topic_id, forum_id
0954          FROM ' . POSTS_TABLE . '
0955          WHERE ' . $where_clause;
0956      $result = $db->sql_query($sql);
0957   
0958      while ($row = $db->sql_fetchrow($result))
0959      {
0960          $post_ids[] = (int) $row['post_id'];
0961          $poster_ids[] = (int) $row['poster_id'];
0962          $topic_ids[] = (int) $row['topic_id'];
0963          $forum_ids[] = (int) $row['forum_id'];
0964   
0965          if ($row['post_postcount'] && $post_count_sync && $row['post_visibility'] == ITEM_APPROVED)
0966          {
0967              $post_counts[$row['poster_id']] = (!empty($post_counts[$row['poster_id']])) ? $post_counts[$row['poster_id']] + 1 : 1;
0968          }
0969   
0970          if ($row['post_visibility'] == ITEM_APPROVED)
0971          {
0972              $approved_posts++;
0973          }
0974      }
0975      $db->sql_freeresult($result);
0976   
0977      if (!sizeof($post_ids))
0978      {
0979          return false;
0980      }
0981   
0982      $db->sql_transaction('begin');
0983   
0984      $table_ary = array(POSTS_TABLE, REPORTS_TABLE);
0985   
0986      /**
0987      * Perform additional actions during post(s) deletion before running the queries
0988      *
0989      * @event core.delete_posts_in_transaction_before
0990      * @var    array    post_ids                    Array with deleted posts' ids
0991      * @var    array    poster_ids                    Array with deleted posts' author ids
0992      * @var    array    topic_ids                    Array with deleted posts' topic ids
0993      * @var    array    forum_ids                    Array with deleted posts' forum ids
0994      * @var    string    where_type                    Variable containing posts deletion mode
0995      * @var    mixed    where_ids                    Array or comma separated list of post ids to delete
0996      * @var    array    delete_notifications_types    Array with notifications types to delete
0997      * @var    array    table_ary                    Array with table names to delete data from
0998      * @since 3.1.7-RC1
0999      */
1000      $vars = array(
1001          'post_ids',
1002          'poster_ids',
1003          'topic_ids',
1004          'forum_ids',
1005          'where_type',
1006          'where_ids',
1007          'delete_notifications_types',
1008          'table_ary',
1009      );
1010      extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction_before', compact($vars)));
1011   
1012      foreach ($table_ary as $table)
1013      {
1014          $sql = "DELETE FROM $table
1015              WHERE " . $db->sql_in_set('post_id', $post_ids);
1016          $db->sql_query($sql);
1017      }
1018      unset($table_ary);
1019   
1020      // Adjust users post counts
1021      if (sizeof($post_counts) && $post_count_sync)
1022      {
1023          foreach ($post_counts as $poster_id => $substract)
1024          {
1025              $sql = 'UPDATE ' . USERS_TABLE . '
1026                  SET user_posts = 0
1027                  WHERE user_id = ' . $poster_id . '
1028                  AND user_posts < ' . $substract;
1029              $db->sql_query($sql);
1030   
1031              $sql = 'UPDATE ' . USERS_TABLE . '
1032                  SET user_posts = user_posts - ' . $substract . '
1033                  WHERE user_id = ' . $poster_id . '
1034                  AND user_posts >= ' . $substract;
1035              $db->sql_query($sql);
1036          }
1037      }
1038   
1039      // Remove topics now having no posts?
1040      if (sizeof($topic_ids))
1041      {
1042          $sql = 'SELECT topic_id
1043              FROM ' . POSTS_TABLE . '
1044              WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
1045              GROUP BY topic_id';
1046          $result = $db->sql_query($sql);
1047   
1048          while ($row = $db->sql_fetchrow($result))
1049          {
1050              $remove_topics[] = $row['topic_id'];
1051          }
1052          $db->sql_freeresult($result);
1053   
1054          // Actually, those not within remove_topics should be removed. ;)
1055          $remove_topics = array_diff($topic_ids, $remove_topics);
1056      }
1057   
1058      // Remove the message from the search index
1059      $search_type = $config['search_type'];
1060   
1061      if (!class_exists($search_type))
1062      {
1063          trigger_error('NO_SUCH_SEARCH_MODULE');
1064      }
1065   
1066      $error = false;
1067      $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher);
1068   
1069      if ($error)
1070      {
1071          trigger_error($error);
1072      }
1073   
1074      $search->index_remove($post_ids, $poster_ids, $forum_ids);
1075   
1076      /** @var \phpbb\attachment\manager $attachment_manager */
1077      $attachment_manager = $phpbb_container->get('attachment.manager');
1078      $attachment_manager->delete('post', $post_ids, false);
1079      unset($attachment_manager);
1080   
1081      /**
1082      * Perform additional actions during post(s) deletion
1083      *
1084      * @event core.delete_posts_in_transaction
1085      * @var    array    post_ids                    Array with deleted posts' ids
1086      * @var    array    poster_ids                    Array with deleted posts' author ids
1087      * @var    array    topic_ids                    Array with deleted posts' topic ids
1088      * @var    array    forum_ids                    Array with deleted posts' forum ids
1089      * @var    string    where_type                    Variable containing posts deletion mode
1090      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
1091      * @var    array    delete_notifications_types    Array with notifications types to delete
1092      * @since 3.1.0-a4
1093      */
1094      $vars = array(
1095          'post_ids',
1096          'poster_ids',
1097          'topic_ids',
1098          'forum_ids',
1099          'where_type',
1100          'where_ids',
1101          'delete_notifications_types',
1102      );
1103      extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction', compact($vars)));
1104   
1105      $db->sql_transaction('commit');
1106   
1107      /**
1108      * Perform additional actions after post(s) deletion
1109      *
1110      * @event core.delete_posts_after
1111      * @var    array    post_ids                    Array with deleted posts' ids
1112      * @var    array    poster_ids                    Array with deleted posts' author ids
1113      * @var    array    topic_ids                    Array with deleted posts' topic ids
1114      * @var    array    forum_ids                    Array with deleted posts' forum ids
1115      * @var    string    where_type                    Variable containing posts deletion mode
1116      * @var    mixed    where_ids                    Array or comma separated list of posts ids to delete
1117      * @var    array    delete_notifications_types    Array with notifications types to delete
1118      * @since 3.1.0-a4
1119      */
1120      $vars = array(
1121          'post_ids',
1122          'poster_ids',
1123          'topic_ids',
1124          'forum_ids',
1125          'where_type',
1126          'where_ids',
1127          'delete_notifications_types',
1128      );
1129      extract($phpbb_dispatcher->trigger_event('core.delete_posts_after', compact($vars)));
1130   
1131      // Resync topics_posted table
1132      if ($posted_sync)
1133      {
1134          update_posted_info($topic_ids);
1135      }
1136   
1137      if ($auto_sync)
1138      {
1139          sync('topic_reported', 'topic_id', $topic_ids);
1140          sync('topic', 'topic_id', $topic_ids, true);
1141          sync('forum', 'forum_id', $forum_ids, true, true);
1142      }
1143   
1144      if ($approved_posts && $post_count_sync)
1145      {
1146          $config->increment('num_posts', $approved_posts * (-1), false);
1147      }
1148   
1149      // We actually remove topics now to not be inconsistent (the delete_topics function calls this function too)
1150      if (sizeof($remove_topics) && $call_delete_topics)
1151      {
1152          delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false);
1153      }
1154   
1155      /* @var $phpbb_notifications \phpbb\notification\manager */
1156      $phpbb_notifications = $phpbb_container->get('notification_manager');
1157   
1158      $phpbb_notifications->delete_notifications($delete_notifications_types, $post_ids);
1159   
1160      return sizeof($post_ids);
1161  }
1162   
1163  /**
1164  * Delete Attachments
1165  *
1166  * @deprecated 3.2.0-a1 (To be removed: 3.4.0)
1167  *
1168  * @param string $mode can be: post|message|topic|attach|user
1169  * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids
1170  * @param bool $resync set this to false if you are deleting posts or topics
1171  */
1172  function delete_attachments($mode, $ids, $resync = true)
1173  {
1174      global $phpbb_container;
1175   
1176      /** @var \phpbb\attachment\manager $attachment_manager */
1177      $attachment_manager = $phpbb_container->get('attachment.manager');
1178      $num_deleted = $attachment_manager->delete($mode, $ids, $resync);
1179   
1180      unset($attachment_manager);
1181   
1182      return $num_deleted;
1183  }
1184   
1185  /**
1186  * Deletes shadow topics pointing to a specified forum.
1187  *
1188  * @param int        $forum_id        The forum id
1189  * @param string        $sql_more        Additional WHERE statement, e.g. t.topic_time < (time() - 1234)
1190  * @param bool        $auto_sync        Will call sync() if this is true
1191  *
1192  * @return array        Array with affected forums
1193  */
1194  function delete_topic_shadows($forum_id, $sql_more = '', $auto_sync = true)
1195  {
1196      global $db;
1197   
1198      if (!$forum_id)
1199      {
1200          // Nothing to do.
1201          return;
1202      }
1203   
1204      // Set of affected forums we have to resync
1205      $sync_forum_ids = array();
1206   
1207      // Amount of topics we select and delete at once.
1208      $batch_size = 500;
1209   
1210      do
1211      {
1212          $sql = 'SELECT t2.forum_id, t2.topic_id
1213              FROM ' . TOPICS_TABLE . ' t2, ' . TOPICS_TABLE . ' t
1214              WHERE t2.topic_moved_id = t.topic_id
1215                  AND t.forum_id = ' . (int) $forum_id . '
1216                  ' . (($sql_more) ? 'AND ' . $sql_more : '');
1217          $result = $db->sql_query_limit($sql, $batch_size);
1218   
1219          $topic_ids = array();
1220          while ($row = $db->sql_fetchrow($result))
1221          {
1222              $topic_ids[] = (int) $row['topic_id'];
1223   
1224              $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id'];
1225          }
1226          $db->sql_freeresult($result);
1227   
1228          if (!empty($topic_ids))
1229          {
1230              $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1231                  WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1232              $db->sql_query($sql);
1233          }
1234      }
1235      while (sizeof($topic_ids) == $batch_size);
1236   
1237      if ($auto_sync)
1238      {
1239          sync('forum', 'forum_id', $sync_forum_ids, true, true);
1240      }
1241   
1242      return $sync_forum_ids;
1243  }
1244   
1245  /**
1246  * Update/Sync posted information for topics
1247  */
1248  function update_posted_info(&$topic_ids)
1249  {
1250      global $db, $config;
1251   
1252      if (empty($topic_ids) || !$config['load_db_track'])
1253      {
1254          return;
1255      }
1256   
1257      // First of all, let us remove any posted information for these topics
1258      $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . '
1259          WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1260      $db->sql_query($sql);
1261   
1262      // Now, let us collect the user/topic combos for rebuilding the information
1263      $sql = 'SELECT poster_id, topic_id
1264          FROM ' . POSTS_TABLE . '
1265          WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . '
1266              AND poster_id <> ' . ANONYMOUS . '
1267          GROUP BY poster_id, topic_id';
1268      $result = $db->sql_query($sql);
1269   
1270      $posted = array();
1271      while ($row = $db->sql_fetchrow($result))
1272      {
1273          // Add as key to make them unique (grouping by) and circumvent empty keys on array_unique
1274          $posted[$row['poster_id']][] = $row['topic_id'];
1275      }
1276      $db->sql_freeresult($result);
1277   
1278      // Now add the information...
1279      $sql_ary = array();
1280      foreach ($posted as $user_id => $topic_row)
1281      {
1282          foreach ($topic_row as $topic_id)
1283          {
1284              $sql_ary[] = array(
1285                  'user_id'        => (int) $user_id,
1286                  'topic_id'        => (int) $topic_id,
1287                  'topic_posted'    => 1,
1288              );
1289          }
1290      }
1291      unset($posted);
1292   
1293      $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary);
1294  }
1295   
1296  /**
1297  * Delete attached file
1298  *
1299  * @deprecated 3.2.0-a1 (To be removed: 3.4.0)
1300  */
1301  function phpbb_unlink($filename, $mode = 'file', $entry_removed = false)
1302  {
1303      global $phpbb_container;
1304   
1305      /** @var \phpbb\attachment\manager $attachment_manager */
1306      $attachment_manager = $phpbb_container->get('attachment.manager');
1307      $unlink = $attachment_manager->unlink($filename, $mode, $entry_removed);
1308      unset($attachment_manager);
1309   
1310      return $unlink;
1311  }
1312   
1313  /**
1314  * All-encompasing sync function
1315  *
1316  * Exaples:
1317  * <code>
1318  * sync('topic', 'topic_id', 123);            // resync topic #123
1319  * sync('topic', 'forum_id', array(2, 3));    // resync topics from forum #2 and #3
1320  * sync('topic');                            // resync all topics
1321  * sync('topic', 'range', 'topic_id BETWEEN 1 AND 60');    // resync a range of topics/forums (only available for 'topic' and 'forum' modes)
1322  * </code>
1323  *
1324  * Modes:
1325  * - forum                Resync complete forum
1326  * - topic                Resync topics
1327  * - topic_moved            Removes topic shadows that would be in the same forum as the topic they link to
1328  * - topic_visibility    Resyncs the topic_visibility flag according to the status of the first post
1329  * - post_reported        Resyncs the post_reported flag, relying on actual reports
1330  * - topic_reported        Resyncs the topic_reported flag, relying on post_reported flags
1331  * - post_attachement    Same as post_reported, but with attachment flags
1332  * - topic_attachement    Same as topic_reported, but with attachment flags
1333  */
1334  function sync($mode, $where_type = '', $where_ids = '', $resync_parents = false, $sync_extra = false)
1335  {
1336      global $db;
1337   
1338      if (is_array($where_ids))
1339      {
1340          $where_ids = array_unique($where_ids);
1341          $where_ids = array_map('intval', $where_ids);
1342      }
1343      else if ($where_type != 'range')
1344      {
1345          $where_ids = ($where_ids) ? array((int) $where_ids) : array();
1346      }
1347   
1348      if ($mode == 'forum' || $mode == 'topic' || $mode == 'topic_visibility' || $mode == 'topic_reported' || $mode == 'post_reported')
1349      {
1350          if (!$where_type)
1351          {
1352              $where_sql = '';
1353              $where_sql_and = 'WHERE';
1354          }
1355          else if ($where_type == 'range')
1356          {
1357              // Only check a range of topics/forums. For instance: 'topic_id BETWEEN 1 AND 60'
1358              $where_sql = 'WHERE (' . $mode[0] . ".$where_ids)";
1359              $where_sql_and = $where_sql . "\n\tAND";
1360          }
1361          else
1362          {
1363              // Do not sync the "global forum"
1364              $where_ids = array_diff($where_ids, array(0));
1365   
1366              if (!sizeof($where_ids))
1367              {
1368                  // Empty array with IDs. This means that we don't have any work to do. Just return.
1369                  return;
1370              }
1371   
1372              // Limit the topics/forums we are syncing, use specific topic/forum IDs.
1373              // $where_type contains the field for the where clause (forum_id, topic_id)
1374              $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
1375              $where_sql_and = $where_sql . "\n\tAND";
1376          }
1377      }
1378      else
1379      {
1380          if (!sizeof($where_ids))
1381          {
1382              return;
1383          }
1384   
1385          // $where_type contains the field for the where clause (forum_id, topic_id)
1386          $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids);
1387          $where_sql_and = $where_sql . "\n\tAND";
1388      }
1389   
1390      switch ($mode)
1391      {
1392          case 'topic_moved':
1393              $db->sql_transaction('begin');
1394              switch ($db->get_sql_layer())
1395              {
1396                  case 'mysql4':
1397                  case 'mysqli':
1398                      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1399                          USING ' . TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
1400                          WHERE t1.topic_moved_id = t2.topic_id
1401                              AND t1.forum_id = t2.forum_id";
1402                      $db->sql_query($sql);
1403                  break;
1404   
1405                  default:
1406                      $sql = 'SELECT t1.topic_id
1407                          FROM ' .TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2
1408                          WHERE t1.topic_moved_id = t2.topic_id
1409                              AND t1.forum_id = t2.forum_id";
1410                      $result = $db->sql_query($sql);
1411   
1412                      $topic_id_ary = array();
1413                      while ($row = $db->sql_fetchrow($result))
1414                      {
1415                          $topic_id_ary[] = $row['topic_id'];
1416                      }
1417                      $db->sql_freeresult($result);
1418   
1419                      if (!sizeof($topic_id_ary))
1420                      {
1421                          return;
1422                      }
1423   
1424                      $sql = 'DELETE FROM ' . TOPICS_TABLE . '
1425                          WHERE ' . $db->sql_in_set('topic_id', $topic_id_ary);
1426                      $db->sql_query($sql);
1427   
1428                  break;
1429              }
1430   
1431              $db->sql_transaction('commit');
1432              break;
1433   
1434          case 'topic_visibility':
1435   
1436              $db->sql_transaction('begin');
1437   
1438              $sql = 'SELECT t.topic_id, p.post_visibility
1439                  FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
1440                  $where_sql_and p.topic_id = t.topic_id
1441                      AND p.post_visibility = " . ITEM_APPROVED;
1442              $result = $db->sql_query($sql);
1443   
1444              $topics_approved = array();
1445              while ($row = $db->sql_fetchrow($result))
1446              {
1447                  $topics_approved[] = (int) $row['topic_id'];
1448              }
1449              $db->sql_freeresult($result);
1450   
1451              $sql = 'SELECT t.topic_id, p.post_visibility
1452                  FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
1453                  $where_sql_and " . $db->sql_in_set('t.topic_id', $topics_approved, true, true) . '
1454                      AND p.topic_id = t.topic_id
1455                      AND p.post_visibility = ' . ITEM_DELETED;
1456              $result = $db->sql_query($sql);
1457   
1458              $topics_softdeleted = array();
1459              while ($row = $db->sql_fetchrow($result))
1460              {
1461                  $topics_softdeleted[] = (int) $row['topic_id'];
1462              }
1463              $db->sql_freeresult($result);
1464   
1465              $topics_softdeleted = array_diff($topics_softdeleted, $topics_approved);
1466              $topics_not_unapproved = array_merge($topics_softdeleted, $topics_approved);
1467   
1468              $update_ary = array(
1469                  ITEM_UNAPPROVED    => (!empty($topics_not_unapproved)) ? $where_sql_and . ' ' . $db->sql_in_set('topic_id', $topics_not_unapproved, true) : '',
1470                  ITEM_APPROVED    => (!empty($topics_approved)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_approved) : '',
1471                  ITEM_DELETED    => (!empty($topics_softdeleted)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_softdeleted) : '',
1472              );
1473   
1474              foreach ($update_ary as $visibility => $sql_where)
1475              {
1476                  if ($sql_where)
1477                  {
1478                      $sql = 'UPDATE ' . TOPICS_TABLE . '
1479                          SET topic_visibility = ' . $visibility . '
1480                          ' . $sql_where;
1481                      $db->sql_query($sql);
1482                  }
1483              }
1484   
1485              $db->sql_transaction('commit');
1486              break;
1487   
1488          case 'post_reported':
1489              $post_ids = $post_reported = array();
1490   
1491              $db->sql_transaction('begin');
1492   
1493              $sql = 'SELECT p.post_id, p.post_reported
1494                  FROM ' . POSTS_TABLE . " p
1495                  $where_sql
1496                  GROUP BY p.post_id, p.post_reported";
1497              $result = $db->sql_query($sql);
1498   
1499              while ($row = $db->sql_fetchrow($result))
1500              {
1501                  $post_ids[$row['post_id']] = $row['post_id'];
1502                  if ($row['post_reported'])
1503                  {
1504                      $post_reported[$row['post_id']] = 1;
1505                  }
1506              }
1507              $db->sql_freeresult($result);
1508   
1509              $sql = 'SELECT DISTINCT(post_id)
1510                  FROM ' . REPORTS_TABLE . '
1511                  WHERE ' . $db->sql_in_set('post_id', $post_ids) . '
1512                      AND report_closed = 0';
1513              $result = $db->sql_query($sql);
1514   
1515              $post_ids = array();
1516              while ($row = $db->sql_fetchrow($result))
1517              {
1518                  if (!isset($post_reported[$row['post_id']]))
1519                  {
1520                      $post_ids[] = $row['post_id'];
1521                  }
1522                  else
1523                  {
1524                      unset($post_reported[$row['post_id']]);
1525                  }
1526              }
1527              $db->sql_freeresult($result);
1528   
1529              // $post_reported should be empty by now, if it's not it contains
1530              // posts that are falsely flagged as reported
1531              foreach ($post_reported as $post_id => $void)
1532              {
1533                  $post_ids[] = $post_id;
1534              }
1535   
1536              if (sizeof($post_ids))
1537              {
1538                  $sql = 'UPDATE ' . POSTS_TABLE . '
1539                      SET post_reported = 1 - post_reported
1540                      WHERE ' . $db->sql_in_set('post_id', $post_ids);
1541                  $db->sql_query($sql);
1542              }
1543   
1544              $db->sql_transaction('commit');
1545              break;
1546   
1547          case 'topic_reported':
1548              if ($sync_extra)
1549              {
1550                  sync('post_reported', $where_type, $where_ids);
1551              }
1552   
1553              $topic_ids = $topic_reported = array();
1554   
1555              $db->sql_transaction('begin');
1556   
1557              $sql = 'SELECT DISTINCT(t.topic_id)
1558                  FROM ' . POSTS_TABLE . " t
1559                  $where_sql_and t.post_reported = 1";
1560              $result = $db->sql_query($sql);
1561   
1562              while ($row = $db->sql_fetchrow($result))
1563              {
1564                  $topic_reported[$row['topic_id']] = 1;
1565              }
1566              $db->sql_freeresult($result);
1567   
1568              $sql = 'SELECT t.topic_id, t.topic_reported
1569                  FROM ' . TOPICS_TABLE . " t
1570                  $where_sql";
1571              $result = $db->sql_query($sql);
1572   
1573              while ($row = $db->sql_fetchrow($result))
1574              {
1575                  if ($row['topic_reported'] ^ isset($topic_reported[$row['topic_id']]))
1576                  {
1577                      $topic_ids[] = $row['topic_id'];
1578                  }
1579              }
1580              $db->sql_freeresult($result);
1581   
1582              if (sizeof($topic_ids))
1583              {
1584                  $sql = 'UPDATE ' . TOPICS_TABLE . '
1585                      SET topic_reported = 1 - topic_reported
1586                      WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1587                  $db->sql_query($sql);
1588              }
1589   
1590              $db->sql_transaction('commit');
1591              break;
1592   
1593          case 'post_attachment':
1594              $post_ids = $post_attachment = array();
1595   
1596              $db->sql_transaction('begin');
1597   
1598              $sql = 'SELECT p.post_id, p.post_attachment
1599                  FROM ' . POSTS_TABLE . " p
1600                  $where_sql
1601                  GROUP BY p.post_id, p.post_attachment";
1602              $result = $db->sql_query($sql);
1603   
1604              while ($row = $db->sql_fetchrow($result))
1605              {
1606                  $post_ids[$row['post_id']] = $row['post_id'];
1607                  if ($row['post_attachment'])
1608                  {
1609                      $post_attachment[$row['post_id']] = 1;
1610                  }
1611              }
1612              $db->sql_freeresult($result);
1613   
1614              $sql = 'SELECT DISTINCT(post_msg_id)
1615                  FROM ' . ATTACHMENTS_TABLE . '
1616                  WHERE ' . $db->sql_in_set('post_msg_id', $post_ids) . '
1617                      AND in_message = 0';
1618              $result = $db->sql_query($sql);
1619   
1620              $post_ids = array();
1621              while ($row = $db->sql_fetchrow($result))
1622              {
1623                  if (!isset($post_attachment[$row['post_msg_id']]))
1624                  {
1625                      $post_ids[] = $row['post_msg_id'];
1626                  }
1627                  else
1628                  {
1629                      unset($post_attachment[$row['post_msg_id']]);
1630                  }
1631              }
1632              $db->sql_freeresult($result);
1633   
1634              // $post_attachment should be empty by now, if it's not it contains
1635              // posts that are falsely flagged as having attachments
1636              foreach ($post_attachment as $post_id => $void)
1637              {
1638                  $post_ids[] = $post_id;
1639              }
1640   
1641              if (sizeof($post_ids))
1642              {
1643                  $sql = 'UPDATE ' . POSTS_TABLE . '
1644                      SET post_attachment = 1 - post_attachment
1645                      WHERE ' . $db->sql_in_set('post_id', $post_ids);
1646                  $db->sql_query($sql);
1647              }
1648   
1649              $db->sql_transaction('commit');
1650              break;
1651   
1652          case 'topic_attachment':
1653              if ($sync_extra)
1654              {
1655                  sync('post_attachment', $where_type, $where_ids);
1656              }
1657   
1658              $topic_ids = $topic_attachment = array();
1659   
1660              $db->sql_transaction('begin');
1661   
1662              $sql = 'SELECT DISTINCT(t.topic_id)
1663                  FROM ' . POSTS_TABLE . " t
1664                  $where_sql_and t.post_attachment = 1";
1665              $result = $db->sql_query($sql);
1666   
1667              while ($row = $db->sql_fetchrow($result))
1668              {
1669                  $topic_attachment[$row['topic_id']] = 1;
1670              }
1671              $db->sql_freeresult($result);
1672   
1673              $sql = 'SELECT t.topic_id, t.topic_attachment
1674                  FROM ' . TOPICS_TABLE . " t
1675                  $where_sql";
1676              $result = $db->sql_query($sql);
1677   
1678              while ($row = $db->sql_fetchrow($result))
1679              {
1680                  if ($row['topic_attachment'] ^ isset($topic_attachment[$row['topic_id']]))
1681                  {
1682                      $topic_ids[] = $row['topic_id'];
1683                  }
1684              }
1685              $db->sql_freeresult($result);
1686   
1687              if (sizeof($topic_ids))
1688              {
1689                  $sql = 'UPDATE ' . TOPICS_TABLE . '
1690                      SET topic_attachment = 1 - topic_attachment
1691                      WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
1692                  $db->sql_query($sql);
1693              }
1694   
1695              $db->sql_transaction('commit');
1696   
1697              break;
1698   
1699          case 'forum':
1700   
1701              $db->sql_transaction('begin');
1702   
1703              // 1: Get the list of all forums
1704              $sql = 'SELECT f.*
1705                  FROM ' . FORUMS_TABLE . " f
1706                  $where_sql";
1707              $result = $db->sql_query($sql);
1708   
1709              $forum_data = $forum_ids = $post_ids = $last_post_id = $post_info = array();
1710              while ($row = $db->sql_fetchrow($result))
1711              {
1712                  if ($row['forum_type'] == FORUM_LINK)
1713                  {
1714                      continue;
1715                  }
1716   
1717                  $forum_id = (int) $row['forum_id'];
1718                  $forum_ids[$forum_id] = $forum_id;
1719   
1720                  $forum_data[$forum_id] = $row;
1721                  if ($sync_extra)
1722                  {
1723                      $forum_data[$forum_id]['posts_approved'] = 0;
1724                      $forum_data[$forum_id]['posts_unapproved'] = 0;
1725                      $forum_data[$forum_id]['posts_softdeleted'] = 0;
1726                      $forum_data[$forum_id]['topics_approved'] = 0;
1727                      $forum_data[$forum_id]['topics_unapproved'] = 0;
1728                      $forum_data[$forum_id]['topics_softdeleted'] = 0;
1729                  }
1730                  $forum_data[$forum_id]['last_post_id'] = 0;
1731                  $forum_data[$forum_id]['last_post_subject'] = '';
1732                  $forum_data[$forum_id]['last_post_time'] = 0;
1733                  $forum_data[$forum_id]['last_poster_id'] = 0;
1734                  $forum_data[$forum_id]['last_poster_name'] = '';
1735                  $forum_data[$forum_id]['last_poster_colour'] = '';
1736              }
1737              $db->sql_freeresult($result);
1738   
1739              if (!sizeof($forum_ids))
1740              {
1741                  break;
1742              }
1743   
1744              $forum_ids = array_values($forum_ids);
1745   
1746              // 2: Get topic counts for each forum (optional)
1747              if ($sync_extra)
1748              {
1749                  $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS total_topics
1750                      FROM ' . TOPICS_TABLE . '
1751                      WHERE ' . $db->sql_in_set('forum_id', $forum_ids) . '
1752                      GROUP BY forum_id, topic_visibility';
1753                  $result = $db->sql_query($sql);
1754   
1755                  while ($row = $db->sql_fetchrow($result))
1756                  {
1757                      $forum_id = (int) $row['forum_id'];
1758   
1759                      if ($row['topic_visibility'] == ITEM_APPROVED)
1760                      {
1761                          $forum_data[$forum_id]['topics_approved'] = $row['total_topics'];
1762                      }
1763                      else if ($row['topic_visibility'] == ITEM_UNAPPROVED || $row['topic_visibility'] == ITEM_REAPPROVE)
1764                      {
1765                          $forum_data[$forum_id]['topics_unapproved'] = $row['total_topics'];
1766                      }
1767                      else if ($row['topic_visibility'] == ITEM_DELETED)
1768                      {
1769                          $forum_data[$forum_id]['topics_softdeleted'] = $row['total_topics'];
1770                      }
1771                  }
1772                  $db->sql_freeresult($result);
1773              }
1774   
1775              // 3: Get post count for each forum (optional)
1776              if ($sync_extra)
1777              {
1778                  if (sizeof($forum_ids) == 1)
1779                  {
1780                      $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
1781                          FROM ' . TOPICS_TABLE . ' t
1782                          WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1783                              AND t.topic_status <> ' . ITEM_MOVED;
1784                  }
1785                  else
1786                  {
1787                      $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
1788                          FROM ' . TOPICS_TABLE . ' t
1789                          WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1790                              AND t.topic_status <> ' . ITEM_MOVED . '
1791                          GROUP BY t.forum_id';
1792                  }
1793   
1794                  $result = $db->sql_query($sql);
1795   
1796                  while ($row = $db->sql_fetchrow($result))
1797                  {
1798                      $forum_id = (sizeof($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
1799   
1800                      $forum_data[$forum_id]['posts_approved'] = (int) $row['forum_posts_approved'];
1801                      $forum_data[$forum_id]['posts_unapproved'] = (int) $row['forum_posts_unapproved'];
1802                      $forum_data[$forum_id]['posts_softdeleted'] = (int) $row['forum_posts_softdeleted'];
1803                  }
1804                  $db->sql_freeresult($result);
1805              }
1806   
1807              // 4: Get last_post_id for each forum
1808              if (sizeof($forum_ids) == 1)
1809              {
1810                  $sql = 'SELECT MAX(t.topic_last_post_id) as last_post_id
1811                      FROM ' . TOPICS_TABLE . ' t
1812                      WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1813                          AND t.topic_visibility = ' . ITEM_APPROVED;
1814              }
1815              else
1816              {
1817                  $sql = 'SELECT t.forum_id, MAX(t.topic_last_post_id) as last_post_id
1818                      FROM ' . TOPICS_TABLE . ' t
1819                      WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . '
1820                          AND t.topic_visibility = ' . ITEM_APPROVED . '
1821                      GROUP BY t.forum_id';
1822              }
1823   
1824              $result = $db->sql_query($sql);
1825   
1826              while ($row = $db->sql_fetchrow($result))
1827              {
1828                  $forum_id = (sizeof($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id'];
1829   
1830                  $forum_data[$forum_id]['last_post_id'] = (int) $row['last_post_id'];
1831   
1832                  $post_ids[] = $row['last_post_id'];
1833              }
1834              $db->sql_freeresult($result);
1835   
1836              // 5: Retrieve last_post infos
1837              if (sizeof($post_ids))
1838              {
1839                  $sql = 'SELECT p.post_id, p.poster_id, p.post_subject, p.post_time, p.post_username, u.username, u.user_colour
1840                      FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
1841                      WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
1842                          AND p.poster_id = u.user_id';
1843                  $result = $db->sql_query($sql);
1844   
1845                  while ($row = $db->sql_fetchrow($result))
1846                  {
1847                      $post_info[$row['post_id']] = $row;
1848                  }
1849                  $db->sql_freeresult($result);
1850   
1851                  foreach ($forum_data as $forum_id => $data)
1852                  {
1853                      if ($data['last_post_id'])
1854                      {
1855                          if (isset($post_info[$data['last_post_id']]))
1856                          {
1857                              $forum_data[$forum_id]['last_post_subject'] = $post_info[$data['last_post_id']]['post_subject'];
1858                              $forum_data[$forum_id]['last_post_time'] = $post_info[$data['last_post_id']]['post_time'];
1859                              $forum_data[$forum_id]['last_poster_id'] = $post_info[$data['last_post_id']]['poster_id'];
1860                              $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'];
1861                              $forum_data[$forum_id]['last_poster_colour'] = $post_info[$data['last_post_id']]['user_colour'];
1862                          }
1863                          else
1864                          {
1865                              // For some reason we did not find the post in the db
1866                              $forum_data[$forum_id]['last_post_id'] = 0;
1867                              $forum_data[$forum_id]['last_post_subject'] = '';
1868                              $forum_data[$forum_id]['last_post_time'] = 0;
1869                              $forum_data[$forum_id]['last_poster_id'] = 0;
1870                              $forum_data[$forum_id]['last_poster_name'] = '';
1871                              $forum_data[$forum_id]['last_poster_colour'] = '';
1872                          }
1873                      }
1874                  }
1875                  unset($post_info);
1876              }
1877   
1878              // 6: Now do that thing
1879              $fieldnames = array('last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour');
1880   
1881              if ($sync_extra)
1882              {
1883                  array_push($fieldnames, 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'topics_approved', 'topics_unapproved', 'topics_softdeleted');
1884              }
1885   
1886              foreach ($forum_data as $forum_id => $row)
1887              {
1888                  $sql_ary = array();
1889   
1890                  foreach ($fieldnames as $fieldname)
1891                  {
1892                      if ($row['forum_' . $fieldname] != $row[$fieldname])
1893                      {
1894                          if (preg_match('#(name|colour|subject)$#', $fieldname))
1895                          {
1896                              $sql_ary['forum_' . $fieldname] = (string) $row[$fieldname];
1897                          }
1898                          else
1899                          {
1900                              $sql_ary['forum_' . $fieldname] = (int) $row[$fieldname];
1901                          }
1902                      }
1903                  }
1904   
1905                  if (sizeof($sql_ary))
1906                  {
1907                      $sql = 'UPDATE ' . FORUMS_TABLE . '
1908                          SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
1909                          WHERE forum_id = ' . $forum_id;
1910                      $db->sql_query($sql);
1911                  }
1912              }
1913   
1914              $db->sql_transaction('commit');
1915              break;
1916   
1917          case 'topic':
1918              $topic_data = $post_ids = $resync_forums = $delete_topics = $delete_posts = $moved_topics = array();
1919   
1920              $db->sql_transaction('begin');
1921   
1922              $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
1923                  FROM ' . TOPICS_TABLE . " t
1924                  $where_sql";
1925              $result = $db->sql_query($sql);
1926   
1927              while ($row = $db->sql_fetchrow($result))
1928              {
1929                  if ($row['topic_moved_id'])
1930                  {
1931                      $moved_topics[] = $row['topic_id'];
1932                      continue;
1933                  }
1934   
1935                  $topic_id = (int) $row['topic_id'];
1936                  $topic_data[$topic_id] = $row;
1937                  $topic_data[$topic_id]['visibility'] = ITEM_UNAPPROVED;
1938                  $topic_data[$topic_id]['posts_approved'] = 0;
1939                  $topic_data[$topic_id]['posts_unapproved'] = 0;
1940                  $topic_data[$topic_id]['posts_softdeleted'] = 0;
1941                  $topic_data[$topic_id]['first_post_id'] = 0;
1942                  $topic_data[$topic_id]['last_post_id'] = 0;
1943                  unset($topic_data[$topic_id]['topic_id']);
1944   
1945                  // This array holds all topic_ids
1946                  $delete_topics[$topic_id] = '';
1947   
1948                  if ($sync_extra)
1949                  {
1950                      $topic_data[$topic_id]['reported'] = 0;
1951                      $topic_data[$topic_id]['attachment'] = 0;
1952                  }
1953              }
1954              $db->sql_freeresult($result);
1955   
1956              // Use "t" as table alias because of the $where_sql clause
1957              // NOTE: 't.post_visibility' in the GROUP BY is causing a major slowdown.
1958              $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
1959                  FROM ' . POSTS_TABLE . " t
1960                  $where_sql
1961                  GROUP BY t.topic_id, t.post_visibility";
1962              $result = $db->sql_query($sql);
1963   
1964              while ($row = $db->sql_fetchrow($result))
1965              {
1966                  $topic_id = (int) $row['topic_id'];
1967   
1968                  $row['first_post_id'] = (int) $row['first_post_id'];
1969                  $row['last_post_id'] = (int) $row['last_post_id'];
1970   
1971                  if (!isset($topic_data[$topic_id]))
1972                  {
1973                      // Hey, these posts come from a topic that does not exist
1974                      $delete_posts[$topic_id] = '';
1975                  }
1976                  else
1977                  {
1978                      // Unset the corresponding entry in $delete_topics
1979                      // When we'll be done, only topics with no posts will remain
1980                      unset($delete_topics[$topic_id]);
1981   
1982                      if ($row['post_visibility'] == ITEM_APPROVED)
1983                      {
1984                          $topic_data[$topic_id]['posts_approved'] = $row['total_posts'];
1985                      }
1986                      else if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE)
1987                      {
1988                          $topic_data[$topic_id]['posts_unapproved'] = $row['total_posts'];
1989                      }
1990                      else if ($row['post_visibility'] == ITEM_DELETED)
1991                      {
1992                          $topic_data[$topic_id]['posts_softdeleted'] = $row['total_posts'];
1993                      }
1994   
1995                      if ($row['post_visibility'] == ITEM_APPROVED)
1996                      {
1997                          $topic_data[$topic_id]['visibility'] = ITEM_APPROVED;
1998                          $topic_data[$topic_id]['first_post_id'] = $row['first_post_id'];
1999                          $topic_data[$topic_id]['last_post_id'] = $row['last_post_id'];
2000                      }
2001                      else if ($topic_data[$topic_id]['visibility'] != ITEM_APPROVED)
2002                      {
2003                          // If there is no approved post, we take the min/max of the other visibilities
2004                          // for the last and first post info, because it is only visible to moderators anyway
2005                          $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'];
2006                          $topic_data[$topic_id]['last_post_id'] = max($topic_data[$topic_id]['last_post_id'], $row['last_post_id']);
2007   
2008                          if ($topic_data[$topic_id]['visibility'] == ITEM_UNAPPROVED || $topic_data[$topic_id]['visibility'] == ITEM_REAPPROVE)
2009                          {
2010                              // Soft delete status is stronger than unapproved.
2011                              $topic_data[$topic_id]['visibility'] = $row['post_visibility'];
2012                          }
2013                      }
2014                  }
2015              }
2016              $db->sql_freeresult($result);
2017   
2018              foreach ($topic_data as $topic_id => $row)
2019              {
2020                  $post_ids[] = $row['first_post_id'];
2021                  if ($row['first_post_id'] != $row['last_post_id'])
2022                  {
2023                      $post_ids[] = $row['last_post_id'];
2024                  }
2025              }
2026   
2027              // Now we delete empty topics and orphan posts
2028              if (sizeof($delete_posts))
2029              {
2030                  delete_posts('topic_id', array_keys($delete_posts), false);
2031                  unset($delete_posts);
2032              }
2033   
2034              if (!sizeof($topic_data))
2035              {
2036                  // If we get there, topic ids were invalid or topics did not contain any posts
2037                  delete_topics($where_type, $where_ids, true);
2038                  return;
2039              }
2040   
2041              if (sizeof($delete_topics))
2042              {
2043                  $delete_topic_ids = array();
2044                  foreach ($delete_topics as $topic_id => $void)
2045                  {
2046                      unset($topic_data[$topic_id]);
2047                      $delete_topic_ids[] = $topic_id;
2048                  }
2049   
2050                  delete_topics('topic_id', $delete_topic_ids, false);
2051                  unset($delete_topics, $delete_topic_ids);
2052              }
2053   
2054              $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
2055                  FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
2056                  WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
2057                      AND u.user_id = p.poster_id';
2058              $result = $db->sql_query($sql);
2059   
2060              while ($row = $db->sql_fetchrow($result))
2061              {
2062                  $topic_id = intval($row['topic_id']);
2063   
2064                  if ($row['post_id'] == $topic_data[$topic_id]['first_post_id'])
2065                  {
2066                      $topic_data[$topic_id]['time'] = $row['post_time'];
2067                      $topic_data[$topic_id]['poster'] = $row['poster_id'];
2068                      $topic_data[$topic_id]['first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2069                      $topic_data[$topic_id]['first_poster_colour'] = $row['user_colour'];
2070                  }
2071   
2072                  if ($row['post_id'] == $topic_data[$topic_id]['last_post_id'])
2073                  {
2074                      $topic_data[$topic_id]['last_poster_id'] = $row['poster_id'];
2075                      $topic_data[$topic_id]['last_post_subject'] = $row['post_subject'];
2076                      $topic_data[$topic_id]['last_post_time'] = $row['post_time'];
2077                      $topic_data[$topic_id]['last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2078                      $topic_data[$topic_id]['last_poster_colour'] = $row['user_colour'];
2079                  }
2080              }
2081              $db->sql_freeresult($result);
2082   
2083              // Make sure shadow topics do link to existing topics
2084              if (sizeof($moved_topics))
2085              {
2086                  $delete_topics = array();
2087   
2088                  $sql = 'SELECT t1.topic_id, t1.topic_moved_id
2089                      FROM ' . TOPICS_TABLE . ' t1
2090                      LEFT JOIN ' . TOPICS_TABLE . ' t2 ON (t2.topic_id = t1.topic_moved_id)
2091                      WHERE ' . $db->sql_in_set('t1.topic_id', $moved_topics) . '
2092                          AND t2.topic_id IS NULL';
2093                  $result = $db->sql_query($sql);
2094   
2095                  while ($row = $db->sql_fetchrow($result))
2096                  {
2097                      $delete_topics[] = $row['topic_id'];
2098                  }
2099                  $db->sql_freeresult($result);
2100   
2101                  if (sizeof($delete_topics))
2102                  {
2103                      delete_topics('topic_id', $delete_topics, false);
2104                  }
2105                  unset($delete_topics);
2106   
2107                  // Make sure shadow topics having no last post data being updated (this only rarely happens...)
2108                  $sql = 'SELECT topic_id, topic_moved_id, topic_last_post_id, topic_first_post_id
2109                      FROM ' . TOPICS_TABLE . '
2110                      WHERE ' . $db->sql_in_set('topic_id', $moved_topics) . '
2111                          AND topic_last_post_time = 0';
2112                  $result = $db->sql_query($sql);
2113   
2114                  $shadow_topic_data = $post_ids = array();
2115                  while ($row = $db->sql_fetchrow($result))
2116                  {
2117                      $shadow_topic_data[$row['topic_moved_id']] = $row;
2118                      $post_ids[] = $row['topic_last_post_id'];
2119                      $post_ids[] = $row['topic_first_post_id'];
2120                  }
2121                  $db->sql_freeresult($result);
2122   
2123                  $sync_shadow_topics = array();
2124                  if (sizeof($post_ids))
2125                  {
2126                      $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
2127                          FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u
2128                          WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '
2129                              AND u.user_id = p.poster_id';
2130                      $result = $db->sql_query($sql);
2131   
2132                      while ($row = $db->sql_fetchrow($result))
2133                      {
2134                          $topic_id = (int) $row['topic_id'];
2135   
2136                          // Ok, there should be a shadow topic. If there isn't, then there's something wrong with the db.
2137                          // However, there's not much we can do about it.
2138                          if (!empty($shadow_topic_data[$topic_id]))
2139                          {
2140                              if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_first_post_id'])
2141                              {
2142                                  $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
2143   
2144                                  if (!isset($sync_shadow_topics[$orig_topic_id]))
2145                                  {
2146                                      $sync_shadow_topics[$orig_topic_id] = array();
2147                                  }
2148   
2149                                  $sync_shadow_topics[$orig_topic_id]['topic_time'] = $row['post_time'];
2150                                  $sync_shadow_topics[$orig_topic_id]['topic_poster'] = $row['poster_id'];
2151                                  $sync_shadow_topics[$orig_topic_id]['topic_first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2152                                  $sync_shadow_topics[$orig_topic_id]['topic_first_poster_colour'] = $row['user_colour'];
2153                              }
2154   
2155                              if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_last_post_id'])
2156                              {
2157                                  $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id'];
2158   
2159                                  if (!isset($sync_shadow_topics[$orig_topic_id]))
2160                                  {
2161                                      $sync_shadow_topics[$orig_topic_id] = array();
2162                                  }
2163   
2164                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_id'] = $row['poster_id'];
2165                                  $sync_shadow_topics[$orig_topic_id]['topic_last_post_subject'] = $row['post_subject'];
2166                                  $sync_shadow_topics[$orig_topic_id]['topic_last_post_time'] = $row['post_time'];
2167                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'];
2168                                  $sync_shadow_topics[$orig_topic_id]['topic_last_poster_colour'] = $row['user_colour'];
2169                              }
2170                          }
2171                      }
2172                      $db->sql_freeresult($result);
2173   
2174                      $shadow_topic_data = array();
2175   
2176                      // Update the information we collected
2177                      if (sizeof($sync_shadow_topics))
2178                      {
2179                          foreach ($sync_shadow_topics as $sync_topic_id => $sql_ary)
2180                          {
2181                              $sql = 'UPDATE ' . TOPICS_TABLE . '
2182                                  SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2183                                  WHERE topic_id = ' . $sync_topic_id;
2184                              $db->sql_query($sql);
2185                          }
2186                      }
2187                  }
2188   
2189                  unset($sync_shadow_topics, $shadow_topic_data);
2190              }
2191   
2192              // These are fields that will be synchronised
2193              $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');
2194   
2195              if ($sync_extra)
2196              {
2197                  // This routine assumes that post_reported values are correct
2198                  // if they are not, use sync('post_reported') first
2199                  $sql = 'SELECT t.topic_id, p.post_id
2200                      FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
2201                      $where_sql_and p.topic_id = t.topic_id
2202                          AND p.post_reported = 1
2203                      GROUP BY t.topic_id, p.post_id";
2204                  $result = $db->sql_query($sql);
2205   
2206                  $fieldnames[] = 'reported';
2207                  while ($row = $db->sql_fetchrow($result))
2208                  {
2209                      $topic_data[intval($row['topic_id'])]['reported'] = 1;
2210                  }
2211                  $db->sql_freeresult($result);
2212   
2213                  // This routine assumes that post_attachment values are correct
2214                  // if they are not, use sync('post_attachment') first
2215                  $sql = 'SELECT t.topic_id, p.post_id
2216                      FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
2217                      $where_sql_and p.topic_id = t.topic_id
2218                          AND p.post_attachment = 1
2219                      GROUP BY t.topic_id, p.post_id";
2220                  $result = $db->sql_query($sql);
2221   
2222                  $fieldnames[] = 'attachment';
2223                  while ($row = $db->sql_fetchrow($result))
2224                  {
2225                      $topic_data[intval($row['topic_id'])]['attachment'] = 1;
2226                  }
2227                  $db->sql_freeresult($result);
2228              }
2229   
2230              foreach ($topic_data as $topic_id => $row)
2231              {
2232                  $sql_ary = array();
2233   
2234                  foreach ($fieldnames as $fieldname)
2235                  {
2236                      if (isset($row[$fieldname]) && isset($row['topic_' . $fieldname]) && $row['topic_' . $fieldname] != $row[$fieldname])
2237                      {
2238                          $sql_ary['topic_' . $fieldname] = $row[$fieldname];
2239                      }
2240                  }
2241   
2242                  if (sizeof($sql_ary))
2243                  {
2244                      $sql = 'UPDATE ' . TOPICS_TABLE . '
2245                          SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
2246                          WHERE topic_id = ' . $topic_id;
2247                      $db->sql_query($sql);
2248   
2249                      $resync_forums[$row['forum_id']] = $row['forum_id'];
2250                  }
2251              }
2252              unset($topic_data);
2253   
2254              $db->sql_transaction('commit');
2255   
2256              // if some topics have been resync'ed then resync parent forums
2257              // except when we're only syncing a range, we don't want to sync forums during
2258              // batch processing.
2259              if ($resync_parents && sizeof($resync_forums) && $where_type != 'range')
2260              {
2261                  sync('forum', 'forum_id', array_values($resync_forums), true, true);
2262              }
2263              break;
2264      }
2265   
2266      return;
2267  }
2268   
2269  /**
2270  * Prune function
2271  */
2272  function prune($forum_id, $prune_mode, $prune_date, $prune_flags = 0, $auto_sync = true, $prune_limit = 0)
2273  {
2274      global $db, $phpbb_dispatcher;
2275   
2276      if (!is_array($forum_id))
2277      {
2278          $forum_id = array($forum_id);
2279      }
2280   
2281      if (!sizeof($forum_id))
2282      {
2283          return;
2284      }
2285   
2286      $sql_and = '';
2287   
2288      if (!($prune_flags & FORUM_FLAG_PRUNE_ANNOUNCE))
2289      {
2290          $sql_and .= ' AND topic_type <> ' . POST_ANNOUNCE;
2291          $sql_and .= ' AND topic_type <> ' . POST_GLOBAL;
2292      }
2293   
2294      if (!($prune_flags & FORUM_FLAG_PRUNE_STICKY))
2295      {
2296          $sql_and .= ' AND topic_type <> ' . POST_STICKY;
2297      }
2298   
2299      if ($prune_mode == 'posted')
2300      {
2301          $sql_and .= " AND topic_last_post_time < $prune_date";
2302      }
2303   
2304      if ($prune_mode == 'viewed')
2305      {
2306          $sql_and .= " AND topic_last_view_time < $prune_date";
2307      }
2308   
2309      if ($prune_mode == 'shadow')
2310      {
2311          $sql_and .= ' AND topic_status = ' . ITEM_MOVED . " AND topic_last_post_time < $prune_date";
2312      }
2313   
2314      /**
2315      * Use this event to modify the SQL that selects topics to be pruned
2316      *
2317      * @event core.prune_sql
2318      * @var string    forum_id        The forum id
2319      * @var string    prune_mode        The prune mode
2320      * @var string    prune_date        The prune date
2321      * @var int        prune_flags        The prune flags
2322      * @var bool        auto_sync        Whether or not to perform auto sync
2323      * @var string    sql_and            SQL text appended to where clause
2324      * @var int        prune_limit        The prune limit
2325      * @since 3.1.3-RC1
2326      * @changed 3.1.10-RC1            Added prune_limit
2327      */
2328      $vars = array(
2329          'forum_id',
2330          'prune_mode',
2331          'prune_date',
2332          'prune_flags',
2333          'auto_sync',
2334          'sql_and',
2335          'prune_limit',
2336      );
2337      extract($phpbb_dispatcher->trigger_event('core.prune_sql', compact($vars)));
2338   
2339      $sql = 'SELECT topic_id
2340          FROM ' . TOPICS_TABLE . '
2341          WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
2342              AND poll_start = 0
2343              $sql_and";
2344      $result = $db->sql_query_limit($sql, $prune_limit);
2345   
2346      $topic_list = array();
2347      while ($row = $db->sql_fetchrow($result))
2348      {
2349          $topic_list[] = $row['topic_id'];
2350      }
2351      $db->sql_freeresult($result);
2352   
2353      if ($prune_flags & FORUM_FLAG_PRUNE_POLL)
2354      {
2355          $sql = 'SELECT topic_id
2356              FROM ' . TOPICS_TABLE . '
2357              WHERE ' . $db->sql_in_set('forum_id', $forum_id) . "
2358                  AND poll_start > 0
2359                  AND poll_last_vote < $prune_date
2360                  $sql_and";
2361          $result = $db->sql_query_limit($sql, $prune_limit);
2362   
2363          while ($row = $db->sql_fetchrow($result))
2364          {
2365              $topic_list[] = $row['topic_id'];
2366          }
2367          $db->sql_freeresult($result);
2368   
2369          $topic_list = array_unique($topic_list);
2370      }
2371   
2372      return delete_topics('topic_id', $topic_list, $auto_sync, false);
2373  }
2374   
2375  /**
2376  * Function auto_prune(), this function now relies on passed vars
2377  */
2378  function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_freq)
2379  {
2380      global $db, $user, $phpbb_log;
2381   
2382      $sql = 'SELECT forum_name
2383          FROM ' . FORUMS_TABLE . "
2384          WHERE forum_id = $forum_id";
2385      $result = $db->sql_query($sql, 3600);
2386      $row = $db->sql_fetchrow($result);
2387      $db->sql_freeresult($result);
2388   
2389      if ($row)
2390      {
2391          $prune_date = time() - ($prune_days * 86400);
2392          $next_prune = time() + ($prune_freq * 86400);
2393   
2394          $result = prune($forum_id, $prune_mode, $prune_date, $prune_flags, true, 300);
2395   
2396          if ($result['topics'] == 0 && $result['posts'] == 0)
2397          {
2398              $sql = 'UPDATE ' . FORUMS_TABLE . "
2399                  SET prune_next = $next_prune
2400                  WHERE forum_id = $forum_id";
2401              $db->sql_query($sql);
2402          }
2403   
2404          $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_AUTO_PRUNE', false, array($row['forum_name']));
2405      }
2406   
2407      return;
2408  }
2409   
2410  /**
2411  * Cache moderators. Called whenever permissions are changed
2412  * via admin_permissions. Changes of usernames and group names
2413  * must be carried through for the moderators table.
2414  *
2415  * @param \phpbb\db\driver\driver_interface $db Database connection
2416  * @param \phpbb\cache\driver\driver_interface Cache driver
2417  * @param \phpbb\auth\auth $auth Authentication object
2418  * @return null
2419  */
2420  function phpbb_cache_moderators($db, $cache, $auth)
2421  {
2422      // Remove cached sql results
2423      $cache->destroy('sql', MODERATOR_CACHE_TABLE);
2424   
2425      // Clear table
2426      switch ($db->get_sql_layer())
2427      {
2428          case 'sqlite3':
2429              $db->sql_query('DELETE FROM ' . MODERATOR_CACHE_TABLE);
2430          break;
2431   
2432          default:
2433              $db->sql_query('TRUNCATE TABLE ' . MODERATOR_CACHE_TABLE);
2434          break;
2435      }
2436   
2437      // We add moderators who have forum moderator permissions without an explicit ACL_NEVER setting
2438      $sql_ary = array();
2439   
2440      // Grab all users having moderative options...
2441      $hold_ary = $auth->acl_user_raw_data(false, 'm_%', false);
2442   
2443      // Add users?
2444      if (sizeof($hold_ary))
2445      {
2446          // At least one moderative option warrants a display
2447          $ug_id_ary = array_keys($hold_ary);
2448   
2449          // Remove users who have group memberships with DENY moderator permissions
2450          $sql_ary_deny = array(
2451              'SELECT'    => 'a.forum_id, ug.user_id, g.group_id',
2452   
2453              'FROM'        => array(
2454                  ACL_OPTIONS_TABLE    => 'o',
2455                  USER_GROUP_TABLE    => 'ug',
2456                  GROUPS_TABLE        => 'g',
2457                  ACL_GROUPS_TABLE    => 'a',
2458              ),
2459   
2460              'LEFT_JOIN'    => array(
2461                  array(
2462                      'FROM'    => array(ACL_ROLES_DATA_TABLE => 'r'),
2463                      'ON'    => 'a.auth_role_id = r.role_id',
2464                  ),
2465              ),
2466   
2467              'WHERE'        => '(o.auth_option_id = a.auth_option_id OR o.auth_option_id = r.auth_option_id)
2468                  AND ((a.auth_setting = ' . ACL_NEVER . ' AND r.auth_setting IS NULL)
2469                      OR r.auth_setting = ' . ACL_NEVER . ')
2470                  AND a.group_id = ug.group_id
2471                  AND g.group_id = ug.group_id
2472                  AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
2473                  AND ' . $db->sql_in_set('ug.user_id', $ug_id_ary) . "
2474                  AND ug.user_pending = 0
2475                  AND o.auth_option " . $db->sql_like_expression('m_' . $db->get_any_char()),
2476          );
2477          $sql = $db->sql_build_query('SELECT', $sql_ary_deny);
2478          $result = $db->sql_query($sql);
2479   
2480          while ($row = $db->sql_fetchrow($result))
2481          {
2482              if (isset($hold_ary[$row['user_id']][$row['forum_id']]))
2483              {
2484                  unset($hold_ary[$row['user_id']][$row['forum_id']]);
2485              }
2486          }
2487          $db->sql_freeresult($result);
2488   
2489          if (sizeof($hold_ary))
2490          {
2491              // Get usernames...
2492              $sql = 'SELECT user_id, username
2493                  FROM ' . USERS_TABLE . '
2494                  WHERE ' . $db->sql_in_set('user_id', array_keys($hold_ary));
2495              $result = $db->sql_query($sql);
2496   
2497              $usernames_ary = array();
2498              while ($row = $db->sql_fetchrow($result))
2499              {
2500                  $usernames_ary[$row['user_id']] = $row['username'];
2501              }
2502              $db->sql_freeresult($result);
2503   
2504              foreach ($hold_ary as $user_id => $forum_id_ary)
2505              {
2506                  // Do not continue if user does not exist
2507                  if (!isset($usernames_ary[$user_id]))
2508                  {
2509                      continue;
2510                  }
2511   
2512                  foreach ($forum_id_ary as $forum_id => $auth_ary)
2513                  {
2514                      $sql_ary[] = array(
2515                          'forum_id'        => (int) $forum_id,
2516                          'user_id'        => (int) $user_id,
2517                          'username'        => (string) $usernames_ary[$user_id],
2518                          'group_id'        => 0,
2519                          'group_name'    => ''
2520                      );
2521                  }
2522              }
2523          }
2524      }
2525   
2526      // Now to the groups...
2527      $hold_ary = $auth->acl_group_raw_data(false, 'm_%', false);
2528   
2529      if (sizeof($hold_ary))
2530      {
2531          $ug_id_ary = array_keys($hold_ary);
2532   
2533          // Make sure not hidden or special groups are involved...
2534          $sql = 'SELECT group_name, group_id, group_type
2535              FROM ' . GROUPS_TABLE . '
2536              WHERE ' . $db->sql_in_set('group_id', $ug_id_ary);
2537          $result = $db->sql_query($sql);
2538   
2539          $groupnames_ary = array();
2540          while ($row = $db->sql_fetchrow($result))
2541          {
2542              if ($row['group_type'] == GROUP_HIDDEN || $row['group_type'] == GROUP_SPECIAL)
2543              {
2544                  unset($hold_ary[$row['group_id']]);
2545              }
2546   
2547              $groupnames_ary[$row['group_id']] = $row['group_name'];
2548          }
2549          $db->sql_freeresult($result);
2550   
2551          foreach ($hold_ary as $group_id => $forum_id_ary)
2552          {
2553              // If there is no group, we do not assign it...
2554              if (!isset($groupnames_ary[$group_id]))
2555              {
2556                  continue;
2557              }
2558   
2559              foreach ($forum_id_ary as $forum_id => $auth_ary)
2560              {
2561                  $flag = false;
2562                  foreach ($auth_ary as $auth_option => $setting)
2563                  {
2564                      // Make sure at least one ACL_YES option is set...
2565                      if ($setting == ACL_YES)
2566                      {
2567                          $flag = true;
2568                          break;
2569                      }
2570                  }
2571   
2572                  if (!$flag)
2573                  {
2574                      continue;
2575                  }
2576   
2577                  $sql_ary[] = array(
2578                      'forum_id'        => (int) $forum_id,
2579                      'user_id'        => 0,
2580                      'username'        => '',
2581                      'group_id'        => (int) $group_id,
2582                      'group_name'    => (string) $groupnames_ary[$group_id]
2583                  );
2584              }
2585          }
2586      }
2587   
2588      $db->sql_multi_insert(MODERATOR_CACHE_TABLE, $sql_ary);
2589  }
2590   
2591  /**
2592  * View log
2593  *
2594  * @param    string    $mode            The mode defines which log_type is used and from which log the entry is retrieved
2595  * @param    array    &$log            The result array with the logs
2596  * @param    mixed    &$log_count        If $log_count is set to false, we will skip counting all entries in the database.
2597  *                                    Otherwise an integer with the number of total matching entries is returned.
2598  * @param    int        $limit            Limit the number of entries that are returned
2599  * @param    int        $offset            Offset when fetching the log entries, f.e. when paginating
2600  * @param    mixed    $forum_id        Restrict the log entries to the given forum_id (can also be an array of forum_ids)
2601  * @param    int        $topic_id        Restrict the log entries to the given topic_id
2602  * @param    int        $user_id        Restrict the log entries to the given user_id
2603  * @param    int        $log_time        Only get log entries newer than the given timestamp
2604  * @param    string    $sort_by        SQL order option, e.g. 'l.log_time DESC'
2605  * @param    string    $keywords        Will only return log entries that have the keywords in log_operation or log_data
2606  *
2607  * @return    int                Returns the offset of the last valid page, if the specified offset was invalid (too high)
2608  */
2609  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 = '')
2610  {
2611      global $phpbb_log;
2612   
2613      $count_logs = ($log_count !== false);
2614   
2615      $log = $phpbb_log->get_logs($mode, $count_logs, $limit, $offset, $forum_id, $topic_id, $user_id, $limit_days, $sort_by, $keywords);
2616      $log_count = $phpbb_log->get_log_count();
2617   
2618      return $phpbb_log->get_valid_offset();
2619  }
2620   
2621  /**
2622  * Removes moderators and administrators from foe lists.
2623  *
2624  * @param \phpbb\db\driver\driver_interface $db Database connection
2625  * @param \phpbb\auth\auth $auth Authentication object
2626  * @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore
2627  * @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore
2628  * @return null
2629  */
2630  function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false)
2631  {
2632      // update foes for some user
2633      if (is_array($user_id) && sizeof($user_id))
2634      {
2635          $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2636              WHERE ' . $db->sql_in_set('zebra_id', $user_id) . '
2637                  AND foe = 1';
2638          $db->sql_query($sql);
2639          return;
2640      }
2641   
2642      // update foes for some group
2643      if (is_array($group_id) && sizeof($group_id))
2644      {
2645          // Grab group settings...
2646          $sql_ary = array(
2647              'SELECT'    => 'a.group_id',
2648   
2649              'FROM'        => array(
2650                  ACL_OPTIONS_TABLE    => 'ao',
2651                  ACL_GROUPS_TABLE    => 'a',
2652              ),
2653   
2654              'LEFT_JOIN'    => array(
2655                  array(
2656                      'FROM'    => array(ACL_ROLES_DATA_TABLE => 'r'),
2657                      'ON'    => 'a.auth_role_id = r.role_id',
2658                  ),
2659              ),
2660   
2661              'WHERE'        => '(ao.auth_option_id = a.auth_option_id OR ao.auth_option_id = r.auth_option_id)
2662                  AND ' . $db->sql_in_set('a.group_id', $group_id) . "
2663                  AND ao.auth_option IN ('a_', 'm_')",
2664   
2665              'GROUP_BY'    => 'a.group_id',
2666          );
2667          $sql = $db->sql_build_query('SELECT', $sql_ary);
2668          $result = $db->sql_query($sql);
2669   
2670          $groups = array();
2671          while ($row = $db->sql_fetchrow($result))
2672          {
2673              $groups[] = (int) $row['group_id'];
2674          }
2675          $db->sql_freeresult($result);
2676   
2677          if (!sizeof($groups))
2678          {
2679              return;
2680          }
2681   
2682          switch ($db->get_sql_layer())
2683          {
2684              case 'mysqli':
2685              case 'mysql4':
2686                  $sql = 'DELETE ' . (($db->get_sql_layer() === 'mysqli' || version_compare($db->sql_server_info(true), '4.1', '>=')) ? 'z.*' : ZEBRA_TABLE) . '
2687                      FROM ' . ZEBRA_TABLE . ' z, ' . USER_GROUP_TABLE . ' ug
2688                      WHERE z.zebra_id = ug.user_id
2689                          AND z.foe = 1
2690                          AND ' . $db->sql_in_set('ug.group_id', $groups);
2691                  $db->sql_query($sql);
2692              break;
2693   
2694              default:
2695                  $sql = 'SELECT user_id
2696                      FROM ' . USER_GROUP_TABLE . '
2697                      WHERE ' . $db->sql_in_set('group_id', $groups);
2698                  $result = $db->sql_query($sql);
2699   
2700                  $users = array();
2701                  while ($row = $db->sql_fetchrow($result))
2702                  {
2703                      $users[] = (int) $row['user_id'];
2704                  }
2705                  $db->sql_freeresult($result);
2706   
2707                  if (sizeof($users))
2708                  {
2709                      $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2710                          WHERE ' . $db->sql_in_set('zebra_id', $users) . '
2711                              AND foe = 1';
2712                      $db->sql_query($sql);
2713                  }
2714              break;
2715          }
2716   
2717          return;
2718      }
2719   
2720      // update foes for everyone
2721      $perms = array();
2722      foreach ($auth->acl_get_list(false, array('a_', 'm_'), false) as $forum_id => $forum_ary)
2723      {
2724          foreach ($forum_ary as $auth_option => $user_ary)
2725          {
2726              $perms = array_merge($perms, $user_ary);
2727          }
2728      }
2729   
2730      if (sizeof($perms))
2731      {
2732          $sql = 'DELETE FROM ' . ZEBRA_TABLE . '
2733              WHERE ' . $db->sql_in_set('zebra_id', array_unique($perms)) . '
2734                  AND foe = 1';
2735          $db->sql_query($sql);
2736      }
2737      unset($perms);
2738  }
2739   
2740  /**
2741  * Lists inactive users
2742  */
2743  function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC')
2744  {
2745      global $db, $user;
2746   
2747      $sql = 'SELECT COUNT(user_id) AS user_count
2748          FROM ' . USERS_TABLE . '
2749          WHERE user_type = ' . USER_INACTIVE .
2750          (($limit_days) ? " AND user_inactive_time >= $limit_days" : '');
2751      $result = $db->sql_query($sql);
2752      $user_count = (int) $db->sql_fetchfield('user_count');
2753      $db->sql_freeresult($result);
2754   
2755      if ($user_count == 0)
2756      {
2757          // Save the queries, because there are no users to display
2758          return 0;
2759      }
2760   
2761      if ($offset >= $user_count)
2762      {
2763          $offset = ($offset - $limit < 0) ? 0 : $offset - $limit;
2764      }
2765   
2766      $sql = 'SELECT *
2767          FROM ' . USERS_TABLE . '
2768          WHERE user_type = ' . USER_INACTIVE .
2769          (($limit_days) ? " AND user_inactive_time >= $limit_days" : '') . "
2770          ORDER BY $sort_by";
2771      $result = $db->sql_query_limit($sql, $limit, $offset);
2772   
2773      while ($row = $db->sql_fetchrow($result))
2774      {
2775          $row['inactive_reason'] = $user->lang['INACTIVE_REASON_UNKNOWN'];
2776          switch ($row['user_inactive_reason'])
2777          {
2778              case INACTIVE_REGISTER:
2779                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REGISTER'];
2780              break;
2781   
2782              case INACTIVE_PROFILE:
2783                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_PROFILE'];
2784              break;
2785   
2786              case INACTIVE_MANUAL:
2787                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_MANUAL'];
2788              break;
2789   
2790              case INACTIVE_REMIND:
2791                  $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REMIND'];
2792              break;
2793          }
2794   
2795          $users[] = $row;
2796      }
2797      $db->sql_freeresult($result);
2798   
2799      return $offset;
2800  }
2801   
2802  /**
2803  * Lists warned users
2804  */
2805  function view_warned_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_warnings DESC')
2806  {
2807      global $db;
2808   
2809      $sql = 'SELECT user_id, username, user_colour, user_warnings, user_last_warning
2810          FROM ' . USERS_TABLE . '
2811          WHERE user_warnings > 0
2812          ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '') . "
2813          ORDER BY $sort_by";
2814      $result = $db->sql_query_limit($sql, $limit, $offset);
2815      $users = $db->sql_fetchrowset($result);
2816      $db->sql_freeresult($result);
2817   
2818      $sql = 'SELECT count(user_id) AS user_count
2819          FROM ' . USERS_TABLE . '
2820          WHERE user_warnings > 0
2821          ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '');
2822      $result = $db->sql_query($sql);
2823      $user_count = (int) $db->sql_fetchfield('user_count');
2824      $db->sql_freeresult($result);
2825   
2826      return;
2827  }
2828   
2829  /**
2830  * Get database size
2831  * Currently only mysql and mssql are supported
2832  */
2833  function get_database_size()
2834  {
2835      global $db, $user, $table_prefix;
2836   
2837      $database_size = false;
2838   
2839      // This code is heavily influenced by a similar routine in phpMyAdmin 2.2.0
2840      switch ($db->get_sql_layer())
2841      {
2842          case 'mysql':
2843          case 'mysql4':
2844          case 'mysqli':
2845              $sql = 'SELECT VERSION() AS mysql_version';
2846              $result = $db->sql_query($sql);
2847              $row = $db->sql_fetchrow($result);
2848              $db->sql_freeresult($result);
2849   
2850              if ($row)
2851              {
2852                  $version = $row['mysql_version'];
2853   
2854                  if (preg_match('#(3\.23|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria)#', $version))
2855                  {
2856                      $db_name = (preg_match('#^(?:3\.23\.(?:[6-9]|[1-9]{2}))|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria#', $version)) ? "`{$db->get_db_name()}`" : $db->get_db_name();
2857   
2858                      $sql = 'SHOW TABLE STATUS
2859                          FROM ' . $db_name;
2860                      $result = $db->sql_query($sql, 7200);
2861   
2862                      $database_size = 0;
2863                      while ($row = $db->sql_fetchrow($result))
2864                      {
2865                          if ((isset($row['Type']) && $row['Type'] != 'MRG_MyISAM') || (isset($row['Engine']) && ($row['Engine'] == 'MyISAM' || $row['Engine'] == 'InnoDB' || $row['Engine'] == 'Aria')))
2866                          {
2867                              if ($table_prefix != '')
2868                              {
2869                                  if (strpos($row['Name'], $table_prefix) !== false)
2870                                  {
2871                                      $database_size += $row['Data_length'] + $row['Index_length'];
2872                                  }
2873                              }
2874                              else
2875                              {
2876                                  $database_size += $row['Data_length'] + $row['Index_length'];
2877                              }
2878                          }
2879                      }
2880                      $db->sql_freeresult($result);
2881                  }
2882              }
2883          break;
2884   
2885          case 'sqlite3':
2886              global $dbhost;
2887   
2888              if (file_exists($dbhost))
2889              {
2890                  $database_size = filesize($dbhost);
2891              }
2892   
2893          break;
2894   
2895          case 'mssql_odbc':
2896          case 'mssqlnative':
2897              $sql = 'SELECT @@VERSION AS mssql_version';
2898              $result = $db->sql_query($sql);
2899              $row = $db->sql_fetchrow($result);
2900              $db->sql_freeresult($result);
2901   
2902              $sql = 'SELECT ((SUM(size) * 8.0) * 1024.0) as dbsize
2903                  FROM sysfiles';
2904   
2905              if ($row)
2906              {
2907                  // Azure stats are stored elsewhere
2908                  if (strpos($row['mssql_version'], 'SQL Azure') !== false)
2909                  {
2910                      $sql = 'SELECT ((SUM(reserved_page_count) * 8.0) * 1024.0) as dbsize
2911                      FROM sys.dm_db_partition_stats';
2912                  }
2913              }
2914   
2915              $result = $db->sql_query($sql, 7200);
2916              $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
2917              $db->sql_freeresult($result);
2918          break;
2919   
2920          case 'postgres':
2921              $sql = "SELECT proname
2922                  FROM pg_proc
2923                  WHERE proname = 'pg_database_size'";
2924              $result = $db->sql_query($sql);
2925              $row = $db->sql_fetchrow($result);
2926              $db->sql_freeresult($result);
2927   
2928              if ($row['proname'] == 'pg_database_size')
2929              {
2930                  $database = $db->get_db_name();
2931                  if (strpos($database, '.') !== false)
2932                  {
2933                      list($database, ) = explode('.', $database);
2934                  }
2935   
2936                  $sql = "SELECT oid
2937                      FROM pg_database
2938                      WHERE datname = '$database'";
2939                  $result = $db->sql_query($sql);
2940                  $row = $db->sql_fetchrow($result);
2941                  $db->sql_freeresult($result);
2942   
2943                  $oid = $row['oid'];
2944   
2945                  $sql = 'SELECT pg_database_size(' . $oid . ') as size';
2946                  $result = $db->sql_query($sql);
2947                  $row = $db->sql_fetchrow($result);
2948                  $db->sql_freeresult($result);
2949   
2950                  $database_size = $row['size'];
2951              }
2952          break;
2953   
2954          case 'oracle':
2955              $sql = 'SELECT SUM(bytes) as dbsize
2956                  FROM user_segments';
2957              $result = $db->sql_query($sql, 7200);
2958              $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false;
2959              $db->sql_freeresult($result);
2960          break;
2961      }
2962   
2963      $database_size = ($database_size !== false) ? get_formatted_filesize($database_size) : $user->lang['NOT_AVAILABLE'];
2964   
2965      return $database_size;
2966  }
2967   
2968  /*
2969  * Tidy Warnings
2970  * Remove all warnings which have now expired from the database
2971  * The duration of a warning can be defined by the administrator
2972  * This only removes the warning and reduces the associated count,
2973  * it does not remove the user note recording the contents of the warning
2974  */
2975  function tidy_warnings()
2976  {
2977      global $db, $config;
2978   
2979      $expire_date = time() - ($config['warnings_expire_days'] * 86400);
2980      $warning_list = $user_list = array();
2981   
2982      $sql = 'SELECT * FROM ' . WARNINGS_TABLE . "
2983          WHERE warning_time < $expire_date";
2984      $result = $db->sql_query($sql);
2985   
2986      while ($row = $db->sql_fetchrow($result))
2987      {
2988          $warning_list[] = $row['warning_id'];
2989          $user_list[$row['user_id']] = isset($user_list[$row['user_id']]) ? ++$user_list[$row['user_id']] : 1;
2990      }
2991      $db->sql_freeresult($result);
2992   
2993      if (sizeof($warning_list))
2994      {
2995          $db->sql_transaction('begin');
2996   
2997          $sql = 'DELETE FROM ' . WARNINGS_TABLE . '
2998              WHERE ' . $db->sql_in_set('warning_id', $warning_list);
2999          $db->sql_query($sql);
3000   
3001          foreach ($user_list as $user_id => $value)
3002          {
3003              $sql = 'UPDATE ' . USERS_TABLE . " SET user_warnings = user_warnings - $value
3004                  WHERE user_id = $user_id";
3005              $db->sql_query($sql);
3006          }
3007   
3008          $db->sql_transaction('commit');
3009      }
3010   
3011      $config->set('warnings_last_gc', time(), false);
3012  }
3013   
3014  /**
3015  * Tidy database, doing some maintanance tasks
3016  */
3017  function tidy_database()
3018  {
3019      global $config, $db;
3020   
3021      // Here we check permission consistency
3022   
3023      // Sometimes, it can happen permission tables having forums listed which do not exist
3024      $sql = 'SELECT forum_id
3025          FROM ' . FORUMS_TABLE;
3026      $result = $db->sql_query($sql);
3027   
3028      $forum_ids = array(0);
3029      while ($row = $db->sql_fetchrow($result))
3030      {
3031          $forum_ids[] = $row['forum_id'];
3032      }
3033      $db->sql_freeresult($result);
3034   
3035      // Delete those rows from the acl tables not having listed the forums above
3036      $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
3037          WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
3038      $db->sql_query($sql);
3039   
3040      $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
3041          WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
3042      $db->sql_query($sql);
3043   
3044      $config->set('database_last_gc', time(), false);
3045  }
3046   
3047  /**
3048  * Add permission language - this will make sure custom files will be included
3049  */
3050  function add_permission_language()
3051  {
3052      global $user, $phpEx, $phpbb_extension_manager;
3053   
3054      // add permission language files from extensions
3055      $finder = $phpbb_extension_manager->get_finder();
3056   
3057      $lang_files = $finder
3058          ->prefix('permissions_')
3059          ->suffix(".$phpEx")
3060          ->core_path('language/')
3061          ->extension_directory('/language')
3062          ->find();
3063   
3064      foreach ($lang_files as $lang_file => $ext_name)
3065      {
3066          if ($ext_name === '/')
3067          {
3068              $user->add_lang($lang_file);
3069          }
3070          else
3071          {
3072              $user->add_lang_ext($ext_name, $lang_file);
3073          }
3074      }
3075  }
3076   
3077  /**
3078   * Enables a particular flag in a bitfield column of a given table.
3079   *
3080   * @param string    $table_name        The table to update
3081   * @param string    $column_name    The column containing a bitfield to update
3082   * @param int        $flag            The binary flag which is OR-ed with the current column value
3083   * @param string    $sql_more        This string is attached to the sql query generated to update the table.
3084   *
3085   * @return null
3086   */
3087  function enable_bitfield_column_flag($table_name, $column_name, $flag, $sql_more = '')
3088  {
3089      global $db;
3090   
3091      $sql = 'UPDATE ' . $table_name . '
3092          SET ' . $column_name . ' = ' . $db->sql_bit_or($column_name, $flag) . '
3093          ' . $sql_more;
3094      $db->sql_query($sql);
3095  }
3096