1e381f98dSGreg Roach<?php 2e381f98dSGreg Roach 3e381f98dSGreg Roach/** 4e381f98dSGreg Roach * webtrees: online genealogy 5*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 6e381f98dSGreg Roach * This program is free software: you can redistribute it and/or modify 7e381f98dSGreg Roach * it under the terms of the GNU General Public License as published by 8e381f98dSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9e381f98dSGreg Roach * (at your option) any later version. 10e381f98dSGreg Roach * This program is distributed in the hope that it will be useful, 11e381f98dSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12e381f98dSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13e381f98dSGreg Roach * GNU General Public License for more details. 14e381f98dSGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16e381f98dSGreg Roach */ 17e381f98dSGreg Roach 18e381f98dSGreg Roachdeclare(strict_types=1); 19e381f98dSGreg Roach 20e381f98dSGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers; 21e381f98dSGreg Roach 22e381f98dSGreg Roachuse Fisharebest\Webtrees\FlashMessages; 23e381f98dSGreg Roachuse Fisharebest\Webtrees\GuestUser; 2481b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpAccessDeniedException; 2581b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException; 26e381f98dSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait; 27e381f98dSGreg Roachuse Fisharebest\Webtrees\I18N; 2870ca9c90SGreg Roachuse Fisharebest\Webtrees\Services\CaptchaService; 2904626e75SGreg Roachuse Fisharebest\Webtrees\Services\EmailService; 30e381f98dSGreg Roachuse Fisharebest\Webtrees\Services\MessageService; 319ed332c7SGreg Roachuse Fisharebest\Webtrees\Services\RateLimitService; 32e381f98dSGreg Roachuse Fisharebest\Webtrees\Services\UserService; 338d9c2b68SGreg Roachuse Fisharebest\Webtrees\Validator; 34e381f98dSGreg Roachuse Psr\Http\Message\ResponseInterface; 35e381f98dSGreg Roachuse Psr\Http\Message\ServerRequestInterface; 36e381f98dSGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 37e381f98dSGreg Roach 38e381f98dSGreg Roachuse function e; 39e381f98dSGreg Roachuse function in_array; 40e381f98dSGreg Roachuse function preg_match; 41e381f98dSGreg Roachuse function preg_quote; 42e381f98dSGreg Roachuse function redirect; 43e381f98dSGreg Roachuse function route; 44e381f98dSGreg Roach 45e381f98dSGreg Roach/** 46e381f98dSGreg Roach * Send a message from a visitor. 47e381f98dSGreg Roach */ 48e381f98dSGreg Roachclass ContactAction implements RequestHandlerInterface 49e381f98dSGreg Roach{ 50e381f98dSGreg Roach use ViewResponseTrait; 51e381f98dSGreg Roach 52c4943cffSGreg Roach private CaptchaService $captcha_service; 5370ca9c90SGreg Roach 54c4943cffSGreg Roach private EmailService $email_service; 5504626e75SGreg Roach 56c4943cffSGreg Roach private MessageService $message_service; 57e381f98dSGreg Roach 589ed332c7SGreg Roach private RateLimitService $rate_limit_service; 599ed332c7SGreg Roach 60c4943cffSGreg Roach private UserService $user_service; 61e381f98dSGreg Roach 62e381f98dSGreg Roach /** 6370ca9c90SGreg Roach * @param CaptchaService $captcha_service 6404626e75SGreg Roach * @param EmailService $email_service 65e381f98dSGreg Roach * @param MessageService $message_service 669ed332c7SGreg Roach * @param RateLimitService $rate_limit_service 67e381f98dSGreg Roach * @param UserService $user_service 68e381f98dSGreg Roach */ 6970ca9c90SGreg Roach public function __construct( 7070ca9c90SGreg Roach CaptchaService $captcha_service, 7104626e75SGreg Roach EmailService $email_service, 7270ca9c90SGreg Roach MessageService $message_service, 739ed332c7SGreg Roach RateLimitService $rate_limit_service, 7470ca9c90SGreg Roach UserService $user_service 7570ca9c90SGreg Roach ) { 7670ca9c90SGreg Roach $this->captcha_service = $captcha_service; 7704626e75SGreg Roach $this->email_service = $email_service; 78e381f98dSGreg Roach $this->user_service = $user_service; 799ed332c7SGreg Roach $this->rate_limit_service = $rate_limit_service; 80e381f98dSGreg Roach $this->message_service = $message_service; 81e381f98dSGreg Roach } 82e381f98dSGreg Roach 83e381f98dSGreg Roach /** 84e381f98dSGreg Roach * @param ServerRequestInterface $request 85e381f98dSGreg Roach * 86e381f98dSGreg Roach * @return ResponseInterface 87e381f98dSGreg Roach */ 88e381f98dSGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 89e381f98dSGreg Roach { 90b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 91b55cbc6bSGreg Roach $ip = Validator::attributes($request)->string('client-ip'); 921e60ebf4SGreg Roach $base_url = Validator::attributes($request)->string('base_url'); 931e60ebf4SGreg Roach $body = Validator::parsedBody($request)->string('body'); 941e60ebf4SGreg Roach $from_email = Validator::parsedBody($request)->string('from_email'); 951e60ebf4SGreg Roach $from_name = Validator::parsedBody($request)->string('from_name'); 961e60ebf4SGreg Roach $subject = Validator::parsedBody($request)->string('subject'); 971e60ebf4SGreg Roach $to = Validator::parsedBody($request)->string('to'); 98f507cef9SGreg Roach $url = Validator::parsedBody($request)->isLocalUrl()->string('url', $base_url); 99e381f98dSGreg Roach $to_user = $this->user_service->findByUserName($to); 100e381f98dSGreg Roach 101e381f98dSGreg Roach if ($to_user === null) { 102d501c45dSGreg Roach throw new HttpNotFoundException(); 103e381f98dSGreg Roach } 104e381f98dSGreg Roach 105e381f98dSGreg Roach if (!in_array($to_user, $this->message_service->validContacts($tree), false)) { 106d501c45dSGreg Roach throw new HttpAccessDeniedException('Invalid contact user id'); 107e381f98dSGreg Roach } 108e381f98dSGreg Roach 109e381f98dSGreg Roach $errors = $body === '' || $subject === '' || $from_email === '' || $from_name === ''; 110e381f98dSGreg Roach 11170ca9c90SGreg Roach if ($this->captcha_service->isRobot($request)) { 11270ca9c90SGreg Roach FlashMessages::addMessage(I18N::translate('Please try again.'), 'danger'); 11370ca9c90SGreg Roach $errors = true; 11470ca9c90SGreg Roach } 11570ca9c90SGreg Roach 11604626e75SGreg Roach if (!$this->email_service->isValidEmail($from_email)) { 117e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('Please enter a valid email address.'), 'danger'); 118e381f98dSGreg Roach $errors = true; 119e381f98dSGreg Roach } 120e381f98dSGreg Roach 121e381f98dSGreg Roach if (preg_match('/(?!' . preg_quote($base_url, '/') . ')(((?:ftp|http|https):\/\/)[a-zA-Z0-9.-]+)/', $subject . $body, $match)) { 122ad3143ccSGreg Roach FlashMessages::addMessage(I18N::translate('You are not allowed to send messages that contain external links.') . ' ' . /* I18N: e.g. ‘You should delete the “https://” from “https://www.example.com” and try again.’ */ 123e381f98dSGreg Roach I18N::translate('You should delete the “%1$s” from “%2$s” and try again.', $match[2], $match[1]), 'danger'); 124e381f98dSGreg Roach $errors = true; 125e381f98dSGreg Roach } 126e381f98dSGreg Roach 127e381f98dSGreg Roach if ($errors) { 128e381f98dSGreg Roach return redirect(route(ContactPage::class, [ 129e381f98dSGreg Roach 'body' => $body, 130e381f98dSGreg Roach 'from_email' => $from_email, 131e381f98dSGreg Roach 'from_name' => $from_name, 132e381f98dSGreg Roach 'subject' => $subject, 133e381f98dSGreg Roach 'to' => $to, 134e381f98dSGreg Roach 'tree' => $tree->name(), 135e381f98dSGreg Roach 'url' => $url, 136e381f98dSGreg Roach ])); 137e381f98dSGreg Roach } 138e381f98dSGreg Roach 139e381f98dSGreg Roach $sender = new GuestUser($from_email, $from_name); 140e381f98dSGreg Roach 1419ed332c7SGreg Roach $this->rate_limit_service->limitRateForUser($to_user, 20, 1200, 'rate-limit-contact'); 1429ed332c7SGreg Roach 143e381f98dSGreg Roach if ($this->message_service->deliverMessage($sender, $to_user, $subject, $body, $url, $ip)) { 144e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('The message was successfully sent to %s.', e($to_user->realName())), 'success'); 145e381f98dSGreg Roach 146e381f98dSGreg Roach return redirect($url); 147e381f98dSGreg Roach } 148e381f98dSGreg Roach 149e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('The message was not sent.'), 'danger'); 150e381f98dSGreg Roach 151e381f98dSGreg Roach $redirect_url = route(ContactPage::class, [ 152e381f98dSGreg Roach 'body' => $body, 153e381f98dSGreg Roach 'from_email' => $from_email, 154e381f98dSGreg Roach 'from_name' => $from_name, 155e381f98dSGreg Roach 'subject' => $subject, 156e381f98dSGreg Roach 'to' => $to, 157e381f98dSGreg Roach 'tree' => $tree->name(), 158e381f98dSGreg Roach 'url' => $url, 159e381f98dSGreg Roach ]); 160e381f98dSGreg Roach 161e381f98dSGreg Roach return redirect($redirect_url); 162e381f98dSGreg Roach } 163e381f98dSGreg Roach} 164