Issues (2559)

app/Services/MessageService.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Services;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Contracts\UserInterface;
24
use Fisharebest\Webtrees\DB;
25
use Fisharebest\Webtrees\I18N;
26
use Fisharebest\Webtrees\Registry;
27
use Fisharebest\Webtrees\SiteUser;
28
use Fisharebest\Webtrees\Tree;
29
use Fisharebest\Webtrees\User;
30
use Illuminate\Support\Collection;
31
32
use function array_filter;
33
use function in_array;
34
use function view;
35
36
/**
37
 * Send messages between users and from visitors to the site.
38
 */
39
class MessageService
40
{
41
    // Users can be contacted by various methods
42
    public const string CONTACT_METHOD_INTERNAL           = 'messaging';
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 42 at column 24
Loading history...
43
    public const string CONTACT_METHOD_INTERNAL_AND_EMAIL = 'messaging2';
44
    public const string CONTACT_METHOD_EMAIL              = 'messaging3';
45
    public const string CONTACT_METHOD_MAILTO             = 'mailto';
46
    public const string CONTACT_METHOD_NONE               = 'none';
47
48
    private const string BROADCAST_ALL   = 'all';
49
    private const string BROADCAST_NEVER = 'never';
50
    private const string BROADCAST_GONE  = 'gone';
51
52
    public function __construct(
53
        private readonly EmailService $email_service,
54
        private readonly UserService $user_service,
55
    ) {
56
    }
57
58
    /**
59
     * Contact messages can only be sent to the designated contacts
60
     *
61
     * @param Tree $tree
62
     *
63
     * @return array<UserInterface>
64
     */
65
    public function validContacts(Tree $tree): array
66
    {
67
        $contacts = [
68
            $this->user_service->find((int) $tree->getPreference('CONTACT_USER_ID')),
69
            $this->user_service->find((int) $tree->getPreference('WEBMASTER_USER_ID')),
70
        ];
71
72
        return array_filter($contacts);
73
    }
74
75
    /**
76
     * Add a message to a user's inbox, send it to them via email, or both.
77
     *
78
     * @param UserInterface $sender
79
     * @param UserInterface $recipient
80
     * @param string        $subject
81
     * @param string        $body
82
     * @param string        $url
83
     * @param string        $ip
84
     *
85
     * @return bool
86
     */
87
    public function deliverMessage(UserInterface $sender, UserInterface $recipient, string $subject, string $body, string $url, string $ip): bool
88
    {
89
        $success = true;
90
91
        // Temporarily switch to the recipient's language
92
        $old_language = I18N::languageTag();
93
        I18N::init($recipient->getPreference(UserInterface::PREF_LANGUAGE, 'en-US'));
94
95
        $body_text = view('emails/message-user-text', [
96
            'sender'    => $sender,
97
            'recipient' => $recipient,
98
            'message'   => $body,
99
            'url'       => $url,
100
        ]);
101
102
        $body_html = view('emails/message-user-html', [
103
            'sender'    => $sender,
104
            'recipient' => $recipient,
105
            'message'   => $body,
106
            'url'       => $url,
107
        ]);
108
109
        // Send via the internal messaging system.
110
        if ($this->sendInternalMessage($recipient)) {
111
            DB::table('message')->insert([
112
                'sender'     => Auth::check() ? Auth::user()->email() : $sender->email(),
113
                'ip_address' => $ip,
114
                'user_id'    => $recipient->id(),
115
                'subject'    => $subject,
116
                'body'       => $body_text,
117
            ]);
118
        }
119
120
        // Send via email
121
        if ($this->sendEmail($recipient)) {
122
            $success = $this->email_service->send(
123
                new SiteUser(),
124
                $recipient,
125
                $sender,
126
                I18N::translate('webtrees message') . ' - ' . $subject,
127
                $body_text,
128
                $body_html
129
            );
130
        }
131
132
        I18N::init($old_language);
133
134
        return $success;
135
    }
136
137
    /**
138
     * Should we send messages to this user via internal messaging?
139
     *
140
     * @param UserInterface $user
141
     *
142
     * @return bool
143
     */
144
    public function sendInternalMessage(UserInterface $user): bool
145
    {
146
        return in_array($user->getPreference(UserInterface::PREF_CONTACT_METHOD), [
147
            self::CONTACT_METHOD_INTERNAL,
148
            self::CONTACT_METHOD_INTERNAL_AND_EMAIL,
149
            self::CONTACT_METHOD_MAILTO,
150
            self::CONTACT_METHOD_NONE,
151
        ], true);
152
    }
153
154
    /**
155
     * Should we send messages to this user via email?
156
     *
157
     * @param UserInterface $user
158
     *
159
     * @return bool
160
     */
161
    public function sendEmail(UserInterface $user): bool
162
    {
163
        return in_array($user->getPreference(UserInterface::PREF_CONTACT_METHOD), [
164
            self::CONTACT_METHOD_INTERNAL_AND_EMAIL,
165
            self::CONTACT_METHOD_EMAIL,
166
            self::CONTACT_METHOD_MAILTO,
167
            self::CONTACT_METHOD_NONE,
168
        ], true);
169
    }
170
171
    /**
172
     * Convert a username (or mailing list name) into an array of recipients.
173
     *
174
     * @param string $to
175
     *
176
     * @return Collection<int,User>
177
     */
178
    public function recipientUsers(string $to): Collection
179
    {
180
        switch ($to) {
181
            default:
182
            case self::BROADCAST_ALL:
183
                return $this->user_service->all();
184
            case self::BROADCAST_NEVER:
185
                return $this->user_service->all()
186
                    ->filter(static fn (UserInterface $user): bool => $user->getPreference(UserInterface::PREF_IS_ACCOUNT_APPROVED) === '1' && $user->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED) > $user->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE));
187
            case self::BROADCAST_GONE:
188
                $six_months_ago = Registry::timestampFactory()->now()->subtractMonths(6)->timestamp();
189
190
                return $this->user_service->all()->filter(static function (UserInterface $user) use ($six_months_ago): bool {
191
                    $session_time = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE);
192
193
                    return $session_time > 0 && $session_time < $six_months_ago;
194
                });
195
        }
196
    }
197
198
    /**
199
     * Recipients for broadcast messages
200
     *
201
     * @return array<string,string>
202
     */
203
    public function recipientTypes(): array
204
    {
205
        return [
206
            self::BROADCAST_ALL   => I18N::translate('Send a message to all users'),
207
            self::BROADCAST_NEVER => I18N::translate('Send a message to users who have never signed in'),
208
            self::BROADCAST_GONE  => I18N::translate('Send a message to users who have not signed in for 6 months'),
209
        ];
210
    }
211
212
    /**
213
     * A list of contact methods (e.g. for an edit control).
214
     *
215
     * @return array<string>
216
     */
217
    public function contactMethods(): array
218
    {
219
        return [
220
            self::CONTACT_METHOD_INTERNAL           => I18N::translate('Internal messaging'),
221
            self::CONTACT_METHOD_INTERNAL_AND_EMAIL => I18N::translate('Internal messaging with emails'),
222
            self::CONTACT_METHOD_EMAIL              => I18N::translate('webtrees sends emails with no storage'),
223
            self::CONTACT_METHOD_MAILTO             => I18N::translate('Mailto link'),
224
            self::CONTACT_METHOD_NONE               => I18N::translate('No contact'),
225
        ];
226
    }
227
}
228