1e381f98dSGreg Roach<?php 2e381f98dSGreg Roach 3e381f98dSGreg Roach/** 4e381f98dSGreg Roach * webtrees: online genealogy 55bfc6897SGreg Roach * Copyright (C) 2022 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 /** 63e381f98dSGreg Roach * MessagePage constructor. 64e381f98dSGreg Roach * 6570ca9c90SGreg Roach * @param CaptchaService $captcha_service 6604626e75SGreg Roach * @param EmailService $email_service 67e381f98dSGreg Roach * @param MessageService $message_service 689ed332c7SGreg Roach * @param RateLimitService $rate_limit_service 69e381f98dSGreg Roach * @param UserService $user_service 70e381f98dSGreg Roach */ 7170ca9c90SGreg Roach public function __construct( 7270ca9c90SGreg Roach CaptchaService $captcha_service, 7304626e75SGreg Roach EmailService $email_service, 7470ca9c90SGreg Roach MessageService $message_service, 759ed332c7SGreg Roach RateLimitService $rate_limit_service, 7670ca9c90SGreg Roach UserService $user_service 7770ca9c90SGreg Roach ) { 7870ca9c90SGreg Roach $this->captcha_service = $captcha_service; 7904626e75SGreg Roach $this->email_service = $email_service; 80e381f98dSGreg Roach $this->user_service = $user_service; 819ed332c7SGreg Roach $this->rate_limit_service = $rate_limit_service; 82e381f98dSGreg Roach $this->message_service = $message_service; 83e381f98dSGreg Roach } 84e381f98dSGreg Roach 85e381f98dSGreg Roach /** 86e381f98dSGreg Roach * @param ServerRequestInterface $request 87e381f98dSGreg Roach * 88e381f98dSGreg Roach * @return ResponseInterface 89e381f98dSGreg Roach */ 90e381f98dSGreg Roach public function handle(ServerRequestInterface $request): ResponseInterface 91e381f98dSGreg Roach { 92b55cbc6bSGreg Roach $tree = Validator::attributes($request)->tree(); 93b55cbc6bSGreg Roach $ip = Validator::attributes($request)->string('client-ip'); 941e60ebf4SGreg Roach $base_url = Validator::attributes($request)->string('base_url'); 951e60ebf4SGreg Roach $body = Validator::parsedBody($request)->string('body'); 961e60ebf4SGreg Roach $from_email = Validator::parsedBody($request)->string('from_email'); 971e60ebf4SGreg Roach $from_name = Validator::parsedBody($request)->string('from_name'); 981e60ebf4SGreg Roach $subject = Validator::parsedBody($request)->string('subject'); 991e60ebf4SGreg Roach $to = Validator::parsedBody($request)->string('to'); 100*f507cef9SGreg Roach $url = Validator::parsedBody($request)->isLocalUrl()->string('url', $base_url); 101e381f98dSGreg Roach $to_user = $this->user_service->findByUserName($to); 102e381f98dSGreg Roach 103e381f98dSGreg Roach if ($to_user === null) { 104d501c45dSGreg Roach throw new HttpNotFoundException(); 105e381f98dSGreg Roach } 106e381f98dSGreg Roach 107e381f98dSGreg Roach if (!in_array($to_user, $this->message_service->validContacts($tree), false)) { 108d501c45dSGreg Roach throw new HttpAccessDeniedException('Invalid contact user id'); 109e381f98dSGreg Roach } 110e381f98dSGreg Roach 111e381f98dSGreg Roach $errors = $body === '' || $subject === '' || $from_email === '' || $from_name === ''; 112e381f98dSGreg Roach 11370ca9c90SGreg Roach if ($this->captcha_service->isRobot($request)) { 11470ca9c90SGreg Roach FlashMessages::addMessage(I18N::translate('Please try again.'), 'danger'); 11570ca9c90SGreg Roach $errors = true; 11670ca9c90SGreg Roach } 11770ca9c90SGreg Roach 11804626e75SGreg Roach if (!$this->email_service->isValidEmail($from_email)) { 119e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('Please enter a valid email address.'), 'danger'); 120e381f98dSGreg Roach $errors = true; 121e381f98dSGreg Roach } 122e381f98dSGreg Roach 123e381f98dSGreg Roach if (preg_match('/(?!' . preg_quote($base_url, '/') . ')(((?:ftp|http|https):\/\/)[a-zA-Z0-9.-]+)/', $subject . $body, $match)) { 124ad3143ccSGreg 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.’ */ 125e381f98dSGreg Roach I18N::translate('You should delete the “%1$s” from “%2$s” and try again.', $match[2], $match[1]), 'danger'); 126e381f98dSGreg Roach $errors = true; 127e381f98dSGreg Roach } 128e381f98dSGreg Roach 129e381f98dSGreg Roach if ($errors) { 130e381f98dSGreg Roach return redirect(route(ContactPage::class, [ 131e381f98dSGreg Roach 'body' => $body, 132e381f98dSGreg Roach 'from_email' => $from_email, 133e381f98dSGreg Roach 'from_name' => $from_name, 134e381f98dSGreg Roach 'subject' => $subject, 135e381f98dSGreg Roach 'to' => $to, 136e381f98dSGreg Roach 'tree' => $tree->name(), 137e381f98dSGreg Roach 'url' => $url, 138e381f98dSGreg Roach ])); 139e381f98dSGreg Roach } 140e381f98dSGreg Roach 141e381f98dSGreg Roach $sender = new GuestUser($from_email, $from_name); 142e381f98dSGreg Roach 1439ed332c7SGreg Roach $this->rate_limit_service->limitRateForUser($to_user, 20, 1200, 'rate-limit-contact'); 1449ed332c7SGreg Roach 145e381f98dSGreg Roach if ($this->message_service->deliverMessage($sender, $to_user, $subject, $body, $url, $ip)) { 146e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('The message was successfully sent to %s.', e($to_user->realName())), 'success'); 147e381f98dSGreg Roach 148e381f98dSGreg Roach return redirect($url); 149e381f98dSGreg Roach } 150e381f98dSGreg Roach 151e381f98dSGreg Roach FlashMessages::addMessage(I18N::translate('The message was not sent.'), 'danger'); 152e381f98dSGreg Roach 153e381f98dSGreg Roach $redirect_url = route(ContactPage::class, [ 154e381f98dSGreg Roach 'body' => $body, 155e381f98dSGreg Roach 'from_email' => $from_email, 156e381f98dSGreg Roach 'from_name' => $from_name, 157e381f98dSGreg Roach 'subject' => $subject, 158e381f98dSGreg Roach 'to' => $to, 159e381f98dSGreg Roach 'tree' => $tree->name(), 160e381f98dSGreg Roach 'url' => $url, 161e381f98dSGreg Roach ]); 162e381f98dSGreg Roach 163e381f98dSGreg Roach return redirect($redirect_url); 164e381f98dSGreg Roach } 165e381f98dSGreg Roach} 166