[ Index ]

PHP Cross Reference of phpBB-3.1.12-deutsch

title

Body

[close]

/includes/ -> functions_admin.php (source)

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


Generated: Thu Jan 11 00:25:41 2018 Cross-referenced by PHPXref 0.7.1