xref: /webtrees/app/Services/MessageService.php (revision f25fc0f929f69ab8124cf0cecde45e457db7574a)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2023 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
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Services;
21
22use Fisharebest\Webtrees\Auth;
23use Fisharebest\Webtrees\Contracts\UserInterface;
24use Fisharebest\Webtrees\DB;
25use Fisharebest\Webtrees\I18N;
26use Fisharebest\Webtrees\Registry;
27use Fisharebest\Webtrees\SiteUser;
28use Fisharebest\Webtrees\Tree;
29use Fisharebest\Webtrees\User;
30use Illuminate\Support\Collection;
31
32use function array_filter;
33use function in_array;
34use function view;
35
36/**
37 * Send messages between users and from visitors to the site.
38 */
39class MessageService
40{
41    // Users can be contacted by various methods
42    public const CONTACT_METHOD_INTERNAL           = 'messaging';
43    public const CONTACT_METHOD_INTERNAL_AND_EMAIL = 'messaging2';
44    public const CONTACT_METHOD_EMAIL              = 'messaging3';
45    public const CONTACT_METHOD_MAILTO             = 'mailto';
46    public const CONTACT_METHOD_NONE               = 'none';
47
48    private const BROADCAST_ALL   = 'all';
49    private const BROADCAST_NEVER = 'never';
50    private const BROADCAST_GONE  = 'gone';
51
52    private EmailService $email_service;
53
54    private UserService $user_service;
55
56    /**
57     * @param EmailService $email_service
58     * @param UserService  $user_service
59     */
60    public function __construct(EmailService $email_service, UserService $user_service)
61    {
62        $this->email_service = $email_service;
63        $this->user_service  = $user_service;
64    }
65
66    /**
67     * Contact messages can only be sent to the designated contacts
68     *
69     * @param Tree $tree
70     *
71     * @return array<UserInterface>
72     */
73    public function validContacts(Tree $tree): array
74    {
75        $contacts = [
76            $this->user_service->find((int) $tree->getPreference('CONTACT_USER_ID')),
77            $this->user_service->find((int) $tree->getPreference('WEBMASTER_USER_ID')),
78        ];
79
80        return array_filter($contacts);
81    }
82
83    /**
84     * Add a message to a user's inbox, send it to them via email, or both.
85     *
86     * @param UserInterface $sender
87     * @param UserInterface $recipient
88     * @param string        $subject
89     * @param string        $body
90     * @param string        $url
91     * @param string        $ip
92     *
93     * @return bool
94     */
95    public function deliverMessage(UserInterface $sender, UserInterface $recipient, string $subject, string $body, string $url, string $ip): bool
96    {
97        $success = true;
98
99        // Temporarily switch to the recipient's language
100        $old_language = I18N::languageTag();
101        I18N::init($recipient->getPreference(UserInterface::PREF_LANGUAGE));
102
103        $body_text = view('emails/message-user-text', [
104            'sender'    => $sender,
105            'recipient' => $recipient,
106            'message'   => $body,
107            'url'       => $url,
108        ]);
109
110        $body_html = view('emails/message-user-html', [
111            'sender'    => $sender,
112            'recipient' => $recipient,
113            'message'   => $body,
114            'url'       => $url,
115        ]);
116
117        // Send via the internal messaging system.
118        if ($this->sendInternalMessage($recipient)) {
119            DB::table('message')->insert([
120                'sender'     => Auth::check() ? Auth::user()->email() : $sender->email(),
121                'ip_address' => $ip,
122                'user_id'    => $recipient->id(),
123                'subject'    => $subject,
124                'body'       => $body_text,
125            ]);
126        }
127
128        // Send via email
129        if ($this->sendEmail($recipient)) {
130            $success = $this->email_service->send(
131                new SiteUser(),
132                $recipient,
133                $sender,
134                I18N::translate('webtrees message') . ' - ' . $subject,
135                $body_text,
136                $body_html
137            );
138        }
139
140        I18N::init($old_language);
141
142        return $success;
143    }
144
145    /**
146     * Should we send messages to this user via internal messaging?
147     *
148     * @param UserInterface $user
149     *
150     * @return bool
151     */
152    public function sendInternalMessage(UserInterface $user): bool
153    {
154        return in_array($user->getPreference(UserInterface::PREF_CONTACT_METHOD), [
155            self::CONTACT_METHOD_INTERNAL,
156            self::CONTACT_METHOD_INTERNAL_AND_EMAIL,
157            self::CONTACT_METHOD_MAILTO,
158            self::CONTACT_METHOD_NONE,
159        ], true);
160    }
161
162    /**
163     * Should we send messages to this user via email?
164     *
165     * @param UserInterface $user
166     *
167     * @return bool
168     */
169    public function sendEmail(UserInterface $user): bool
170    {
171        return in_array($user->getPreference(UserInterface::PREF_CONTACT_METHOD), [
172            self::CONTACT_METHOD_INTERNAL_AND_EMAIL,
173            self::CONTACT_METHOD_EMAIL,
174            self::CONTACT_METHOD_MAILTO,
175            self::CONTACT_METHOD_NONE,
176        ], true);
177    }
178
179    /**
180     * Convert a username (or mailing list name) into an array of recipients.
181     *
182     * @param string $to
183     *
184     * @return Collection<int,User>
185     */
186    public function recipientUsers(string $to): Collection
187    {
188        switch ($to) {
189            default:
190            case self::BROADCAST_ALL:
191                return $this->user_service->all();
192            case self::BROADCAST_NEVER:
193                return $this->user_service->all()
194                    ->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));
195            case self::BROADCAST_GONE:
196                $six_months_ago = Registry::timestampFactory()->now()->subtractMonths(6)->timestamp();
197
198                return $this->user_service->all()->filter(static function (UserInterface $user) use ($six_months_ago): bool {
199                    $session_time = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE);
200
201                    return $session_time > 0 && $session_time < $six_months_ago;
202                });
203        }
204    }
205
206    /**
207     * Recipients for broadcast messages
208     *
209     * @return array<string,string>
210     */
211    public function recipientTypes(): array
212    {
213        return [
214            self::BROADCAST_ALL   => I18N::translate('Send a message to all users'),
215            self::BROADCAST_NEVER => I18N::translate('Send a message to users who have never signed in'),
216            self::BROADCAST_GONE  => I18N::translate('Send a message to users who have not signed in for 6 months'),
217        ];
218    }
219
220    /**
221     * A list of contact methods (e.g. for an edit control).
222     *
223     * @return array<string>
224     */
225    public function contactMethods(): array
226    {
227        return [
228            self::CONTACT_METHOD_INTERNAL           => I18N::translate('Internal messaging'),
229            self::CONTACT_METHOD_INTERNAL_AND_EMAIL => I18N::translate('Internal messaging with emails'),
230            self::CONTACT_METHOD_EMAIL              => I18N::translate('webtrees sends emails with no storage'),
231            self::CONTACT_METHOD_MAILTO             => I18N::translate('Mailto link'),
232            self::CONTACT_METHOD_NONE               => I18N::translate('No contact'),
233        ];
234    }
235}
236