Covivo/mobicoop

View on GitHub
api/src/Community/Service/CommunityManager.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

/**
 * Copyright (c) 2020, MOBICOOP. All rights reserved.
 * This project is dual licensed under AGPL and proprietary licence.
 ***************************
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <gnu.org/licenses>.
 ***************************
 *    Licence MOBICOOP described in the file
 *    LICENSE
 */

namespace App\Community\Service;

use App\Action\Event\ActionEvent;
use App\Action\Repository\ActionRepository;
use App\Auth\Entity\AuthItem;
use App\Auth\Entity\UserAuthAssignment;
use App\Auth\Repository\AuthItemRepository;
use App\Carpool\Entity\MapsAd\MapsAds;
use App\Carpool\Entity\Proposal;
use App\Carpool\Repository\ProposalRepository;
use App\Carpool\Service\AdManager;
use App\Community\Entity\Community;
use App\Community\Entity\CommunityMember;
use App\Community\Entity\CommunityMembersList;
use App\Community\Entity\CommunitySecurity;
use App\Community\Entity\CommunityUser;
use App\Community\Event\CommunityCreatedEvent;
use App\Community\Event\CommunityLeftEvent;
use App\Community\Event\CommunityMembershipPendingEvent;
use App\Community\Event\CommunityNewMemberEvent;
use App\Community\Event\CommunityNewMembershipRequestEvent;
use App\Community\Repository\CommunityRepository;
use App\Community\Repository\CommunityUserRepository;
use App\Community\Resource\MCommunity;
use App\User\Entity\User;
use App\User\Repository\UserRepository;
use App\User\Service\UserManager;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * Community manager.
 *
 * This service contains methods related to community management.
 *
 * @author Sylvain Briat <sylvain.briat@mobicoop.org>
 * @author Maxime Bardot <maxime.bardot@mobicoop.org>
 * @author Remi Wortemann <remi.wortemann@mobicoop.org>
 */
class CommunityManager
{
    private $entityManager;
    private $logger;
    private $securityPath;
    private $userRepository;
    private $communityRepository;
    private $communityUserRepository;
    private $proposalRepository;
    private $authItemRepository;
    private $userManager;
    private $adManager;
    private $eventDispatcher;
    private $actionRepository;

    /**
     * Constructor.
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        LoggerInterface $logger,
        UserRepository $userRepository,
        CommunityRepository $communityRepository,
        CommunityUserRepository $communityUserRepository,
        ProposalRepository $proposalRepository,
        AuthItemRepository $authItemRepository,
        UserManager $userManager,
        AdManager $adManager,
        EventDispatcherInterface $eventDispatcher,
        ActionRepository $actionRepository,
        string $securityPath
    ) {
        $this->entityManager = $entityManager;
        $this->logger = $logger;
        $this->securityPath = $securityPath;
        $this->userRepository = $userRepository;
        $this->communityRepository = $communityRepository;
        $this->communityUserRepository = $communityUserRepository;
        $this->proposalRepository = $proposalRepository;
        $this->authItemRepository = $authItemRepository;
        $this->userManager = $userManager;
        $this->adManager = $adManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->actionRepository = $actionRepository;
    }

    /**
     * Check if a user can join a community
     * To join an opened community, no credentials is needed, the user just need to be registered.
     * To join a closed community, a user needs to give credentials, we will call them login and password
     * even if they represent other kind of information (id, date of birth...).
     *
     * @return bool
     */
    public function canJoin(User $user, Community $community)
    {
        $authorized = true;

        $communityUser = $this->communityUserRepository->findBy(['community' => $community, 'user' => $user]);
        if (is_array($communityUser) && count($communityUser) > 0) {
            throw new \LogicException('Aleady member of this community');
        }

        // we check if the community is secured

        if (count($community->getCommunitySecurities()) > 0) {
            $authorized = false;
            // we check the values of the credentials for each possible security file
            if (!is_null($community->getLogin()) && !is_null($community->getPassword())) {
                foreach ($community->getCommunitySecurities() as $communitySecurity) {
                    if ($this->checkSecurity($communitySecurity, $community->getLogin(), $community->getPassword())) {
                        $authorized = true;

                        break;
                    }
                }
            }
        }

        if (!$authorized) {
            return false;
        }
        if (Community::DOMAIN_VALIDATION == $community->getValidationType()) {
            $authorized = false; // Unauthorized by default.

            $userDomain = explode('@', $user->getEmail())[1];

            $communityDomains = explode(';', str_replace('@', '', $community->getDomain()));

            foreach ($communityDomains as $communityDomain) {
                if (trim($communityDomain) == $userDomain) {
                    $authorized = true;

                    break;
                }
            }
        }

        return $authorized;
    }

