246 lines
8.8 KiB
PHP
246 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Whatsapp;
|
|
|
|
use App\Models\WhatsappInviteLog;
|
|
use App\Services\EmailService;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class WhatsappInviteEmailService
|
|
{
|
|
public function __construct(private EmailService $emailService) {}
|
|
|
|
public function send(array $payload): array
|
|
{
|
|
$to = array_values(array_unique(array_filter((array) ($payload['to_emails'] ?? []))));
|
|
$cc = array_values(array_unique(array_filter((array) ($payload['cc_emails'] ?? []))));
|
|
$cc = array_values(array_diff($cc, $to));
|
|
|
|
if (empty($to) && ! empty($cc)) {
|
|
$to = $cc;
|
|
$cc = [];
|
|
}
|
|
if (empty($to)) {
|
|
Log::warning('WhatsappInvite: no recipients after normalization.');
|
|
|
|
return ['ok' => false, 'message' => 'No recipients provided.'];
|
|
}
|
|
|
|
$sections = (array) ($payload['sections'] ?? []);
|
|
$links = array_values(array_unique(array_filter((array) ($payload['links'] ?? []))));
|
|
$parent = $payload['parent'] ?? null;
|
|
|
|
$items = $this->buildItems($sections, $links);
|
|
if (empty($items)) {
|
|
Log::warning('WhatsappInvite: no invite items built.');
|
|
|
|
return ['ok' => false, 'message' => 'No invite links found.'];
|
|
}
|
|
|
|
$subject = count($items) > 1
|
|
? 'Your WhatsApp Group Links (Multiple Classes)'
|
|
: 'Your WhatsApp Group Link';
|
|
|
|
$lines = ['Assalamu Alaikum,', '', 'Here are your WhatsApp link(s):'];
|
|
foreach ($items as $it) {
|
|
$lines[] = ' - '.$it['class_section_name'].': '.$it['invite_link'];
|
|
}
|
|
$lines[] = '';
|
|
$lines[] = 'Jazakumu-llahu khayran.';
|
|
$bodyHtml = nl2br(implode("\n", $lines));
|
|
|
|
$ok = $this->emailService->send($to, $subject, $bodyHtml, 'notifications', $cc);
|
|
|
|
$singleSectionId = count($items) === 1
|
|
? ((int) ($items[0]['class_section_id'] ?? 0) ?: null)
|
|
: null;
|
|
$parentId = (int) ($parent['id'] ?? 0);
|
|
|
|
if ($ok) {
|
|
WhatsappInviteLog::logSuccess($parentId, implode(', ', $to), $singleSectionId, null);
|
|
Log::info('Whatsapp invite sent.', ['to' => $to, 'cc' => $cc]);
|
|
} else {
|
|
WhatsappInviteLog::logFailure($parentId, implode(', ', $to), 'Email send failed', $singleSectionId, null);
|
|
Log::error('Whatsapp invite failed.', ['to' => $to, 'cc' => $cc]);
|
|
}
|
|
|
|
return [
|
|
'ok' => $ok,
|
|
'recipients' => count($to),
|
|
];
|
|
}
|
|
|
|
private function buildItems(array $sections, array $links): array
|
|
{
|
|
$seen = [];
|
|
$items = [];
|
|
|
|
foreach ($sections as $section) {
|
|
$sid = isset($section['class_section_id']) ? (int) $section['class_section_id'] : 0;
|
|
$name = trim((string) ($section['class_section_name'] ?? ($sid ? ('Section '.$sid) : 'Class')));
|
|
$url = trim((string) ($section['invite_link'] ?? ''));
|
|
if ($url === '') {
|
|
continue;
|
|
}
|
|
$key = $sid.'|'.$url;
|
|
if (isset($seen[$key])) {
|
|
continue;
|
|
}
|
|
$seen[$key] = true;
|
|
|
|
$items[] = [
|
|
'class_section_id' => $sid,
|
|
'class_section_name' => $name,
|
|
'invite_link' => $url,
|
|
'qr_src' => $this->generateQrImage($url),
|
|
];
|
|
}
|
|
|
|
if (empty($items) && ! empty($links)) {
|
|
foreach ($links as $i => $url) {
|
|
$url = trim((string) $url);
|
|
if ($url === '') {
|
|
continue;
|
|
}
|
|
$key = '0|'.$url;
|
|
if (isset($seen[$key])) {
|
|
continue;
|
|
}
|
|
$seen[$key] = true;
|
|
$items[] = [
|
|
'class_section_id' => 0,
|
|
'class_section_name' => 'Class Link '.($i + 1),
|
|
'invite_link' => $url,
|
|
'qr_src' => $this->generateQrImage($url),
|
|
];
|
|
}
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
private function generateQrImage(string $inviteLink): string
|
|
{
|
|
if ($inviteLink === '') {
|
|
return $this->qrFallback($inviteLink);
|
|
}
|
|
|
|
$dir = storage_path('app/qrcodes');
|
|
if (! is_dir($dir)) {
|
|
if (! @mkdir($dir, 0775, true) && ! is_dir($dir)) {
|
|
Log::error('QR: cannot create dir', ['dir' => $dir]);
|
|
|
|
return $this->qrFallback($inviteLink);
|
|
}
|
|
}
|
|
if (! is_writable($dir)) {
|
|
Log::error('QR: dir not writable', ['dir' => $dir]);
|
|
|
|
return $this->qrFallback($inviteLink);
|
|
}
|
|
|
|
$filename = 'wa_'.md5($inviteLink).'.png';
|
|
$path = $dir.DIRECTORY_SEPARATOR.$filename;
|
|
|
|
if (! is_file($path)) {
|
|
$v5BuilderClass = '\\Endroid\\QrCode\\Builder\\Builder';
|
|
$v5WriterClass = '\\Endroid\\QrCode\\Writer\\PngWriter';
|
|
$v5Encoding = '\\Endroid\\QrCode\\Encoding\\Encoding';
|
|
|
|
$writerClass = '\\Endroid\\QrCode\\Writer\\PngWriter';
|
|
$qrClass = '\\Endroid\\QrCode\\QrCode';
|
|
|
|
$chlQRCode = '\\chillerlan\\QRCode\\QRCode';
|
|
$chlQROptions = '\\chillerlan\\QRCode\\QROptions';
|
|
$chlQRMatrix = '\\chillerlan\\QRCode\\Data\\QRMatrix';
|
|
|
|
$lastErr = null;
|
|
|
|
if (class_exists($v5BuilderClass) && class_exists($v5WriterClass)) {
|
|
try {
|
|
$builder = $v5BuilderClass::create()
|
|
->writer(new $v5WriterClass)
|
|
->data($inviteLink);
|
|
if (class_exists($v5Encoding)) {
|
|
$builder->encoding(new $v5Encoding('UTF-8'));
|
|
}
|
|
$result = $builder->size(500)->margin(2)->build();
|
|
$result->saveToFile($path);
|
|
} catch (\Throwable $e) {
|
|
$lastErr = 'Endroid v5: '.$e->getMessage();
|
|
}
|
|
}
|
|
|
|
if (! is_file($path) && class_exists($writerClass)) {
|
|
try {
|
|
if (class_exists($qrClass) && method_exists($qrClass, 'create')) {
|
|
$qr = $qrClass::create($inviteLink);
|
|
} else {
|
|
$qr = class_exists($qrClass) ? new $qrClass($inviteLink) : null;
|
|
}
|
|
if ($qr) {
|
|
if (method_exists($qr, 'setSize')) {
|
|
$qr->setSize(500);
|
|
}
|
|
if (method_exists($qr, 'setMargin')) {
|
|
$qr->setMargin(2);
|
|
}
|
|
$writer = new $writerClass;
|
|
$result = $writer->write($qr);
|
|
if (method_exists($result, 'saveToFile')) {
|
|
$result->saveToFile($path);
|
|
} else {
|
|
$raw = method_exists($result, 'getString') ? $result->getString() : (string) $result;
|
|
file_put_contents($path, $raw);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$lastErr = 'Endroid v3/v4: '.$e->getMessage();
|
|
}
|
|
}
|
|
|
|
if (! is_file($path) && class_exists($chlQRCode) && class_exists($chlQROptions)) {
|
|
try {
|
|
$optsArr = [
|
|
'version' => 5,
|
|
'outputType' => constant($chlQRCode.'::OUTPUT_IMAGE_PNG'),
|
|
'scale' => 6,
|
|
'imageBase64' => false,
|
|
'quietzoneSize' => 2,
|
|
];
|
|
if (class_exists($chlQRMatrix) && defined($chlQRMatrix.'::ECC_H')) {
|
|
$optsArr['eccLevel'] = constant($chlQRMatrix.'::ECC_H');
|
|
}
|
|
|
|
$opts = new $chlQROptions($optsArr);
|
|
$qrcode = new $chlQRCode($opts);
|
|
$png = $qrcode->render($inviteLink);
|
|
file_put_contents($path, $png);
|
|
} catch (\Throwable $e) {
|
|
$lastErr = 'chillerlan: '.$e->getMessage();
|
|
}
|
|
}
|
|
|
|
if (! is_file($path) && $lastErr) {
|
|
Log::error('QR generation failed', ['error' => $lastErr]);
|
|
}
|
|
}
|
|
|
|
if (is_file($path)) {
|
|
$data = @file_get_contents($path);
|
|
if ($data !== false) {
|
|
return 'data:image/png;base64,'.base64_encode($data);
|
|
}
|
|
}
|
|
|
|
return $this->qrFallback($inviteLink);
|
|
}
|
|
|
|
private function qrFallback(string $inviteLink): string
|
|
{
|
|
$link = rawurlencode($inviteLink);
|
|
|
|
return 'https://api.qrserver.com/v1/create-qr-code/?size=260x260&qzone=2&data='.$link;
|
|
}
|
|
}
|