View file src/addons/ThemeHouse/Reactions/Entity/ReactedContent.php

File size: 12.02Kb
<?php

namespace ThemeHouse\Reactions\Entity;

use ThemeHouse\Reactions\React\AbstractHandler;
use ThemeHouse\Reactions\XF\Entity\User;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
use XF\Repository;

/**
 * Class ReactedContent
 * @package ThemeHouse\Reactions\Entity#
 *
 * @property integer react_id
 * @property integer reaction_id
 * @property string content_type
 * @property integer content_id
 * @property integer react_user_id
 * @property integer react_date
 * @property integer content_user_id
 * @property boolean is_counted
 *
 * @property mixed Content
 * @property mixed NewReactCount
 *
 * @property User User
 * @property UserReactionCount UserReactorCount
 * @property UserReactionCount UserReactedCount
 * @property User Owner
 * @property Reaction Reaction
 */
class ReactedContent extends Entity
{
    /**
     * @param null $error
     * @return bool
     * @throws \Exception
     */
    public function canView(&$error = null)
    {
        $handler = $this->getHandler();
        $content = $this->Content;

        if ($handler && $content) {
            return $handler->canViewContent($content, $this, $error);
        } else {
            return false;
        }
    }

    /**
     * @param null $error
     * @return bool
     * @throws \Exception
     */
    public function canReact(&$error = null)
    {
        $handler = $this->getHandler();
        $content = $this->Content;

        if ($handler && $content) {
            return $handler->canReactContent($content, $this, $error);
        } else {
            return false;
        }
    }

    /**
     * @param null $error
     * @return bool
     * @throws \Exception
     */
    public function canUnreact(&$error = null)
    {
        $handler = $this->getHandler();
        $content = $this->getContent();

        if ($handler && $content) {
            return $handler->canUnreactContent($content, $this, $error);
        } else {
            return false;
        }
    }

    /**
     * @return AbstractHandler
     * @throws \Exception
     */
    public function getHandler()
    {
        return $this->getReactHandlerRepo()->getReactHandlerByType($this->content_type);
    }

    /**
     * @return null|Entity
     * @throws \Exception
     */
    public function getContent()
    {
        $handler = $this->getHandler();
        return $handler ? $handler->getContent($this->content_id) : null;
    }

    /**
     * @param Entity|null $content
     */
    public function setContent(Entity $content = null)
    {
        $this->_getterCache['Content'] = $content;
    }

    /**
     * @return bool
     */
    public function getNewReactCount()
    {
        if (array_key_exists('NewReactCount', $this->_getterCache)) {
            return $this->_getterCache['NewReactCount'];
        }

        return false;
    }

    /**
     * @param array $newReactCount
     */
    public function setNewReactCount(array $newReactCount = [])
    {
        $this->_getterCache['NewReactCount'] = $newReactCount;
    }

    /**
     * @return bool|string
     * @throws \Exception
     */
    public function render()
    {
        $handler = $this->getHandler();
        return $handler ? $handler->render($this) : '';
    }

    protected function _postSave()
    {
        if ($this->isInsert()) {
            if ($this->is_counted) {
                $this->adjustReactCount($this->react_user_id, $this->content_user_id, $this->reaction_id, $this->content_id, $this->content_type, 1);
            }

            $this->toggleLike();
        } else {
            if ($this->isChanged('content_user_id')) {
                if ($this->getExistingValue('is_counted')) {
                    $this->adjustReactCount($this->react_user_id, $this->getExistingValue('content_user_id'), $this->reaction_id, $this->content_id, $this->content_type, -1);
                }
                if ($this->is_counted) {
                    $this->adjustReactCount($this->react_user_id, $this->content_user_id, $this->reaction_id, $this->content_id, $this->content_type, 1);
                }
            } elseif ($this->isChanged('is_counted')) {
                $this->adjustReactCount($this->react_user_id, $this->content_user_id, $this->reaction_id, $this->content_id, $this->content_type, $this->is_counted ? 1 : -1);
            }
        }

        if ($this->isChanged(['reaction_id', 'content_type', 'content_id', 'content_user_id', 'react_date', 'react_user_id'])) {
            $this->rebuildContentReactCache();
        }
    }

