modules-forms-lyndi/module.php

345 lines
9.7 KiB
PHP
Executable file

<?php
use Gregwar\Captcha\CaptchaBuilder;
use Lewp\Resolve;
return new class extends Lewp\Module
{
const KEY_CSRF_TOKEN = 'csrf_token';
const KEY_CAPTCHA_PHRASE = 'captcha_phrase';
const KEY_POST_EMAIL = 'email';
const KEY_POST_NAME = 'name';
const KEY_POST_MESSAGE = 'message';
const OPTIONS_REDIRECT_URI = 'location_on_success';
const OPTIONS_MAIL_FROM = 'mail_from';
const OPTIONS_MAIL_TO = 'mail_to';
private function createForm(bool $group_input_and_labels = true)
{
$form = $this->createAndSetupElement(
'form',
'',
[
'method' => 'post',
'action' => '',
]
);
// LABEL NAME
$label_name = $this->createAndSetupElement(
'label',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'name'
])),
[
'for' => 'name',
]
);
// NAME INPUT
$name = $this->createAndSetupElement(
'input',
'',
[
'id' => 'name',
'name' => 'name',
'type' => 'text',
'value' => $_POST[self::KEY_POST_NAME] ?? '',
'required' => 'required',
'placeholder' => ' ',
]
);
// LABEL Email
$label_email = $this->createAndSetupElement(
'label',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'email'
])),
[
'for' => 'email'
]
);
// EMAIL INPUT
$email = $this->createAndSetupElement(
'input',
'',
[
'id' => 'email',
'name' => 'email',
'type' => 'email',
'value' => $_POST[self::KEY_POST_EMAIL] ?? '',
'required' => 'required',
'placeholder' => ' ',
]
);
// LABEL Message
$label_message = $this->createAndSetupElement(
'label',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'message'
])),
[
'for' => 'message'
]
);
// MESSAGE INPUT
$message = $this->createAndSetupElement(
'textarea',
$_POST[self::KEY_POST_MESSAGE] ?? '',
[
'id' => 'message',
'name' => 'message',
'required' => 'required',
'placeholder' => ' ',
]
);
// LABEL Captcha
$label_captcha = $this->createAndSetupElement(
'label',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'captcha'
])),
[
'for' => self::KEY_CAPTCHA_PHRASE
]
);
// CAPTCHA INPUT
$captcha = $this->createAndSetupElement(
'input',
'',
[
'id' => self::KEY_CAPTCHA_PHRASE,
'name' => self::KEY_CAPTCHA_PHRASE,
'type' => 'text',
'required' => 'required',
'placeholder' => ' ',
]
);
// SUBMIT BUTTON
$submit = $this->createAndSetupElement(
'button',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'submit'
])),
[
'id' => 'submit',
'type' => 'submit',
]
);
// APPEND THEM TO DOCUMENT
if ($group_input_and_labels) {
$div = $this->createAndSetupElement(
'div', '', ['class' => 'fieldwrapper']
);
$div->appendChild($name);
$div->appendChild($label_name);
$form->appendChild($div);
$div = $this->createAndSetupElement(
'div', '', ['class' => 'fieldwrapper']
);
$div->appendChild($email);
$div->appendChild($label_email);
$form->appendChild($div);
$div = $this->createAndSetupElement(
'div', '', ['class' => 'fieldwrapper']
);
$div->appendChild($message);
$div->appendChild($label_message);
$form->appendChild($div);
} else {
$form->appendChild($label_name);
$form->appendChild($name);
$form->appendChild($label_email);
$form->appendChild($email);
$form->appendChild($label_message);
$form->appendChild($message);
}
$form->appendChild($this->generateCaptcha());
if ($group_input_and_labels) {
$div = $this->createAndSetupElement(
'div', '', ['class' => 'fieldwrapper']
);
$div->appendChild($captcha);
$div->appendChild($label_captcha);
$form->appendChild($div);
} else {
$form->appendChild($label_captcha);
$form->appendChild($captcha);
}
$form->appendChild($submit);
// ADD CSRF CHECK
$form->appendChild($this->generateCsrfInput());
return $form;
}
private function generateCsrfInput()
{
// create token and save to session
$token = bin2hex(random_bytes(6));
$this->getSession()->set(self::KEY_CSRF_TOKEN, $token);
return $this->createAndSetupElement(
'input',
'',
[
'type' => 'hidden',
'name' => self::KEY_CSRF_TOKEN,
'value' => $token,
]
);
}
private function validateCsrfInput() : bool
{
if (!$this->formSubmitted()) {
return true;
}
$token = $this->getSession()->get(self::KEY_CSRF_TOKEN);
if (
is_null($_POST[self::KEY_CSRF_TOKEN])
|| $_POST[self::KEY_CSRF_TOKEN] !== $token
) {
return false;
}
return true;
}
private function generateCaptcha()
{
$builder = new CaptchaBuilder();
$builder->build();
$this->getSession()->set(self::KEY_CAPTCHA_PHRASE, $builder->getPhrase());
$img_captcha = $this->createAndSetupElement(
'img',
'',
[
'id' => 'captcha',
'src' => $builder->inline(),
]
);
return $img_captcha;
}
private function validateCaptcha() : bool
{
if (!$this->formSubmitted()) {
return true;
}
$valid_phrase = $this->getSession()->get(self::KEY_CAPTCHA_PHRASE);
if (
is_null($_POST[self::KEY_CAPTCHA_PHRASE])
|| $_POST[self::KEY_CAPTCHA_PHRASE] !== $valid_phrase
) {
return false;
}
return true;
}
private function formSubmitted()
{
return (
!is_null($_POST['name'])
|| !is_null($_POST['email'])
|| !is_null($_POST['message'])
) ? true : false;
}
private function sendMail($payload, $options)
{
mail(
$options[self::OPTIONS_MAIL_TO],
'['.$this->getTLD().'] '.$payload[self::KEY_POST_NAME],
$payload[self::KEY_POST_MESSAGE],
'From: '.$options[self::OPTIONS_MAIL_FROM]
."\r\n"
.'Reply-To: '.$payload[self::KEY_POST_EMAIL]
."\r\n"
.'MIME-Version: 1.0'
."\r\n"
.'Content-type: text/plain; charset=UTF-8'
."\r\n"
);
}
public function run(array $options = []) : bool
{
// set default options
$options += [
self::OPTIONS_REDIRECT_URI => "/"
];
if (!$this->validateCsrfInput()) {
$this->getSession()->getFlashBag()->add(
'error',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'flashmessages',
'error',
'csrf-failed'
]))
);
}
if (!$this->validateCaptcha()) {
$this->getSession()->getFlashBag()->add(
'error',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'flashmessages',
'error',
'captcha-failed'
]))
);
}
// send form again if captcha or csrf not valid, or form not submitted yet
if (
!$this->validateCaptcha()
|| !$this->validateCsrfInput()
|| !$this->formSubmitted()
) {
$form = $this->createForm();
$this->appendChild($form);
return true;
}
// send email
if (
!is_null($options[self::OPTIONS_MAIL_TO])
|| !is_null($options[self::OPTIONS_MAIL_FROM])
) {
$this->sendMail($_POST, $options);
}
// notify user about success
$this->getSession()->getFlashBag()->add(
'success',
$this->loadTextFile(Resolve::arrayToId([
$this->getLanguage(),
'flashmessages',
'success',
'thanks-for-message'
]))
);
header('Location: '.$options[self::OPTIONS_REDIRECT_URI]);
exit; // if not present, session gets not saved
return true;
}
public function onWebsocketRequest(
\Lewp\Websocket\Message $message
) : \Lewp\Websocket\Message {
}
};