Replace v1 with v2 codebase
Full rewrite: swap out the v1 framework (src/, controllers/, views/, twig/, sass/, skeleton/) for the working v2 codebase from phpproject (App/, novaconium/, public/).
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Response;
|
||||
use Lib\Csrf;
|
||||
use Lib\FormValidator;
|
||||
use Lib\Input;
|
||||
use Lib\Mailer;
|
||||
use Lib\SpamGuard;
|
||||
|
||||
$errors = [];
|
||||
$old = ['name' => '', 'email' => '', 'message' => ''];
|
||||
$spamGuard = new SpamGuard();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!Csrf::verify(Input::post('csrf_token'))) {
|
||||
return Response::redirect('/contact?error=security');
|
||||
}
|
||||
|
||||
$old = [
|
||||
'name' => Input::post('name', ''),
|
||||
'email' => Input::post('email', ''),
|
||||
'message' => Input::post('message', ''),
|
||||
];
|
||||
|
||||
$validator = (new FormValidator())
|
||||
->required($old['name'], 'name', 'Name is required.')
|
||||
->email($old['email'], 'email', 'A valid email is required.')
|
||||
->required($old['message'], 'message', 'Message is required.');
|
||||
|
||||
if ($validator->passes()) {
|
||||
// $spamGuard checks the honeypot field + submission timing (see
|
||||
// Lib\SpamGuard and index.twig's hidden fields) — a bot gets the
|
||||
// exact same redirect a human would either way, with nothing in
|
||||
// the response revealing which check it tripped, or that a check
|
||||
// exists at all. Only Mailer::send() is skipped for spam.
|
||||
if (!$spamGuard->isSpam(Input::post())) {
|
||||
(new Mailer())->send($old['name'], $old['email'], $old['message']);
|
||||
}
|
||||
|
||||
return Response::redirect('/contact?sent=1');
|
||||
}
|
||||
|
||||
$errors = $validator->errors();
|
||||
}
|
||||
|
||||
return [
|
||||
'errors' => $errors,
|
||||
'old' => $old,
|
||||
'sent' => Input::get('sent') !== null,
|
||||
'securityError' => Input::get('error') === 'security',
|
||||
'renderedAt' => $spamGuard->renderedAt(),
|
||||
'csrfField' => Csrf::fieldName(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
];
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends layout %}
|
||||
|
||||
{% block title %}Contact{% endblock %}
|
||||
{% block description %}Get in touch — send a message using the contact form.{% endblock %}
|
||||
|
||||
{# Every block below is optional — the layout already supplies a sensible
|
||||
default for each (see /admin/docs/seo). Shown here uncommented as a
|
||||
reference for every override a page can make; delete what you don't
|
||||
need. #}
|
||||
{% block robots %}index, follow{% endblock %}
|
||||
{% block canonical %}{{ request_path|default('/') }}{% endblock %}
|
||||
|
||||
{% block og_type %}website{% endblock %}
|
||||
{% block og_title %}{{ block('title') }}{% endblock %}
|
||||
{% block og_description %}{{ block('description') }}{% endblock %}
|
||||
{% block og_url %}{{ block('canonical') }}{% endblock %}
|
||||
|
||||
{% block twitter_card %}summary{% endblock %}
|
||||
{% block twitter_title %}{{ block('title') }}{% endblock %}
|
||||
{% block twitter_description %}{{ block('description') }}{% endblock %}
|
||||
|
||||
{% import '_layout/icons.twig' as icons %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1 class="icon-heading">{{ icons.email() }}Contact</h1>
|
||||
|
||||
<p>This form is handled entirely by <code>App/pages/contact/index.php</code>, a sidecar demonstrating the classic POST/redirect/GET pattern: it validates <code>$_POST</code>, calls <code>Lib\Mailer::send()</code> on success, and redirects to <code>/contact?sent=1</code> — so refreshing the page after submitting never resubmits the form. Because this page has a sidecar, it's never served from the static cache; see <a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a> and <a class="icon-link" href="/admin/docs/libraries">{{ icons.book() }}Libraries</a> for the full contract. It's also a working example of self-hosted spam prevention (honeypot field + submission-timing check, no external CAPTCHA service) — see <a class="icon-link" href="/admin/docs/sidecars">{{ icons.book() }}Sidecars</a>' "Spam prevention" section.</p>
|
||||
|
||||
{% if sent %}
|
||||
<p><strong>Thanks — your message has been sent.</strong></p>
|
||||
{% endif %}
|
||||
|
||||
{% if securityError %}
|
||||
<p><strong>Your session expired before submitting — please try again.</strong></p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/contact">
|
||||
<input type="hidden" name="{{ csrfField }}" value="{{ csrfToken }}">
|
||||
<div class="hp-field" aria-hidden="true">
|
||||
<label for="website">Leave this field blank</label>
|
||||
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
|
||||
</div>
|
||||
<input type="hidden" name="rendered_at" value="{{ renderedAt }}">
|
||||
<p>
|
||||
<label for="name">Name</label><br>
|
||||
<input type="text" id="name" name="name" value="{{ old.name }}">
|
||||
{% if errors.name %}<br><small>{{ errors.name }}</small>{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<label for="email">Email</label><br>
|
||||
<input type="text" id="email" name="email" value="{{ old.email }}">
|
||||
{% if errors.email %}<br><small>{{ errors.email }}</small>{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<label for="message">Message</label><br>
|
||||
<textarea id="message" name="message" rows="5">{{ old.message }}</textarea>
|
||||
{% if errors.message %}<br><small>{{ errors.message }}</small>{% endif %}
|
||||
</p>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user