2019-10-25 14:59:20 +02:00
< ? php
2021-04-30 15:34:04 +02:00
namespace App\Entity ;
use App\Traits\RandomTrait ;
use App\Traits\TimeStampableTrait ;
use DateTime ;
use DateTimeInterface ;
use Doctrine\Common\Collections\ArrayCollection ;
use Doctrine\Common\Collections\Collection ;
use Doctrine\ORM\Mapping as ORM ;
use ErrorException ;
use JMS\Serializer\Annotation as Serializer ;
/**
* @ ORM\Entity ( repositoryClass = " App \R epository \ PollRepository " )
* @ Serializer\ExclusionPolicy ( " all " )
*/
2021-11-25 12:57:24 +01:00
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 ()
*/
2022-03-21 10:30:53 +01:00
public $votesMax = 1024 * 10 ;
2021-11-25 12:57:24 +01:00
/**
* 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<App \ Entity \ Vote> " )
* @ 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<App \ Entity \ Choice> " )
*/
public $choices ;
/**
* @ ORM\OneToMany ( targetEntity = " App \ Entity \ Comment " , mappedBy = " poll " , orphanRemoval = true , cascade = { " persist " , " remove " })
* @ Serializer\Expose ()
* @ Serializer\Type ( " ArrayCollection<App \ Entity \ Comment> " )
*/
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 ();
2022-02-07 11:24:53 +01:00
ksort ( $content );
2021-11-25 12:57:24 +01:00
return $content ;
}
// counts each number of answer for this choice
2022-02-07 11:24:53 +01:00
public function display () : array
2021-11-25 12:57:24 +01:00
{
$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 (),
];
2022-02-07 11:24:53 +01:00
ksort ( $resp );
2021-11-25 12:57:24 +01:00
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 ;
}
2021-04-30 15:34:04 +02:00
}