✨ admin area newsletter send one (#45)
This commit is contained in:
parent
d5172c58a0
commit
5faa6b83bb
2
meta.php
2
meta.php
|
@ -46,5 +46,7 @@
|
|||
["path" => "admin/login", "target" => "page/admin/login"],
|
||||
["path" => "admin/newsletter", "target" => "page/admin/newsletter/overview.php"],
|
||||
["path" => "admin/newsletter/:content", "target" => "page/admin/newsletter/content.php"],
|
||||
|
||||
["path" => "admin/newsletter/api/send-one", "target" => "page/admin/newsletter/api/send_one.php"],
|
||||
];
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace Kimendisch\Sbgg_Jetzt;
|
||||
use Flake\Csrf;
|
||||
|
||||
// DECODE REQUEST //
|
||||
// get json string
|
||||
$json_body = file_get_contents("php://input");
|
||||
if(strlen($json_body) <= 0){
|
||||
http_response_code(400);
|
||||
echo("malformed request body");
|
||||
die();
|
||||
}
|
||||
|
||||
// try decoding json
|
||||
$request = json_decode($json_body, true);
|
||||
if(json_last_error() != JSON_ERROR_NONE){
|
||||
http_response_code(400);
|
||||
echo("malformed request body");
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
// VALIDATE VALUES //
|
||||
// csrf token
|
||||
Csrf::check(token: $request["csrf_token"] ?? "");
|
||||
|
||||
// mail address
|
||||
$mail_address = $request["mail_address"] ?? "";
|
||||
if(!is_string($mail_address)){
|
||||
http_response_code(400);
|
||||
echo("invalid mail address");
|
||||
die();
|
||||
}
|
||||
if(!preg_match("/^[a-zA-Z0-9\.\-\_\+]+@([a-z0-9\-]+\.)+[a-z0-9\-]{2,}$/", $mail_address)){
|
||||
http_response_code(400);
|
||||
echo("invalid mail address");
|
||||
die();
|
||||
}
|
||||
|
||||
// content name
|
||||
$content_name = $request["content_name"] ?? "";
|
||||
if(!is_string($content_name)){
|
||||
http_response_code(400);
|
||||
echo("invalid content name");
|
||||
die();
|
||||
}
|
||||
if(!preg_match("/^\d{4}-\d{2}-\d{2}(-[a-z0-9]+)+$/", $content_name)){
|
||||
http_response_code(400);
|
||||
echo("invalid content name");
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
// CHECK WHETHER THIS ADDRESS IS A MEMBER //
|
||||
if(!Newsletter::is_member(mail_address: $mail_address)){
|
||||
http_response_code(200);
|
||||
echo(json_encode([
|
||||
"success" => false,
|
||||
"reason" => "no_member"
|
||||
]));
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
// TRY SENDING //
|
||||
if(!Newsletter::send_one(mail_address: $mail_address, content_name: $content_name)){
|
||||
http_response_code(200);
|
||||
echo(json_encode([
|
||||
"success" => false
|
||||
]));
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
// POSITIVE RESPONSE //
|
||||
http_response_code(200);
|
||||
echo(json_encode([
|
||||
"success" => true
|
||||
]));
|
||||
?>
|
|
@ -6,6 +6,7 @@
|
|||
use Flake\Page;
|
||||
use Flake\Project;
|
||||
use Flake\Excuse;
|
||||
use Flake\Csrf;
|
||||
|
||||
// HANDLE AUTHENTICATION //
|
||||
require("./page/admin/auth_handler.php");
|
||||
|
@ -70,6 +71,7 @@
|
|||
Page::$head["analytics"] = '<script defer data-domain="sbgg.jetzt" src="https://analytics.tjdev.de/js/script.js"></script>';
|
||||
Page::css("./page/start/style.css");
|
||||
Page::js(__DIR__ . "/iframe_magic.js");
|
||||
Page::js(__DIR__ . "/send_one.js");
|
||||
|
||||
Page::font("ubuntu");
|
||||
Page::font("tabler");
|
||||
|
@ -117,6 +119,60 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="header">
|
||||
<span class="icon ti ti-mail-fast"></span>
|
||||
<span class="text">Delivery</span>
|
||||
</div>
|
||||
<div class="content rows">
|
||||
<div class="box">
|
||||
<span class="title">Send One</span>
|
||||
|
||||
<div id="newsletter-send-one-form-container" class="form-container">
|
||||
<div id="newsletter-send-one-form" class="form">
|
||||
<div class="key-value-pair">
|
||||
<div class="key">
|
||||
<span class="ti ti-at"></span>
|
||||
</div>
|
||||
<div class="value-list">
|
||||
<div class="inputwrapper">
|
||||
<input id="newsletter-send-one-form-mail-address" class="value" type="text" placeholder="Member Mail Address" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="newsletter-send-one-form-content-name" type="hidden" value="<?= $content_name ?>" />
|
||||
<input id="newsletter-send-one-form-csrf-token" type="hidden" value="<?= Csrf::token() ?>" />
|
||||
|
||||
<button id="newsletter-send-one-form-submit" class="button primary">
|
||||
<span class="text">Send One</span>
|
||||
<span class="icon ti ti-chevron-right"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="newsletter-send-one-form-feedback" class="form-feedback gone">
|
||||
<div id="newsletter-send-one-form-feedback-wait" class="form-feedback-wait centertext gone">
|
||||
<span class="icon spinning ti ti-loader-2"></span>
|
||||
<span class="text">Sending</span>
|
||||
</div>
|
||||
<div id="newsletter-send-one-form-feedback-success" class="form-feedback-success centertext gone">
|
||||
<span class="icon ti ti-check"></span>
|
||||
<span class="text">Successfully sent</span>
|
||||
</div>
|
||||
<div id="newsletter-send-one-form-feedback-failure" class="form-feedback-failure centertext gone">
|
||||
<span class="icon ti ti-x"></span>
|
||||
<span class="text">Sending failed</span>
|
||||
</div>
|
||||
<div id="newsletter-send-one-form-feedback-failure-no-member" class="form-feedback-failure centertext gone">
|
||||
<span class="icon ti ti-x"></span>
|
||||
<span class="text">Not a newsletter member</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
"use strict";
|
||||
|
||||
let newsletter_form;
|
||||
let newsletter_input_mail_address;
|
||||
let newsletter_input_content_name;
|
||||
let newsletter_input_csrf_token;
|
||||
let newsletter_input_submit;
|
||||
let newsletter_feedback;
|
||||
let newsletter_feedback_wait;
|
||||
let newsletter_feedback_success;
|
||||
let newsletter_feedback_failure;
|
||||
let newsletter_feedback_failure_no_member;
|
||||
|
||||
let newsletter_valid_mail_address = false;
|
||||
|
||||
window.addEventListener("load", function(){
|
||||
// STORE ELEMENTS //
|
||||
newsletter_form = document.getElementById("newsletter-send-one-form");
|
||||
newsletter_input_mail_address = document.getElementById("newsletter-send-one-form-mail-address");
|
||||
newsletter_input_content_name = document.getElementById("newsletter-send-one-form-content-name");
|
||||
newsletter_input_csrf_token = document.getElementById("newsletter-send-one-form-csrf-token");
|
||||
newsletter_input_submit = document.getElementById("newsletter-send-one-form-submit");
|
||||
newsletter_feedback = document.getElementById("newsletter-send-one-form-feedback");
|
||||
newsletter_feedback_wait = document.getElementById("newsletter-send-one-form-feedback-wait");
|
||||
newsletter_feedback_success = document.getElementById("newsletter-send-one-form-feedback-success");
|
||||
newsletter_feedback_failure = document.getElementById("newsletter-send-one-form-feedback-failure");
|
||||
newsletter_feedback_failure_no_member = document.getElementById("newsletter-send-one-form-feedback-failure-no-member");
|
||||
|
||||
|
||||
// INITIALIZE INPUTS //
|
||||
newsletter_init_mail_address();
|
||||
newsletter_init_submit();
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* HELPER: Initialize mail address input.
|
||||
*/
|
||||
async function newsletter_init_mail_address(){
|
||||
// REGISTER INPUT HANDLER //
|
||||
newsletter_input_mail_address.addEventListener("input", newsletter_update_mail_address);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* HELPER: Initialize submit button input.
|
||||
*/
|
||||
async function newsletter_init_submit(){
|
||||
// REGISTER CLICK HANDLER //
|
||||
newsletter_input_submit.addEventListener("click", newsletter_submit);
|
||||
|
||||
|
||||
// UPDATE STATE //
|
||||
newsletter_update_submit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CALLBACK: Update mail address state.
|
||||
*/
|
||||
async function newsletter_update_mail_address(){
|
||||
// VALIDATE INPUT //
|
||||
// load value
|
||||
let value = newsletter_input_mail_address.value;
|
||||
|
||||
// check against regex
|
||||
newsletter_valid_mail_address = (value.match(/^[a-zA-Z0-9\.\-\_\+]+@([a-z0-9\-]+\.)+[a-z0-9\-]{2,}$/) !== null);
|
||||
|
||||
|
||||
// UPDATE VALIDITY INDICATOR //
|
||||
// get element
|
||||
let validity_indicator = newsletter_input_mail_address.parentElement;
|
||||
|
||||
// reset state
|
||||
validity_indicator.classList.remove("valid", "invalid");
|
||||
|
||||
// set state
|
||||
if(newsletter_valid_mail_address){
|
||||
validity_indicator.classList.add("valid");
|
||||
} else {
|
||||
validity_indicator.classList.add("invalid");
|
||||
}
|
||||
|
||||
|
||||
// UPDATE SUBMIT BUTTON //
|
||||
newsletter_update_submit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* HELPER: Update submit button state.
|
||||
*/
|
||||
async function newsletter_update_submit(){
|
||||
// DISABLE //
|
||||
newsletter_input_submit.classList.add("disabled");
|
||||
|
||||
|
||||
// MAYBE ENABLE //
|
||||
if(newsletter_valid_mail_address){
|
||||
newsletter_input_submit.classList.remove("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CALLBACK: Maybe submit the form.
|
||||
*/
|
||||
async function newsletter_submit(){
|
||||
// MAKE SURE ALL INPUTS ARE VALID //
|
||||
if(!newsletter_valid_mail_address) return;
|
||||
|
||||
|
||||
// SHOW WAIT FEEDBACK //
|
||||
newsletter_feedback.classList.remove("hidden", "gone");
|
||||
newsletter_form.classList.add("hidden");
|
||||
newsletter_feedback_wait.classList.remove("hidden", "gone");
|
||||
|
||||
|
||||
// COLLECT VALUES //
|
||||
// mail address
|
||||
let mail_address = newsletter_input_mail_address.value;
|
||||
|
||||
// content name
|
||||
let content_name = newsletter_input_content_name.value;
|
||||
|
||||
// csrf token
|
||||
let csrf_token = newsletter_input_csrf_token.value;
|
||||
|
||||
|
||||
// SEND API REQUEST //
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/admin/newsletter/api/send-one", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({
|
||||
mail_address: mail_address,
|
||||
content_name: content_name,
|
||||
csrf_token: csrf_token
|
||||
}));
|
||||
|
||||
xhr.onload = function(){
|
||||
let success = true;
|
||||
|
||||
// validate http status code
|
||||
if(xhr.status !== 200) success = false;
|
||||
|
||||
// check response
|
||||
let response = null;
|
||||
if(success){
|
||||
try {
|
||||
response = JSON.parse(xhr.response);
|
||||
} catch(e){}
|
||||
|
||||
if(typeof response !== "object") success = false;
|
||||
if(success && response === null) success = false;
|
||||
if(success && response.success !== true) success = false;
|
||||
}
|
||||
|
||||
// positive feedback
|
||||
if(success){
|
||||
newsletter_feedback_wait.classList.add("gone");
|
||||
newsletter_feedback_success.classList.remove("hidden", "gone");
|
||||
return;
|
||||
}
|
||||
|
||||
// negative feedback: no member
|
||||
if(response !== null && (response.reason ?? "") === "no_member"){
|
||||
newsletter_feedback_wait.classList.add("gone");
|
||||
newsletter_feedback_failure_no_member.classList.remove("hidden", "gone");
|
||||
return;
|
||||
}
|
||||
|
||||
// negative feedback: default
|
||||
newsletter_feedback_wait.classList.add("gone");
|
||||
newsletter_feedback_failure.classList.remove("hidden", "gone");
|
||||
}
|
||||
}
|
|
@ -274,8 +274,7 @@
|
|||
|
||||
if($success){
|
||||
// send welcome mail
|
||||
$content = self::content_file_read("0000-00-00-welcome");
|
||||
self::send(mail_address: $mail_address, content: $content);
|
||||
self::send_one(mail_address: $mail_address, content_name: "0000-00-00-welcome");
|
||||
|
||||
} else {
|
||||
// fake delay
|
||||
|
@ -343,6 +342,51 @@
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a mail address is contained in the newsletter list.
|
||||
*
|
||||
* @param string $mail_address Mail address to search for.
|
||||
*
|
||||
* @return bool Whether this mail address is a newsletter member.
|
||||
*/
|
||||
public static function is_member(string $mail_address): bool {
|
||||
// TRY LOADING FROM DATABASE //
|
||||
// open
|
||||
$old_ignore_user_abort = (bool)ignore_user_abort(true);
|
||||
$newsletter_db = new Dat("./.dat/newsletter", Dat::MODE_READ);
|
||||
|
||||
// read
|
||||
$dataset = $newsletter_db->get(["list", $mail_address]);
|
||||
|
||||
// close
|
||||
$newsletter_db->close();
|
||||
ignore_user_abort($old_ignore_user_abort);
|
||||
|
||||
|
||||
// CHECK //
|
||||
return ($dataset !== null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send content to one newsletter member.
|
||||
*
|
||||
* @param string $mail_address Member mail address.
|
||||
* @param string $content_name Name of content to send.
|
||||
*
|
||||
* @return bool Whether sending was successful.
|
||||
*/
|
||||
public static function send_one(string $mail_address, string $content_name): bool {
|
||||
// read content
|
||||
$content = self::content_file_read($content_name);
|
||||
|
||||
// send content
|
||||
return self::send(mail_address: $mail_address, content: $content);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* HELPER: Initialize the database with empty values.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue