$cfg["dev_name"] ?? "Modulos Design", "from_email" => $cfg["from_address"] ?? "drafting@modulosdesign.com.au", "bcc_email" => $cfg["bcc_email"] ?? "drafting@modulosdesign.com.au", "secret" => $cfg["loa_secret"] ?? ($cfg["admin_secret"] ?? ""), ]; // Compute base URL $https = !empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] !== "off"; $scheme = $https ? "https" : "http"; $host = $_SERVER["HTTP_HOST"] ?? "localhost"; $base = rtrim(dirname($_SERVER["SCRIPT_NAME"] ?? ""), "/\\"); $CFG["base_url"] = $scheme . "://" . $host . ($base ? $base . "/" : "/"); /* ----------------------------- helpers ----------------------------- */ function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, "UTF-8"); } function getClientIp(): string { if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $parts = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); foreach ($parts as $ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) return $ip; } foreach ($parts as $ip) if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip; } if (!empty($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP)) return $_SERVER['HTTP_X_REAL_IP']; if (!empty($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) return $_SERVER['REMOTE_ADDR']; return "UNKNOWN"; } function tokenForJob(string $job, string $secret): string { return hash_hmac("sha256", "loa|" . $job, $secret); } function verifyToken(string $job, string $token, string $secret): bool { return $secret !== "" && $token !== "" && hash_equals(tokenForJob($job, $secret), $token); } /* --------------------------- Front matter --------------------------- */ function _fm_trim_quotes(string $v): string { $v = trim($v); if ($v !== "" && $v[0] === "'" && substr($v, -1) === "'") return stripslashes(substr($v, 1, -1)); if ($v !== "" && $v[0] === '"' && substr($v, -1) === '"') return stripslashes(substr($v, 1, -1)); return $v; } function parseFrontMatter(string $text): array { if (function_exists("yaml_parse")) { $arr = @yaml_parse($text); return is_array($arr) ? $arr : []; } $lines = preg_split('/\R/', rtrim($text)); $root = []; $stack = [ ["indent" => -1, "ref" => &$root] ]; foreach ($lines as $raw) { if ($raw === "") continue; $trimmed = ltrim($raw, " "); if ($trimmed === "" || $trimmed[0] === "#") continue; $indent = strlen($raw) - strlen($trimmed); while (count($stack) > 1 && $indent <= $stack[array_key_last($stack)]["indent"]) { array_pop($stack); } $parent =& $stack[array_key_last($stack)]["ref"]; // List item if (preg_match('/^-\s*(.*)$/', $trimmed, $m)) { $val = $m[1]; if (!is_array($parent)) $parent = []; if ($val === "") { $parent[] = []; $stack[] = ["indent" => $indent, "ref" => &$parent[array_key_last($parent)]]; } else { $parent[] = _fm_trim_quotes($val); } continue; } // Key: value or Key: if (preg_match('/^([A-Za-z0-9_.-]+):\s*(.*)$/', $trimmed, $m)) { $key = $m[1]; $val = $m[2]; if ($val === "") { if (!isset($parent[$key]) || !is_array($parent[$key])) $parent[$key] = []; $stack[] = ["indent" => $indent, "ref" => &$parent[$key]]; } else { $parent[$key] = _fm_trim_quotes($val); } } } return $root; } function getByPath($arr, string $path, $default = "") { $keys = explode(".", $path); foreach ($keys as $k) { if ($k === "") continue; if (is_array($arr) && array_key_exists($k, $arr)) { $arr = $arr[$k]; } else { return $default; } } return $arr; } function setByPath(array &$arr, string $path, $value): void { $keys = explode(".", $path); $ref =& $arr; foreach ($keys as $k) { if ($k === "") continue; if (!isset($ref[$k]) || !is_array($ref[$k])) $ref[$k] = []; $ref =& $ref[$k]; } $ref = $value; } function parseFrontMatterForJob(string $job): array { $safe = preg_match('/^\d{1,10}$/', $job) ? $job : "default"; $path = __DIR__ . "/loa/$safe/" . $safe . ".md"; if (!is_file($path)) $path = __DIR__ . "/loa/default-authorisation.md"; $md = @file_get_contents($path); if ($md && preg_match('/^\s*---\s*\n(.*?)\n---\s*\n/s', $md, $m)) { return parseFrontMatter($m[1]); } return []; } function abs_url(string $rel): string { $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $scheme = $https ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; $dir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/\\'); $root = $scheme . '://' . $host . ($dir ? $dir . '/' : '/'); return $root . ltrim($rel, '/'); } function council_recipients(array $vars, array $cfg): array { $to = []; if (!empty($cfg['council_email'])) $to[] = $cfg['council_email']; $councilName = (string)getByPath($vars, 'property.council', ''); if ($councilName && !empty($cfg['council_map'][$councilName])) { $to[] = $cfg['council_map'][$councilName]; } // TODO: if you later store locality/postcode, you can look up by locality too. return array_values(array_unique(array_filter($to))); } /* --------------------- Render LOA Markdown to HTML --------------------- */ function md_to_html(string $md): string { $pd = class_exists('ParsedownExtra') ? new ParsedownExtra() : new Parsedown(); $pd->setSafeMode(true); // Optional: $pd->setBreaksEnabled(true); // if you want single newlines as
return $pd->text($md); } function loadLoaHtml(string $job, array $overrides = []): string { $safe = preg_match('/^\d{1,10}$/', $job) ? $job : "default"; $path = __DIR__ . "/loa/$safe/" . $safe . ".md"; if (!is_file($path)) $path = __DIR__ . "/loa/default-authorisation.md"; $md = file_get_contents($path); // Split front matter and body $vars = []; $body = $md; if (preg_match('/^\s*---\s*\n(.*?)\n---\s*\n(.*)$/s', $md, $m)) { $front = $m[1]; $body = $m[2]; $vars = parseFrontMatter($front); } // Defaults available $base = [ "dev" => [ "name" => $GLOBALS["cfg"]["dev_name"] ?? "Modulos Design", "email" => $GLOBALS["cfg"]["dev_email"] ?? "drafting@modulosdesign.com.au", "phone" => $GLOBALS["cfg"]["dev_phone"] ?? "0402 984 082", "address" => $GLOBALS["cfg"]["dev_address"] ?? "34 Coplestone St, Scottsdale, Tas 7260", ], "client" => [ "name" => "", "email" => "", "phone" => "", "address" => "", ], "job" => $safe, "today" => date("F j, Y"), ]; // Merge: overrides > front matter > base $merged = array_replace_recursive($base, $vars, $overrides); // Flat GET overrides like client_name=... foreach (["client_name" => "client.name", "client_email" => "client.email", "client_phone" => "client.phone"] as $q => $pathKey) { if (isset($_GET[$q]) && $_GET[$q] !== "") { setByPath($merged, $pathKey, (string)$_GET[$q]); } } // Replace [path.to.value] placeholders $body = preg_replace_callback('/\[([a-zA-Z0-9_.-]+)\]/', function ($m) use ($merged) { $val = getByPath($merged, $m[1]); return is_scalar($val) ? (string)$val : ""; }, $body); // Convert Markdown to HTML (ParsedownExtra supports tables) return md_to_html($body); } /* --------------------------- HTML wrappers --------------------------- */ function headerWithTitle(string $title, ?string $job = null, ?string $preparedDate = null, string $context = "web"): string { $safeTitle = h($title); $safeJob = h((string)$job); $safePreparedDate = h((string)$preparedDate); $https = !empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] !== "off"; $scheme = $https ? "https" : "http"; $host = $_SERVER["HTTP_HOST"] ?? "localhost"; $dir = rtrim(dirname($_SERVER["SCRIPT_NAME"] ?? ""), "/\\") . "/"; $cssLinks = $context === "web" ? << HTML : << HTML; $jsLinks = $context === "web" ? << HTML : ""; $nav = $context === "web" ? <<
Modulos Design Modulos Design
HTML : ""; return << {$safeJob} - {$safeTitle} {$cssLinks} {$jsLinks} {$nav}
HTML; } function footerFor(string $context = "web"): string { $extra = $context === "web" ? << function printDoc(){ window.print(); } HTML : ""; return <<
{$extra} HTML; } /* --------------------------- Email helpers --------------------------- */ function salutationFromName(string $fullName): string { $name = str_replace("\xC2\xA0", " ", $fullName); $name = trim(preg_replace('/\s+/u', ' ', $name)); if ($name === "") return "there"; $name = preg_replace('/,?\s*(Jr\.?|Sr\.?|II|III|IV|MD|Ph\.?D|Esq\.?|J\.?D\.?|M\.?B\.?A\.?|RN|DDS|DMD)\s*$/iu', '', $name); $honorifics = '(mr|mrs|ms|miss|mx|dr|prof|sir|dame|lord|lady|hon|rev|fr|father|pastor|rabbi|imam|capt|cpt|gen|col|maj|sgt|officer|chief|coach|pres|sen|rep)'; $name = preg_replace('/^(?:' . $honorifics . ')\.?[\s\x{00A0}]+/iu', '', $name); while (preg_match('/^' . $honorifics . '\.?[\s\x{00A0}]+/iu', $name)) { $name = preg_replace('/^' . $honorifics . '(\.?)[\s\x{00A0}]+/iu', '', $name, 1); } $tokens = preg_split('/[\s\x{00A0}]+/u', $name); if (!$tokens) return "there"; foreach ($tokens as $tok) { $t = rtrim($tok, "."); if (!preg_match('/^[A-Za-z]\.?$/u', $t)) return $t; } return $tokens[0] ?: "there"; } function email_logo_png_cid(PHPMailer $mail, string $dataUrl, string $alt = "Modulos Design", int $width = 140): string { if ($dataUrl === "") return ""; $dataUrl = trim($dataUrl); $prefix = "data:image/png;base64,"; if (stripos($dataUrl, $prefix) !== 0) return ""; $bin = base64_decode(substr($dataUrl, strlen($prefix)), true); if ($bin === false || $bin === "") return ""; $cid = "logo_" . substr(sha1($bin), 0, 12) . "@modulos"; $mail->addStringEmbeddedImage($bin, $cid, "logo.png", "base64", "image/png"); return '' . h($alt) . ''; } function email_cta(string $href, string $label): string { $safeUrl = h($href); $safeLbl = h($label); return <<
{$safeLbl}
{$safeLbl} HTML; } function email_signature_block(string $safeSignatureHtml, string $company = "Modulos Design"): string { $safeCo = h($company); return << Kind Regards,

{$safeSignatureHtml}
Benjamin Harris
{$safeCo}
0402 984 082 | drafting@modulosdesign.com.au HTML; } /** * Reusable frame: preheader + header band + {contentHtml} + footer band */ function email_frame(string $preheader, string $logoHtml, string $job, string $contentHtml, string $footerNote = "This is an automated message. Please reply to this email if you have any questions."): string { $safePre = h($preheader); $safeJob = h($job); $safeFoot = h($footerNote); return <<
{$safePre}
{$contentHtml}
{$logoHtml} Job #{$safeJob}
{$safeFoot}
HTML; } function buildSignedLoaEmail( string $logoHtml, string $viewUrl, string $job, string $clientName = "", string $preparedDate = "", string $company = "Modulos Design", string $safeSignature = "" ): array { $firstName = salutationFromName($clientName); $firstNameSafe = h($firstName); $safeUrl = h($viewUrl); $safeJob = h($job); $safePrepared = h($preparedDate); $preparedPart = $preparedDate ? " (prepared {$safePrepared})" : ""; $subject = "{$safeJob} – Copy of Signed Authorisation"; $cta = email_cta($safeUrl, "View Authorisation"); $sig = email_signature_block($safeSignature, $company); $content = <<
Hello {$firstNameSafe},
Thank you for signing the authorisation{$preparedPart}. A copy is attached for your records, and you can view or download it anytime using the link below:
{$cta}
If the button doesn’t work, copy and paste this link into your browser:
{$safeUrl}
{$sig} HTML; $html = email_frame( "Thank you for signing your authorisation — here’s your copy and access link.", $logoHtml, $job, $content ); $alt = "Hello {$firstName},\n\nThe authorisation has been signed{$preparedPart}.\n\nView/download: {$viewUrl}\n\nThanks,\n{$company}"; return [$subject, $html, $alt]; } function buildCouncilRequestEmail( string $logoHtml, string $job, array $vars, string $loaPublicUrl, string $company = "Modulos Design", string $safeSignature = "" ): array { $addr = (string)getByPath($vars, 'property.address', ''); $pid = (string)getByPath($vars, 'property.pid', ''); $title = (string)getByPath($vars, 'property.title', ''); $vol = (string)getByPath($vars, 'property.vol', ''); $folio = (string)getByPath($vars, 'property.folio', ''); $owners = (string)getByPath($vars, 'client.name', ''); $prepared = (string)getByPath($vars, 'dates.prepared', date('F j, Y')); $titleRef = $title ?: trim($vol . '/' . $folio, '/'); $safeAddr = h($addr); $safePid = h($pid); $safeTitle = h($titleRef); $safeOwners= h($owners); $safeJob = h($job); $safeDate = h($prepared); $safeUrl = h($loaPublicUrl); $safeCo = h($company); $subject = "Request for Planning & Plumbing Information – {$safeAddr} (PID {$safePid}, Title {$safeTitle}) – Job #{$safeJob}"; $sig = email_signature_block($safeSignature, $company); $cta = email_cta($safeUrl, "View Signed LOA"); $content = <<

Good day,

We’re requesting planning and plumbing information for:

Property Address{$safeAddr}
Registered Owner(s){$safeOwners}
PID{$safePid}
Title / Volume–Folio{$safeTitle}
LOA prepared{$safeDate}

Specifically, could you please supply or confirm:

A signed Letter of Authority is attached. You can also view it here:

{$cta}
If the button doesn’t work, copy and paste this link:
{$safeUrl}
{$sig} HTML; $html = email_frame( "Request for planning & plumbing information for {$addr}.", $logoHtml, $job, $content, "Please reply to this email with the requested information or next steps." ); $alt = "Request for planning & plumbing info\n\n" . "Address: {$addr}\n" . "Owners: {$owners}\n" . "PID: {$pid}\n" . "Title: {$titleRef}\n" . "LOA prepared: {$prepared}\n\n" . "Please provide scheme & zoning, overlays, constraints/easements, stormwater/sewer/water service info, " . "any recent DA/BA/Plumbing records, and any other relevant advice.\n\n" . "LOA (view): {$loaPublicUrl}\n\n" . "Thanks,\n{$company}"; return [$subject, $html, $alt]; } function getHtmlUrl(string $htmlName): string { $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $scheme = $https ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; $dir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/\\'); return $scheme . '://' . $host . ($dir ? $dir : '') . '/' . $htmlName; } /* ------------------------------- Routing ------------------------------- */ $job = isset($_REQUEST["job"]) ? preg_replace('/\D+/', '', (string)$_REQUEST["job"]) : ""; $token = $_REQUEST["token"] ?? ""; // Resolve some prepared/derived values $preparedDate = getByPath(parseFrontMatterForJob($job), "dates.prepared", date("F j, Y")); if ($_SERVER["REQUEST_METHOD"] === "GET") { if (!$job || !verifyToken($job, $token, $CFG["secret"])) { http_response_code(403); echo "Auth required"; exit; } // If a signed file already exists for this job, redirect there $pattern = __DIR__ . "/loa/{$job}/{$job}_signed_loa*.pdf"; $matches = glob($pattern); if ($matches) { usort($matches, fn($a, $b) => filemtime($b) <=> filemtime($a)); $latest = basename($matches[0]); header("Location: loa/{$job}/" . $latest, true, 302); exit; } // Render unsigned page $HEADER = headerWithTitle("Unsigned Authorisation", $job, $preparedDate, "web"); $LOA_HTML = loadLoaHtml($job); echo $HEADER; echo $LOA_HTML; // Signature UI (mirrors contracts.php) ?>
format("F j, Y \a\t g:i:s A T"); } else { $clientDate = gmdate("F j, Y \a\t g:i:s A \G\M\T"); } $clientIp = getClientIp(); $CLIENT_SIGNATURE = '' . h(getByPath($vars, "client.name", "")) . ''; $CLIENT_SIGNATURE .= '
' . 'Signed on: ' . h($clientDate) . '
' . 'Client IP: ' . h($clientIp) . '
' . 'Client Signature'; $compiled = <<
{$CLIENT_SIGNATURE}

Download PDF
HTML; // Build final HTML (web and pdf versions) $headerWeb = headerWithTitle("{$job} - Signed Authorisation", $job, getByPath($vars, "dates.prepared", date("F j, Y")), "web"); $footerWeb = footerFor("web"); $outputWeb = $headerWeb . $LOA_HTML . $compiled . $footerWeb; $headerPdf = headerWithTitle("{$job} - Signed Authorisation", $job, getByPath($vars, "dates.prepared", date("F j, Y")), "pdf"); $footerPdf = footerFor("pdf"); $outputPdf = $headerPdf . $LOA_HTML . $compiled . $footerPdf; // Render and save PDF $options = new \Dompdf\Options(); $options->set('defaultFont', 'Helvetica'); $options->set('isRemoteEnabled', true); $dompdf = new \Dompdf\Dompdf($options); $dompdf->loadHtml($outputPdf, "UTF-8"); $dompdf->setPaper("A4", "portrait"); $https = !empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] !== "off"; $scheme = $https ? "https" : "http"; $host = $_SERVER["HTTP_HOST"] ?? "localhost"; $dir = rtrim(dirname($_SERVER["SCRIPT_NAME"] ?? ""), "/\\") . "/"; $dompdf->setBasePath($scheme . "://" . $host . $dir); $dompdf->render(); $pdfPathRel = "{$job}_signed_loa.pdf"; $pdfPathAbs = __DIR__ . "/loa/{$job}/" . $pdfPathRel; $pdfPublicRel = "loa/{$job}/{$pdfPathRel}"; file_put_contents($pdfPathAbs, $dompdf->output()); // Email client + dev $clientEmail = (string)(getByPath($vars, "client.email", "") ?: ""); $devEmail = (string)($cfg["dev_email"] ?? "drafting@modulosdesign.com.au"); $fromAddress = (string)($cfg["from_address"] ?? "drafting@modulosdesign.com.au"); // --- Dorset council PDF + email (only if Dorset is the council) --- try { // Guard so we only do this for Dorset; set in your LOA front matter: // council: // name: "Dorset Council" // email: "development@dorset.tas.gov.au" $councilName = (string) getByPath($vars, 'council.name', ''); $councilEmail = (string) getByPath($vars, 'council.email', ''); if ($councilEmail && stripos($councilEmail, 'tazz.com.au') !== false) { //dorset.tas.gov.au require_once __DIR__ . '/dorset_fill.php'; $templatePath = __DIR__ . '/loa/dorset_consent_form.pdf'; $dorsetOutAbs = __DIR__ . "/loa/{$job}/{$job}_dorset_consent_form.pdf"; $dorsetPdf = generate_dorset_application($job, $vars, $cfg, $templatePath, $dorsetOutAbs); if ($dorsetPdf) { // Email Dorset + BCC dev $mailCouncil = new PHPMailer(true); // SMTP (optional but recommended if set in config) if (!empty($cfg['smtp_host'])) { $mailCouncil->isSMTP(); $mailCouncil->Host = $cfg['smtp_host'] ?? ''; $mailCouncil->SMTPAuth = true; $mailCouncil->Username = $cfg['smtp_username'] ?? ''; $mailCouncil->Password = $cfg['smtp_password'] ?? ''; $mailCouncil->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; $mailCouncil->Port = (int)($cfg['smtp_port'] ?? 465); } // From/Reply-To $fromAddress = (string)($cfg['from_address'] ?? 'drafting@modulosdesign.com.au'); $mailCouncil->CharSet = 'UTF-8'; $mailCouncil->Encoding = 'base64'; $mailCouncil->setFrom($fromAddress, $CFG['brand_name'] ?? 'Modulos Design'); if (!empty($cfg['dev_email'])) $mailCouncil->addReplyTo($cfg['dev_email']); // ✅ REQUIRED: recipient(s) $mailCouncil->addAddress($councilEmail); if (!empty($CFG['bcc_email'])) $mailCouncil->addBCC($CFG['bcc_email']); // Build body using shared template $logoCouncil = email_logo_png_cid($mailCouncil, $cfg['dark_logo'] ?? "", $CFG['brand_name'] ?? 'Modulos Design', 200); $sigCouncil = email_logo_png_cid($mailCouncil, $cfg['dev_signature'] ?? "", "Signature", 100); $loaUrl = abs_url($pdfPublicRel); [$subject, $html, $alt] = buildCouncilRequestEmail( $logoCouncil, $job, $vars, $loaUrl, (string)($CFG['brand_name'] ?? 'Modulos Design'), $sigCouncil ); $mailCouncil->isHTML(true); $mailCouncil->Subject = $subject; $mailCouncil->Body = $html; $mailCouncil->AltBody = $alt; // Attach Dorset form + (optionally) the signed LOA if (is_file($dorsetPdf)) $mailCouncil->addAttachment($dorsetPdf, basename($dorsetPdf)); if (is_file($pdfPathAbs)) $mailCouncil->addAttachment($pdfPathAbs, basename($pdfPathAbs)); try { $mailCouncil->send(); } catch (Throwable $e) { error_log("Council email failed for job {$job} to {$councilEmail}: ".$e->getMessage()); } } } } catch (Throwable $e) { error_log("Dorset generation error for job {$job}: ".$e->getMessage()); } // Build the mailer once per recipient (same as contracts.php) $targets = []; if ($clientEmail) $targets[] = ["to" => $clientEmail, "kind" => "client"]; if ($devEmail) $targets[] = ["to" => $devEmail, "kind" => "dev"]; foreach ($targets as $t) { $mail = new PHPMailer(true); $mail->SMTPDebug = SMTP::DEBUG_OFF; if (!empty($cfg["smtp_host"])) { $mail->isSMTP(); $mail->Host = $cfg["smtp_host"] ?? ""; $mail->SMTPAuth = true; $mail->Username = $cfg["smtp_username"] ?? ""; $mail->Password = $cfg["smtp_password"] ?? ""; $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // 465/SSL $mail->Port = $cfg["smtp_port"] ?? 465; } $mail->CharSet = "UTF-8"; $mail->Encoding = "base64"; $mail->setFrom($fromAddress, $CFG["brand_name"] ?? "Modulos Design"); if ($t["kind"] === "client" && $devEmail) $mail->addReplyTo($devEmail); if ($t["kind"] === "dev" && $clientEmail)$mail->addReplyTo($clientEmail); $mail->addAddress($t["to"]); $mail->isHTML(true); // Embed assets per message $logoHtml = email_logo_png_cid($mail, $cfg["dark_logo"] ?? "", $CFG["brand_name"] ?? "Modulos Design", 200); $safeSignature = email_logo_png_cid($mail, $cfg["dev_signature"] ?? "", "Signature", 100); [$subject, $html, $alt] = buildSignedLoaEmail( $logoHtml, abs_url($pdfPublicRel), // <= FIX: public URL, not the filesystem path $job, (string)getByPath($vars, "client.name", ""), (string)getByPath($vars, "dates.prepared", date("F j, Y")), (string)($CFG["brand_name"] ?? "Modulos Design"), $safeSignature ); // Developer copy extra info if ($t["kind"] === "dev") { $subject = $job . " – Authorisation has been signed"; $signedBy = h($clientEmail ?: "unknown"); $inject = 'Signed by: ' . $signedBy . ''; $html = preg_replace('/(\s*]*>.*?<\/td>\s*<\/tr>)/s', '$1' . $inject, $html, 1) ?: str_replace('', '' . $inject . '', $html); $alt .= "\n\nSigned by: " . ($clientEmail ?: "unknown"); } $mail->Subject = $subject; $mail->Body = $html; if (!empty($alt)) $mail->AltBody = $alt; if (!empty($CFG["bcc_email"])) $mail->addBCC($CFG["bcc_email"]); if (is_file($pdfPathAbs)) $mail->addAttachment($pdfPathAbs, basename($pdfPathAbs)); try { $mail->send(); } catch (MailerException $e) { error_log("LOA mailer error to {$t['to']}: {$mail->ErrorInfo}\n"); } } // Public URL to the signed LOA (for the link in the email body) $loaUrl = abs_url($pdfPublicRel); // Council recipients $councilTo = council_recipients($vars, $cfg); foreach ($councilTo as $addr) { try { $mail = new PHPMailer(true); $mail->SMTPDebug = SMTP::DEBUG_OFF; if (!empty($cfg["smtp_host"])) { $mail->isSMTP(); $mail->Host = $cfg["smtp_host"] ?? ""; $mail->SMTPAuth = true; $mail->Username = $cfg["smtp_username"] ?? ""; $mail->Password = $cfg["smtp_password"] ?? ""; $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; $mail->Port = $cfg["smtp_port"] ?? 465; } $mail->CharSet = "UTF-8"; $mail->Encoding = "base64"; $mail->setFrom($CFG["from_email"] ?? "drafting@modulosdesign.com.au", $CFG["brand_name"] ?? "Modulos Design"); // Replies come back to you if (!empty($cfg["dev_email"])) $mail->addReplyTo($cfg["dev_email"]); $mail->addAddress($addr); $mail->isHTML(true); // Embed assets $logoHtml = email_logo_png_cid($mail, $cfg["dark_logo"] ?? "", $CFG["brand_name"] ?? "Modulos Design", 200); $safeSignature = email_logo_png_cid($mail, $cfg["dev_signature"] ?? "", "Signature", 100); // Build council email [$subject, $html, $alt] = buildCouncilRequestEmail( $logoHtml, $job, $vars, $loaUrl, (string)($CFG["brand_name"] ?? "Modulos Design"), $safeSignature ); $mail->Subject = $subject; $mail->Body = $html; $mail->AltBody = $alt; if (!empty($CFG["bcc_email"])) $mail->addBCC($CFG["bcc_email"]); if (is_file($pdfPathAbs)) $mail->addAttachment($pdfPathAbs, basename($pdfPathAbs)); // attach signed LOA $mail->send(); } catch (MailerException $e) { error_log("Council mailer error to {$addr}: {$mail->ErrorInfo}\n"); } } // Redirect to signed HTML header("Location: " . $pdfPublicRel, true, 303); exit; }