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;
7
8use App\Traits\Validatable;
9
10/**
11 * @property \Carbon\Carbon $created_at
12 * @property array|null $details
13 * @property string $name
14 * @property int $id
15 * @property \Carbon\Carbon $updated_at
16 * @property User $user
17 * @property int $user_id
18 */
19class UserNotificationOption extends Model
20{
21 use Validatable;
22
23 const BEATMAPSET_DISQUALIFIABLE_NOTIFICATIONS = [
24 Notification::BEATMAPSET_DISCUSSION_QUALIFIED_PROBLEM,
25 Notification::BEATMAPSET_DISQUALIFY,
26 ];
27
28 const BEATMAPSET_MODDING = 'beatmapset:modding'; // matches Follow notifiable_type:subtype
29 const CHANNEL_ANNOUNCEMENT = 'channel_announcement';
30 const COMMENT_REPLY = 'comment_reply';
31 const DELIVERY_MODES = ['mail', 'push'];
32 const FORUM_TOPIC_REPLY = Notification::FORUM_TOPIC_REPLY;
33 const MAPPING = 'mapping';
34
35 const HAS_DELIVERY_MODES = [
36 Notification::BEATMAP_OWNER_CHANGE,
37 self::MAPPING,
38 self::BEATMAPSET_MODDING,
39 Notification::CHANNEL_MESSAGE,
40 Notification::COMMENT_NEW,
41 self::FORUM_TOPIC_REPLY,
42 ];
43
44 const SUPPORTS_NOTIFICATIONS = [
45 ...self::BEATMAPSET_DISQUALIFIABLE_NOTIFICATIONS,
46 ...self::HAS_DELIVERY_MODES,
47 self::CHANNEL_ANNOUNCEMENT,
48 ];
49
50 protected $casts = [
51 'details' => 'array',
52 ];
53
54 public static function supportsNotifications(string $name)
55 {
56 return in_array($name, static::SUPPORTS_NOTIFICATIONS, true);
57 }
58
59 public function user()
60 {
61 return $this->belongsTo(User::class, 'user_id');
62 }
63
64 public function setDetailsAttribute($value)
65 {
66 $details = $this->details ?? [];
67
68 if (!is_array($value)) {
69 $value = null;
70 }
71
72 if (in_array($this->name, static::BEATMAPSET_DISQUALIFIABLE_NOTIFICATIONS, true)) {
73 if (is_array($value['modes'] ?? null)) {
74 $modes = array_filter($value['modes'], 'is_string');
75 $validModes = array_keys(Beatmap::MODES);
76
77 $details['modes'] = array_values(array_intersect($modes, $validModes));
78 }
79 }
80
81 if (in_array($this->name, static::HAS_DELIVERY_MODES, true)) {
82 foreach (static::DELIVERY_MODES as $mode) {
83 if (isset($value[$mode])) {
84 $details[$mode] = get_bool($value[$mode] ?? null);
85 }
86 }
87 }
88
89 if ($this->name === Notification::COMMENT_NEW) {
90 if (isset($value[static::COMMENT_REPLY])) {
91 $details[static::COMMENT_REPLY] = get_bool($value[static::COMMENT_REPLY]);
92 }
93 }
94
95 if (!empty($details)) {
96 $detailsString = json_encode($details);
97 }
98
99 $this->attributes['details'] = $detailsString ?? null;
100 }
101
102 public function setNameAttribute($value)
103 {
104 if (!static::supportsNotifications($value)) {
105 $value = null;
106 }
107
108 $this->attributes['name'] = $value;
109 }
110
111 public function isValid()
112 {
113 $this->validationErrors()->reset();
114
115 if (!present($this->user_id)) {
116 $this->validationErrors()->add('user_id', 'required');
117 }
118
119 if (!present($this->name)) {
120 $this->validationErrors()->add('name', 'required');
121 }
122
123 return $this->validationErrors()->isEmpty();
124 }
125
126 public function save(array $options = [])
127 {
128 return $this->isValid() && parent::save($options);
129 }
130
131 public function validationErrorsTranslationPrefix(): string
132 {
133 return 'user_notification_option';
134 }
135}