<?php

/**
 * Kunena Component
 *
 * @package         Kunena.Site
 * @subpackage      Controllers
 *
 * @copyright       Copyright (C) 2008 - 2026 Kunena Team. All rights reserved.
 * @license         https://www.gnu.org/copyleft/gpl.html GNU/GPL
 * @link            https://www.kunena.org
 **/

namespace Kunena\Forum\Site\Controllers;

\defined('_JEXEC') or die();

use Exception;
use Joomla\CMS\Captcha\Captcha;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\Transport\StreamTransport;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\MailHelper;
use Joomla\CMS\Mail\MailerFactoryInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
use Kunena\Forum\Libraries\Access\KunenaAccess;
use Kunena\Forum\Libraries\Attachment\KunenaAttachment;
use Kunena\Forum\Libraries\Attachment\KunenaAttachmentHelper;
use Kunena\Forum\Libraries\Controller\KunenaController;
use Kunena\Forum\Libraries\Email\KunenaEmail;
use Kunena\Forum\Libraries\Error\KunenaError;
use Kunena\Forum\Libraries\Factory\KunenaFactory;
use Kunena\Forum\Libraries\Forum\Category\KunenaCategoryHelper;
use Kunena\Forum\Libraries\Forum\KunenaForum;
use Kunena\Forum\Libraries\Forum\Message\KunenaMessage;
use Kunena\Forum\Libraries\Forum\Message\KunenaMessageHelper;
use Kunena\Forum\Libraries\Forum\Message\Thankyou\KunenaMessageThankyouHelper;
use Kunena\Forum\Libraries\Forum\Topic\KunenaTopicHelper;
use Kunena\Forum\Libraries\Forum\Topic\Rate\KunenaRateHelper;
use Kunena\Forum\Libraries\Html\KunenaParser;
use Kunena\Forum\Libraries\Icons\KunenaSvgIcons;
use Kunena\Forum\Libraries\Image\KunenaImage;
use Kunena\Forum\Libraries\KunenaPrivate\KunenaPrivateMessage;
use Kunena\Forum\Libraries\KunenaPrivate\Message\KunenaPrivateMessageFinder;
use Kunena\Forum\Libraries\Layout\KunenaLayout;
use Kunena\Forum\Libraries\Log\KunenaLog;
use Kunena\Forum\Libraries\Response\KunenaResponseJson;
use Kunena\Forum\Libraries\Route\KunenaRoute;
use Kunena\Forum\Libraries\Template\KunenaTemplate;
use Kunena\Forum\Libraries\Upload\KunenaUpload;
use Kunena\Forum\Libraries\User\KunenaUserHelper;
use RuntimeException;
use stdClass;

/**
 * Kunena Topic Controller
 *
 * @property int catid
 * @property int id
 * @property int mesid
 * @property int return
 *
 * @since   Kunena 2.0
 */
class TopicController extends KunenaController
{
    public $catid;

    public $return;

    public $id;

    public $mesid;

