1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Models\Forum;
7
8use App\Libraries\BBCodeForDB;
9use DB;
10
11/**
12 * @property int $poll_option_id
13 * @property string $poll_option_text
14 * @property int $poll_option_total
15 * @property Post $post
16 * @property Topic $topic
17 * @property int $topic_id
18 * @property \Illuminate\Database\Eloquent\Collection $votes PollVote
19 */
20class PollOption extends Model
21{
22 protected $table = 'phpbb_poll_options';
23 protected $primaryKey = null;
24 public $incrementing = false;
25 public $timestamps = false;
26
27 public function getKey(): string
28 {
29 return $this->topic_id.'-'.$this->poll_option_id;
30 }
31
32 // For bbcode_uid, the first post (even if the post is deleted).
33 public function post()
34 {
35 return $this
36 ->belongsTo(Post::class, 'topic_id', 'topic_id')
37 ->withTrashed()
38 ->orderBy('post_id', 'ASC')
39 ->limit(1);
40 }
41
42 public function topic()
43 {
44 return $this->belongsTo(Topic::class, 'topic_id');
45 }
46
47 public function votes()
48 {
49 return $this->hasMany(PollVote::class, 'poll_option_id', 'poll_option_id')->where('topic_id', $this->topic_id);
50 }
51
52 public static function summary($topic, $user)
53 {
54 $summary = [
55 'options' => [],
56 'total' => 0,
57 'user_votes' => 0,
58 ];
59
60 if ($topic->poll()->exists()) {
61 if ($user === null) {
62 $userVotes = [];
63 } else {
64 $userVotes = array_flip(model_pluck($topic->pollVotes()->where('vote_user_id', $user->getKey()), 'poll_option_id'));
65 }
66
67 foreach ($topic->pollOptions as $poll) {
68 $votedByUser = array_key_exists($poll->poll_option_id, $userVotes);
69
70 $summary['options'][$poll->poll_option_id] = [
71 'textHTML' => $poll->optionTextHTML(),
72 'total' => $poll->poll_option_total,
73 'voted_by_user' => $votedByUser,
74 ];
75
76 $summary['total'] += $poll->poll_option_total;
77 $summary['user_votes'] += $votedByUser ? 1 : 0;
78 }
79 }
80
81 return $summary;
82 }
83
84 public static function updateTotals($filters)
85 {
86 $staticTable = (new static())->table;
87 $countQuery = PollVote::where([
88 'topic_id' => DB::raw($staticTable.'.topic_id'),
89 'poll_option_id' => DB::raw($staticTable.'.poll_option_id'),
90 ])
91 // raw because ->count() immediately executes the query.
92 // DISTINCT because lack of unique index causing duplicated votes.
93 ->select(DB::raw('COUNT(DISTINCT vote_user_id)'))
94 ->toSql();
95
96 return static::where($filters)
97 ->update(['poll_option_total' => DB::raw("({$countQuery})")]);
98 }
99
100 public function userHasVoted($user)
101 {
102 if ($user === null) {
103 return false;
104 }
105
106 return $this->votes()->where('vote_user_id', $user->user_id)->exists();
107 }
108
109 public function setPollOptionTextAttribute($value)
110 {
111 $this->attributes['poll_option_text'] = (new BBCodeForDB($value))->generate();
112 }
113
114 public function optionTextHTML()
115 {
116 return bbcode(
117 $this->poll_option_text,
118 $this->post->bbcode_uid,
119 ['withGallery' => true]
120 );
121 }
122
123 public function optionTextRaw()
124 {
125 return bbcode_for_editor($this->poll_option_text, $this->post->bbcode_uid);
126 }
127}