    /**
     * Get communities available for a user.
     *
     * @param int $userId The user id
     *
     * @return null|array
     */
    public function getAvailableCommunitiesForUser(?int $userId)
    {
        $user = null;
        if ($userId && !$user = $this->userRepository->find($userId)) {
            return [];
        }

        return $this->communityRepository->findAvailableCommunitiesForUser($user);
    }

    /**
     * Get communities where a user is registered.
     *
     * @param int $userId The user id
     *
     * @return null|array The communities
     */
    public function getCommunitiesForUser(?int $userId)
    {
        $user = null;
        if ($userId && !$user = $this->userRepository->find($userId)) {
            return [];
        }

        return $this->communityRepository->findByUser($user);
    }

    /**
     * Check if a community already exists with this name.
     *
     * @return null|Community
     */
    public function exists(?string $name)
    {
        if (is_null($name)) {
            return null;
        }

        return $this->communityRepository->findBy(['name' => $name]);
    }

    /**
     * Check if a user is a member of a community.
     *
     * @return bool
     */
    public function isRegistered(int $communityId, int $userId)
    {
        return $this->communityRepository->isRegisteredById($communityId, $userId);
    }

    /**
     * Get a community by its id.
     *
     * @param null|User $user If a user is provided check and set that if he's in community and/or he's creator
     *
     * @return null|Community
     */
    public function getCommunity(int $communityId, ?User $user = null)
    {
        if ($community = $this->communityRepository->find($communityId)) {
            $community->setUrlKey($this->generateUrlKey($community));
            if ($user) {
                $this->setCommunityUserInfo($community, $user);
            }
        }

        return $community;
    }

    /**
     * Set the ads of a community.
     *
     * @param int Community's id
     *
     * @return Community
     */
    public function getAdsOfCommunity(int $communityId)
    {
        $mapsAds = [];

        // We get only the public proposal (we exclude searches)
        $proposals = $this->proposalRepository->findCommunityAds($this->communityRepository->find($communityId));

        foreach ($proposals as $proposal) {
            if (!$proposal->hasExpired()) {
                $mapsAd = $this->adManager->makeMapsAdFromProposal($proposal);
                $mapsAd->setEntityId($communityId);
                $mapsAds[] = $mapsAd;
            }
        }

        return new MapsAds($mapsAds);
    }

    /**
     * Remove the link between the journeys of a user and a community.
     */
    public function unlinkCommunityJourneys(CommunityUser $communityUser)
    {
        foreach ($communityUser->getUser()->getProposals() as $proposal) {
            foreach ($proposal->getCommunities() as $community) {
                if ($community->getId() == $communityUser->getCommunity()->getId()) {
                    $proposal->removeCommunity($community);
                    $this->entityManager->persist($proposal);
                }
            }
        }
        $this->entityManager->flush();

        return $communityUser;
    }

    /**
     * retrive communities owned by a user.
     */
    public function getOwnedCommunities(int $userId): ?array
    {
        $ownedCommunities = $this->communityRepository->getOwnedCommunities($userId);
        foreach ($ownedCommunities as $community) {
            $community->setUrlKey($this->generateUrlKey($community));
        }

        return $ownedCommunities;
    }

    /**
     * Give the roles : community_manager_public to the creator of a public community and save the data.
     *
     * @param Community $community The community created
     */
    public function save(Community $community)
    {
        $user = $community->getUser();

        $authItem = $this->authItemRepository->find(AuthItem::ROLE_COMMUNITY_MANAGER_PUBLIC);

        // Check if the user dont have the ROLE_COMMUNITY_MANAGER_PUBLIC right yet
        if (!$this->userManager->checkUserHaveAuthItem($user, $authItem)) {
            $userAuthAssignment = new UserAuthAssignment();
            $userAuthAssignment->setAuthItem($authItem);
            $user->addUserAuthAssignment($userAuthAssignment);

            $this->entityManager->persist($user);
        }

        if (!is_null($community->getDomain())) {
            $community->setValidationType(Community::DOMAIN_VALIDATION);
            $community->setDomain(preg_replace('/\s+/', '', $community->getDomain()));
        } else {
            $community->setValidationType(Community::AUTO_VALIDATION);
        }

        $this->entityManager->persist($community);
        $this->entityManager->flush();

        //  we dispatch the gamification event associated

        $event = new CommunityCreatedEvent($community);
        $this->eventDispatcher->dispatch($event, CommunityCreatedEvent::NAME);

        return $community;
    }