    /**
     * @param   array  $config  config
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function __construct($config = [])
    {
        parent::__construct($config);
        $this->catid  = $this->app->getInput()->getInt('catid', 0);
        $this->return = $this->app->getInput()->getInt('return', $this->catid);
        $this->id     = $this->app->getInput()->getInt('id', 0);
        $this->mesid  = $this->app->getInput()->getInt('mesid', 0);
    }

    /**
     * Get attachments on edit which was attached to a message with AJAX.
     *
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function loadattachments()
    {
        // Only support JSON requests.
        if ($this->input->getWord('format', 'html') != 'json') {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        if (!Session::checkToken('request')) {
            throw new RuntimeException(Text::_('Forbidden'), 403);
        }

        $mes_id      = $this->input->getInt('mes_id', 0);
        $attachments = KunenaAttachmentHelper::getByMessage($mes_id);
        $list        = [];

        foreach ($attachments as $attach) {
            $object            = new stdClass();
            $object->id        = $attach->id;
            $object->size      = round($attach->size / '1024', 0);
            $object->name      = $attach->filename;
            $object->protected = $attach->protected;
            $object->folder    = $attach->folder;
            $object->caption   = $attach->caption;
            $object->type      = $attach->filetype;
            $object->path      = $attach->getUrl();
            $object->image     = $attach->isImage();
            $object->inline    = $attach->isInline();
            $list['files'][]   = $object;
        }

        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo json_encode($list);

        jexit();
    }

    /**
     * Set inline to 1 on the attachment object.
     *
     * @return void
     * @throws Exception
     * @since Kunena 5.1
     */
    public function setinline()
    {
        $attachs_id = $this->input->getString('files_id', '');
        $attachs_id = json_decode($attachs_id);

        if ($attachs_id === null) {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        $attach_ids_final = [];

        foreach ($attachs_id as $attach) {
            if (\is_array($attach)) {
                $attach_ids_final[] = $attach['0'];
            } else {
                $attach_ids_final[] = $attach;
            }
        }

        $instances = KunenaAttachmentHelper::getById($attach_ids_final, 'none');

        $this->changeinline($instances, '1');
    }

    /**
     * Set private on the attachment object.
     *
     * @return void
     * @throws Exception
     * @since Kunena 6.3
     */
    public function setprivate()
    {
        $attachs_id = $this->input->getString('files_id', '');
        $attachs_id = json_decode($attachs_id);

        if ($attachs_id === null) {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        $attach_ids_final = [];

        foreach ($attachs_id as $attach) {
            if (\is_array($attach)) {
                $attach_ids_final[] = $attach['0'];
            } else {
                $attach_ids_final[] = $attach;
            }
        }

        foreach ($attach_ids_final as $id) {
            $attachment = KunenaAttachmentHelper::get($id);
            $attachment->protected = KunenaAttachment::PROTECTION_PRIVATE;
            $attachment->save();
        }
    }

    /**
     * Set inline to 0 or 1 on the attachment object.
     *
     * @return void
     * @throws Exception
     * @since Kunena 5.1
     */
    protected function changeinline($attachments, $inline)
    {
        // Only support JSON requests.
        if ($this->input->getWord('format', 'html') != 'json') {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        if (!Session::checkToken('request')) {
            throw new RuntimeException(Text::_('Forbidden'), 403);
        }

        $response = [];

        if (\is_object($attachments)) {
            $editor_text               = $this->input->get->get('editor_text', '', 'raw');
            $find                      = ['/\[attachment=' . $attachments->id . '\](.*?)\[\/attachment\]/su'];
            $replace                   = '';
            $text                      = preg_replace($find, $replace, $editor_text);
            $response['text_prepared'] = $text;
        } else {
            foreach ($attachments as $instance) {
                $response['result'] = $instance->setInline($inline);
                $response['value']  = $inline;
            }
        }

        unset($attachments);

        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        if (ob_get_length()) {
            ob_end_clean();
        }

        echo json_encode($response);

        jexit();
    }

    /**
     * Set inline to 0 on one attachment object.
     *
     * @return void
     * @throws Exception
     * @since Kunena 5.1
     */
    public function removeinlineonattachment()
    {
        $attach_id = $this->input->getInt('file_id', 0);

        $instance = KunenaAttachmentHelper::get($attach_id);

        $this->checkpermissions($instance->userid);

        $this->changeinline($instance, '0');
    }

    /**
     * Check permissions.
     *
     * @return void
     * @throws Exception
     * @since Kunena 5.1
     */
    protected function checkpermissions($attachment_userid)
    {
        if (KunenaUserHelper::getMyself()->userid != $attachment_userid || !KunenaUserHelper::getMyself()->isAdmin() || !KunenaUserHelper::getMyself()->isModerator()) {
            throw new RuntimeException(Text::_('Forbidden'), 403);
        }
    }

    /**
     * Remove files with AJAX.
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function removeattachments()
    {
        // Only support JSON requests.
        if ($this->input->getWord('format', 'html') != 'json') {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        if (!Session::checkToken('request')) {
            throw new RuntimeException(Text::_('Forbidden'), 403);
        }

        $attach_id = $this->input->getInt('files_id_delete', 0);
        $userid = $this->input->getInt('userid', 0);
        $success   = [];
        $instance  = KunenaAttachmentHelper::get($attach_id);

        if (
            KunenaUserHelper::getMyself()->userid == $userid || KunenaUserHelper::getMyself()->isAdmin()
            || KunenaUserHelper::getMyself()->isModerator()
        ) {
            $editor_text = $this->app->getInput()->get->get('editor_text', '', 'raw');

            $success['text_prepared'] = $instance->removeBBCodeInMessage($editor_text);

            $success['result'] = $instance->delete();
            unset($instance);
        } else {
            throw new RuntimeException(Text::_('Forbidden'), 403);
        }

        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo json_encode($success);

        jexit();
    }

    /**
     * Load categorytemplate from the category with given catid
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function categorytemplate()
    {
        $catid    = $this->app->getInput()->getInt('catid', 0);

        $category = KunenaCategoryHelper::get($catid);

        // Set the MIME type and header for JSON output.
        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo new KunenaResponseJson($category->topictemplate);

        jexit();
    }

    /**
     * Load global rate for the topic
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function loadrate()
    {
        $user = $this->app->getIdentity();

        $topicid  = $this->app->getInput()->get('topic_id', 0, 'int');

        if ($user->id == 0 || KunenaTopicHelper::get($topicid)->first_post_userid == $this->me->userid) {
            $response = KunenaRateHelper::getSelected($topicid);
        } else {
            $response = KunenaRateHelper::getRate($topicid, $user->id);
        }

        // Set the MIME type and header for JSON output.
        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo new KunenaResponseJson($response);

        jexit();
    }

    /**
     * Save rate for user logged in by JSON call
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function setrate()
    {
        $starid   = $this->app->getInput()->get('starid', 0, 'int');
        $topicid  = $this->app->getInput()->get('topic_id', 0, 'int');
        $response = [];
        $user     = KunenaUserHelper::getMyself();

        if ($user->exists() || $this->config->ratingEnabled) {
            $rate           = KunenaRateHelper::get($topicid);
            $rate->rate     = $starid;
            $rate->topic_id = $topicid;

            $response = $rate->save($this->me);

            $selected = KunenaRateHelper::getSelected($topicid);

            $topic         = KunenaTopicHelper::get($topicid);
            $topic->rating = $selected;
            $topic->save();
        }

        // Set the MIME type and header for JSON output.
        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo $response;

        jexit();
    }

    /**
     * Upload files with AJAX.
     *
     * @return  void
     *
     * @throws  null
     * @since   Kunena 6.0
     */
    public function upload()
    {
        // Only support JSON requests.
        if ($this->input->getWord('format', 'html') != 'json') {
            throw new RuntimeException(Text::_('Bad Request'), 400);
        }

        $upload = KunenaUpload::getInstance();

        // We are converting all exceptions into JSON.
        try {
            if (!Session::checkToken('request')) {
                throw new RuntimeException(Text::_('Forbidden'), 403);
            }

            $me    = KunenaUserHelper::getMyself();
            $catid = $this->input->getInt('catid', 0);
            $mesid = $this->input->getInt('mesid', 0);

            if ($mesid) {
                $message = KunenaMessageHelper::get($mesid);
                $message->tryAuthorise('attachment.create');
                $category = $message->getCategory();
            } else {
                $category = KunenaCategoryHelper::get($catid);

                if ($category->id) {
                    if (stripos($this->input->getString('mime'), 'image/') !== false) {
                        $category->tryAuthorise('topic.post.attachment.createimage');
                    } else {
                        $category->tryAuthorise('topic.post.attachment.createfile');
                    }
                }
            }

            $caption = $this->input->getString('caption');
            $options = [
                'filename'   => $this->input->getString('filename'),
                'size'       => $this->input->getInt('size'),
                'mime'       => $this->input->getString('mime'),
                'hash'       => $this->input->getString('hash'),
                'chunkStart' => $this->input->getInt('chunkStart', 0),
                'chunkEnd'   => $this->input->getInt('chunkEnd', 0),
                'image_type' => 'attachment',
            ];

            // Upload!
            $upload->addExtensions(KunenaAttachmentHelper::getExtensions($category->id, $me->userid));
            $response = (object) $upload->ajaxUpload($options);

            if (!empty($response->completed)) {
                // We have it all, lets create the attachment.
                $uploadFile = $upload->getProtectedFile();
                list($basename, $extension) = $upload->splitFilename();
                $attachment = new KunenaAttachment();
                $attachment->bind(
                    [
                        'mesid'         => 0,
                        'userid'        => (int) $me->userid,
                        'protected'     => null,
                        'hash'          => $response->hash,
                        'size'          => $response->size,
                        'folder'        => null,
                        'filetype'      => $response->mime,
                        'filename'      => null,
                        'filename_real' => $response->filename,
                        'caption'       => $caption,
                        'inline'        => null,
                    ]
                );

                // Resize image if needed.
                if ($attachment->isImage()) {
                    $imageInfo = KunenaImage::getImageFileProperties($uploadFile);

                    if ($imageInfo->width > $this->config->imageWidth || $imageInfo->height > $this->config->imageHeight) {
                        // Calculate quality for both JPG and PNG.
                        $quality = $this->config->imageQuality;

                        if ($quality < 1 || $quality > 100) {
                            $quality = 70;
                        }

                        if ($imageInfo->type == IMAGETYPE_PNG) {
                            $quality = \intval(($quality - 1) / 10);
                        }

                        $image = new KunenaImage($uploadFile);
                        $image->correctImageOrientation();
                        $image->resize($this->config->imageWidth, $this->config->imageHeight, false);

                        $options = ['quality' => $quality];
                        $image->toFile($uploadFile, $imageInfo->type, $options);

                        unset($image);

                        $attachment->hash = md5_file($uploadFile);
                        $attachment->size = fileSize($uploadFile);
                    }
                }

                $attachment->saveFile($uploadFile, $basename, $extension, true);

                // Set id and override response variables just in case if attachment was modified.
                $response->id       = $attachment->id;
                $response->hash     = $attachment->hash;
                $response->size     = $attachment->size;
                $response->mime     = $attachment->filetype;
                $response->filename = $attachment->filename_real;
                $response->inline   = $attachment->inline;
            }
        } catch (Exception $response) {
            $upload->cleanup();

            // Use the exception as the response.
        }

        header('Content-type: application/json');
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");

        while (@ob_end_clean()) {
        }

        echo new KunenaResponseJson($response);

        jexit();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function post()
    {
        $this->id = $this->app->getInput()->getInt('parentid', 0);
        $fields   = [
            'catid'             => $this->catid,
            'name'              => $this->app->getInput()->getString('authorname', $this->me->getName()),
            'email'             => $this->app->getInput()->getString('email', null),
            'subject'           => $this->app->getInput()->post->get('subject', '', 'raw'),
            'message'           => $this->app->getInput()->post->get('message', '', 'raw'),
            'icon_id'           => $this->app->getInput()->getInt('topic_emoticon', null),
            'anonymous'         => $this->app->getInput()->getInt('anonymous', 0),
            'poll_title'        => $this->app->getInput()->getString('poll_title', ''),
            'poll_options'      => $this->app->getInput()->post->get('polloptionsID', [], 'array'),
            'poll_time_to_live' => $this->app->getInput()->getString('poll_time_to_live', 0),
            'subscribe'         => $this->app->getInput()->getInt('subscribeMe', 0),
            'private'           => (string) $this->app->getInput()->getRaw('message_private'),
            'rating'            => 0,
            'params'            => '',
            'quote'             => 0,
        ];

        $this->app->setUserState('com_kunena.postfields', $fields);

        if (!Session::checkToken('post')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        if (!$this->catid) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ACTION_NO_CATEGORY_SELECTED'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Need to do to the replacement in case of VBA code is inserted without the bbcode tags [code][/code]
        $fields['message'] = preg_replace('~"",""~', '"", ""', $fields['message']);


        if (!$this->id) {
            // Create topic
            $category = KunenaCategoryHelper::get($this->catid);

            try {
                $category->isAuthorised('topic.create');
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
                $this->setRedirectBack();

                return;
            }

            list($topic, $message) = $category->newTopic($fields);
        } else {
            // Reply topic
            $parent = KunenaMessageHelper::get($this->id);

            try {
                $parent->isAuthorised('reply');
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
                $this->setRedirectBack();

                return;
            }

            list($topic, $message) = $parent->newReply($fields);
            $category = $topic->getCategory();
        }

        if ($this->me->canDoCaptcha()) {
            $app    = Factory::getApplication();
            $plugin = $app->get('captcha', '0');

            if ($plugin !== 0 && $plugin !== '0' && $plugin !== '' && $plugin !== null && PluginHelper::isEnabled('captcha', $plugin)) {
                try {
                    $id      = 'kunena_captcha';
                    $captcha = Captcha::getInstance((string) $plugin, ['namespace' => $id]);

                    if (!$captcha->checkAnswer(\null)) {
                        $this->setRedirectBack();

                        return;
                    }
                } catch (\RuntimeException $e) {
                    $app->enqueueMessage($e->getMessage(), 'error');

                    $this->setRedirectBack();

                    return;
                }
            }
        }

        $isNew = !$topic->exists();

        // Redirect to full reply instead.
        if ($this->app->getInput()->getString('fullreply')) {
            $this->setRedirect(KunenaRoute::_("index.php?option=com_kunena&view=topic&layout=reply&catid={$fields->catid}&id={$parent->getTopic()->id}&mesid={$parent->id}", false));

            return;
        }

        // Flood protection
        if ($this->config->floodProtection && !$this->me->isModerator($category) && $isNew) {
            $timelimit = Factory::getDate()->toUnix() - $this->config->floodProtection;
            $ip        = KunenaUserHelper::getUserIp();

            $query = $this->db->createQuery();
            $query->select('COUNT(*)')
                ->from($this->db->quoteName('#__kunena_messages'))
                ->where('ip=' . $this->db->quote($ip) . ' AND time > ' . $this->db->quote($timelimit));
            $this->db->setQuery($query);

            try {
                $count = $this->db->loadResult();
            } catch (ExecutionFailureException $e) {
                KunenaError::displayDatabaseError($e);
            }

            if ($count) {
                $this->app->enqueueMessage(Text::sprintf('COM_KUNENA_POST_TOPIC_FLOOD', $this->config->floodProtection), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        // Ignore identical for 5 minutes
        $duplicatetimewindow = Factory::getDate()->toUnix() - 1 * 60;
        $lastTopic           = $topic->getCategory()->getLastTopic();

        if (
            $lastTopic->subject == $topic->subject && $lastTopic->last_post_time >= $duplicatetimewindow
            && $lastTopic->category_id == $topic->category_id && $lastTopic->last_post_id == $topic->last_post_id
            && $lastTopic->id == $topic->id && $lastTopic->last_post_message == $message->message
        ) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_DUPLICATE_IGNORED'), 'error');

            return $this->setRedirect(KunenaRoute::_("index.php?option=com_kunena&view=topic&catid={$topic->getCategory()->id}&id={$lastTopic->id}&mesid={$lastTopic->last_post_id}", false));
        }

        // Set topic icon if permitted
        if ($this->config->topicIcons && isset($fields['icon_id']) && $topic->isAuthorised('edit', null, false)) {
            $topic->icon_id = $fields['icon_id'];
        }

        // Check for guest user if the IP, username or email are blacklisted
        if ($message->getCategory()->allowAnonymous && !$this->me->userid) {
            if ($this->checkIfBlacklisted($message)) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_MESSAGES_ERROR_BALCKLISTED'), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        // Remove IP address
        if (!$this->config->ipTracking) {
            $message->ip = '';
        }

        // If requested: Make message to be anonymous
        if ($fields['anonymous'] && $message->getCategory()->allowAnonymous) {
            $message->makeAnonymous();
        }

        // If configured: Hold posts from guests
        if (!$this->me->userid && $this->config->holdGuestPosts) {
            $message->hold = 1;
        }

        // If configured: Hold posts from users
        if ($this->me->userid && !$this->me->isModerator($category) && $this->me->posts < $this->config->holdNewUsersPosts) {
            $message->hold = 1;
        }

        // Prevent user abort from this point in order to maintain data integrity.
        @ignore_user_abort(true);

        // Mark attachments to be added or deleted.
        $attachments = $this->app->getInput()->get('attachments', [], 'post', 'array');
        $attachment  = $this->app->getInput()->get('attachment', [], 'post', 'array');
        $message->addAttachments(array_keys(array_intersect_key($attachments, $attachment)));
        $message->removeAttachments(array_keys(array_diff_key($attachments, $attachment)));

        // Upload new attachments
        foreach ($_FILES as $key => $file) {
            $intkey = 0;

            if (preg_match('/\D*(\d+)/', $key, $matches)) {
                $intkey = (int) $matches[1];
            }

            if ($file['error'] != UPLOAD_ERR_NO_FILE) {
                $message->uploadAttachment($intkey, $key, $this->catid);
            }
        }

        if ($this->config->urlSubjectTopic) {
            $url_subject = $this->checkURLInSubject($message->subject);

            if ($url_subject) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_MESSAGES_ERROR_URL_IN_SUBJECT'), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        if (!$this->me->checkUserAllowedLinksImages()) {
            $message->message = $this->removeLinksInMessage($message->message);

            if (!$message->message) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_MESSAGE_EMPTY_LINKS_IMAGES_REMOVED_NOT_ALLOWED'), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        // Make sure that message has visible content (text, images or objects) to be shown.
        $text = KunenaParser::parseBBCode($message->message);

        if (!preg_match('!(<img |<object |<iframe )!', $text)) {
            $text = trim(OutputFilter::cleanText($text));
        }

        if (!$text) {
            if (trim($fields['private'])) {
                // Allow empty message if private message part has been filled up by setting a timestapm to avoid the identical message error.
                $date = new Date();
                $message->message = trim($message->message) ? $message->message : "[PRIVATE={$message->userid}{$date->getTimestamp()}]";
            } else {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_LIB_TABLE_MESSAGES_ERROR_NO_MESSAGE'), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        $maxlinks = $this->checkMaxLinks($text, $topic);

        if (!$maxlinks) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_SPAM_LINK_PROTECTION'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Activity integration
        $activity = KunenaFactory::getActivityIntegration();

        if ($message->hold == 0) {
            if (!$topic->exists()) {
                $activity->onBeforePost($message);
            } else {
                $activity->onBeforeReply($message);
            }
        } else {
            $activity->onBeforeHold($message);
        }

        // Save message
        try {
            $message->save();
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
            $this->setRedirectBack();
        }

        // Save IP address of user
        if ($this->config->ipTracking) {
            $this->me->ip = $message->ip;
            $this->me->save();
        }

        if ($this->me->isModerator($category) && $this->config->logModeration) {
            KunenaLog::log(
                KunenaLog::TYPE_ACTION,
                $isNew ? KunenaLog::LOG_TOPIC_CREATE : KunenaLog::LOG_POST_CREATE,
                ['mesid' => $message->id, 'parentid' => $this->id],
                $category,
                $topic
            );
        }

        // Message has been sent, we can now clear saved form
        $this->app->setUserState('com_kunena.postfields', null);

        // Create Poll
        $poll_title   = $fields['poll_title'];
        $poll_options = $fields['poll_options'];

        if (!empty($poll_options) && !empty($poll_title)) {
            try {
                $topic->isAuthorised('poll.create', null, false);
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
            }

            $poll        = $topic->getPoll();
            $poll->title = $poll_title;

            if (!empty($fields['poll_time_to_live'])) {
                $polltimetolive       = new Date($fields['poll_time_to_live']);
                $poll->polltimetolive = $polltimetolive->toSql();
            }

            $poll->setOptions($poll_options);

            try {
                $poll->save();
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
            }

            $topic->poll_id = $poll->id;

            try {
                $topic->save();
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POLL_CREATED'), 'success');
        }

        // Removed orphaned attachments (one has added on the form and removed after before to submit)        
        $this->removeOrphanedAttachments();

        // Post Private message
        try {
            $this->postPrivate($message);
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        $message->sendNotification();

        // Now try adding any new subscriptions if asked for by the poster
        $usertopic = $topic->getUserTopic();

        if ($fields['subscribe'] && !$usertopic->subscribed) {
            try {
                $topic->subscribe(1);

                $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUBSCRIBED_TOPIC'), 'success');

                // Activity integration
                $activity = KunenaFactory::getActivityIntegration();
                $activity->onAfterSubscribe($topic, 1);
            } catch (Exception $e) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_NO_SUBSCRIBED_TOPIC') . ' ' . $e->getMessage(), 'error');
            }
        }

        if ($message->hold == 1) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUCCES_REVIEW'), 'success');
        } else {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUCCESS_POSTED'), 'success');
        }

        $category = KunenaCategoryHelper::get($this->return);

        if ($message->isAuthorised('read', null, false) && $this->id) {
            $this->setRedirect($message->getUrl($category, false));
        } elseif ($topic->isAuthorised('read', null, false)) {
            $this->setRedirect($topic->getUrl($category, false));
        } else {
            $this->setRedirect($category->getUrl(null, false));
        }
    }

    /**    
     * Remove orhpaned attachments when submit (which have mesid=0).
     *    
     * @return  void
     *    
     * @throws  Exception
     * 
     * @since   Kunena 6.4    
     */
    public function removeOrphanedAttachments()
    {
        $params['file'] = '1';
        $params['image'] = '1';

        $attachments = KunenaAttachmentHelper::getByUserid($this->me, $params);

        if (!empty($attachments)) {
            foreach ($attachments as $attachment) {
                if ($attachment->mesid == 0) {
                    $attachment->delete();
                }
            }
        }
    }

    /**
     * Check if the IP, username or email address given are blacklisted
     *
     * @param   string  $message  message
     *
     * @return  boolean
     *
     * @since   Kunena 6
     */
    protected function checkIfBlacklisted($message)
    {
        $ip    = $message->ip;
        $name  = $message->name;
        $email = $message->email;

        // Prepare the request to stopforumspam
        if (KunenaUserHelper::isIPv6($message->ip)) {
            $ip = '[' . $message->ip . ']';
        }

        $data = 'ip=' . $ip;

        if (!empty($name)) {
            $data .= '&username=' . $name;
        }

        if (!empty($email)) {
            $data .= '&email=' . $email;
        }

        $options = new Registry();

        $transport = new StreamTransport($options);

        // Create a 'stream' transport.
        $http = new Http($options, $transport);

        $response = $http->post('https://api.stopforumspam.org/api', $data . '&json');

        if ($response->code == '200') {
            // The query has worked
            $result = json_decode($response->body);

            if ($result->success) {
                if ($result->ip->appears) {
                    return true;
                } elseif (!empty($result->username)) {
                    if ($result->username->appears) {
                        return true;
                    } else {
                        return false;
                    }
                } elseif (!empty($result->email)) {
                    if ($result->email->appears) {
                        return true;
                    } else {
                        return false;
                    }
                }
            } else {
                // TODO : log the result or display something in debug mode

                return false;
            }
        } else {
            // The query has failed or has been refused

            // TODO : log the result or display something in debug mode

            return false;
        }
    }

    /**
     * Check if title of topic or message contains URL to limit part of spam
     *
     * @param   string  $subject  subject
     *
     * @return  boolean
     *
     * @internal param string $usbject
     *
     * @since    Kunena 6.0
     */
    protected function checkURLInSubject($subject)
    {
        if ($this->config->urlSubjectTopic) {
            preg_match_all('/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i', $subject, $matches);

            $ignore = false;

            foreach ($matches as $match) {
                if (!empty($match)) {
                    $ignore = true;
                }
            }

            return $ignore;
        }

        return true;
    }

    /**
     * Remove links in message content
     *
     * @param $text
     *
     * @return array|string|string[]|null
     * @since Kunena 5.2.0
     */
    protected function removeLinksInMessage($text)
    {
        $text = preg_replace('/\[url=(.*?)\](.*?)\[\/url\]/su', '', $text);
        $text = preg_replace('/\[img=(.*?)\](.*?)\[\/img\]/su', '', $text);

        // When the bbcode urls and images are removed just remove the others links
        $text = preg_replace('/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)(#?[\w \.-]*)(\??[\w \.-]*)(\=?[\w \.-]*)/i', '', $text);

        $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SAVED_WITHOUT_LINKS_AND_IMAGES'), 'success');

        return $text;
    }

    /**
     * Check in the text the max links
     *
     * @param   string  $text   text
     * @param   object  $topic  topic
     *
     * @return  boolean
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    protected function checkMaxLinks($text, $topic)
    {
        $category = $topic->getCategory();

        if ($this->me->isAdmin() || $this->me->isModerator($category)) {
            return true;
        }

        preg_match_all('/<div class=\"kunena_ebay_widget\"(.*?)>(.*?)<\/div>/s', $text, $ebay_matches);

        $ignore = false;

        foreach ($ebay_matches as $match) {
            if (!empty($match)) {
                $ignore = true;
            }
        }

        preg_match_all('/<div id=\"kunena_x_widget\"(.*?)>(.*?)<\/div>/s', $text, $x_social_matches);

        foreach ($x_social_matches as $match) {
            if (!empty($match)) {
                $ignore = true;
            }
        }

        if (!$ignore) {
            preg_match_all('@\(((https?://)?([-\\w]+\\.[-\\w\\.]+)+\\w(:\\d+)?(/([-\\w/_\\.]*(\\?\\S+)?)?)*)\)@', $text, $matches);

            if (empty($matches[0])) {
                preg_match_all("/<a\s[^>]*href=\"([^\"]*)\"[^>]*>(.*)<\/a>/siU", $text, $matches);
            }

            $countlink = \count($matches[0]);

            // Ignore internal links
            foreach ($matches[1] as $link) {
                $uri  = Uri::getInstance($link);
                $host = $uri->getHost();

                // The cms will catch most of these well
                if (empty($host) || Uri::isInternal($link)) {
                    $countlink--;
                }
            }

            if (!$topic->isAuthorised('approve') && $countlink >= $this->config->maxLinks + 1) {
                return false;
            }
        }

        return true;
    }

    /**
     * Save private data from message
     *
     * @param   KunenaMessage  $message  message
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    protected function postPrivate(KunenaMessage $message)
    {
        if (!$this->me->userid) {
            return;
        }

        $body      = (string) $this->input->getRaw('message_private');
        $attachIds = $this->input->get('attachment_private', [], 'array');

        if (!trim($body) && !$attachIds) {
            return;
        }

        $attachIds          = explode(',', $attachIds[0]);
        $moderator          = $this->me->isModerator($message->getCategory());
        $parent             = $message->getParent();
        $author             = $message->getAuthor();
        $pAuthor            = $parent->getAuthor();
        $private            = new KunenaPrivateMessage();
        $private->author_id = $author->userid;
        $private->subject   = $message->subject;

        if (!trim($body)) {
            $private->body      = Text::_('COM_KUNENA_POST_WITH_PRIVATE_ATTACHMENTS_SAVED');
        } else {
            $private->body      = $body;
        }

        // Attach message.
        $private->posts()->add($message->id);

        // Attach author of the message.
        if ($author->exists()) {
            $private->users()->add($author->userid);
        }

        if ($pAuthor->exists() && ($moderator || $pAuthor->isModerator($message->getCategory()))) {
            // Attach receiver (but only if moderator either posted or replied parent post).
            if ($pAuthor->exists()) {
                $private->users()->add($pAuthor->userid);
            }
        }

        $private->attachments()->setMapped($attachIds);

        try {
            $private->save();
        } catch (Exception $e) {
            KunenaError::displayDatabaseError($e);
        }

        KunenaLog::log(
            KunenaLog::TYPE_ACTION,
            KunenaLog::LOG_PRIVATE_POST_CREATE,
            ['id' => $private->id, 'mesid' => $message->id],
            $message->getCategory(),
            $message->getTopic(),
            $pAuthor
        );
    }

    /**
     * @return  void
     *
     * @throws  null
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function edit()
    {
        $this->id = $this->app->getInput()->getInt('mesid', 0);

        $message = KunenaMessageHelper::get($this->id);
        $topic   = $message->getTopic();
        $fields  = [
            'name'              => $this->app->getInput()->getString('authorname', $message->name),
            'email'             => $this->app->getInput()->getString('email', $message->email),
            'subject'           => $this->app->getInput()->post->get('subject', '', 'raw'),
            'message'           => $this->app->getInput()->post->get('message', '', 'raw'),
            'modified_reason'   => $this->app->getInput()->getString('modified_reason', $message->modified_reason),
            'icon_id'           => $this->app->getInput()->getInt('topic_emoticon', $topic->icon_id),
            'anonymous'         => $this->app->getInput()->getInt('anonymous', 0),
            'poll_title'        => $this->app->getInput()->getString('poll_title', null),
            'poll_options'      => $this->app->getInput()->get('polloptionsID', [], 'array'),
            'poll_time_to_live' => $this->app->getInput()->getString('poll_time_to_live', 0),
            'subscribe'         => $this->app->getInput()->getInt('subscribeMe', 0),
            'private'           => (string) $this->input->getRaw('private_message'),
            'params'            => '',
        ];

        if (!Session::checkToken('post')) {
            $this->app->setUserState('com_kunena.postfields', $fields);
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        try {
            $message->isAuthorised('edit');
        } catch (Exception $e) {
            $this->app->setUserState('com_kunena.postfields', $fields);
            $this->app->enqueueMessage($e->getMessage(), 'error');
            $this->setRedirectBack();

            return;
        }

        // Load language file from the template.
        KunenaFactory::getTemplate()->loadLanguage();

        // Update message contents
        $message->edit($fields);

        // If requested: Make message to be anonymous
        if ($fields['anonymous'] && $message->getCategory()->allowAnonymous) {
            $message->makeAnonymous();
        }

        // Prevent user abort from this point in order to maintain data integrity.
        @ignore_user_abort(true);

        // Mark attachments to be added or deleted.
        $attachments = $this->app->getInput()->get('attachments', [], 'post', 'array');
        $attachment  = $this->app->getInput()->get('attachment', [], 'post', 'array');

        $addList    = array_keys(array_intersect_key($attachments, $attachment));
        $addList    = ArrayHelper::toInteger($addList);
        $removeList = array_keys(array_diff_key($attachments, $attachment));
        $removeList = ArrayHelper::toInteger($removeList);

        $message->addAttachments($addList);
        $message->removeAttachments($removeList);

        // Upload new attachments
        foreach ($_FILES as $key => $file) {
            $intkey = 0;

            if (preg_match('/\D*(\d+)/', $key, $matches)) {
                $intkey = (int) $matches[1];
            }

            if ($file['error'] != UPLOAD_ERR_NO_FILE) {
                $message->uploadAttachment($intkey, $key, $this->catid);
            }
        }

        $url_subject = $this->checkURLInSubject($message->subject);

        if ($url_subject && $this->config->urlSubjectTopic) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_MESSAGES_ERROR_URL_IN_SUBJECT'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Set topic icon if permitted
        if ($this->config->topicIcons && isset($fields['icon_id']) && $topic->isAuthorised('edit', null)) {
            $topic->icon_id = $fields['icon_id'];
        }

        // Check if we are editing first post and update topic if we are!
        if ($topic->first_post_id == $message->id || $this->config->allowChangeSubject && $topic->first_post_userid == $message->userid || KunenaUserHelper::getMyself()->isModerator()) {
            $topic->subject = $fields['subject'];
        }

        if (!$this->me->checkUserAllowedLinksImages()) {
            $message->message = $this->removeLinksInMessage($message->message);

            if (!$message->message) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_MESSAGE_EMPTY_LINKS_IMAGES_REMOVED_NOT_ALLOWED'), 'error');
                $this->setRedirectBack();

                return;
            }
        }

        // If user removed all the text and message doesn't contain images or objects, delete the message instead.
        $text = KunenaParser::parseBBCode($message->message);

        if (!preg_match('!(<img |<object |<iframe )!', $text)) {
            $text = trim(OutputFilter::cleanText($text));
        }

        if (!$text && $this->config->userDeleteMessage == 1) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_LIB_TABLE_MESSAGES_ERROR_NO_MESSAGE'), 'error');

            return;
        } elseif (!$text) {
            if (trim($fields['private'])) {
                // Allow empty message if private message part has been filled up.
                $message->message = trim($message->message) ? $message->message : "[PRIVATE={$message->userid}]";
            } else {
                // Reload message (we don't want to change it).
                $message->load();

                try {
                    $message->publish(KunenaForum::DELETED);
                } catch (Exception $e) {
                    $this->app->enqueueMessage($e->getMessage(), 'error');
                }

                if ($message->publish(KunenaForum::DELETED)) {
                    $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUCCESS_DELETE'), 'success');
                }

                $this->setRedirect($message->getUrl($this->return, false));

                return;
            }
        }

        $maxlinks = $this->checkMaxLinks($text, $topic);

        if (!$maxlinks) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_SPAM_LINK_PROTECTION'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Activity integration
        $activity = KunenaFactory::getActivityIntegration();
        $activity->onBeforeEdit($message);

        // Save message
        try {
            $message->save();
        } catch (Exception $e) {
            $this->app->setUserState('com_kunena.postfields', $fields);
            $this->app->enqueueMessage($e->getMessage(), 'error');
            $this->setRedirectBack();

            return;
        }

        $isMine = $this->me->userid == $message->userid;

        if ($this->config->logModeration) {
            KunenaLog::log(
                $isMine ? KunenaLog::TYPE_ACTION : KunenaLog::TYPE_MODERATION,
                KunenaLog::LOG_POST_EDIT,
                ['mesid' => $message->id, 'reason' => $fields['modified_reason']],
                $topic->getCategory(),
                $topic,
                !$isMine ? $message->getAuthor() : null
            );
        }

        $subscribe = $this->app->getInput()->getInt('subscribeMe');
        $usertopic = $topic->getUserTopic();

        if ($topic->isAuthorised('subscribe')) {
            if ($subscribe) {
                $usertopic->subscribed = 1;
            } else {
                $usertopic->subscribed = 0;
            }

            $usertopic->save();
        }

        $poll_title = $fields['poll_title'];

        if ($poll_title !== null) {
            // Save changes into poll
            $poll_options = $fields['poll_options'];
            $poll         = $topic->getPoll();

            if (!empty($poll_options) && !empty($poll_title)) {
                $poll->title = $poll_title;

                if (!empty($fields['poll_time_to_live'])) {
                    $polltimetolive       = new Date($fields['poll_time_to_live']);
                    $poll->polltimetolive = $polltimetolive->toSql();
                }

                $poll->setOptions($poll_options);

                if (!$topic->poll_id) {
                    // Create a new poll
                    try {
                        $topic->isAuthorised('poll.create');
                    } catch (Exception $e) {
                        $this->app->enqueueMessage($e->getMessage(), 'error');
                    }

                    try {
                        $poll->save();
                    } catch (Exception $e) {
                        $this->app->enqueueMessage($e->getMessage(), 'error');
                    }

                    $topic->poll_id = $poll->id;
                    $topic->save();
                    $this->app->enqueueMessage(Text::_('COM_KUNENA_POLL_CREATED'), 'success');
                } else {
                    if ($this->config->allowUserEditPoll || (!$this->config->allowUserEditPoll && !$poll->getUserCount())) {
                        // Edit existing poll
                        try {
                            $topic->isAuthorised('poll.edit');
                        } catch (Exception $e) {
                            $this->app->enqueueMessage($e->getMessage(), 'error');
                        }

                        try {
                            $poll->save();
                        } catch (Exception $e) {
                            $this->app->enqueueMessage($e->getMessage(), 'error');
                        }

                        $this->app->enqueueMessage(Text::_('COM_KUNENA_POLL_EDITED'), 'success');
                    }
                }
            } elseif ($poll->exists() && $topic->isAuthorised('poll.edit')) {
                // Delete poll
                try {
                    $topic->isAuthorised('poll.delete');
                } catch (Exception $e) {
                    $this->app->enqueueMessage($e->getMessage(), 'error');
                }

                try {
                    $poll->delete();
                } catch (Exception $e) {
                    $this->app->enqueueMessage($e->getMessage(), 'error');
                }

                $this->app->enqueueMessage(Text::_('COM_KUNENA_POLL_DELETED'), 'success');
            }
        }

        // Removed orphaned attachments (one has added on the form and removed after before to submit)
        $this->removeOrphanedAttachments();

        // Edit Private message.
        $this->editPrivate($message);

        $activity->onAfterEdit($message);

        $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUCCESS_EDIT'), 'success');

        if ($message->hold == 1) {
            // If user cannot approve message by himself, send email to moderators.
            if (!$topic->isAuthorised('approve')) {
                $message->sendNotification();
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_GEN_MODERATED'), 'success');
        }

        // Redirect edit first message when category is under review
        if ($message->hold == 1 && $message->getCategory()->review && $topic->first_post_id == $message->id && !$this->me->isModerator()) {
            $this->setRedirect($message->getCategory()->getUrl($this->return, false));
        } else {
            $this->setRedirect($message->getUrl($this->return, false));
        }
    }

    /**
     * Load private data information when edit message
     *
     * @param   KunenaMessage  $message  message
     *
     * @return  void
     *
     * @throws  Exception
     * @since   Kunena 6.0
     */
    protected function editPrivate(KunenaMessage $message)
    {
        if (!$this->me->userid) {
            return;
        }

        $body      = (string) $this->input->getRaw('message_private');
        $attachIds = $this->input->get('attachment_private', [], 'array');

        if (!trim($body) && !$attachIds) {
            return;
        }

        $attachIds = explode(',', $attachIds[0]);

        $finder    = new KunenaPrivateMessageFinder();
        $finder
            ->filterByMessage($message)
            ->where('parent_id', '=', 0)
            ->where('author_id', '=', $message->userid)
            ->order('id')
            ->limit(1);
        $private = $finder->firstOrNew();

        if (!$private->exists()) {
            $this->postPrivate($message);

            return;
        }

        $private->subject = $message->subject;

        if (!trim($body)) {
            $private->body      = Text::_('COM_KUNENA_POST_WITH_PRIVATE_ATTACHMENTS_SAVED');
        } else {
            $private->body      = $body;
        }

        if (!empty($attachIds)) {
            $private->attachments()->setMapped($attachIds);
        }

        if (!$private->body && !$private->attachments) {
            try {
                $private->delete();
            } catch (Exception $e) {
                KunenaError::displayDatabaseError($e);
            }
        }

        try {
            $private->save();
        } catch (Exception $e) {
            KunenaError::displayDatabaseError($e);
        }
    }

    /**
     * @return  void
     *
     * @throws  null
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function thankyou()
    {
        $type = $this->app->getInput()->getString('task');
        $this->setThankyou($type);
    }

    /**
     * @param   string  $type  type
     *
     * @return  void
     *
     * @throws  null
     * @throws  Exception
     * @since   Kunena 6.0
     */
    protected function setThankyou($type)
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $message = KunenaMessageHelper::get($this->mesid);

        try {
            $message->isAuthorised($type);
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        $category            = KunenaCategoryHelper::get($this->catid);
        $thankyou            = KunenaMessageThankyouHelper::get($this->mesid);
        $activityIntegration = KunenaFactory::getActivityIntegration();

        if ($type == 'thankyou') {
            try {
                $thankyou->save($this->me);
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
                $this->setRedirectBack();

                return;
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_THANKYOU_SUCCESS'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_ACTION,
                    KunenaLog::LOG_POST_THANKYOU,
                    ['mesid' => $message->id],
                    $category,
                    $message->getTopic(),
                    $message->getAuthor()
                );
            }

            $activityIntegration->onAfterThankyou($this->me->userid, $message->userid, $message);
        } else {
            $userid = $this->app->getInput()->getInt('userid', '0');

            try {
                $thankyou->delete($userid);
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
                $this->setRedirectBack();

                return;
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_THANKYOU_REMOVED_SUCCESS'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_MODERATION,
                    KunenaLog::LOG_POST_UNTHANKYOU,
                    ['mesid' => $message->id, 'userid' => $userid],
                    $category,
                    $message->getTopic(),
                    $message->getAuthor()
                );
            }

            $activityIntegration->onAfterUnThankyou($this->me->userid, $userid, $message);
        }

        $this->setRedirect($message->getUrl($category->exists() ? $category->id : $message->catid, false));
    }

    /**
     * @return  void
     *
     * @throws  null
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function unthankyou()
    {
        $type = $this->app->getInput()->getString('task');
        $this->setThankyou($type);
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function subscribe()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('read');
            $topic->subscribe(1);

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUBSCRIBED_TOPIC'), 'success');

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterSubscribe($topic, 1);
        } catch (Exception $e) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_NO_SUBSCRIBED_TOPIC') . ' ' . $e->getMessage(), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function unsubscribe()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        if ($topic->isAuthorised('read')) {
            try {
                $topic->subscribe(0);
            } catch (Exception $e) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_NO_UNSUBSCRIBED_TOPIC') . ' ' . $e->getMessage(), 'error');
                $this->setRedirectBack();
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_UNSUBSCRIBED_TOPIC'), 'success');

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterSubscribe($topic, 0);
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function favorite()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('read');
            $topic->favorite(1);

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_FAVORITED_TOPIC'), 'success');

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterFavorite($topic, 1);
        } catch (Exception $e) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_NO_FAVORITED_TOPIC') . ' ' . $e->getMessage(), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function unfavorite()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('read');
            $topic->favorite(0);

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_UNFAVORITED_TOPIC'), 'success');

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterFavorite($topic, 0);
        } catch (Exception $e) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_NO_UNFAVORITED_TOPIC') . ' ' . $e->getMessage(), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function sticky()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('sticky');
            $topic->sticky(1);

            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_STICKY_SET'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_MODERATION,
                    KunenaLog::LOG_TOPIC_STICKY,
                    [],
                    $topic->getCategory(),
                    $topic
                );
            }

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterSticky($topic, 1);
        } catch (Exception $e) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_STICKY_NOT_SET') . $e->getMessage(), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function unsticky()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('sticky');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if ($topic->sticky(0)) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_STICKY_UNSET'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_MODERATION,
                    KunenaLog::LOG_TOPIC_UNSTICKY,
                    [],
                    $topic->getCategory(),
                    $topic
                );
            }

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterSticky($topic, 0);
        } else {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_STICKY_NOT_UNSET'), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function lock()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('lock');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if ($topic->lock(1)) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_LOCK_SET'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_MODERATION,
                    KunenaLog::LOG_TOPIC_LOCK,
                    [],
                    $topic->getCategory(),
                    $topic
                );
            }

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterLock($topic, 1);
        } else {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_LOCK_NOT_SET'), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function unlock()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);

        try {
            $topic->isAuthorised('lock');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if ($topic->lock(0)) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_LOCK_UNSET'), 'success');

            if ($this->config->logModeration) {
                KunenaLog::log(
                    KunenaLog::TYPE_MODERATION,
                    KunenaLog::LOG_TOPIC_UNLOCK,
                    [],
                    $topic->getCategory(),
                    $topic
                );
            }

            // Activity integration
            $activity = KunenaFactory::getActivityIntegration();
            $activity->onAfterLock($topic, 0);
        } else {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_LOCK_NOT_UNSET'), 'error');
        }

        $this->setRedirectBack();
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function delete()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        if ($this->mesid) {
            // Delete message
            $message = $target = KunenaMessageHelper::get($this->mesid);
            $topic   = $message->getTopic();
            $log     = KunenaLog::LOG_POST_DELETE;
            $hold    = KunenaForum::DELETED;
            $msg     = Text::_('COM_KUNENA_POST_SUCCESS_DELETE');
        } else {
            // Delete topic
            $topic = $target = KunenaTopicHelper::get($this->id);
            $log   = KunenaLog::LOG_TOPIC_DELETE;
            $hold  = KunenaForum::TOPIC_DELETED;
            $msg   = Text::_('COM_KUNENA_TOPIC_SUCCESS_DELETE');
        }

        $category = $topic->getCategory();

        try {
            $target->isAuthorised('delete');
            $target->publish($hold);

            if ($this->config->logModeration) {
                KunenaLog::log(
                    $this->me->isModerator($category) ? KunenaLog::TYPE_MODERATION : KunenaLog::TYPE_ACTION,
                    $log,
                    isset($message) ? ['mesid' => $message->id] : [],
                    $category,
                    $topic
                );
            }

            $this->app->enqueueMessage($msg, 'success');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if (!$target->isAuthorised('read')) {
            if ($target instanceof KunenaMessage && $target->getTopic()->isAuthorised('read')) {
                $target = $target->getTopic();
                $target = KunenaMessageHelper::get($target->last_post_id);
            } else {
                $target = $target->getCategory();
            }
        }

        $this->setRedirect($target->getUrl($this->return, false));
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function undelete()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        if ($this->mesid) {
            // Undelete message
            $message = $target = KunenaMessageHelper::get($this->mesid);
            $topic   = $message->getTopic();
            $log     = KunenaLog::LOG_POST_UNDELETE;
            $msg     = Text::_('COM_KUNENA_POST_SUCCESS_UNDELETE');
        } else {
            // Undelete topic
            $topic = $target = KunenaTopicHelper::get($this->id);
            $log   = KunenaLog::LOG_TOPIC_UNDELETE;
            $msg   = Text::_('COM_KUNENA_TOPIC_SUCCESS_UNDELETE');
        }

        $category = $topic->getCategory();

        try {
            $target->isAuthorised('undelete');
            $target->publish(KunenaForum::PUBLISHED);

            if ($this->config->logModeration) {
                KunenaLog::log(
                    $this->me->isModerator($category) ? KunenaLog::TYPE_MODERATION : KunenaLog::TYPE_ACTION,
                    $log,
                    isset($message) ? ['mesid' => $message->id] : [],
                    $category,
                    $topic
                );
            }

            $this->app->enqueueMessage($msg, 'success');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        $this->setRedirect($target->getUrl($this->return, false));
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function permdelete()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        if ($this->mesid) {
            // Delete message
            $message = $target = KunenaMessageHelper::get($this->mesid);
            $log     = KunenaLog::LOG_POST_DESTROY;
            $topic   = KunenaTopicHelper::get($target->getTopic());

            if ($topic->attachments > 0) {
                $topic->attachments = $topic->attachments - 1;
                $topic->save(false);
            }
        } else {
            // Delete topic
            $topic = $target = KunenaTopicHelper::get($this->id);
            $log   = KunenaLog::LOG_TOPIC_DESTROY;
        }

        $category = $topic->getCategory();

        try {
            $topic->isAuthorised('permdelete');
            $target->delete();

            if ($this->config->logModeration) {
                KunenaLog::log(
                    $this->me->isModerator($category) ? KunenaLog::TYPE_MODERATION : KunenaLog::TYPE_ACTION,
                    $log,
                    isset($message) ? ['mesid' => $message->id] : [],
                    $category,
                    $topic
                );
            }

            if ($topic->exists()) {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_POST_SUCCESS_DELETE'), 'success');
                $url = $topic->getUrl($this->return, false);
            } else {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_SUCCESS_DELETE'), 'success');
                $url = $topic->getCategory()->getUrl($this->return, false);
            }
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if (isset($url)) {
            $this->setRedirect($url);
        }
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function approve()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Load language file from the template.
        KunenaFactory::getTemplate()->loadLanguage();

        if ($this->mesid) {
            // Approve message
            $target  = KunenaMessageHelper::get($this->mesid);
            $message = $target;
            $log     = KunenaLog::LOG_POST_APPROVE;
        } else {
            // Approve topic
            $target  = KunenaTopicHelper::get($this->id);
            $message = KunenaMessageHelper::get($target->first_post_id);
            $log     = KunenaLog::LOG_TOPIC_APPROVE;
        }

        $topic    = $message->getTopic();
        $category = $topic->getCategory();

        try {
            $target->isAuthorised('approve');
            $target->publish(KunenaForum::PUBLISHED);
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if ($this->config->logModeration) {
            KunenaLog::log(
                $this->me->isModerator($category) ? KunenaLog::TYPE_MODERATION : KunenaLog::TYPE_ACTION,
                $log,
                ['mesid' => $message->id],
                $category,
                $topic,
                $message->getAuthor()
            );
        }

        $this->app->enqueueMessage(Text::_('COM_KUNENA_MODERATE_APPROVE_SUCCESS'), 'success');

        // Only email if message wasn't modified by the author before approval
        // TODO: this is just a workaround for #1862, we need to find better solution.

        $modifiedByAuthor = ($message->modified_by == $message->userid);

        if (!$modifiedByAuthor) {
            $target->sendNotification(null, true);
        }

        $this->setRedirect($target->getUrl($this->return, false));
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function move()
    {
        if (!Session::checkToken('post')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topicId        = $this->app->getInput()->getInt('id', 0);
        $messageId      = $this->app->getInput()->getInt('mesid', 0);
        $targetCategory = $this->app->getInput()->getInt('targetcategory', 0);
        $targetTopic    = $this->app->getInput()->getInt('targettopic', 0);

        if ($targetTopic < 0) {
            $targetTopic = $this->app->getInput()->getInt('targetid', 0);
        }

        if ($messageId) {
            $message = $object = KunenaMessageHelper::get($messageId);
            $topic   = $message->getTopic();
        } else {
            $topic   = $object = KunenaTopicHelper::get($topicId);
            $message = KunenaMessageHelper::get($topic->first_post_id);
        }

        if ($targetTopic) {
            $target = KunenaTopicHelper::get($targetTopic);
        } else {
            $target = KunenaCategoryHelper::get($targetCategory);
        }

        $error        = null;
        $targetobject = null;

        try {
            $object->isAuthorised('move');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        try {
            $target->isAuthorised('read');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        $changesubject  = $this->app->getInput()->getBool('changesubject', false);
        $subject        = $this->app->getInput()->getString('subject', '');
        $shadow         = $this->app->getInput()->getBool('shadow', false);
        $topic_emoticon = $this->app->getInput()->getInt('topic_emoticon', null);
        $keep_poll      = $this->app->getInput()->getInt('keep_poll', false);

        if ($object instanceof KunenaMessage) {
            $mode = $this->app->getInput()->getWord('mode', 'selected');

            switch ($mode) {
                case 'newer':
                    $ids = new Date($object->time);
                    break;
                case 'selected':
                default:
                    $ids = $object->id;
                    break;
            }
        } else {
            $ids = false;
        }

        try {
            $targetobject = $topic->move($target, $ids, $shadow, $subject, $changesubject, $topic_emoticon, $keep_poll);
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if ($this->config->logModeration) {
            KunenaLog::log(
                KunenaLog::TYPE_MODERATION,
                $messageId ? KunenaLog::LOG_POST_MODERATE : KunenaLog::LOG_TOPIC_MODERATE,
                [
                    'move'    => ['id' => $topicId, 'mesid' => $messageId, 'mode' => isset($mode) ? $mode : 'topic'],
                    'target'  => ['category_id' => $targetCategory, 'topic_id' => $targetTopic],
                    'options' => ['emo' => $topic_emoticon, 'subject' => $subject, 'changeAll' => $changesubject, 'shadow' => $shadow],
                ],
                $topic->getCategory(),
                $topic,
                $message->getAuthor()
            );
        }

        if ($error) {
            $this->app->enqueueMessage($error, 'error');
        } else {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ACTION_TOPIC_SUCCESS_MOVE'), 'success');
        }

        if ($targetobject) {
            $this->setRedirect($targetobject->getUrl($this->return, false, 'last'));
        } else {
            $this->setRedirect($topic->getUrl($this->return, false, 'first'));
        }
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function report()
    {
        if (!Session::checkToken('post')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        if (!$this->me->exists() || $this->config->reportMsg == 0) {
            // Deny access if report feature has been disabled or user is guest
            $this->app->enqueueMessage(Text::_('COM_KUNENA_NO_ACCESS'), 'warning');
            $this->setRedirectBack();

            return;
        }

        if (!$this->config->sendEmails) {
            // Emails have been disabled
            $this->app->enqueueMessage(Text::_('COM_KUNENA_EMAIL_DISABLED'), 'warning');
            $this->setRedirectBack();

            return;
        }

        if (!$this->config->email || !MailHelper::isEmailAddress($this->config->email)) {
            // Error: email address is invalid
            $this->app->enqueueMessage(Text::_('COM_KUNENA_EMAIL_INVALID'), 'error');
            $this->setRedirectBack();

            return;
        }

        // Get target object for the report
        if ($this->mesid) {
            $message = $target = KunenaMessageHelper::get($this->mesid);
            $topic   = $target->getTopic();
            $log     = KunenaLog::LOG_POST_REPORT;
        } else {
            $topic   = $target = KunenaTopicHelper::get($this->id);
            $message = KunenaMessageHelper::get($topic->first_post_id);
            $log     = KunenaLog::LOG_TOPIC_REPORT;
        }

        try {
            $target->isAuthorised('read');
        } catch (Exception $e) {
            // Deny access if user cannot read target
            $this->app->enqueueMessage($e->getMessage(), 'error');
            $this->setRedirectBack();
        }

        $reason = $this->app->getInput()->getString('reason');
        $text   = $this->app->getInput()->getString('text');

        $template = KunenaTemplate::getInstance();

        if (method_exists($template, 'reportMessage')) {
            $template->reportMessage($message, $reason, $text);
        }

        // Load language file from the template.
        KunenaFactory::getTemplate()->loadLanguage();

        if (empty($reason) && empty($text)) {
            // Do nothing: empty subject or reason is empty
            $this->app->enqueueMessage(Text::_('COM_KUNENA_REPORT_FORG0T_SUB_MES'), 'error');
            $this->setRedirectBack();

            return;
        } else {
            $acl         = KunenaAccess::getInstance();
            $emailToList = $acl->getSubscribers($topic->category_id, $topic->id, false, true, false);

            if (!empty($emailToList)) {
                $mailnamesender = !empty($this->config->emailSenderName) ? MailHelper::cleanAddress($this->config->emailSenderName) : MailHelper::cleanAddress($this->config->boardTitle . ': ' . $this->me->getName());
                $mailsubject    = "[" . $this->config->boardTitle . " " . Text::_('COM_KUNENA_FORUM') . "] " . Text::_('COM_KUNENA_REPORT_MSG') . ": ";

                if ($reason) {
                    $mailsubject .= $reason;
                } else {
                    $mailsubject .= $topic->subject;
                }

                $msglink = Uri::getInstance()->toString(['scheme', 'host', 'port']) . $target->getPermaUrl(null, false);

                $mail = Factory::getContainer()->get(MailerFactoryInterface::class)->createMailer();
                $mail->setSender([$this->config->email, $mailnamesender]);
                $mail->setSubject($mailsubject);
                $mail->addReplyTo($this->me->email, $this->me->username);

                // Render the email.
                $layout = KunenaLayout::factory('Email/Report')->debug(false)
                    ->set('mail', $mail)
                    ->set('message', $message)
                    ->set('me', $this->me)
                    ->set('title', $reason)
                    ->set('content', $text)
                    ->set('messageLink', $msglink);

                try {
                    $body = trim($layout->render());
                    $mail->setBody($body);
                } catch (Exception $e) {
                    // TODO: display exception
                }

                $receivers = [];

                foreach ($emailToList as $emailTo) {
                    if (!MailHelper::isEmailAddress($emailTo->email)) {
                        continue;
                    } else {
                        $receivers[] = $emailTo->email;
                    }
                }

                KunenaEmail::send($mail, $receivers);

                if ($this->config->logModeration) {
                    KunenaLog::log(
                        KunenaLog::TYPE_REPORT,
                        $log,
                        [
                            'mesid'   => $message->id,
                            'reason'  => $reason,
                            'message' => $text,
                        ],
                        $topic->getCategory(),
                        $topic,
                        $message->getAuthor()
                    );
                }

                $this->app->enqueueMessage(Text::_('COM_KUNENA_REPORT_SUCCESS'), 'success');
            } else {
                $this->app->enqueueMessage(Text::_('COM_KUNENA_REPORT_NOT_SEND'), 'success');
            }
        }

        $this->setRedirect($target->getUrl($this->return, false));
    }

    /**
     * @return  void
     *
     * @throws  null
     * @throws  Exception
     * @since   Kunena 6.0
     */
    public function vote()
    {
        if (!Session::checkToken('post')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $vote  = $this->app->getInput()->getInt('kpollradio', 0);
        $id    = $this->app->getInput()->getInt('id', 0);

        $topic = KunenaTopicHelper::get($id);
        $poll  = $topic->getPoll();

        try {
            $topic->isAuthorised('poll.vote');
        } catch (Exception $e) {
            $this->app->enqueueMessage($e->getMessage(), 'error');
        }

        if (!$poll->getMyVotes()) {
            try {
                // Give a new vote
                $poll->vote($vote);
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_VOTE_SUCCESS'), 'success');
        } elseif (!$this->config->pollAllowVoteOne) {
            try {
                // Change existing vote
                $poll->vote($vote, true);
            } catch (Exception $e) {
                $this->app->enqueueMessage($e->getMessage(), 'error');
            }

            $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_VOTE_CHANGED_SUCCESS'), 'success');
        }

        $this->setRedirect($topic->getUrl($this->return, false));
    }

    /**
     * @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.0
     */
    public function resetvotes()
    {
        if (!Session::checkToken('get')) {
            $this->app->enqueueMessage(Text::_('COM_KUNENA_ERROR_TOKEN'), 'error');
            $this->setRedirectBack();

            return;
        }

        $topic = KunenaTopicHelper::get($this->id);
        $topic->resetvotes();

        if ($this->config->logModeration) {
            KunenaLog::log(
                KunenaLog::TYPE_MODERATION,
                KunenaLog::LOG_POLL_MODERATE,
                [],
                $topic->getCategory(),
                $topic,
                null
            );
        }

        $this->app->enqueueMessage(Text::_('COM_KUNENA_TOPIC_VOTE_RESET_SUCCESS'), 'success');
        $this->setRedirect($topic->getUrl($this->return, false));
    }

    /**
     * Returns the topic icons set corresponding at the category selected 
     *
     @return  void
     *
     * @throws  Exception
     * @throws  null
     * @since   Kunena 6.3
     */
    public function topicicons()
    {
        $catid = $this->app->getInput()->getInt('catid', 0);

        $category        = KunenaCategoryHelper::get($catid);
        $categoryIconset = $category->iconset;
        $app             = Factory::getApplication();

        if (empty($categoryIconset)) {
            $response = [];

            // Set the MIME type and header for JSON output.
            $this->document->setMimeEncoding('application/json');
            $app->setHeader('Content-Disposition', 'attachment; filename="' . $this->getName() . '.' . $this->getLayout() . '.json"');
            Factory::getApplication()->sendHeaders();

            echo json_encode($response);
        }

        $topicIcons = [];

        $template = KunenaFactory::getTemplate();

        $xmlfile = JPATH_ROOT . '/media/kunena/topic_icons/' . $categoryIconset . '/topicicons.xml';

        if (is_file($xmlfile)) {
            $xml = simplexml_load_file($xmlfile);

            foreach ($xml->icons as $icons) {
                $type   = (string) $icons->attributes()->type;
                $width  = (int) $icons->attributes()->width;
                $height = (int) $icons->attributes()->height;

                foreach ($icons->icon as $icon) {
                    $attributes = $icon->attributes();
                    $icon       = new stdClass();
                    $icon->id   = (int) $attributes->id;
                    $icon->type = (string) $attributes->type ? (string) $attributes->type : $type;
                    $icon->name = (string) $attributes->name;

                    if ($icon->type != 'user') {
                        $icon->id = $icon->type . '_' . $icon->name;
                    }

                    $icon->iconset   = $categoryIconset;
                    $icon->published = (int) $attributes->published;
                    $icon->title     = (string) $attributes->title;
                    $icon->fa        = (string) $attributes->fa;
                    $icon->svg       = (string) KunenaSvgIcons::loadsvg($attributes->svg);
                    $icon->filename  = (string) $attributes->src;
                    $icon->width     = (int) $attributes->width ? (int) $attributes->width : $width;
                    $icon->height    = (int) $attributes->height ? (int) $attributes->height : $height;
                    $icon->path      = Uri::root() . 'media/kunena/topic_icons/' . $categoryIconset . '/' . $icon->filename;
                    $icon->relpath   = $template->getTopicIconPath("{$icon->filename}", false);
                    $topicIcons[]    = $icon;
                }
            }
        }

        if (ob_get_length()) {
            ob_end_clean();
        }

        echo json_encode($topicIcons);

        jexit();
    }
}
