/** * Move topic or parts of it into another category or topic. * * @param object $target Target KunenaForumCategory or KunenaForumTopic * @param mixed $ids false, array of message Ids or JDate * @param bool $shadow Leave visible shadow topic. * @param string $subject New subject * @param bool $subjectall Change subject from every message * @param int $topic_iconid Define a new topic icon * * @return bool|KunenaForumCategory|KunenaForumTopic Target KunenaForumCategory or KunenaForumTopic or false on failure */ public function move($target, $ids = false, $shadow = false, $subject = '', $subjectall = false, $topic_iconid = null) { // Warning: logic in this function is very complicated and even with full understanding its easy to miss some details! // Clear authentication cache $this->_authfcache = $this->_authccache = $this->_authcache = array(); // Cleanup input if (!$ids instanceof JDate) { if (!is_array($ids)) { $ids = explode(',', (string) $ids); } $mesids = array(); foreach ($ids as $id) { $mesids[(int) $id] = (int) $id; } unset($mesids[0]); $ids = implode(',', $mesids); } $subject = (string) $subject; // First we need to check if there will be messages left in the old topic if ($ids) { $query = new KunenaDatabaseQuery(); $query->select('COUNT(*)')->from('#__kunena_messages')->where("thread={$this->id}"); if ($ids instanceof JDate) { // All older messages will remain (including unapproved, deleted) $query->where("time<{$ids->toUnix()}"); } else { // All messages that were not selected will remain $query->where("id NOT IN ({$ids})"); } $this->_db->setQuery($query); $oldcount = (int) $this->_db->loadResult(); if ($this->_db->getErrorNum()) { $this->setError($this->_db->getError()); return false; } // So are we moving the whole topic? if (!$oldcount) { $ids = ''; } } $categoryFrom = $this->getCategory(); // Find out where we are moving the messages if (!$target || !$target->exists()) { $this->setError(JText::printf('COM_KUNENA_MODERATION_ERROR_NO_TARGET', $this->id)); return false; } elseif ($target instanceof KunenaForumTopic) { // Move messages into another topic (original topic will always remain, either as real one or shadow) if ($target == $this) { // We cannot move topic into itself $this->setError(JText::sprintf('COM_KUNENA_MODERATION_ERROR_SAME_TARGET_THREAD', $this->id, $this->id)); return false; } if ($this->moved_id) { // Moved topic cannot be merged with another topic -- it has no posts to be moved $this->setError(JText::sprintf('COM_KUNENA_MODERATION_ERROR_ALREADY_SHADOW', $this->id)); return false; } if ($this->poll_id && $target->poll_id) { // We cannot currently have 2 polls in one topic -- fail $this->setError(JText::_('COM_KUNENA_MODERATION_CANNOT_MOVE_TOPIC_WITH_POLL_INTO_ANOTHER_WITH_POLL')); return false; } if ($subjectall) { $subject = $target->subject; } } elseif ($target instanceof KunenaForumCategory) { // Move messages into category if ($target->isSection()) { // Section cannot have any topics $this->setError(JText::_('COM_KUNENA_MODERATION_ERROR_NOT_MOVE_SECTION')); return false; } // Save category information for later use $categoryTarget = $target; if ($this->moved_id) { // Move shadow topic and we are done $this->category_id = $categoryTarget->id; if ($subject) { $this->subject = $subject; } $this->save(false); return $target; } if ($shadow || $ids) { // Create new topic for the moved messages $target = clone $this; $target->exists(false); $target->id = 0; $target->hits = 0; $target->params = ''; } else { // If we just move into another category, we can keep using the old topic $target = $this; } // Did user want to change the subject? if ($subject) { $target->subject = $subject; } // Did user want to change the topic icon? if (!is_null($topic_iconid)) { $target->icon_id = $topic_iconid; } // Did user want to change category? $target->category_id = $categoryTarget->id; } else { $this->setError(JText::_('COM_KUNENA_MODERATION_ERROR_WRONG_TARGET')); return false; } // For now on we assume that at least one message will be moved (=authorization check was called on topic/message) // We will soon need target topic id, so save if it doesn't exist if (!$target->exists()) { if (!$target->save(false)) { $this->setError($target->getError()); return false; } } // Move messages (set new category and topic) $query = new KunenaDatabaseQuery(); $query->update('#__kunena_messages')->set("catid={$target->category_id}")->set("thread={$target->id}")->where("thread={$this->id}"); // Did we want to change subject from all the messages? if ($subjectall && !empty($subject)) { $query->set("subject={$this->_db->quote($subject)}"); } if ($ids instanceof JDate) { // Move all newer messages (includes unapproved, deleted messages) $query->where("time>={$ids->toUnix()}"); } elseif ($ids) { // Move individual messages $query->where("id IN ({$ids})"); } $this->_db->setQuery($query); $this->_db->query(); if ($this->_db->getErrorNum()) { $this->setError($this->_db->getError()); return false; } // Make sure that all messages in topic have unique time (deterministic without ORDER BY time, id) $query = "SET @ktime:=0"; $this->_db->setQuery($query); $this->_db->query(); if ($this->_db->getErrorNum()) { $this->setError($this->_db->getError()); return false; } $query = "UPDATE #__kunena_messages SET time=IF(time<=@ktime,@ktime:=@ktime+1,@ktime:=time) WHERE thread={$target->id} ORDER BY time ASC, id ASC"; $this->_db->setQuery($query); $this->_db->query(); if ($this->_db->getErrorNum()) { $this->setError($this->_db->getError()); return false; } // If all messages were moved into another topic, we need to move poll as well if ($this->poll_id && !$ids && $target != $this) { // Note: We may already have saved cloned target (having poll_id already in there) $target->poll_id = $this->poll_id; // Note: Do not remove poll from shadow: information could still be used to show icon etc $query = "UPDATE #__kunena_polls SET `threadid`={$this->_db->Quote($target->id)} WHERE `threadid`={$this->_db->Quote($this->id)}"; $this->_db->setQuery($query); $this->_db->query(); if ($this->_db->getErrorNum()) { $this->setError($this->_db->getError()); return false; } } // When moving only first message keep poll only on target topic if ($this->poll_id && $target != $this && $ids) { if ($ids && $this->first_post_id) { $this->poll_id = 0; } } if (!$ids && $target != $this) { // Leave shadow from old topic $this->moved_id = $target->id; if (!$shadow) { // Mark shadow topic as deleted $this->hold = 2; } } // Note: We already saved possible target earlier, now save only $this if (!$this->save(false)) { return false; } if (!$ids && !empty($categoryTarget)) { // Move topic into another category // Update user topic information (topic, category) KunenaForumTopicUserHelper::move($this, $target); // TODO: do we need this? //KunenaForumTopicUserReadHelper::move($this, $target); // Remove topic and posts from the old category $categoryFrom->update($this, -1, -$this->posts); // Add topic and posts into the new category $categoryTarget->update($target, 1, $this->posts); } elseif (!$ids) { // Moving topic into another topic // Add new posts, hits and attachments into the target topic $target->posts += $this->posts; $target->hits += $this->hits; $target->attachments += $this->attachments; // Update first and last post information into the target topic $target->updatePostInfo($this->first_post_id, $this->first_post_time, $this->first_post_userid, $this->first_post_message, $this->first_post_guest_name); $target->updatePostInfo($this->last_post_id, $this->last_post_time, $this->last_post_userid, $this->last_post_message, $this->last_post_guest_name); // Save target topic if (!$target->save(false)) { $this->setError($target->getError()); return false; } // Update user topic information (topic, category) KunenaForumTopicUserHelper::merge($this, $target); // TODO: do we need this? //KunenaForumTopicUserReadHelper::merge($this, $target); // Remove topic and posts from the old category $this->getCategory()->update($this, -1, -$this->posts); // Add posts into the new category $target->getCategory()->update($target, 0, $this->posts); } else { // Both topics have changed and we have no idea how much: force full recount // TODO: we can do this faster.. $this->recount(); $target->recount(); } return $target; }