    /**
     * @throws \Exception
     */
    protected function _postDelete()
    {
        if ($this->is_counted) {
            $this->adjustReactCount($this->react_user_id, $this->content_user_id, $this->reaction_id, $this->content_id, $this->content_type, -1);
        }

        $this->rebuildContentReactCache();

        $handler = $this->getHandler();
        if ($handler) {
            $handler->removeReactAlert($this);
            $handler->unpublishReactNewsFeed($this);
        }

        $this->toggleLike(false);
    }

    /**
     * @param bool $isCreate
     * @return bool
     * @throws \XF\PrintableException
     */
    public function toggleLike($isCreate = true)
    {
        if (!$this->Reaction) {
            return false;
        }

        $reaction = $this->Reaction;
        if ($reaction->like_wrapper) {
            /** @var \XF\Repository\LikedContent $likeRepo */
            $likeRepo = $this->repository('XF:LikedContent');

            $existingLike = $likeRepo->getLikeByContentAndLiker($this->content_type, $this->content_id, $this->react_user_id);

            if ($isCreate && !$existingLike && $this->Reactor instanceof \XF\Entity\User) {
                try {
                    $likeRepo->insertLike($this->content_type, $this->content_id, $this->Reactor, false);
                } catch (\Exception $e) {
                }
            }

            if (!$isCreate && $existingLike) {
                $existingLike->delete(false);
            }
        }
    }

    /**
     * @param $reactorUserId
     * @param $reactedUserId
     * @param $reactionId
     * @param $contentId
     * @param $contentType
     * @param $amount
     */
    protected function adjustReactCount($reactorUserId, $reactedUserId, $reactionId, $contentId, $contentType, $amount)
    {
        if (!$reactorUserId && !$reactedUserId) {
            return;
        }

        $this->adjustUserReactCount($reactionId, $amount);

        $this->db()->query("
            INSERT INTO xf_th_reaction_user_count (user_id, reaction_id, content_type, count_given) VALUES
                (?, ?, ?, 1)
            ON DUPLICATE KEY UPDATE
                count_given = GREATEST(0, count_given + ?)
        ", [
            $reactorUserId, $reactionId, $contentType, $amount,
        ]);

