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