    /**
     * We update the community.
     */
    public function updateCommunity(Community $community)
    {
        $user = $community->getUser();

        // We check if the user is already a user of the community if not we add it
        $communityUsers = $community->getCommunityUsers();
        $members = [];
        foreach ($communityUsers as $communityUser) {
            $members[] = $communityUser->getUser()->getId();
        }

        if (!in_array($user->getId(), $members)) {
            $communityUser = new CommunityUser();
            $communityUser->setUser($user);
            $communityUser->setCommunity($community);

            $this->entityManager->persist($communityUser);
        }

        // Check if the user dont have the ROLE_COMMUNITY_MANAGER_PUBLIC right yet
        $authItem = $this->authItemRepository->find(AuthItem::ROLE_COMMUNITY_MANAGER_PUBLIC);

        if (!$this->userManager->checkUserHaveAuthItem($user, $authItem)) {
            $userAuthAssignment = new UserAuthAssignment();
            $userAuthAssignment->setAuthItem($authItem);
            $user->addUserAuthAssignment($userAuthAssignment);

            $this->entityManager->persist($user);
        }

        $this->entityManager->persist($community);
        $this->entityManager->flush();

        return $community;
    }

    /**
     * Persist and save community User for POST.
     *
     * @param CommunityUser $communityUser The community user to create
     */
    public function saveCommunityUser(CommunityUser $communityUser): CommunityUser
    {
        $this->entityManager->persist($communityUser);
        $this->entityManager->flush();

        $community = $communityUser->getCommunity();

        switch ($communityUser->getStatus()) {
            case CommunityUser::STATUS_PENDING:
                $event = new CommunityNewMembershipRequestEvent($communityUser);
                $this->eventDispatcher->dispatch(CommunityNewMembershipRequestEvent::NAME, $event);
                $event = new CommunityMembershipPendingEvent($community, $communityUser->getUser());
                $this->eventDispatcher->dispatch(CommunityMembershipPendingEvent::NAME, $event);

                break;

            case CommunityUser::STATUS_ACCEPTED_AS_MEMBER:
                $event = new CommunityNewMemberEvent($communityUser);
                $this->eventDispatcher->dispatch(CommunityNewMemberEvent::NAME, $event);

                break;
        }

        //  we dispatch the gamification event associated
        $action = $this->actionRepository->findOneBy(['name' => 'community_joined']);
        $actionEvent = new ActionEvent($action, $communityUser->getUser());
        $actionEvent->setCommunity($community);
        $this->eventDispatcher->dispatch($actionEvent, ActionEvent::NAME);

        return $communityUser;
    }

    /**
     * Update communityUser #### used only by old admin ####.
     */
    public function updateCommunityUser(CommunityUser $communityUser)
    {
        $this->entityManager->persist($communityUser);
        $this->entityManager->flush();

        return $communityUser;
    }

    public function getLastUsers(int $communityId): array
    {
        if ($community = $this->communityRepository->find($communityId)) {
            return $this->communityUserRepository->findNLastUsersOfACommunity($community);
        }

        return [];
    }

    public function getMembers(int $communityId, array $context = []): CommunityMembersList
    {
        $communityMembers = [];

        $community = $this->communityRepository->find($communityId);

        if ($community) {
            $communityUsers = $this->communityUserRepository->findForCommunity($community, $context);
            foreach ($communityUsers as $communityUser) {
                $communityMember = new CommunityMember();
                $communityMember->setId($communityUser->getUser()->getId());
                $communityMember->setFirstName($communityUser->getUser()->getGivenName());
                $communityMember->setShortFamilyName($communityUser->getUser()->getShortFamilyName());

                if ($community->getUser()->getId() == $communityUser->getUser()->getId()) {
                    $communityMember->setReferrer(true);
                }

                if (CommunityUser::STATUS_ACCEPTED_AS_MODERATOR == $communityUser->getStatus()) {
                    $communityMember->setModerator(true);
                }
                if (is_array($communityUser->getUser()->getAvatars()) && count($communityUser->getUser()->getAvatars()) > 0) {
                    $communityMember->setAvatar($communityUser->getUser()->getAvatars()[count($communityUser->getUser()->getAvatars()) - 1]);
                }

                $communityMembers[] = $communityMember;
            }
        }

        return new CommunityMembersList($communityMembers, count($communityUsers));
    }

    // MCommunity management

    /**
     * Get the MCommunities query.
     *
     * @param UserInterface $user The current user
     */
    public function getMCommunitiesRequest(UserInterface $user)
    {
        return $this->communityRepository->findAvailableCommunitiesForUser($user instanceof User ? $user : null, ['c.name' => 'asc']);
    }

