, * vevent: VEvent * } $ctx * @return array{subject?:string, html:string, text:string} */ public static function render(array $ctx): array { $method = (string)($ctx['method'] ?? 'request'); $subject = (string)($ctx['subject'] ?? ''); $data = (array)($ctx['data'] ?? []); /** @var VEvent $vevent */ $vevent = $ctx['vevent']; // --- tokens (Figma) --- $W = 564; $border = '#D7D7D7'; $textMain = '#151515'; $textMuted = '#8A8A8A'; // --- brand/logo/icons --- $logoUrl = (string)($data['logo_url'] ?? 'https://forbion.f7cloud.ru/themes/forbion/images/logo_gorizontal.png'); $brand = (string)($data['brand_name'] ?? 'Forbion F7'); $baseIcons = (string)($data['icons_base'] ?? 'https://forbion.f7cloud.ru/themes/forbion/images/'); $icons = [ 'title' => (string)($data['icon_title'] ?? ($baseIcons . 'text.png')), 'when' => (string)($data['icon_when'] ?? ($baseIcons . 'date.png')), 'location' => (string)($data['icon_location'] ?? ($baseIcons . 'location.png')), 'description' => (string)($data['icon_description'] ?? ($baseIcons . 'notes.png')), 'link' => (string)($data['icon_link'] ?? ($baseIcons . 'text.png')), 'attendees' => (string)($data['icon_attendees'] ?? ($baseIcons . 'user.png')), ]; // --- main strings --- $invitee = (string)($data['invitee_name'] ?? ''); $title = (string)($data['meeting_title'] ?? ''); $topLine = match (strtolower($method)) { 'cancel' => "{$invitee} отменил(а) событие «{$title}»", 'reply' => "{$invitee} ответил(а) на приглашение «{$title}»", default => "{$invitee} приглашает вас принять участие в событии «{$title}»", }; $heading = match (strtolower($method)) { 'cancel' => 'Отмена встречи', 'reply' => 'Ответ на приглашение', default => 'Приглашение на встречу', }; // fields $when = (string)($data['meeting_when'] ?? ''); $locationText = (string)($data['meeting_location'] ?? ''); $locationHtml = (string)($data['meeting_location_html'] ?? htmlspecialchars($locationText, ENT_QUOTES)); $desc = (string)($data['meeting_description'] ?? ''); $linkText = (string)($data['meeting_url'] ?? ''); $linkHtml = (string)($data['meeting_url_html'] ?? htmlspecialchars($linkText, ENT_QUOTES)); // buttons $acceptUrl = (string)($data['accept_url'] ?? ''); $declineUrl = (string)($data['decline_url'] ?? ''); $moreUrl = (string)($data['more_url'] ?? ''); $people = self::peopleLines($vevent); $esc = static fn(string $s): string => htmlspecialchars($s, ENT_QUOTES); // --- fonts: exactly like your site (TTF) --- $fontCss = ' '; $logoHtml = $logoUrl ? ''.$esc($brand).'' : '
'.$esc($brand).'
'; // Row builder: 12px between items, 4px between label/value $row = function (string $iconUrl, string $label, string $valueHtml) use ($esc, $textMuted, $textMain): string { if (trim(strip_tags($valueHtml)) === '') { return ''; } $icon = $iconUrl ? '' : ''; return '
'.$icon.'
'.$esc($label).'
'.$valueHtml.'
'; }; $peopleHtml = $people ? implode('
', array_map([self::class, 'escNoLinkify'], $people)) : ''; // Buttons: height 36, radius 8, padding 8/12, min-width 107 $buttonsHtml = ''; if ($acceptUrl && $declineUrl) { $buttonsHtml = ' Принять Отклонить '; if ($moreUrl) { $buttonsHtml .= ' Ещё варианты '; } } // --- HTML --- // Required paddings: // inside card: top/bottom 32, sides 16 // logo -> topLine: 20 // topLine -> heading: 36 // heading -> list: 20 $html = ''.$fontCss.' '.$fontCss.'
'.$buttonsHtml.'
'; // --- TEXT --- $text = $topLine . "\n\n" . $heading . "\n" . "Название: {$title}\n" . ($when ? "Когда: {$when}\n" : '') . ($locationText ? "Местонахождение: {$locationText}\n" : '') . ($desc ? "Описание: {$desc}\n" : '') . ($linkText ? "Ссылка: {$linkText}\n" : ''); if ($people) { $text .= "\nКто:\n - " . implode("\n - ", $people) . "\n"; } if ($acceptUrl && $declineUrl) { $text .= "\nПринять: {$acceptUrl}\nОтклонить: {$declineUrl}\n"; if ($moreUrl) { $text .= "Ещё варианты: {$moreUrl}\n"; } } return ['subject' => $subject, 'html' => $html, 'text' => $text]; } /** @return string[] */ private static function peopleLines(VEvent $vevent): array { $out = []; if (isset($vevent->ORGANIZER)) { $org = $vevent->ORGANIZER; $orgEmail = self::mailtoToEmail((string)$org->getNormalizedValue()); $orgName = isset($org->CN) ? (string)$org->CN->getValue() : ''; $out[] = trim(($orgName ? $orgName.' ' : '').'<' . $orgEmail . '>') . ' (организатор)'; } foreach ($vevent->select('ATTENDEE') as $att) { $email = self::mailtoToEmail((string)$att->getNormalizedValue()); $name = isset($att['CN']) ? (string)$att['CN']->getValue() : ''; $line = trim(($name ? $name.' ' : '').'<' . $email . '>'); if ($line) { $out[] = $line; } } return array_values(array_unique($out)); } private static function mailtoToEmail(string $mailto): string { return str_starts_with($mailto, 'mailto:') ? substr($mailto, 7) : $mailto; } private static function escNoLinkify(string $s): string { $out = htmlspecialchars($s, ENT_QUOTES); $out = str_replace('@', '@', $out); $out = str_replace('.', '.', $out); return $out; } }