        $this->db()->query("
            INSERT INTO xf_th_reaction_user_count (user_id, reaction_id, content_type, count_received) VALUES
                (?, ?, ?, 1)
            ON DUPLICATE KEY UPDATE
                count_received = GREATEST(0, count_received + ?)
        ", [
            $reactedUserId, $reactionId, $contentType, $amount
        ]);

        $this->db()->query("
		    INSERT INTO xf_th_reaction_content_count (content_id, content_type, reaction_id) VALUES
		        (?, ?, ?)
            ON DUPLICATE KEY UPDATE
                count = GREATEST(0, count + ?)
        ", [
            $contentId, $contentType, $reactionId, $amount
        ]);
    }

    /**
     * @param $reactionId
     * @param $amount
     */
    protected function adjustUserReactCount($reactionId, $amount)
    {
        if (empty($this->Owner)) {
            return;
        }

        $reaction = $this->getThReactionRepo()->getReactionById($reactionId);

        if (!$reactCount = $this->getNewReactCount()) {
            $reactCount = $this->Owner->react_count;
        }

        $reactCount[$reaction['reaction_type_id']] = max(0, (isset($reactCount[$reaction['reaction_type_id']]) ? $reactCount[$reaction['reaction_type_id']] : 0) + $amount);
        $this->setNewReactCount($reactCount);

        $this->db()->query("
			UPDATE xf_user
			SET react_count = ?
			WHERE user_id = ?
		", [serialize($reactCount), $this->content_user_id]);
    }

    protected function rebuildContentReactCache()
    {
        $repo = $this->getReactRepo();
        $repo->rebuildContentReactCache($this->content_type, $this->content_id, $this->reaction_id);
    }

    /**
     * @param Structure $structure
     * @return Structure
     */
    public static function getStructure(Structure $structure)
    {
        $structure->table = 'xf_th_reacted_content';
        $structure->shortName = 'ThemeHouse\Reactions:ReactedContent';
        $structure->primaryKey = 'react_id';
        $structure->columns = [
            'react_id' => ['type' => self::UINT, 'autoIncrement' => true],
            'reaction_id' => ['type' => self::UINT, 'required' => true],
            'content_type' => ['type' => self::STR, 'maxLength' => 25, 'required' => true],
            'content_id' => ['type' => self::UINT, 'required' => true],
            'react_user_id' => ['type' => self::UINT, 'required' => true],
            'react_date' => ['type' => self::UINT, 'default' => \XF::$time],
            'content_user_id' => ['type' => self::UINT, 'required' => true],
            'is_counted' => ['type' => self::BOOL, 'default' => true],
        ];
        $structure->getters = [
            'Content' => true,
            'NewReactCount' => true
        ];
        $structure->relations = [
            'Reactor' => [
                'entity' => 'XF:User',
                'type' => self::TO_ONE,
                'conditions' => [['user_id', '=', '$react_user_id']],
                'primary' => true
            ],
            'UserReactorCount' => [
                'entity' => 'ThemeHouse\Reactions:UserReactionCount',
                'type' => self::TO_ONE,
                'conditions' => [
                    ['user_id', '=', '$react_user_id'],
                    ['reaction_id', '=', '$reaction_id'],
                    ['content_type', '=', '$content_type'],
                ],
                'primary' => true
            ],
            'UserReactedCount' => [
                'entity' => 'ThemeHouse\Reactions:UserReactionCount',
                'type' => self::TO_ONE,
                'conditions' => [
                    ['user_id', '=', '$content_user_id'],
                    ['reaction_id', '=', '$reaction_id'],
                    ['content_type', '=', '$content_type'],
                ],
                'primary' => true
            ],
            'Owner' => [
                'entity' => 'XF:User',
                'type' => self::TO_ONE,
                'conditions' => [['user_id', '=', '$content_user_id']],
                'primary' => true
            ],
            'Reaction' => [
                'entity' => 'ThemeHouse\Reactions:Reaction',
                'type' => self::TO_ONE,
                'conditions' => [['reaction_id', '=', '$reaction_id']],
                'primary' => true
            ],
        ];

        $structure->defaultWith[] = 'Owner';

        return $structure;
    }

    /**
     * @return \ThemeHouse\Reactions\Repository\ReactedContent
     */
    protected function getReactRepo()
    {
        /** @noinspection PhpIncompatibleReturnTypeInspection */
        return $this->repository('ThemeHouse\Reactions:ReactedContent');
    }

    /**
     * @return \ThemeHouse\Reactions\Repository\Reaction
     */
    protected function getThReactionRepo()
    {
        /** @noinspection PhpIncompatibleReturnTypeInspection */
        return $this->repository('ThemeHouse\Reactions:Reaction');
    }

    /**
     * @return \ThemeHouse\Reactions\Repository\ReactionType
     */
    protected function getReactionTypeRepo()
    {
        /** @noinspection PhpIncompatibleReturnTypeInspection */
        return $this->repository('ThemeHouse\Reactions:ReactionType');
    }

    /**
     * @return \ThemeHouse\Reactions\Repository\ReactHandler
     */
    protected function getReactHandlerRepo()
    {
        /** @noinspection PhpIncompatibleReturnTypeInspection */
        return $this->repository('ThemeHouse\Reactions:ReactHandler');
    }
}