api/src/User/Service/ReviewManager.php
<?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\User\Service;
use App\Carpool\Repository\AskRepository;
use App\User\Ressource\Review;
use App\User\Entity\Review as ReviewEntity;
use App\User\Entity\User;
use App\Carpool\Entity\Ask;
use App\Carpool\Entity\Criteria;
use App\User\Event\ReviewReceivedEvent;
use App\User\Repository\ReviewRepository;
use App\User\Ressource\ReviewDashboard;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Review manager service.
*
* @author Maxime Bardot <maxime.bardot@mobicoop.org>
*/
class ReviewManager
{
private $entityManager;
private $reviewRepository;
private $askRepository;
private $userReview;
private $eventDispatcher;
public function __construct(
EntityManagerInterface $entityManager,
ReviewRepository $reviewRepository,
AskRepository $askRepository,
EventDispatcherInterface $dispatcher,
bool $userReview
) {
$this->entityManager = $entityManager;
$this->reviewRepository = $reviewRepository;
$this->askRepository = $askRepository;
$this->userReview = $userReview;
$this->eventDispatcher = $dispatcher;
}
/**
* Build a Review (Ressource) from en Review (Entity)
*
* @param ReviewEntity $reviewEntity The review Entity
* @param boolean $isLeft true : the review has already been left
* @return Review
*/
private function buildReviewFromEntity(ReviewEntity $reviewEntity, bool $isLeft = true): Review
{
$review = new Review($reviewEntity->getId());
$review->setReviewer($reviewEntity->getReviewer());
$review->setReviewed($reviewEntity->getReviewed());
$review->setContent(nl2br($reviewEntity->getContent()));
$review->setDate((!is_null($reviewEntity->getUpdatedDate())) ? $reviewEntity->getUpdatedDate() : $reviewEntity->getCreatedDate());
$review->setLeft($isLeft);
return $review;
}
/**
* Build an array of Review (Ressources) from an array of Review (Entities)
*
* @param ReviewEntity[] $reviewEntities The array of Review entities
* @param boolean $isLeft true : the review has already been left
* @return Review[]
*/
private function buildReviewsFromEntities(array $reviewEntities, bool $isLeft = true): array
{
$reviews = [];
foreach ($reviewEntities as $reviewEntity) {
$reviews[] = $this->buildReviewFromEntity($reviewEntity, $isLeft);
}
return $reviews;
}
/**
* Build a Review (Entity) from en Review (Ressource)
*
* @param Review $review The review Entity
* @return ReviewEntity
*/
private function buildReviewFromRessource(Review $review): ReviewEntity
{
$reviewEntity = new ReviewEntity();
$reviewEntity->setReviewer($review->getReviewer());
$reviewEntity->setReviewed($review->getReviewed());
$reviewEntity->setContent($review->getContent());
return $reviewEntity;
}
/**
* Create a Review
*
* @param Review $review
* @return Review
*/
public function createReview(Review $review): Review
{
if (!$this->userReview) {
throw new \LogicException('Review system is disable');
}
$reviewEntity = $this->buildReviewFromRessource($review);
$this->entityManager->persist($reviewEntity);
$this->entityManager->flush();
// Event
$event = new ReviewReceivedEvent($reviewEntity);
$this->eventDispatcher->dispatch(ReviewReceivedEvent::NAME, $event);
return $this->buildReviewFromEntity($reviewEntity);
}
/**
* Get a Review
*
* @param integer $id
* @return Review|null
*/
public function getReview(int $id): ?Review
{
if (!$this->userReview) {
// Review system is disable
return null;
}
$reviewEntity = $this->reviewRepository->find($id);
if (!is_null($reviewEntity)) {
return $this->buildReviewFromEntity($reviewEntity);
}
return null;
}
/**
* Get reviews with specific reviewer and/or specific reviewed
*
* @param User $reviewer The reviewer
* @param User $reviewed The reviewed
* @return Review[]
*/
public function getSpecificReviews(User $reviewer=null, User $reviewed=null)
{
$reviews = [];
if (!$this->userReview) {
// Review system is disable
return $reviews;
}
$reviewEntities = $this->reviewRepository->findSpecificReviews($reviewer, $reviewed);
if (!is_null($reviewEntities)) {
return $this->buildReviewsFromEntities($reviewEntities);
}
return [];
}
/**
* Return all reviews concerning a User
* Those who he's the reviewer, the reviewed or those he can still leave on someone.
*
* @param User $reviewer The reviewer
* @param User $reviewed A specifiec reviewed
* @return Review[]
*/
public function getReviews(User $reviewer, User $reviewed=null): array
{
$reviews = [];
if (!$this->userReview) {
// Review system is disable
return $reviews;
}
// We get the reviews involving the User as reviewer or reviewd
$reviewEntities = $this->reviewRepository->findReviewsInvolvingUser($reviewer);
foreach ($reviewEntities as $reviewEntity) {
$reviews[] = $this->buildReviewFromEntity($reviewEntity);
}
// We get the list of the Users already reviewed
$userLeftReviews = $this->getSpecificReviews($reviewer, $reviewed);
$userIdAlreadyReviewed = [];
foreach ($userLeftReviews as $userLeftReview) {
if (!in_array($userLeftReview->getReviewed()->getId(), $userIdAlreadyReviewed)) {
$userIdAlreadyReviewed[] = $userLeftReview->getReviewed()->getId();
}
}
// We get the accepted Ask involving the User
$asks = $this->askRepository->findAcceptedAsksForUser($reviewer, $reviewed);
foreach ($asks as $ask) {
// We keep only oneway or outward
if ($ask->getType() == Ask::TYPE_ONE_WAY || $ask->getType() == Ask::TYPE_OUTWARD_ROUNDTRIP) {
// We will check if the review is already available to be left
$reviewAvailable = false;
// Determine the reviewed
if ($ask->getUser()->getId()==$reviewer->getId()) {
$reviewed = $ask->getUserRelated();
} else {
$reviewed = $ask->getUser();
}
// We check if the User has already reviewed the other user
if (in_array($reviewed->getId(), $userIdAlreadyReviewed)) {
// Already reviewed this user. We break the loop
continue;
} else {
// We need only one review to give by reviewed. We use this array also to store this information
$userIdAlreadyReviewed[] = $reviewed->getId();
}
$now = new \DateTime();
$nowDate = \DateTime::createFromFormat("d/m/Y H:i:s", $now->format("d/m/Y")." 00:00:00");
// $nowDate = \DateTime::createFromFormat("d/m/Y H:i:s", "03/11/2020 00:00:00");
$fromDate = clone $ask->getCriteria()->getFromDate();
if ($ask->getCriteria()->getFrequency()==Criteria::FREQUENCY_PUNCTUAL) {
// If the ask is punctual, the carpool must be passed
if ($nowDate>$fromDate) {
$reviewAvailable = true;
}
} else {
// The Ask is regular, the first week has to be passed
$day = $fromDate->format('w');
$diffToTheEndOfFirstWeek = ($day==0) ? 0 : 7 - $day;
$endOfFirstWeekDate = $fromDate->modify('+'.$diffToTheEndOfFirstWeek.'day');
if ($nowDate>$endOfFirstWeekDate) {
$reviewAvailable = true;
}
}
if ($reviewAvailable) {
// We create a Review to leave from the Ask
$reviewToLeave = new Review();
$reviewToLeave->setReviewer($reviewer);
$reviewToLeave->setReviewed($reviewed);
$reviewToLeave->setLeft(false);
$reviews[] = $reviewToLeave;
}
}
}
return $reviews;
}
/**
* Return the reviews Dashboard of a User
*
* @param User $user
* @return ReviewDashboard
*/
public function getReviewDashboard(User $reviewer, User $reviewed=null): ReviewDashboard
{
$reviewDashboard = new ReviewDashboard();
$reviewDashboard->setReviewActive($this->userReview);
// Get all reviews involving the User
$reviews = $this->getReviews($reviewer, $reviewed);
foreach ($reviews as $review) {
if (!$review->isLeft()) {
// It's a review to give
$reviewDashboard->addReviewsToGive($review);
} else {
if ($review->getReviewer()->getId() == $reviewer->getId()) {
$reviewDashboard->addGivenReviews($review);
} elseif ($review->getReviewed()->getId() == $reviewer->getId()) {
$reviewDashboard->addReceivedReviews($review);
}
}
}
return $reviewDashboard;
}
/**
* Determine if a user can get a review from another user
*
* @param User $reviewer The reviewer
* @param User $reviewed The reviewed
* @return bool The result
*/
public function canReceiveReview(User $reviewer, User $reviewed): bool
{
// Using the dashboard of the currentUser but specifically with the user possibly to review
// If there is a 'reviewsToGive' in the array, then the current user can leave a review for this specific user
$reviews = $this->getReviewDashboard($reviewer, $reviewed);
if (!$this->userReview) {
// Review system disable.
return false;
} elseif (is_array($reviews->getReviewsToGive()) && count($reviews->getReviewsToGive())>0) {
return true;
}
return false;
}
}