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