489 lines
17 KiB
PHP
489 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Speclial:Contact, a contact form for visitors.
|
|
* Based on SpecialEmailUser.php
|
|
*
|
|
* @file
|
|
* @ingroup SpecialPage
|
|
* @author Daniel Kinzler, brightbyte.de
|
|
* @copyright © 2007-2014 Daniel Kinzler, Sam Reed
|
|
* @license GPL-2.0-or-later
|
|
*/
|
|
|
|
/**
|
|
* Provides the contact form
|
|
* @ingroup SpecialPage
|
|
*/
|
|
class SpecialContact extends UnlistedSpecialPage
|
|
{
|
|
public function __construct()
|
|
{
|
|
parent::__construct('Contact');
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getDescription()
|
|
{
|
|
return $this->msg('contactpage')->text();
|
|
}
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $formType;
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
protected function getTypeConfig()
|
|
{
|
|
$contactConfig = $this->getConfig()->get('ContactConfig');
|
|
|
|
if ($contactConfig['default']['SenderName'] === null) {
|
|
$sitename = $this->getConfig()->get('Sitename');
|
|
$contactConfig['default']['SenderName'] = "Contact Form on $sitename";
|
|
}
|
|
|
|
if (isset($contactConfig[$this->formType])) {
|
|
return $contactConfig[$this->formType] + $contactConfig['default'];
|
|
}
|
|
return $contactConfig['default'];
|
|
}
|
|
|
|
/**
|
|
* Main execution function
|
|
*
|
|
* @param string|null $par Parameters passed to the page
|
|
* @throws UserBlockedError
|
|
* @throws ErrorPageError
|
|
*/
|
|
public function execute($par)
|
|
{
|
|
global $wgReCaptchaSiteKey, $wgReCaptchaSecretKey;
|
|
|
|
if (!$this->getConfig()->get('EnableEmail')) {
|
|
// From Special:EmailUser
|
|
throw new ErrorPageError('usermaildisabled', 'usermaildisabledtext');
|
|
}
|
|
|
|
$request = $this->getRequest();
|
|
$this->formType = strtolower($request->getText('formtype', $par));
|
|
|
|
$config = $this->getTypeConfig();
|
|
|
|
if ($config['MustBeLoggedIn']) {
|
|
$this->requireLogin('contactpage-mustbeloggedin');
|
|
}
|
|
|
|
if (!$config['RecipientUser']) {
|
|
$this->getOutput()->showErrorPage(
|
|
'contactpage-config-error-title',
|
|
'contactpage-config-error'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$user = $this->getUser();
|
|
|
|
$nu = User::newFromName($config['RecipientUser']);
|
|
if ($nu === null || !$nu->canReceiveEmail()) {
|
|
$this->getOutput()->showErrorPage('noemailtitle', 'noemailtext');
|
|
return;
|
|
}
|
|
|
|
// Blocked users cannot use the contact form if they're disabled from sending email.
|
|
if ($user->isBlockedFromEmailuser()) {
|
|
throw new UserBlockedError($this->getUser()->getBlock());
|
|
}
|
|
|
|
$pageTitle = '';
|
|
if ($this->formType != '') {
|
|
$message = $this->msg('contactpage-title-' . $this->formType);
|
|
if (!$message->isDisabled()) {
|
|
$pageTitle = $message;
|
|
}
|
|
}
|
|
|
|
if ($pageTitle === '') {
|
|
$pageTitle = $this->msg('contactpage-title');
|
|
}
|
|
$this->getOutput()->setPageTitle($pageTitle);
|
|
|
|
$subject = '';
|
|
|
|
# Check for type in [[Special:Contact/type]]: change pagetext and prefill form fields
|
|
if ($this->formType != '') {
|
|
$message = $this->msg('contactpage-pagetext-' . $this->formType);
|
|
if (!$message->isDisabled()) {
|
|
$formText = $message->parseAsBlock();
|
|
} else {
|
|
$formText = $this->msg('contactpage-pagetext')->parseAsBlock();
|
|
}
|
|
|
|
$message = $this->msg('contactpage-subject-' . $this->formType);
|
|
if (!$message->isDisabled()) {
|
|
$subject = $message->inContentLanguage()->plain();
|
|
}
|
|
} else {
|
|
$formText = $this->msg('contactpage-pagetext')->parseAsBlock();
|
|
}
|
|
|
|
$subject = trim($subject);
|
|
|
|
if ($subject === '') {
|
|
$subject = $this->msg('contactpage-defsubject')->inContentLanguage()->text();
|
|
}
|
|
|
|
$fromAddress = '';
|
|
$fromName = '';
|
|
if ($user->isLoggedIn()) {
|
|
// Use real name if set
|
|
$realName = $user->getRealName();
|
|
if ($realName) {
|
|
$fromName = $realName;
|
|
} else {
|
|
$fromName = $user->getName();
|
|
}
|
|
$fromAddress = $user->getEmail();
|
|
}
|
|
|
|
$additional = $config['AdditionalFields'];
|
|
|
|
$formItems = [
|
|
'FromName' => [
|
|
'label-message' => 'contactpage-fromname',
|
|
'type' => 'text',
|
|
'required' => $config['RequireDetails'],
|
|
'default' => $fromName,
|
|
],
|
|
'FromAddress' => [
|
|
'label-message' => 'contactpage-fromaddress',
|
|
'type' => 'email',
|
|
'required' => $config['RequireDetails'],
|
|
'default' => $fromAddress,
|
|
],
|
|
'Subject' => [
|
|
'label-message' => 'emailsubject',
|
|
'type' => 'text',
|
|
'required' => $config['RequireDetails'],
|
|
'default' => $subject,
|
|
],
|
|
] + $additional + [
|
|
'CCme' => [
|
|
'label-message' => 'emailccme',
|
|
'type' => 'check',
|
|
'default' => $this->getUser()->getBoolOption('ccmeonemails'),
|
|
],
|
|
'FormType' => [
|
|
'class' => 'HTMLHiddenField',
|
|
'label' => 'Type',
|
|
'default' => $this->formType,
|
|
],
|
|
'Captcha' => [
|
|
'type' => 'info',
|
|
'default' => "<div class=\"g-recaptcha\" data-sitekey=\"{$wgReCaptchaSiteKey}\"></div>",
|
|
'raw' => true,
|
|
],
|
|
];
|
|
|
|
$form = HTMLForm::factory(
|
|
'ooui',
|
|
$formItems,
|
|
$this->getContext(),
|
|
"contactpage-{$this->formType}"
|
|
);
|
|
$form->setWrapperLegendMsg('contactpage-legend');
|
|
$form->setSubmitTextMsg('emailsend');
|
|
if ($this->formType != '') {
|
|
$form->setId("contactpage-{$this->formType}");
|
|
|
|
$msg = $this->msg("contactpage-legend-{$this->formType}");
|
|
if (!$msg->isDisabled()) {
|
|
$form->setWrapperLegendMsg($msg);
|
|
}
|
|
|
|
$msg = $this->msg("contactpage-emailsend-{$this->formType}");
|
|
if (!$msg->isDisabled()) {
|
|
$form->setSubmitTextMsg($msg);
|
|
}
|
|
}
|
|
$form->setSubmitCallback([ $this, 'processInput' ]);
|
|
$form->loadData();
|
|
|
|
// Stolen from Special:EmailUser
|
|
if (!Hooks::run('EmailUserForm', [ &$form ])) {
|
|
return;
|
|
}
|
|
|
|
$result = $form->show();
|
|
|
|
if ($result === true || ($result instanceof Status && $result->isGood())) {
|
|
$out = $this->getOutput();
|
|
$pageTitle = $this->msg('emailsent');
|
|
$pageText = 'emailsenttext';
|
|
if ($this->formType !== '') {
|
|
$msg = $this->msg("contactpage-emailsent-{$this->formType}");
|
|
if (!$msg->isDisabled()) {
|
|
$pageTitle = $msg;
|
|
}
|
|
if (!$this->msg("contactpage-emailsenttext-{$this->formType}")->isDisabled()) {
|
|
$pageText = "contactpage-emailsenttext-{$this->formType}";
|
|
}
|
|
}
|
|
$out->setPageTitle($pageTitle);
|
|
$out->addWikiMsg($pageText);
|
|
|
|
$out->returnToMain(false);
|
|
} else {
|
|
if ($config['RLStyleModules']) {
|
|
$this->getOutput()->addModuleStyles($config['RLStyleModules']);
|
|
}
|
|
if ($config['RLModules']) {
|
|
$this->getOutput()->addModules($config['RLModules']);
|
|
}
|
|
$this->getOutput()->prependHTML(trim($formText));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $formData
|
|
* @return bool|string
|
|
* true: Form won't be displayed
|
|
* false: Form will be redisplayed
|
|
* string: Error message to display
|
|
*/
|
|
public function processInput($formData)
|
|
{
|
|
global $wgRequest;
|
|
|
|
$config = $this->getTypeConfig();
|
|
|
|
$request = $this->getRequest();
|
|
$user = $this->getUser();
|
|
|
|
// get the sender's real IP
|
|
if (!is_null($wgRequest->getHeader('CF-Connecting-IP'))) {
|
|
// if the website is behind cloudflare
|
|
$senderIP = $wgRequest->getHeader('CF-Connecting-IP');
|
|
} elseif (!is_null($wgRequest->getHeader('X-Forwarded-For'))) {
|
|
// if the website is behind a reverse proxy
|
|
$senderIP = $wgRequest->getHeader('X-Forwarded-For');
|
|
} else {
|
|
$senderIP = $request->getIP();
|
|
}
|
|
|
|
// Setup user that is going to recieve the contact page response
|
|
$contactRecipientUser = User::newFromName($config['RecipientUser']);
|
|
$contactRecipientAddress = MailAddress::newFromUser($contactRecipientUser);
|
|
|
|
// Used when user hasn't set an email, or when sending CC email to user
|
|
$contactSender = new MailAddress(
|
|
$this->getConfig()->get('PasswordSender'),
|
|
$config['SenderName']
|
|
);
|
|
|
|
$replyTo = null;
|
|
|
|
$fromAddress = $formData['FromAddress'];
|
|
$fromName = $formData['FromName'];
|
|
|
|
// verify the user actually entered an email address
|
|
// the frontend form should verify the email but this is just a backup for spammers
|
|
if (!filter_var($fromAddress, FILTER_VALIDATE_EMAIL)) {
|
|
throw new Exception("$fromAddress is not a valid email address.");
|
|
}
|
|
|
|
$contactSender = new MailAddress(
|
|
$this->getConfig()->get('PasswordSender'),
|
|
$config['SenderName']
|
|
);
|
|
|
|
$msgSubject = $formData['Subject'];
|
|
$subject = "❗❗ New Contact Me Form ❗❗";
|
|
|
|
$senderAddress = new MailAddress(
|
|
$this->getConfig()->get('PasswordSender'),
|
|
$config['SenderName']
|
|
);
|
|
$replyTo = new MailAddress($fromAddress, $fromName);
|
|
|
|
// email subject line
|
|
$subject = $this->msg(
|
|
'contactpage-subject-and-sender'
|
|
)->inContentLanguage()->text();
|
|
|
|
|
|
$text = '';
|
|
|
|
// add main fields
|
|
$text .= "<b>Email:</b> {$fromAddress}<br><b>Name:</b> {$fromName}<br><b>IP:</b> {$senderIP}<br><br><b>Subject:</b> {$msgSubject}<br>";
|
|
|
|
foreach ($config['AdditionalFields'] as $name => $field) {
|
|
$class = HTMLForm::getClassFromDescriptor($name, $field);
|
|
|
|
$value = '';
|
|
if (isset($field['options-messages'])) { // Multiple values!
|
|
if (is_string($formData[$name])) {
|
|
$optionValues = array_flip($field['options-messages']);
|
|
if (isset($optionValues[$formData[$name]])) {
|
|
$value = $this->msg($optionValues[$formData[$name]])->inContentLanguage()->text();
|
|
} else {
|
|
$value = $formData[$name];
|
|
}
|
|
} elseif (count($formData[$name])) {
|
|
$formValues = array_flip($formData[$name]);
|
|
$value .= "\n";
|
|
foreach ($field['options-messages'] as $msg => $optionValue) {
|
|
$msg = $this->msg($msg)->inContentLanguage()->text();
|
|
$optionValue = $this->getYesOrNoMsg(isset($formValues[$optionValue]));
|
|
$value .= "\t$msg: $optionValue\n";
|
|
}
|
|
}
|
|
} elseif (isset($field['options'])) {
|
|
if (is_string($formData[$name])) {
|
|
$value = $formData[$name];
|
|
} elseif (count($formData[$name])) {
|
|
$formValues = array_flip($formData[$name]);
|
|
$value .= "\n";
|
|
foreach ($field['options'] as $msg => $optionValue) {
|
|
$optionValue = $this->getYesOrNoMsg(isset($formValues[$optionValue]));
|
|
$value .= "\t$msg: $optionValue\n";
|
|
}
|
|
}
|
|
} elseif ($class === 'HTMLCheckField') {
|
|
$value = $this->getYesOrNoMsg($formData[$name] xor
|
|
(isset($field['invert']) && $field['invert']));
|
|
} elseif (isset($formData[$name])) {
|
|
// HTMLTextField, HTMLTextAreaField
|
|
// HTMLFloatField, HTMLIntField
|
|
|
|
// Just dump the value if its wordy
|
|
$value = $formData[$name];
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (isset($field['contactpage-email-label'])) {
|
|
$name = $field['contactpage-email-label'];
|
|
} elseif (isset($field['label-message'])) {
|
|
$name = $this->msg($field['label-message'])->inContentLanguage()->text();
|
|
} else {
|
|
$name = $field['label'];
|
|
}
|
|
|
|
$value = htmlspecialchars(html_entity_decode($value)); // convert html to plain text
|
|
$text .= "<b>{$name}</b><br>{$value}<br>";
|
|
}
|
|
|
|
// Stolen from Special:EmailUser
|
|
$error = '';
|
|
if (!Hooks::run('EmailUser', [ &$contactRecipientAddress, &$senderAddress, &$subject,
|
|
&$text, &$error ])
|
|
) {
|
|
return $error;
|
|
}
|
|
|
|
if (!Hooks::run('ContactForm', [ &$contactRecipientAddress, &$replyTo, &$subject,
|
|
&$text, $this->formType, $formData ])
|
|
) {
|
|
return false; // TODO: Need to do some proper error handling here
|
|
}
|
|
|
|
wfDebug(
|
|
__METHOD__ . ': sending mail from ' . $senderAddress->toString() .
|
|
' to ' . $contactRecipientAddress->toString() .
|
|
' replyto ' . ($replyTo == null ? '-/-' : $replyTo->toString()) . "\n"
|
|
);
|
|
|
|
// verify captcha
|
|
$verify = self::verifyCaptcha($request->getVal("g-recaptcha-response"));
|
|
if (!$verify) {
|
|
throw new Exception("captcha verification failed");
|
|
}
|
|
|
|
$mailResult = UserMailer::send(
|
|
$contactRecipientAddress,
|
|
$senderAddress,
|
|
$subject,
|
|
$text,
|
|
['replyTo' => $replyTo, 'contentType' => 'text/html']
|
|
);
|
|
|
|
if (!$mailResult->isOK()) {
|
|
wfDebug(__METHOD__ . ': got error from UserMailer: ' .
|
|
$mailResult->getMessage()->text() . "\n");
|
|
return $this->msg('contactpage-usermailererror')->text() . $mailResult->getMessage()->text();
|
|
}
|
|
|
|
// if the user requested a copy of this mail, do this now,
|
|
// unless they are emailing themselves, in which case one copy of the message is sufficient.
|
|
if ($formData['CCme'] && $fromAddress) {
|
|
$cc_subject = $this->msg('emailccsubject', $contactRecipientUser->getName(), $subject)->text();
|
|
if (Hooks::run(
|
|
'ContactForm',
|
|
[ &$senderAddress, &$contactSender, &$cc_subject, &$text, $this->formType, $formData ]
|
|
)
|
|
) {
|
|
wfDebug(
|
|
__METHOD__ . ': sending cc mail from ' . $contactSender->toString() .
|
|
' to ' . $senderAddress->toString() . "\n"
|
|
);
|
|
$ccResult = UserMailer::send($senderAddress, $contactSender, $cc_subject, $text);
|
|
if (!$ccResult->isOK()) {
|
|
// At this stage, the user's CC mail has failed, but their
|
|
// original mail has succeeded. It's unlikely, but still, what to do?
|
|
// We can either show them an error, or we can say everything was fine,
|
|
// or we can say we sort of failed AND sort of succeeded. Of these options,
|
|
// simply saying there was an error is probably best.
|
|
return $this->msg('contactpage-usermailererror')->text() . $ccResult->getMessage()->text();
|
|
}
|
|
}
|
|
}
|
|
|
|
Hooks::run('ContactFromComplete', [ $contactRecipientAddress, $replyTo, $subject, $text ]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param bool $value
|
|
* @return string
|
|
*/
|
|
private function getYesOrNoMsg($value)
|
|
{
|
|
return $this->msg($value ? 'htmlform-yes' : 'htmlform-no')->inContentLanguage()->text();
|
|
}
|
|
|
|
private function verifyCaptcha($recaptchaResponse)
|
|
{
|
|
global $wgRequest, $wgReCaptchaSecretKey;
|
|
$x = $wgRequest->getHeader("REMOTE_ADDR");
|
|
echo $x;
|
|
$post_data = http_build_query(
|
|
array(
|
|
'secret' => $wgReCaptchaSecretKey,
|
|
'response' => $_POST['g-recaptcha-response'],
|
|
'remoteip' => $_SERVER['REMOTE_ADDR']
|
|
)
|
|
);
|
|
$opts = array('http' =>
|
|
array(
|
|
'method' => 'POST',
|
|
'header' => 'Content-type: application/x-www-form-urlencoded',
|
|
'content' => $post_data
|
|
)
|
|
);
|
|
$context = stream_context_create($opts);
|
|
$response = file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $context);
|
|
$result = json_decode($response);
|
|
if (!$result->success) {
|
|
return false;
|
|
// throw new Exception('CAPTCHA verification failed.', 1);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|