Skip to main content
Topic: RemoveTopics() (Read 2800 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

RemoveTopics()

Here's a look in to my scatter brain. I am about to go out so I didn't finish it, but I wanted to give the start to you guys so I don't forget.

https://gist.github.com/joshuaadickerson/9025761
Code: [Select]
<?php

/**
 * Removes the passed id_topic's.
 * Permissions are NOT checked here because the function is used in a scheduled task
 *
 * @param int[]|int $topic_ids The topics to remove (can be an id or an array of ids).
 * @param bool $decreasePostCount if true users' post count will be reduced
 * @param bool $ignoreRecycling if true topics are not moved to the recycle board (if it exists).
 */
function removeTopics($topic_ids, $decreasePostCount = true, $ignoreRecycling = false)
{
global $modSettings;

$db = database();

// Nothing to do?
if (empty($topic_ids))
return;

// Only a single topic.
if (is_numeric($topic_ids))
$topic_ids = array($topic_ids);

$do_recycle = !$ignoreRecycling && !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0;

$request = $db->query('', '
SELECT id_topic, id_board, approved, unapproved_posts, id_poll, num_replies, b.count_posts
FROM {db_prefix}topics
LEFT JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

$topics = array();
$polls = array();
$boards = array();
$recycle_topics = array();
while ($row = $db->fetch_assoc($request))
{
$topics[] = $row;

// All of the board info
if (!isset($boards[$row['id_board']]))
{
$boards[$row['id_board']] = array(
'unapproved_posts' => 0,
'approved_topics' => 0,
'unapproved_topics' => 0,
'num_posts' => 0,
'num_topics' => 0,
'count_posts' => (bool) $row['count_posts'],
);
}
$boards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
$boards[$row['id_board']]['posts'] += $row['num_replies'] + 1;
$boards[$row['id_board']]['num_topics']++;

if ((bool) $row['approved'])
{
$boards[$row['id_board']]['approved_topics']++;
}
else
{
$boards[$row['id_board']]['unapproved_topics']++;
}

// These topics will be recycled
if ($do_recycle && $row['id_board'] != $modSettings['recycle_board'])
{
$recycle_topics[] = $row['id_board'];
}

// Get all of the polls
if (!empty($row['id_poll']))
{
$polls[] = $row['id_poll'];
}
}

// @todo check if we have any topics or should we leave it like this so we can make sure to get rid of any orphaned data?

// @todo move to Members.subs.php -> adjustPostCount()
// Decrease the post counts for members.
if ($decreasePostCount)
{
$messages = anotherGetTopicFunction($topic_ids);
$members = getMemberAdjustmentsFromMessageInfo($messages, $boards);

foreach ($members as $id_mem => $member)
{

}
if ($db->num_rows($requestMembers) > 0)
{
while ($rowMembers = $db->fetch_assoc($requestMembers))
updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $member['approved_posts']));
}
}

// @todo move to recycleTopics()
// Recycle topics that aren't in the recycle board...
if ($do_recycle)
{
recycleTopics($topic_ids);

// Still topics left to delete?
if (empty($topic_ids))
return;
}

// @todo move to Boards.subs.php -> adjustCounts()
// Decrease number of posts and topics for each board.
require_once(SUBSDIR . '/Boards.subs.php');
adjustBoardCounts($boards);

// Remove the polls
if (!empty($polls))
{
require_once(SUBSDIR . '/Polls.subs.php');
removePoll($polls);
}

// Get rid of the attachment(s).
require_once(SUBSDIR . '/ManageAttachments.subs.php');
$attachmentQuery = array(
'attachment_type' => 0,
'id_topic' => $topics,
);
removeAttachments($attachmentQuery, 'messages');

// @todo move to Search.subs.php
// Delete search index entries.
if (!empty($modSettings['search_custom_index_config']))
{
require_once(SUBSDIR . '/Search.subs.php');
//removeSearchWordsMessagesByTopics($topic_ids);
// @todo pretty sure this is the better way
if (empty($messages))
{
$messages = anotherGetTopicMessagesFunction($topic_ids);
}
removeSearchLogByMessages(array_keys($messages));
}

// @todo move to Likes.subs.php or Messages.subs.php?
// Remove all likes now that the topic is gone
$db->query('', '
DELETE FROM {db_prefix}message_likes
WHERE id_msg IN ({array_int:messages})',
array(
'messages' => array_keys($messages),
)
);

// @todo move to Mentions.subs.php
// Remove all mentions now that the topic is gone
$db->query('', '
DELETE FROM {db_prefix}log_mentions
WHERE id_msg IN ({array_int:messages})',
array(
'messages' => array_keys($messages),
)
);

// @todo move to Messages.subs.php
// Delete messages in each topic.
$db->query('', '
DELETE FROM {db_prefix}messages
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

// Remove linked calendar events.
// @todo if unlinked events are enabled, wouldn't this be expected to keep them?
$db->query('', '
DELETE FROM {db_prefix}calendar
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

// Delete log_topics data
$db->query('', '
DELETE FROM {db_prefix}log_topics
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

// @todo move to Notifications.subs.php
// Delete notifications
$db->query('', '
DELETE FROM {db_prefix}log_notify
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

// Delete the topics themselves
$db->query('', '
DELETE FROM {db_prefix}topics
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);

// @todo move to Search.subs.php
// Remove data from the subjects for search cache
$db->query('', '
DELETE FROM {db_prefix}log_search_subjects
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topic_ids,
)
);
require_once(SUBSDIR . '/FollowUps.subs.php');
removeFollowUpsByTopic($topics);

// Maybe there's an addon that wants to delete topic related data of its own
call_integration_hook('integrate_remove_topics', array('topics' => $topics, 'messages' => $messages, 'messages' => $members));

// Update the totals...
updateStats('message');
updateTopicStats();
updateSettings(array(
'calendar_updated' => time(),
));

require_once(SUBSDIR . '/Post.subs.php');
$updates = array();
foreach ($boards as $id_board => $stats)
$updates[] = $id_board;

updateLastMessages($updates);
}

function recycleTopics(array $topic_ids)
{
$db = database();

$request = $db->query('', '
SELECT id_topic, id_board, unapproved_posts, approved
FROM {db_prefix}topics
WHERE id_topic IN ({array_int:topics})
AND id_board != {int:recycle_board}
LIMIT ' . count($topics),
array(
'recycle_board' => $modSettings['recycle_board'],
'topics' => $topic_ids,
)
);

if ($db->num_rows($request) > 0)
{
// Get topics that will be recycled.
$recycleTopics = array();
while ($row = $db->fetch_assoc($request))
{
if (function_exists('apache_reset_timeout'))
@apache_reset_timeout();

$recycleTopics[] = $row['id_topic'];

// Set the id_previous_board for this topic - and make it not sticky.
$db->query('', '
UPDATE {db_prefix}topics
SET id_previous_board = {int:id_previous_board}, is_sticky = {int:not_sticky}
WHERE id_topic = {int:id_topic}',
array(
'id_previous_board' => $row['id_board'],
'id_topic' => $row['id_topic'],
'not_sticky' => 0,
)
);
}
$db->free_result($request);

// @todo move to Messages.subs.php recycleMessages()
// Mark recycled topics as recycled.
$db->query('', '
UPDATE {db_prefix}messages
SET icon = {string:recycled}
WHERE id_topic IN ({array_int:recycle_topics})',
array(
'recycle_topics' => $recycleTopics,
'recycled' => 'recycled',
)
);

// Move the topics to the recycle board.
require_once(SUBSDIR . '/Topic.subs.php');
moveTopics($recycleTopics, $modSettings['recycle_board']);

// Close reports that are being recycled.
require_once(SUBSDIR . '/Moderation.subs.php');

$db->query('', '
UPDATE {db_prefix}log_reported
SET closed = {int:is_closed}
WHERE id_topic IN ({array_int:recycle_topics})',
array(
'recycle_topics' => $recycleTopics,
'is_closed' => 1,
)
);

updateSettings(array('last_mod_report_action' => time()));
recountOpenReports();

// Topics that were recycled don't need to be deleted, so subtract them.
$topic_ids = array_diff($topic_ids, $recycleTopics);
}
else
$db->free_result($request);

return $topic_ids;
}

function removeSearchWordsByTopics($topics)
{
global $modSettings;

$db = database();

$customIndexSettings = unserialize($modSettings['search_custom_index_config']);

$words = array();
$messages = array();

// @todo Pretty sure this isn't needed. We're deleting the entire topic, so why worry about individual words?
//       Just delete all of the messages, right?
$request = $db->query('', '
SELECT id_msg, body
FROM {db_prefix}messages
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topics,
)
);
while ($row = $db->fetch_assoc($request))
{
if (function_exists('apache_reset_timeout'))
@apache_reset_timeout();

$words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true));
$messages[] = $row['id_msg'];
}
$db->free_result($request);
$words = array_unique($words);

if (!empty($words) && !empty($messages))
{
// @todo I think that id_word IN() isn't needed, so I removed it.
$db->query('', '
DELETE FROM {db_prefix}log_search_words
WHERE id_msg IN ({array_int:message_list})',
array(
'word_list' => $words,
'message_list' => $messages,
)
);
}
}

