✨ templating engine and mail send function (#45)
This commit is contained in:
parent
7453d66ef8
commit
f5510a7960
1
meta.php
1
meta.php
|
@ -13,6 +13,7 @@
|
|||
static::$ext[] = "hidden";
|
||||
static::$ext[] = "project";
|
||||
static::$ext[] = "excuse";
|
||||
static::$ext[] = "error";
|
||||
|
||||
|
||||
// ROUTES //
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue