[ Index ]

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


Generated: Sun Feb 19 19:47:08 2017 Cross-referenced by PHPXref 0.7.1