/**
 *
 * @param array $messages
 */
function removeSearchLogByMessages(array $messages)
{
$db = database();

$db->query('', '
DELETE FROM {db_prefix}log_search_words
WHERE id_msg IN ({array_int:message_list})',
array(
'message_list' => $messages,
)
);
}

function anotherGetTopicMessagesFunction(array $topics)
{
$db = database();

$messages = array();
$request = $db->query('', '
SELECT id_msg, id_member, icon, approved
FROM {db_prefix}messages
WHERE id_topic IN ({array_int:topics})',
array(
'topics' => $topics,
)
);

while ($row = $db->fetch_assoc($request))
{
$messages[$row['id_msg']] = $row;
}

return $messages;
}

function getMemberAdjustmentsFromMessageInfo(array $messages, array $boards)
{
$members = array();

foreach ($messages as $message)
{
$id_mem = $message['id_member'];
if (!isset($members[$id_mem]))
{
$members[$id_mem] = array(
'messages' => 0,
'approved_messages' => 0,
'unapproved_messages' => 0,
'adjustment' => 0,
);
}
$members[$id_mem] = array(
'messages' => $members[$id_mem]['messages'] + 1,
'approved_messages' => (bool) $message['approved'] ? $members[$id_mem]['approved_messages'] + 1 : $members[$id_mem]['approved_messages'],
'unapproved_messages' => (bool) !$message['approved'] ? $members[$id_mem]['unapproved_messages'] + 1 : $members[$id_mem]['unapproved_messages'],
);
}

return $members;
}
/* $requestMembers = $db->query('', '
SELECT m.id_member, COUNT(*) AS posts
FROM {db_prefix}messages AS m
INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
WHERE m.id_topic IN ({array_int:topics})
AND m.icon != {string:recycled}
AND b.count_posts = {int:do_count_posts}
AND m.approved = {int:is_approved}
GROUP BY m.id_member',
array(
'do_count_posts' => 0,
'recycled' => 'recycled',
'topics' => $topic_ids,
'is_approved' => 1,
)
);
 *
 */

