diff --git a/src/Controller/api/v1/PollController.php b/src/Controller/api/v1/PollController.php index 04009e7..5499a76 100644 --- a/src/Controller/api/v1/PollController.php +++ b/src/Controller/api/v1/PollController.php @@ -104,7 +104,7 @@ class PollController extends EmailsController { } else { // free access to poll - return $this->json( $poll->display() ); + return $this->json( sort( $poll->display()) ); } } diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php index b0131b6..ff7a513 100755 --- a/src/Entity/Poll.php +++ b/src/Entity/Poll.php @@ -16,809 +16,861 @@ use JMS\Serializer\Annotation as Serializer; * @ORM\Entity(repositoryClass="App\Repository\PollRepository") * @Serializer\ExclusionPolicy("all") */ -class Poll { - - use RandomTrait; - - use TimeStampableTrait; - - /** - * @ORM\Id() - * @ORM\GeneratedValue() - * @ORM\Column(type="integer") - * @Serializer\Expose() - * @Serializer\Type("integer") - */ - public $id; - - /** - * @ORM\Column(type="string", length=255) - * @Serializer\Expose() - * @Serializer\Type("string") - */ - public $title; - /** - * @ORM\Column(type="string", length=255, nullable=false, unique=true) - * @Serializer\Expose() - * @Serializer\Type("string") - */ - public $customUrl; - /** - * @ORM\Column(type="string", length=1000) - * @Serializer\Expose() - * @Serializer\Type("string") - */ - public $description; - - /** - * @ORM\Column(type="datetime") - * @Serializer\Expose() - */ - public $expiracyDate; - /** - * @ORM\ManyToOne(targetEntity="App\Entity\Owner", inversedBy="polls",cascade={"persist"}) - * @ORM\JoinColumn(nullable=false) - * @Serializer\Type("App\Entity\Owner") - * @Serializer\Expose() - */ - public $owner; - /** - * kind of poll, 'text' or 'date' - * @ORM\Column(type="string", length=255) - * @Serializer\Type("string") - * @Serializer\Expose() - */ - public $kind = 'text'; - /** - * array of possible answer to each choice, by default: "yes" or nothing only. - * could be also "yes", "maybe", "no". extensible to anything - * @ORM\Column(type="array") - * @Serializer\Type("array") - * @Serializer\Expose() - */ - public $allowedAnswers; - - /** - * people can add votes - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $votesAllowed = true; - - /** - * people can add votes - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $isZeroKnowledge; - - /** - * max number of stack of votes possible. - * limits the number of people who can answer. as long as you trust the people to give only one answer with a reliable system. - * @ORM\Column(type="smallint", nullable=true) - * @Serializer\Type("smallint") - * @Serializer\Expose() - */ - public $votesMax = 1024; - - /** - * max number of choices people can answer in a stack of vote. for text polls only, not date kind. - * by default, people can check as many answers as they want. - * If the question is "check your 3 favourite flavours" and displays 10 flavours, only the first 3 clicked will be taken into account. GUI should be able to make this clear and togglable so that people can change their 3 favourite flavours in their answer. - * @ORM\Column(type="smallint", nullable=true) - * @Serializer\Type("smallint") - * @Serializer\Expose() - */ - public $choicesMax = - 1; - - /** - * people can add comments - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $commentsAllowed = true; - - /** - * kind of way the people can modify the poll - * everybody - can modify votes - * self - one can only modify its own vote - * nobody - no one can modify the votes (excepted admin), pray to have it right at first - * @ORM\Column(type="string", length=255) - * @Serializer\Type("string") - * @Serializer\Expose() - */ - public $modificationPolicy = 'everybody'; - /** - * send a mail on a new comment - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $mailOnComment = true; - /** - * send a mail on a new vote - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $mailOnVote = false; - /** - * hide publicly results - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $hideResults = false; - /** - * show publicly results even if there is a password to access the vote - * @ORM\Column(type="boolean", nullable=true) - * @Serializer\Type("boolean") - * @Serializer\Expose() - */ - public $showResultEvenIfPasswords = false; - /** - * @ORM\OneToMany(targetEntity="App\Entity\Vote", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) - * @Serializer\Type("ArrayCollection") - * @Serializer\Expose() - */ - public $votes; - /** - * @ORM\OneToMany(targetEntity="App\Entity\StackOfVotes", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) - * @Serializer\Expose() - */ - public $stacksOfVotes; - /** - * @ORM\OneToMany(targetEntity="App\Entity\Choice", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) - * @Serializer\Expose() - * @Serializer\Type("ArrayCollection") - */ - public $choices; - /** - * @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) - * @Serializer\Expose() - * @Serializer\Type("ArrayCollection") - */ - public $comments; - /** - * number of days from now for default expiracy date - * @var int - * @Serializer\Expose() - */ - public $defaultExpiracyDaysFromNow = 60; - /** - * vote restricted by a password in md5 format - * @ORM\Column(type="string", length=255, nullable=true) - */ - private $password; - /** - * used to allow administration - * @ORM\Column(type="string", length=255) - * @Serializer\Type("string") - */ - private $adminKey; - private $maxChoicesLimit = 25; - - public function __construct() { - - $this->initiate(); - $this->setCreatedAt( new DateTime() ); - $this->setAdminKey( $this->generateRandomKey() ); - $this->votes = new ArrayCollection(); - $this->stacksOfVotes = new ArrayCollection(); - $this->choices = new ArrayCollection(); - $this->comments = new ArrayCollection(); - } - - private function initiate() { - $this->votes = new ArrayCollection(); - $this->stacksOfVotes = new ArrayCollection(); - $this->choices = new ArrayCollection(); - $this->comments = new ArrayCollection(); - $this->setAdminKey( $this->generateRandomKey() ); - $this->setCreatedAt( new DateTime() ); - $this->setExpiracyDate( $this->addDaysToDate( - new DateTime(), - $this->defaultExpiracyDaysFromNow - ) ); - $this->setAllowedAnswers( [ 'yes', 'maybe', 'no' ] ); - } - - public function setCreatedAt( DateTimeInterface $createdAt ): self { - $this->createdAt = $createdAt; - - return $this; - } - - public function displayForAdmin() { - $content = $this->display(); - $content[ 'owner' ] = $this->getOwner()->displayForAdmin(); - $content[ 'admin_key' ] = $this->getAdminKey(); - $content[ 'password_hash' ] = $this->getPassword(); - $content[ 'id' ] = $this->getId(); - - return $content; - } - - // counts each number of answer for this choice - - public function display() { - - $computedAnswers = $this->computeAnswers(); - $displayedStackOfVotes = []; - foreach ( $this->getStacksOfVotes() as $stack ) { - $displayedStackOfVotes[] = $stack->display(); - } - $displayedChoices = []; - foreach ( $this->getChoices() as $choice ) { - $displayedChoices[] = $choice->display( $this->getKind() ); - } - $displayedComments = []; - foreach ( $this->getComments() as $comment ) { - $displayedComments[] = $comment->display(); - } - - - $resp = [ - 'title' => $this->getTitle(), - 'description' => $this->getDescription(), - 'choices_grouped' => $computedAnswers[ 'grouped_dates' ], - 'created_at' => $this->getCreatedAt()->format( 'c' ), - 'expiracy_date' => $this->getExpiracyDate()->format( 'c' ), - 'votes_max' => $this->getVotesMax(), - 'custom_url' => $this->getCustomUrl(), - 'choices_max' => $this->getChoicesMax(), - 'kind' => $this->getKind(), - 'allowed_answers' => $this->getAllowedAnswers(), - 'votes_allowed' => $this->getVotesAllowed(), - 'modification_policy' => $this->getModificationPolicy(), - 'hide_results' => $this->getHideResults(), - 'show_results_even_if_password' => $this->getShowResultEvenIfPasswords(), - 'owner' => [ - 'pseudo' => $this->getOwner()->getPseudo(), - ] - , - 'choices' => $computedAnswers[ 'answers' ], - 'password_protected' => $this->getPassword() ? 'yes' : 'no', - 'max_score' => $computedAnswers[ 'max_score' ], - 'stacks' => $displayedStackOfVotes, - 'comments' => $displayedComments, - ]; - -// if ( $this->getKind() == 'text' ) { -// $resp[ 'choices' ] = $computedAnswers[ 'grouped_dates' ]; -// $resp[ 'choices_grouped' ] = $computedAnswers[ 'answers' ]; -// } elseif ( $this->getKind() == 'date' ) { -// $resp[ 'choices' ] = $computedAnswers[ 'answers' ]; -// $resp[ 'choices_grouped' ] = $computedAnswers[ 'grouped_dates' ]; -// } - - return $resp; - } - - public function computeAnswers() { - $computedArray = []; - $grouped_dates = []; - $maxScore = 0; - - if ( $this->getKind() == 'date' ) { - foreach ( $this->getChoices() as $choice ) { - $boom = explode( ' >>> ', $choice->getName() ); - - // handle sub time slices - if ( count( $boom ) == 2 ) { - - - if ( ! isset( $grouped_dates[ $boom[ 0 ] ] ) ) { - - $grouped_dates[ $boom[ 0 ] ] = [ - - "date_string" => $boom[ 0 ], - "choices" => [], - ]; - } - $grouped_dates[ $boom[ 0 ] ][ "choices" ][] = [ - "choice_id" => $choice->getId(), - "name" => $boom[ 1 ], - ]; - } - elseif(count( $boom ) == 1){ - $name = $choice->getName(); - $grouped_dates[ $name ] = [ - - "date_string" => $name, - "choices" => [], - ]; - $grouped_dates[ $name ][ "choices" ][] = [ - "choice_id" => $choice->getId(), - "name" => $name, - ]; - } - } - } elseif ( $this->getKind() == 'text' ) { - foreach ( $this->getChoices() as $choice ) { - $name = $choice->getName(); - - - if ( ! isset( $grouped_dates[ $name ] ) ) { - - $grouped_dates[ $name ] = [ - - "date_string" => $name, - "choices" => [], - ]; - } - $grouped_dates[ $name ][ "choices" ][] = [ - "choice_id" => $choice->getId(), - "name" => $name, - ]; - } - } - $scoreInfos = [ - - 'id' => null, - 'name' => null, - 'score' => 0, - 'created_at' => null, - 'yes' => [ - 'count' => 0, - 'people' => [], - ], - 'maybe' => [ - 'count' => 0, - 'people' => [], - ], - 'no' => [ - 'count' => 0, - 'people' => [], - ], - ]; - - // first, prefill all choices - foreach ( $this->getChoices() as $choice ) { - $computedArray[ $choice->getId() ] = array_merge( $scoreInfos, $choice->display( $this->getKind() ) ); - } - // then, compute stack of votes scores on each choice - foreach ( $this->getStacksOfVotes() as $stack_of_vote ) { - foreach ( $stack_of_vote->getVotes() as $vote ) { - $answer = $vote->getValue(); - $choice_id = $vote->getChoice()->getId(); - $choice_url = $vote->getChoice()->getUrl(); - - if ( ! isset( $computedArray[ $choice_id ] ) ) { - $computedArray[ $choice_id ] = [ - 'id' => $choice_id, - 'url' => $choice_url, - 'name' => $vote->getChoice()->getName(), - 'score' => 0, - 'yes' => [ - 'count' => 0, - 'people' => [], - ], - 'maybe' => [ - 'count' => 0, - 'people' => [], - ], - 'no' => [ - 'count' => 0, - 'people' => [], - ], - - ]; - } - $computedArray[ $choice_id ][ $answer ][ 'count' ] ++; - $computedArray[ $choice_id ][ $answer ][ 'people' ][] = $stack_of_vote->getOwner()->getPseudo(); - - if ( $answer == 'yes' ) { - $computedArray[ $choice_id ][ 'score' ] += 1; - } elseif ( $answer == 'maybe' ) { - $computedArray[ $choice_id ][ 'score' ] += 0.5; - } - // compare with max value - if ( $computedArray[ $choice_id ][ 'score' ] > $maxScore ) { - $maxScore = $computedArray[ $choice_id ][ 'score' ]; - } - } - } - $answersWithStats = []; - foreach ( $computedArray as $choice_stat ) { - $answersWithStats[] = $choice_stat; - } - $groupsOfDates = []; - foreach ( $grouped_dates as $group ) { - $ii = 0; - foreach ( $group[ "choices" ] as $slice ) { - $slice[ 'score' ] = $computedArray[ $slice[ 'choice_id' ] ][ 'score' ]; - $slice[ 'yes' ] = $computedArray[ $slice[ 'choice_id' ] ][ 'yes' ]; - $slice[ 'maybe' ] = $computedArray[ $slice[ 'choice_id' ] ][ 'maybe' ]; - $slice[ 'no' ] = $computedArray[ $slice[ 'choice_id' ] ][ 'no' ]; - $slice[ 'id' ] = $slice[ 'choice_id' ]; - $group[ "choices" ][ $ii ] = $slice; - $ii ++; - } - - $groupsOfDates[] = $group; - } - - return [ - 'answers' => $answersWithStats, - 'grouped_dates' => $groupsOfDates, - 'max_score' => $maxScore, - ]; - } - - /** - * @return Collection|Choice[] - */ - public function getChoices(): Collection { - return $this->choices; - } - - public function getKind(): ?string { - return $this->kind; - } - - public function setKind( string $kind ): self { - $this->kind = $kind; - - return $this; - } - - public function getStacksOfVotes() { - return $this->stacksOfVotes; - } - - public function setStacksOfVotes( ?StackOfVotes $stacksOfVotes ): self { - $this->stacksOfVotes = $stacksOfVotes; - - return $this; - } - - /** - * @return Collection|Comment[] - */ - public function getComments(): Collection { - return $this->comments; - } - - public function getTitle(): ?string { - return $this->title; - } - - public function setTitle( string $title ): self { - $this->title = $title; - - return $this; - } - - public function getDescription(): ?string { - return $this->description; - } - - public function setDescription( string $description ): self { - $this->description = $description; - - return $this; - } - - public function getCreatedAt(): ?DateTimeInterface { - return $this->createdAt; - } - - public function getExpiracyDate(): ?DateTimeInterface { - return $this->expiracyDate; - } - - public function setExpiracyDate( DateTimeInterface $expiracyDate ): self { - $this->expiracyDate = $expiracyDate; - - return $this; - } - - public function getVotesMax() { - return $this->votesMax; - } - - public function setVotesMax( $votesMax ): self { - $this->votesMax = $votesMax; - - return $this; - } - - public function getChoicesMax() { - return $this->choicesMax; - } - - public function setChoicesMax( $choicesMax ): self { - $this->choicesMax = $choicesMax; - - return $this; - } - - public function getAllowedAnswers(): ?array { - return $this->allowedAnswers; - } - - public function setAllowedAnswers( array $allowedAnswers ): self { - if ( ! count( $allowedAnswers ) ) { - $this->allowedAnswers = [ 'yes' ]; - } else { - $this->allowedAnswers = $allowedAnswers; - } - - return $this; - } - - public function getVotesAllowed(): ?bool { - return $this->votesAllowed; - } - - public function setVotesAllowed( ?bool $votesAllowed ): self { - $this->votesAllowed = $votesAllowed; - - return $this; - } - - public function getModificationPolicy(): ?string { - return $this->modificationPolicy; - } - - public function setModificationPolicy( string $modificationPolicy ): self { - $this->modificationPolicy = $modificationPolicy; - - return $this; - } - - public function getHideResults(): ?bool { - return $this->hideResults; - } - - public function setHideResults( bool $hideResults ): self { - $this->hideResults = $hideResults; - - return $this; - } - - public function getShowResultEvenIfPasswords(): ?bool { - return $this->showResultEvenIfPasswords; - } - - public function setShowResultEvenIfPasswords( bool $showResultEvenIfPasswords ): self { - $this->showResultEvenIfPasswords = $showResultEvenIfPasswords; - - return $this; - } - - public function getOwner(): ?Owner { - return $this->owner; - } - - public function setOwner( ?Owner $owner ): self { - $this->owner = $owner; - - return $this; - } - - public function getPassword(): ?string { - return $this->password; - } - - public function setPassword( string $password ): self { - $this->password = md5( $password ); - - return $this; - } - - public function getAdminKey(): ?string { - return $this->adminKey; - } - - public function setAdminKey( string $adminKey ): self { - $this->adminKey = $adminKey; - - return $this; - } - - public function getId(): ?int { - return $this->id; - } - - public function findChoiceById( int $id ) { - - $choices = $this->getChoices(); - $counter = 0; - // there must be something cleaner than this in Doctrine ArrayCollection - foreach ( $choices as $choice ) { - $counter ++; - if ( $counter > $this->maxChoicesLimit ) { - throw new ErrorException( "max number of choices reached for this poll" ); - } - if ( $choice && $choice->getId() == $id ) { - return $choice; - } - - } - - return null; - } - - /** - * @return Collection|Vote[] - */ - public function getVotes(): Collection { - return $this->votes; - } - - public function getCustomUrl(): ?string { - return $this->customUrl; - } - - public function setCustomUrl( string $customUrl ): self { - $this->customUrl = $customUrl; - - return $this; - } - - public function getMailOnComment(): ?bool { - return $this->mailOnComment; - } - - public function setMailOnComment( bool $mailOnComment ): self { - $this->mailOnComment = $mailOnComment; - - return $this; - } - - public function getMailOnVote(): ?bool { - return $this->mailOnVote; - } - - public function setMailOnVote( bool $mailOnVote ): self { - $this->mailOnVote = $mailOnVote; - - return $this; - } - - public function addComment( Comment $comment ): self { - if ( ! $this->comments->contains( $comment ) ) { - $this->comments[] = $comment; - $comment->setPoll( $this ); - } - - return $this; - } - - public function removeComment( Comment $comment ): self { - if ( $this->comments->contains( $comment ) ) { - $this->comments->removeElement( $comment ); - // set the owning side to null (unless already changed) - if ( $comment->getPoll() === $this ) { - $comment->setPoll( null ); - } - } - - return $this; - } - - public function addStackOfVote( StackOfVotes $stackOfVote ): self { - if ( ! $this->stacksOfVotes->contains( $stackOfVote ) ) { - $this->stacksOfVotes[] = $stackOfVote; - $stackOfVote->setPoll( $this ); - } - - return $this; - } - - public function removeStackOfVote( StackOfVotes $stackOfVote ): self { - if ( $this->stacksOfVotes->contains( $stackOfVote ) ) { - $this->stacksOfVotes->removeElement( $stackOfVote ); - // set the owning side to null (unless already changed) - if ( $stackOfVote->getPoll() === $this ) { - $stackOfVote->setPoll( null ); - } - } - - return $this; - } - - public function addVote( Vote $vote ): self { - if ( ! $this->votes->contains( $vote ) ) { - $this->votes[] = $vote; - $vote->setPoll( $this ); - } - - return $this; - } - - public function removeVote( Vote $vote ): self { - if ( $this->votes->contains( $vote ) ) { - $this->votes->removeElement( $vote ); - // set the owning side to null (unless already changed) - if ( $vote->getPoll() === $this ) { - $vote->setPoll( null ); - } - } - - return $this; - } - - public function addTextChoiceArray( array $choiceTextArray ): self { - foreach ( $choiceTextArray as $text ) { - $newChoice = new Choice(); - $newChoice->setName( $text ); - $this->addChoice( $newChoice ); - } - - return $this; - } - - public function addChoice( Choice $choice ): self { - if ( ! is_null( $this->choices ) ) { - if ( ! $this->choices->contains( $choice ) ) { - $this->choices[] = $choice; - $choice->setPoll( $this ); - } - } else { - $this->choices[] = $choice; - $choice->setPoll( $this ); - } - - - return $this; - } - - public function addStacksOfVote( StackOfVotes $stacksOfVote ): self { - if ( ! $this->stacksOfVotes->contains( $stacksOfVote ) ) { - $this->stacksOfVotes[] = $stacksOfVote; - $stacksOfVote->setPoll( $this ); - } - - return $this; - } - - public function removeStacksOfVote( StackOfVotes $stacksOfVote ): self { - if ( $this->stacksOfVotes->contains( $stacksOfVote ) ) { - $this->stacksOfVotes->removeElement( $stacksOfVote ); - // set the owning side to null (unless already changed) - if ( $stacksOfVote->getPoll() === $this ) { - $stacksOfVote->setPoll( null ); - } - } - - return $this; - } - - public function removeChoice( Choice $choice ): self { - if ( $this->choices->contains( $choice ) ) { - $this->choices->removeElement( $choice ); - // set the owning side to null (unless already changed) - if ( $choice->getPoll() === $this ) { - $choice->setPoll( null ); - } - } - - return $this; - } - - public function getCommentsAllowed(): ?bool { - return $this->commentsAllowed; - } - - public function setCommentsAllowed( ?bool $commentsAllowed ): self { - $this->commentsAllowed = $commentsAllowed; - - return $this; - } - - public function getIsZeroKnowledge(): ?bool { - return $this->isZeroKnowledge; - } - - public function setIsZeroKnowledge( ?bool $isZeroKnowledge ): self { - $this->isZeroKnowledge = $isZeroKnowledge; - - return $this; - } +class Poll +{ + + use RandomTrait; + + use TimeStampableTrait; + + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * @Serializer\Expose() + * @Serializer\Type("integer") + */ + public $id; + + /** + * @ORM\Column(type="string", length=255) + * @Serializer\Expose() + * @Serializer\Type("string") + */ + public $title; + /** + * @ORM\Column(type="string", length=255, nullable=false, unique=true) + * @Serializer\Expose() + * @Serializer\Type("string") + */ + public $customUrl; + /** + * @ORM\Column(type="string", length=1000) + * @Serializer\Expose() + * @Serializer\Type("string") + */ + public $description; + + /** + * @ORM\Column(type="datetime") + * @Serializer\Expose() + */ + public $expiracyDate; + /** + * @ORM\ManyToOne(targetEntity="App\Entity\Owner", inversedBy="polls",cascade={"persist"}) + * @ORM\JoinColumn(nullable=false) + * @Serializer\Type("App\Entity\Owner") + * @Serializer\Expose() + */ + public $owner; + /** + * kind of poll, 'text' or 'date' + * @ORM\Column(type="string", length=255) + * @Serializer\Type("string") + * @Serializer\Expose() + */ + public $kind = 'text'; + /** + * array of possible answer to each choice, by default: "yes" or nothing only. + * could be also "yes", "maybe", "no". extensible to anything + * @ORM\Column(type="array") + * @Serializer\Type("array") + * @Serializer\Expose() + */ + public $allowedAnswers; + + /** + * people can add votes + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $votesAllowed = true; + + /** + * people can add votes + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $isZeroKnowledge; + + /** + * max number of stack of votes possible. + * limits the number of people who can answer. as long as you trust the people to give only one answer with a reliable system. + * @ORM\Column(type="smallint", nullable=true) + * @Serializer\Type("smallint") + * @Serializer\Expose() + */ + public $votesMax = 1024; + + /** + * max number of choices people can answer in a stack of vote. for text polls only, not date kind. + * by default, people can check as many answers as they want. + * If the question is "check your 3 favourite flavours" and displays 10 flavours, only the first 3 clicked will be taken into account. GUI should be able to make this clear and togglable so that people can change their 3 favourite flavours in their answer. + * @ORM\Column(type="smallint", nullable=true) + * @Serializer\Type("smallint") + * @Serializer\Expose() + */ + public $choicesMax = -1; + + /** + * people can add comments + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $commentsAllowed = true; + + /** + * kind of way the people can modify the poll + * everybody - can modify votes + * self - one can only modify its own vote + * nobody - no one can modify the votes (excepted admin), pray to have it right at first + * @ORM\Column(type="string", length=255) + * @Serializer\Type("string") + * @Serializer\Expose() + */ + public $modificationPolicy = 'everybody'; + /** + * send a mail on a new comment + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $mailOnComment = true; + /** + * send a mail on a new vote + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $mailOnVote = false; + /** + * hide publicly results + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $hideResults = false; + /** + * show publicly results even if there is a password to access the vote + * @ORM\Column(type="boolean", nullable=true) + * @Serializer\Type("boolean") + * @Serializer\Expose() + */ + public $showResultEvenIfPasswords = false; + /** + * @ORM\OneToMany(targetEntity="App\Entity\Vote", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) + * @Serializer\Type("ArrayCollection") + * @Serializer\Expose() + */ + public $votes; + /** + * @ORM\OneToMany(targetEntity="App\Entity\StackOfVotes", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) + * @Serializer\Expose() + */ + public $stacksOfVotes; + /** + * @ORM\OneToMany(targetEntity="App\Entity\Choice", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) + * @Serializer\Expose() + * @Serializer\Type("ArrayCollection") + */ + public $choices; + /** + * @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="poll", orphanRemoval=true, cascade={"persist", "remove"}) + * @Serializer\Expose() + * @Serializer\Type("ArrayCollection") + */ + public $comments; + /** + * number of days from now for default expiracy date + * @var int + * @Serializer\Expose() + */ + public $defaultExpiracyDaysFromNow = 60; + /** + * vote restricted by a password in md5 format + * @ORM\Column(type="string", length=255, nullable=true) + */ + private $password; + /** + * used to allow administration + * @ORM\Column(type="string", length=255) + * @Serializer\Type("string") + */ + private $adminKey; + private $maxChoicesLimit = 25; + + public function __construct() + { + + $this->initiate(); + $this->setCreatedAt(new DateTime()); + $this->setAdminKey($this->generateRandomKey()); + $this->votes = new ArrayCollection(); + $this->stacksOfVotes = new ArrayCollection(); + $this->choices = new ArrayCollection(); + $this->comments = new ArrayCollection(); + } + + private function initiate() + { + $this->votes = new ArrayCollection(); + $this->stacksOfVotes = new ArrayCollection(); + $this->choices = new ArrayCollection(); + $this->comments = new ArrayCollection(); + $this->setAdminKey($this->generateRandomKey()); + $this->setCreatedAt(new DateTime()); + $this->setExpiracyDate($this->addDaysToDate( + new DateTime(), + $this->defaultExpiracyDaysFromNow + )); + $this->setAllowedAnswers(['yes', 'maybe', 'no']); + } + + public function setCreatedAt(DateTimeInterface $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function displayForAdmin() + { + $content = $this->display(); + $content['owner'] = $this->getOwner()->displayForAdmin(); + $content['admin_key'] = $this->getAdminKey(); + $content['password_hash'] = $this->getPassword(); + $content['id'] = $this->getId(); + + return $content; + } + + // counts each number of answer for this choice + + public function display() + { + + $computedAnswers = $this->computeAnswers(); + $displayedStackOfVotes = []; + foreach ($this->getStacksOfVotes() as $stack) { + $displayedStackOfVotes[] = $stack->display(); + } + $displayedChoices = []; + foreach ($this->getChoices() as $choice) { + $displayedChoices[] = $choice->display($this->getKind()); + } + $displayedComments = []; + foreach ($this->getComments() as $comment) { + $displayedComments[] = $comment->display(); + } + + + $resp = [ + 'allowed_answers' => $this->getAllowedAnswers(), + 'choices' => $computedAnswers['answers'], + 'choices_grouped' => $computedAnswers['grouped_dates'], + 'choices_max' => $this->getChoicesMax(), + 'comments' => $displayedComments, + 'created_at' => $this->getCreatedAt()->format('c'), + 'custom_url' => $this->getCustomUrl(), + 'description' => $this->getDescription(), + 'expiracy_date' => $this->getExpiracyDate()->format('c'), + 'hide_results' => $this->getHideResults(), + 'kind' => $this->getKind(), + 'max_score' => $computedAnswers['max_score'], + 'modification_policy' => $this->getModificationPolicy(), + 'owner' => ['pseudo' => $this->getOwner()->getPseudo(),], + 'password_protected' => $this->getPassword() ? 'yes' : 'no', + 'show_results_even_if_password' => $this->getShowResultEvenIfPasswords(), + 'stacks' => $displayedStackOfVotes, + 'title' => $this->getTitle(), + 'votes_allowed' => $this->getVotesAllowed(), + 'votes_max' => $this->getVotesMax(), + ]; + + return $resp; + } + + public function computeAnswers() + { + $computedArray = []; + $grouped_dates = []; + $maxScore = 0; + + if ($this->getKind() == 'date') { + foreach ($this->getChoices() as $choice) { + $boom = explode(' >>> ', $choice->getName()); + + // handle sub time slices + if (count($boom) == 2) { + + + if (!isset($grouped_dates[$boom[0]])) { + + $grouped_dates[$boom[0]] = [ + + "date_string" => $boom[0], + "choices" => [], + ]; + } + $grouped_dates[$boom[0]]["choices"][] = [ + "choice_id" => $choice->getId(), + "name" => $boom[1], + ]; + } elseif (count($boom) == 1) { + $name = $choice->getName(); + $grouped_dates[$name] = [ + + "date_string" => $name, + "choices" => [], + ]; + $grouped_dates[$name]["choices"][] = [ + "choice_id" => $choice->getId(), + "name" => $name, + ]; + } + } + } elseif ($this->getKind() == 'text') { + foreach ($this->getChoices() as $choice) { + $name = $choice->getName(); + + + if (!isset($grouped_dates[$name])) { + + $grouped_dates[$name] = [ + + "date_string" => $name, + "choices" => [], + ]; + } + $grouped_dates[$name]["choices"][] = [ + "choice_id" => $choice->getId(), + "name" => $name, + ]; + } + } + $scoreInfos = [ + + 'id' => null, + 'name' => null, + 'score' => 0, + 'created_at' => null, + 'yes' => [ + 'count' => 0, + 'people' => [], + ], + 'maybe' => [ + 'count' => 0, + 'people' => [], + ], + 'no' => [ + 'count' => 0, + 'people' => [], + ], + ]; + + // first, prefill all choices + foreach ($this->getChoices() as $choice) { + $computedArray[$choice->getId()] = array_merge($scoreInfos, $choice->display($this->getKind())); + } + // then, compute stack of votes scores on each choice + foreach ($this->getStacksOfVotes() as $stack_of_vote) { + foreach ($stack_of_vote->getVotes() as $vote) { + $answer = $vote->getValue(); + $choice_id = $vote->getChoice()->getId(); + $choice_url = $vote->getChoice()->getUrl(); + + if (!isset($computedArray[$choice_id])) { + $computedArray[$choice_id] = [ + 'id' => $choice_id, + 'url' => $choice_url, + 'name' => $vote->getChoice()->getName(), + 'score' => 0, + 'yes' => [ + 'count' => 0, + 'people' => [], + ], + 'maybe' => [ + 'count' => 0, + 'people' => [], + ], + 'no' => [ + 'count' => 0, + 'people' => [], + ], + + ]; + } + $computedArray[$choice_id][$answer]['count']++; + $computedArray[$choice_id][$answer]['people'][] = $stack_of_vote->getOwner()->getPseudo(); + + if ($answer == 'yes') { + $computedArray[$choice_id]['score'] += 1; + } elseif ($answer == 'maybe') { + $computedArray[$choice_id]['score'] += 0.5; + } + // compare with max value + if ($computedArray[$choice_id]['score'] > $maxScore) { + $maxScore = $computedArray[$choice_id]['score']; + } + } + } + $answersWithStats = []; + foreach ($computedArray as $choice_stat) { + $answersWithStats[] = $choice_stat; + } + $groupsOfDates = []; + foreach ($grouped_dates as $group) { + $ii = 0; + foreach ($group["choices"] as $slice) { + $slice['score'] = $computedArray[$slice['choice_id']]['score']; + $slice['yes'] = $computedArray[$slice['choice_id']]['yes']; + $slice['maybe'] = $computedArray[$slice['choice_id']]['maybe']; + $slice['no'] = $computedArray[$slice['choice_id']]['no']; + $slice['id'] = $slice['choice_id']; + $group["choices"][$ii] = $slice; + $ii++; + } + + $groupsOfDates[] = $group; + } + + return [ + 'answers' => $answersWithStats, + 'grouped_dates' => $groupsOfDates, + 'max_score' => $maxScore, + ]; + } + + /** + * @return Collection|Choice[] + */ + public function getChoices(): Collection + { + return $this->choices; + } + + public function getKind(): ?string + { + return $this->kind; + } + + public function setKind(string $kind): self + { + $this->kind = $kind; + + return $this; + } + + public function getStacksOfVotes() + { + return $this->stacksOfVotes; + } + + public function setStacksOfVotes(?StackOfVotes $stacksOfVotes): self + { + $this->stacksOfVotes = $stacksOfVotes; + + return $this; + } + + /** + * @return Collection|Comment[] + */ + public function getComments(): Collection + { + return $this->comments; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getCreatedAt(): ?DateTimeInterface + { + return $this->createdAt; + } + + public function getExpiracyDate(): ?DateTimeInterface + { + return $this->expiracyDate; + } + + public function setExpiracyDate(DateTimeInterface $expiracyDate): self + { + $this->expiracyDate = $expiracyDate; + + return $this; + } + + public function getVotesMax() + { + return $this->votesMax; + } + + public function setVotesMax($votesMax): self + { + $this->votesMax = $votesMax; + + return $this; + } + + public function getChoicesMax() + { + return $this->choicesMax; + } + + public function setChoicesMax($choicesMax): self + { + $this->choicesMax = $choicesMax; + + return $this; + } + + public function getAllowedAnswers(): ?array + { + return $this->allowedAnswers; + } + + public function setAllowedAnswers(array $allowedAnswers): self + { + if (!count($allowedAnswers)) { + $this->allowedAnswers = ['yes']; + } else { + $this->allowedAnswers = $allowedAnswers; + } + + return $this; + } + + public function getVotesAllowed(): ?bool + { + return $this->votesAllowed; + } + + public function setVotesAllowed(?bool $votesAllowed): self + { + $this->votesAllowed = $votesAllowed; + + return $this; + } + + public function getModificationPolicy(): ?string + { + return $this->modificationPolicy; + } + + public function setModificationPolicy(string $modificationPolicy): self + { + $this->modificationPolicy = $modificationPolicy; + + return $this; + } + + public function getHideResults(): ?bool + { + return $this->hideResults; + } + + public function setHideResults(bool $hideResults): self + { + $this->hideResults = $hideResults; + + return $this; + } + + public function getShowResultEvenIfPasswords(): ?bool + { + return $this->showResultEvenIfPasswords; + } + + public function setShowResultEvenIfPasswords(bool $showResultEvenIfPasswords): self + { + $this->showResultEvenIfPasswords = $showResultEvenIfPasswords; + + return $this; + } + + public function getOwner(): ?Owner + { + return $this->owner; + } + + public function setOwner(?Owner $owner): self + { + $this->owner = $owner; + + return $this; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): self + { + $this->password = md5($password); + + return $this; + } + + public function getAdminKey(): ?string + { + return $this->adminKey; + } + + public function setAdminKey(string $adminKey): self + { + $this->adminKey = $adminKey; + + return $this; + } + + public function getId(): ?int + { + return $this->id; + } + + public function findChoiceById(int $id) + { + + $choices = $this->getChoices(); + $counter = 0; + // there must be something cleaner than this in Doctrine ArrayCollection + foreach ($choices as $choice) { + $counter++; + if ($counter > $this->maxChoicesLimit) { + throw new ErrorException("max number of choices reached for this poll"); + } + if ($choice && $choice->getId() == $id) { + return $choice; + } + + } + + return null; + } + + /** + * @return Collection|Vote[] + */ + public function getVotes(): Collection + { + return $this->votes; + } + + public function getCustomUrl(): ?string + { + return $this->customUrl; + } + + public function setCustomUrl(string $customUrl): self + { + $this->customUrl = $customUrl; + + return $this; + } + + public function getMailOnComment(): ?bool + { + return $this->mailOnComment; + } + + public function setMailOnComment(bool $mailOnComment): self + { + $this->mailOnComment = $mailOnComment; + + return $this; + } + + public function getMailOnVote(): ?bool + { + return $this->mailOnVote; + } + + public function setMailOnVote(bool $mailOnVote): self + { + $this->mailOnVote = $mailOnVote; + + return $this; + } + + public function addComment(Comment $comment): self + { + if (!$this->comments->contains($comment)) { + $this->comments[] = $comment; + $comment->setPoll($this); + } + + return $this; + } + + public function removeComment(Comment $comment): self + { + if ($this->comments->contains($comment)) { + $this->comments->removeElement($comment); + // set the owning side to null (unless already changed) + if ($comment->getPoll() === $this) { + $comment->setPoll(null); + } + } + + return $this; + } + + public function addStackOfVote(StackOfVotes $stackOfVote): self + { + if (!$this->stacksOfVotes->contains($stackOfVote)) { + $this->stacksOfVotes[] = $stackOfVote; + $stackOfVote->setPoll($this); + } + + return $this; + } + + public function removeStackOfVote(StackOfVotes $stackOfVote): self + { + if ($this->stacksOfVotes->contains($stackOfVote)) { + $this->stacksOfVotes->removeElement($stackOfVote); + // set the owning side to null (unless already changed) + if ($stackOfVote->getPoll() === $this) { + $stackOfVote->setPoll(null); + } + } + + return $this; + } + + public function addVote(Vote $vote): self + { + if (!$this->votes->contains($vote)) { + $this->votes[] = $vote; + $vote->setPoll($this); + } + + return $this; + } + + public function removeVote(Vote $vote): self + { + if ($this->votes->contains($vote)) { + $this->votes->removeElement($vote); + // set the owning side to null (unless already changed) + if ($vote->getPoll() === $this) { + $vote->setPoll(null); + } + } + + return $this; + } + + public function addTextChoiceArray(array $choiceTextArray): self + { + foreach ($choiceTextArray as $text) { + $newChoice = new Choice(); + $newChoice->setName($text); + $this->addChoice($newChoice); + } + + return $this; + } + + public function addChoice(Choice $choice): self + { + if (!is_null($this->choices)) { + if (!$this->choices->contains($choice)) { + $this->choices[] = $choice; + $choice->setPoll($this); + } + } else { + $this->choices[] = $choice; + $choice->setPoll($this); + } + + + return $this; + } + + public function addStacksOfVote(StackOfVotes $stacksOfVote): self + { + if (!$this->stacksOfVotes->contains($stacksOfVote)) { + $this->stacksOfVotes[] = $stacksOfVote; + $stacksOfVote->setPoll($this); + } + + return $this; + } + + public function removeStacksOfVote(StackOfVotes $stacksOfVote): self + { + if ($this->stacksOfVotes->contains($stacksOfVote)) { + $this->stacksOfVotes->removeElement($stacksOfVote); + // set the owning side to null (unless already changed) + if ($stacksOfVote->getPoll() === $this) { + $stacksOfVote->setPoll(null); + } + } + + return $this; + } + + public function removeChoice(Choice $choice): self + { + if ($this->choices->contains($choice)) { + $this->choices->removeElement($choice); + // set the owning side to null (unless already changed) + if ($choice->getPoll() === $this) { + $choice->setPoll(null); + } + } + + return $this; + } + + public function getCommentsAllowed(): ?bool + { + return $this->commentsAllowed; + } + + public function setCommentsAllowed(?bool $commentsAllowed): self + { + $this->commentsAllowed = $commentsAllowed; + + return $this; + } + + public function getIsZeroKnowledge(): ?bool + { + return $this->isZeroKnowledge; + } + + public function setIsZeroKnowledge(?bool $isZeroKnowledge): self + { + $this->isZeroKnowledge = $isZeroKnowledge; + + return $this; + } }