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