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