    /**
     * Get the MCommunities.
     *
     * @param mixed $communities
     *
     * @return array The communities
     */
    public function getMCommunities(?string $userEmail, $communities)
    {
        $mCommunities = [];
        $temporaryCommunities = [];

        foreach ($communities as $community) {
            if (Community::DOMAIN_VALIDATION === $community->getValidationType()) {
                foreach (explode(';', $community->getDomain()) as $domain) {
                    if (str_contains($userEmail, $domain)) {
                        $temporaryCommunities[] = $community;
                    }
                }
            } elseif (Community::MANUAL_VALIDATION === $community->getValidationType() || Community::AUTO_VALIDATION === $community->getValidationType()) {
                $temporaryCommunities[] = $community;
            }
        }

        foreach ($temporaryCommunities as $community) {
            /**
             * @var Community $community
             */
            $mCommunity = new MCommunity();
            $mCommunity->setId($community->getId());
            $mCommunity->setName($community->getName());
            $mCommunity->setValidationType($community->getValidationType());
            $mCommunity->setUrlKey($this->generateUrlKey($community));
            $mCommunities[] = $mCommunity;
        }

        return $mCommunities;
    }

    public function joinCommunity(Community $community, User $user): ?CommunityUser
    {
        if (!$this->canJoin($user, $community, false)) {
            throw new \InvalidArgumentException("You can't join this community");
        }

        return $this->saveCommunityUser($this->makeCommunityUserForJoining($community, $user));

        return null;
    }

    public function leaveCommunity(Community $community, User $user): ?Community
    {
        $communityUser = $this->communityUserRepository->findBy(['community' => $community, 'user' => $user]);
        if (is_array($communityUser) && count($communityUser) > 0) {
            $this->deleteCommunityUser($communityUser[0]);
        }

        return $community;
    }

    /**
     * Delete a community user.
     *
     * @param CommunityUser $communityUser The community user to delete
     */
    public function deleteCommunityUser(CommunityUser $communityUser)
    {
        $user = $communityUser->getUser();
        $community = $communityUser->getCommunity();
        $this->entityManager->remove($communityUser);
        $this->entityManager->flush();
        $event = new CommunityLeftEvent($user, $community);
        $this->eventDispatcher->dispatch(CommunityLeftEvent::NAME, $event);
    }

    /**
     * Generate the UrlKey of a Community.
     *
     * @return string The url key
     */
    public function generateUrlKey(Community $community): string
    {
        $urlKey = $community->getName();
        $urlKey = str_replace(' ', '-', $urlKey);
        $urlKey = str_replace("'", '-', $urlKey);
        $urlKey = strtr(utf8_decode($urlKey), utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'), 'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY');
        $urlKey = preg_replace('/[^A-Za-z0-9\-]/', '', $urlKey);

        // We don't want to finish with a single "-"
        if ('-' == substr($urlKey, -1)) {
            $urlKey = substr($urlKey, 0, strlen($urlKey) - 1);
        }

        return $urlKey;
    }

    public function getNbMembers(Community $community): int
    {
        $communityUsers = $this->communityUserRepository->findForCommunityNoContext($community);

        return count($communityUsers);
    }

    public function checkIfMember(?User $user, Community $community): bool
    {
        if (is_null($user)) {
            return false;
        }

        if ($this->communityUserRepository->findBy(
            [
                'user' => $user,
                'community' => $community,
                'status' => [CommunityUser::STATUS_ACCEPTED_AS_MEMBER, CommunityUser::STATUS_ACCEPTED_AS_MODERATOR],
            ]
        )) {
            return true;
        }

        return false;
    }

    /**
     * Check the credentials against a security file.
     *
     * @return bool
     */
    private function checkSecurity(CommunitySecurity $security, string $login, string $password)
    {
        if ($file = fopen($this->securityPath.$security->getFilename(), 'r')) {
            while ($tab = fgetcsv($file, 4096, ';')) {
                if ($tab[0] === $login && $tab[1] === $password) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param User $user If a user is provided check and set that if he's in community
     */
    private function setCommunityUserInfo(Community $community, User $user)
    {
        $communityUsers = $this->communityUserRepository->findBy(['community' => $community, 'user' => $user]);

        $community->setMember(false);
        if (!is_null($communityUsers) and count($communityUsers) > 0) {
            $community->setMember(true);
            $community->setMemberStatus($communityUsers[0]->getStatus());
        }
    }

    private function makeCommunityUserForJoining(Community $community, User $user): CommunityUser
    {
        $communityUser = new CommunityUser();
        $communityUser->setCommunity($community);
        $communityUser->setUser($user);

        $communityUser->setStatus(CommunityUser::STATUS_ACCEPTED_AS_MEMBER);
        if (Community::MANUAL_VALIDATION == $community->getValidationType()) {
            $communityUser->setStatus(CommunityUser::STATUS_PENDING);
        }

        return $communityUser;
    }
}