templating engine and mail send function (#45)

This commit is contained in:
DrMaxNix 2024-02-10 21:07:25 +01:00
parent 7453d66ef8
commit f5510a7960
2 changed files with 286 additions and 0 deletions

View File

@ -13,6 +13,7 @@
static::$ext[] = "hidden";
static::$ext[] = "project";
static::$ext[] = "excuse";
static::$ext[] = "error";
// ROUTES //

View File

@ -3,8 +3,27 @@
namespace Kimendisch\Sbgg_Jetzt;
use Flake\Dat;
use Flake\Id64;
use Flake\Project;
use Flake\Error;
use PHPMailer\PHPMailer\PHPMailer;
class Newsletter {
/**
* Static content.
*/
private const STATIC_CONTENT = [
];
/**
* @var array $pending_render List of templates which are currently rendering.
*/
private static array $pending_render = [];
/**
* Add a new mail address to the mailing list.
*
@ -72,6 +91,272 @@
private static function send_welcome_mail(string $mail_address): void {
/**/print_r(["send_welcome_mail" => $mail_address]);
//*/ TODO
/**
* HELPER: Read one content file.
*
* @param string $name Name of the content file to read.
*
* @return array Content file data.
*/
private static function content_file_read(string $name): array {
// GET AVAILABLE CONTENT FILES //
$indir = scandir("./newsletter/content");
$content_file_list = [];
foreach($indir as $one_element){
// only get files
if($one_element === "." or $one_element === "..") continue;
$path = "./newsletter/content/" . $one_element;
if(!is_file($path)) continue;
// check extension
if(substr($one_element, strlen($one_element) - strlen(".data.php")) !== ".data.php") continue;
// add to list
$content_file_list[] = $one_element;
}
// PARSE FILE //
// build content file name
$file_name = $name . ".data.php";
// make sure it exists
if(!in_array($file_name, $content_file_list)) Error::error(message: "Unknown content file name", data: ["content_file_list" => $content_file_list, "name" => $name, "file_name" => $file_name]);
// build full file path
$path = "./newsletter/content/" . $file_name;
// run isolated in own function scope
$content_from_file = (function(){
return require(func_get_arg(0));
})($path);
// add static content
return array_merge(self::STATIC_CONTENT, Env::NEWSLETTER_STATIC_CONTENT, $content_from_file);
}
/**
* HELPER: Render content definition to html body.
*
* @param array $content Content data.
* @param array $dataset Member dataset.
* @param string $root Root template.
*
* @return string Html body.
*/
private static function content_render(array $content, array $dataset, string $root = "base"): string {
// START WITH RAW TEMPLATE //
// read
$html = self::template_read(name: $root);
// remember that our template is not done rendering yet
self::$pending_render[] = $root;
// SUBSTITUTE PLACEHOLDERS //
$callback = function(array $matches) use ($content, $dataset): string {
$key = $matches["key"];
$value = $matches["value"];
// content value
if($key === ""){
if(!isset($content[$value][$dataset["language"]])) Error::error(message: "Unable to substitute content placeholder", data: ["matches" => $matches, "content" => $content, "dataset" => $dataset]);
return $content[$value][$dataset["language"]];
}
// template
if($key === "template"){
if(in_array($value, self::$pending_render)) Error::error(message: "Recursive template rendering", data: ["pending_render" => self::$pending_render, "template" => $value]);
return self::content_render(content: $content, dataset: $dataset, root: $value);
}
// dataset value
if($key === "dataset"){
if(!isset($dataset[$value])) Error::error(message: "Unable to substitute dataset placeholder", data: ["matches" => $matches, "dataset" => $dataset]);
return $dataset[$value];
}
// unknown key
Error::error(message: "Unknown placeholder key", data: ["key" => $key, "value" => $value]);
};
$html = preg_replace_callback("/(?:{{)(?:(?<key>[0-9a-z-_\.]+)\:)?(?<value>[0-9a-z-_\.]+)(?:}})/", $callback, $html);
// DONE //
// remove from waiting list
unset(self::$pending_render[array_search($root, self::$pending_render)]);
// return
return $html;
}
/**
* HELPER: Read template file.
*
* @param string $name Name of template to read.
*
* @return string Template.
*/
private static function template_read(string $name): string {
// GET AVAILABLE TEMPLATE FILES //
$indir = scandir("./newsletter/template");
$template_file_list = [];
foreach($indir as $one_element){
// only get files
if($one_element === "." or $one_element === "..") continue;
$path = "./newsletter/template/" . $one_element;
if(!is_file($path)) continue;
// check extension
if(pathinfo($path)["extension"] !== "html") continue;
// add to list
$template_file_list[] = $one_element;
}
// READ FILE //
// build template file name
$file_name = $name . ".html";
// make sure it exists
if(!in_array($file_name, $template_file_list)){
Error::error(message: "Unknown template file name", data: ["template_file_list" => $template_file_list, "name" => $name, "file_name" => $file_name]);
}
// build full file path
$path = "./newsletter/template/" . $file_name;
// return contents
return file_get_contents($path);
}
/**
* HELPER: Send a mail to one newsletter member.
*
* @param string
* @param array $content Mail content.
* @param ?array $dataset Previously read dataset for this member
* (this makes it possible to use one database read for multiple sends).
*
* @return bool Whether process was successful.
*/
private static function send(string $mail_address, array $content, ?array $dataset = null): bool {
// MAYBE OBTAIN MEMBER DATASET //
if($dataset === null){
// 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);
}
// validate
if(!is_array($dataset)){
Error::error(message: "Mail address not contained in database", data: ["mail_address" => $mail_address, "dataset" => $dataset]);
}
// VALIDATE CONTENT //
// check whether subject is set
if(!isset($content["subject"])){
Error::error(message: "Missing content key: 'subject'", data: ["content" => $content]);
}
// check whether main is set
if(!isset($content["main"])){
Error::error(message: "Missing content key: 'main'", data: ["content" => $content]);
}
// RENDER CONTENT //
$body = self::content_render(content: $content, dataset: $dataset);
// SEND MAIL //
// get subject
$subject = $content["subject"][$dataset["language"]];
// send
return self::mail_send_html(mail_address: $mail_address, subject: $subject, body: $body);
}
/**
* HELPER: Actually send html mail using the phpmailer library.
*
* @param string $mail_address Recipient mail address.
* @param string $subject Mail subject.
* @param string $body Mail html body content.
*
* @return bool Whether sending was successful.
*/
private static function mail_send_html(string $mail_address, string $subject, string $body): bool {
// GET NEW PHPMAILER OBJECT //
$phpmailer = new PHPMailer();
// GENERAL SETTINGS //
// we use smtp to send our mail
$phpmailer->IsSMTP();
// we use utf-8 encoding
$phpmailer->CharSet = "UTF-8";
// SMTP SETTINGS //
// host and smtp port
$phpmailer->Host = Env::MAIL["host"];
$phpmailer->Port = Env::MAIL["port"];
// we need to authenticate
$phpmailer->SMTPAuth = true;
// username and password
$phpmailer->Username = Env::MAIL["username"];
$phpmailer->Password = Env::MAIL["password"];
// encryption
$phpmailer->SMTPSecure = (Env::MAIL["starttls"] ? PHPMailer::ENCRYPTION_STARTTLS : PHPMailer::ENCRYPTION_SMTPS);
// FROM AND TO //
// from
$phpmailer->setFrom(Env::MAIL["username"], "SBGG.jetzt");
// recipient
$phpmailer->addAddress($mail_address);
// META STUFF //
// replace x-mailer header
$phpmailer->XMailer = Project::name() . " v" . Project::version();
// CONTENT //
// this mail has html content
$phpmailer->isHTML(true);
// set subject
$phpmailer->Subject = $subject;
// html body
$phpmailer->Body = $body;
// SEND //
return($phpmailer->send());
}
}
?>