// @todo move to Boards.subs.php
function adjustBoardCounts(array $boards)
{
$db = database();

foreach ($boards as $id_board => $stats)
{
if (function_exists('apache_reset_timeout'))
@apache_reset_timeout();

$db->query('', '
UPDATE {db_prefix}boards
SET
num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
WHERE id_board = {int:id_board}',
array(
'id_board' => $id_board,
'num_posts' => $stats['num_posts'],
'num_topics' => $stats['num_topics'],
'unapproved_posts' => $stats['unapproved_posts'],
'unapproved_topics' => $stats['unapproved_topics'],
)
);
}
}

It moves a lot of code around and reduces the size of that massive function. It would also reduce a couple queries.
Last Edit: February 16, 2014, 01:23:13 pm by groundup
Come work with me at Promenade Group

Re: RemoveTopics()

Reply #1

Ohhh that function is a pain, thanks! :D

There is also another issue about it, but at the moment I can't find it... To sum it up: deleting a user, topics in the recycle are deleted permanently, while other topics are moved to the recycle bin. So there is an inconsistency.
Bugs creator.
Features destroyer.
Template killer.


Re: RemoveTopics()

Reply #3

Quote from: emanuele – There is also another issue about it, but at the moment I can't find it... To sum it up: deleting a user, topics in the recycle are deleted permanently, while other topics are moved to the recycle bin. So there is an inconsistency.
I think when you delete a user it should use the $ignoreRecycle parameter.

btw, this isn't workable. It's just more so the concept of it. Pretty sure the queries don't work (it's been a while since I worked with SQL). The part that adjusts the users' posts isn't hashed out.

Re: RemoveTopics()

Reply #4

That is an awesome start, thank you!

Makes me want to get a 1.1 branch going so we can start working on the next round of refactoring :D

Re: RemoveTopics()

Reply #5

 emanuele haz already started, but you already know. :P
Bugs creator.
Features destroyer.
Template killer.