Нужно автоматически формировать счета, накладные или договоры? Раньше это требовало сложных библиотек, мучительной вёрстки в коде и головной боли с кириллицей. С платформой Wepps вы создаёте PDF так же просто, как обычную HTML-страницу — прямо в Smarty-шаблонах.
Возможности:
- ✅ Вывод в браузер — клиент видит документ мгновенно
- ✅ Скачивание файла — сохранение с кастомным именем
- ✅ Отправка по почте — документ сразу в письме
- ✅ Хранение на сервере — архив всех сформированных документов
- ✅ Smarty-шаблоны — верстаете как обычную страницу
- ✅ CSS-позиционирование — точная вёрстка таблиц и блоков
- ✅ UTF-8 из коробки — любые языки (кириллица, иероглифы, арабская вязь)
Давайте разберёмся, как работает система генерации PDF в Wepps и создадим первый документ.
Как это работает: от шаблона до PDF
Представьте, что PDF — это снимок веб-страницы:
- Smarty-шаблон — вы пишете HTML с переменными, как обычную страницу сайта
- CSS-стили — оформляете документ: шрифты, таблицы, отступы
- PHP-класс — подставляет данные из базы, вызывает шаблоны
- mPDF-библиотека — преобразует HTML+CSS в PDF-файл
- Результат — готовый документ в браузере, на почте или на диске
Суть в том, что вы работаете с привычными технологиями веб-разработки, а платформа сама превращает это в PDF.
О библиотеке mPDF
mPDF подключена через Composer — это базовый принцип работы со всеми сторонними библиотеками в Wepps:
# В файле packages/composer.json
"require": {
"mpdf/mpdf": "^8.0"
}
Преимущества использования Composer:
- 🔒 Безопасность — автоматическое получение обновлений безопасности
- 🔄 Актуальность — легкое обновление до последних стабильных версий
- 📦 Зависимости — автоматическое управление всеми зависимостями библиотеки
- ✅ Стабильность — проверенные версии из официального репозитория
- 🚀 Простота — одна команда
composer updateобновляет все библиотеки
Это касается не только mPDF, но и всех других библиотек платформы: Smarty, PhpSpreadsheet, Curl и других сторонних компонентов.
Архитектура системы: из чего состоит PDF-генератор
В Wepps для работы с PDF используется расширение в папке:
packages/WeppsExtensions/Addons/Docs/Pdf/
Структура файлов:
Pdf/
├── Pdf.php # Основной класс генерации
├── Request.php # Точка входа (вызов из браузера)
├── Pdf.css # Базовые стили для всех документов
├── PdfHeader.tpl # Шаблон верхнего колонтитула
├── PdfFooter.tpl # Шаблон нижнего колонтитула
├── PdfInvoice.tpl # Шаблон счёта на оплату
├── PdfInvoice.css # Стили для счёта
├── PdfOrder.tpl # Шаблон заказа
├── PdfOrder.css # Стили для заказа
├── PdfReceipt.tpl # Шаблон квитанции
└── PdfReceipt.css # Стили для квитанции
Как это работает вместе:
- Клиент запрашивает PDF:
/ext/Docs/Pdf/Request.php?action=Order&id=a3f9b2c(ID хешируется для безопасности) - Request.php создаёт объект класса
Pdfи передаёт параметры - Pdf.php определяет тип документа, загружает данные из БД
- Smarty рендерит шаблоны с подстановкой переменных
- mPDF превращает HTML+CSS в PDF и выводит в браузер
Примечание: В реальных проектах ID хешируется для защиты от перебора. Также можно настроить красивые URL через .htaccess: /pdf/order/a3f9b2c
Класс Pdf: сердце системы
Основной класс находится в Pdf.php. Вот упрощённая структура:
namespace WeppsExtensions\Addons\Docs\Pdf;
use WeppsCore\Smarty;
use WeppsCore\Data;
use WeppsCore\TextTransforms;
class Pdf {
private $output; // Содержимое документа (HTML)
private $css; // Стили документа
private $header; // Верхний колонтитул
private $footer; // Нижний колонтитул
private $filename; // Имя файла для скачивания
function __construct($get) {
// 1. Определяем тип документа (Order, Invoice, Receipt)
$action = $get['action'];
$id = $get['id'];
// 2. Загружаем базовые элементы
$smarty = Smarty::getSmarty();
$this->css = $smarty->fetch('Pdf.css');
$this->header = $smarty->fetch('PdfHeader.tpl');
$this->footer = $smarty->fetch('PdfFooter.tpl');
// 3. В зависимости от типа формируем содержимое
switch ($action) {
case "Order":
// Безопасно загружаем заказ из БД через setParams()
$obj = new Data("Orders");
$obj->setParams([$id]);
$order = $obj->fetch("Id=?")[0];
$smarty->assign('order', $order);
// Добавляем специфичные стили
$this->css .= $smarty->fetch('PdfOrder.css');
// Рендерим шаблон
$this->output = $smarty->fetch('PdfOrder.tpl');
// Имя файла при скачивании
$this->filename = "Заказ {$order['Id']}.pdf";
break;
case "Invoice":
// Аналогично для счёта
break;
}
}
function output($download = false) {
// Создаём PDF
$mpdf = new \Mpdf\Mpdf();
// Подключаем стили
$mpdf->WriteHTML($this->css, 1);
// Устанавливаем колонтитулы
$mpdf->SetHTMLHeader($this->header);
$mpdf->SetHTMLFooter($this->footer);
// Записываем содержимое
$mpdf->WriteHTML($this->output);
// Выводим в браузер или скачиваем
if ($download == false) {
$mpdf->Output(); // Показать в браузере
} else {
$mpdf->Output($this->filename, 'D'); // Скачать файл
}
}
}
Ключевые моменты:
- Конструктор загружает данные, рендерит шаблоны, собирает HTML
- Метод output() превращает HTML в PDF и отправляет клиенту
- Smarty-шаблоны — привычная работа с переменными и циклами
- CSS работает! Можете использовать обычные стили для оформления
Создание своего PDF-документа: пошаговое руководство
Создадим документ "Акт выполненных работ" с нуля.
Шаг 1. Создайте шаблон документа
Создайте файл PdfAct.tpl в папке Pdf/:
<div style="text-align: center; font-size: 16pt; font-weight: bold; margin-top: 20mm;">
АКТ ВЫПОЛНЕННЫХ РАБОТ
</div>
<div style="text-align: center; margin-bottom: 10mm;">
№ {$act.Id} от {$act.ActDate|date_format:"%d.%m.%Y"}
</div>
<table width="100%" style="margin-bottom: 5mm;">
<tr>
<td style="width: 30mm;"><strong>Исполнитель:</strong></td>
<td>{$shopInfo.Name}</td>
</tr>
<tr>
<td><strong>Заказчик:</strong></td>
<td>{$act.ClientName}</td>
</tr>
</table>
<table class="invoice_items" width="100%" cellpadding="3" cellspacing="0">
<thead>
<tr>
<th style="width: 15mm;">№</th>
<th>Наименование работ</th>
<th style="width: 25mm;">Кол-во</th>
<th style="width: 30mm;">Цена, руб.</th>
<th style="width: 30mm;">Сумма, руб.</th>
</tr>
</thead>
<tbody>
{foreach name="out" item="item" from=$actItems}
<tr>
<td align="center">{$smarty.foreach.out.iteration}</td>
<td>{$item.Name}</td>
<td align="right">{$item.Qty}</td>
<td align="right">{$item.Price|money}</td>
<td align="right">{$item.Summ|money}</td>
</tr>
{/foreach}
</tbody>
</table>
<table width="100%" style="margin-top: 5mm;">
<tr>
<td></td>
<td style="width: 30mm; font-weight: bold;">ИТОГО:</td>
<td style="width: 30mm; font-weight: bold; text-align: right;">
{$act.Total|money} руб.
</td>
</tr>
</table>
<div style="margin-top: 10mm;">
<p>Всего оказано услуг на сумму: {$actTotalLetter}</p>
</div>
<table width="100%" style="margin-top: 15mm;">
<tr>
<td width="50%">
<div>Исполнитель: _________________</div>
<div style="margin-top: 3mm;">({$shopInfo.DirectorName})</div>
</td>
<td width="50%">
<div>Заказчик: _________________</div>
<div style="margin-top: 3mm;">({$act.ClientName})</div>
</td>
</tr>
</table>
Шаг 2. Создайте стили документа
Файл PdfAct.css:
table.invoice_items {
border: 1px solid #000;
border-collapse: collapse;
}
table.invoice_items th,
table.invoice_items td {
border: 1px solid #000;
padding: 3mm;
}
table.invoice_items th {
background-color: #f0f0f0;
font-weight: bold;
}
Шаг 3. Добавьте обработку в класс Pdf
В Pdf.php добавьте case в конструктор:
switch ($action) {
// ... существующие case
case "Act":
// Оптимизированный способ: JOIN вместо трёх запросов
$sql = "
SELECT
a.*,
c.Name as ClientName,
c.Address as ClientAddress,
c.Phone as ClientPhone,
c.Email as ClientEmail
FROM ServiceActs a
LEFT JOIN Clients c ON c.Id = a.ClientId
WHERE a.Id = ?
";
$act = Connect::$instance->fetch($sql, [$id])[0];
// Загружаем позиции акта
$sql = "SELECT * FROM ServiceActItems WHERE ActId = ? ORDER BY Priority";
$actItems = Connect::$instance->fetch($sql, [$id]);
// Преобразуем сумму прописью
$actTotalLetter = TextTransforms::num2str($act['Total']);
// Передаём данные в шаблон
$smarty->assign('act', $act);
$smarty->assign('actItems', $actItems);
$smarty->assign('actTotalLetter', $actTotalLetter);
// Подключаем стили
$this->css .= $smarty->fetch('PdfAct.css');
// Рендерим шаблон
$this->output = $smarty->fetch('PdfAct.tpl');
// Имя файла
$this->filename = "Акт {$act['Id']}.pdf";
break;
}
Обратите внимание: вместо трёх запросов используется один JOIN. Данные клиента теперь доступны как $act['ClientName'], $act['ClientAddress'] и т.д.
Шаг 4. Используйте в коде
Вывод в браузере:
// URL: /ext/Docs/Pdf/Request.php?action=Act&id=f8e4a1b
// ID хешируется перед передачей в URL для безопасности
$obj = new Pdf($_REQUEST);
$obj->output(false); // false = показать в браузере
Скачивание файла:
// Реальный ID получаем из хеша
$realId = decryptId($_GET['id']); // Ваша функция дешифрования
$obj = new Pdf(['action' => 'Act', 'id' => $realId]);
$obj->output(true); // true = скачать с именем "Акт 15.pdf"
Отправка по почте:
use WeppsExtensions\Addons\Messages\Mail\Mail;
// Создаём PDF во временном файле
$orderId = 15; // Реальный ID из базы
$obj = new Pdf(['action' => 'Act', 'id' => $orderId]);
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML($obj->css, 1);
$mpdf->SetHTMLHeader($obj->header);
$mpdf->SetHTMLFooter($obj->footer);
$mpdf->WriteHTML($obj->output);
// Сохраняем временно
$tmpFile = '/tmp/act_15.pdf';
$mpdf->Output($tmpFile, 'F');
// Отправляем письмо
$mail = new Mail('html');
$mail->setAttach([$tmpFile]);
$mail->mail(
'client@example.com',
'Акт выполненных работ №15',
'<p>Направляем акт выполненных работ во вложении.</p>'
);
// Удаляем временный файл
unlink($tmpFile);
Вот и вся магия! Теперь у вас есть полноценный генератор PDF-документов.
Работа с шаблонами: колонтитулы и содержимое
Верхний колонтитул (PdfHeader.tpl)
Отображается на каждой странице сверху:
<div class="header">
<div class="img">
{if $shopInfo.Logo_FileUrl}
<img src="{$projectDev.protocol}{$projectDev.host}{$shopInfo.Logo_FileUrl}"
width="120"/>
{else}
<img src="{$projectDev.protocol}{$projectDev.host}{$projectInfo.logo}"
width="120"/>
{/if}
</div>
<div class="descr">
<div>{$shopInfo.Name2}</div>
<div>{$shopInfo.City}</div>
<div>{$shopInfo.Address}</div>
</div>
<div class="descr2">
<div>{$shopInfo.Phone}</div>
<div>{$shopInfo.Email}</div>
<div>{$shopInfo.Site}</div>
</div>
<hr/>
</div>
Нижний колонтитул (PdfFooter.tpl)
Отображается внизу каждой страницы:
<div class="footer">
<hr />
<div class="descr">{$shopInfo.Name2}</div>
<div class="descr2">Страница {literal}{PAGENO}{/literal}</div>
<div class="clear"></div>
</div>
Специальная переменная:
{literal}{PAGENO}{/literal}— автоматическая нумерация страниц
Базовые стили (Pdf.css)
Применяются ко всем документам:
body {
font-family: DejaVuSansCondensed, sans-serif;
font-size: 11pt;
}
h1 {
font-size: 26pt;
color: #000066;
border-top: 0.075cm solid #000000;
border-bottom: 0.075cm solid #000000;
}
h2 {
font-size: 12pt;
font-weight: bold;
text-transform: uppercase;
}
table {
border-collapse: collapse;
}
hr {
height: 1px;
color: #999999;
}
Полезные возможности mPDF
Размеры и отступы
// В конструкторе Pdf.php
$mpdf = new \Mpdf\Mpdf([
'format' => 'A4', // Формат: A4, A5, Letter
'orientation' => 'P', // P = Portrait, L = Landscape
'margin_left' => 15, // Левый отступ в мм
'margin_right' => 15, // Правый отступ в мм
'margin_top' => 25, // Верхний отступ в мм
'margin_bottom' => 25, // Нижний отступ в мм
'margin_header' => 10, // Отступ колонтитула сверху
'margin_footer' => 10 // Отступ колонтитула снизу
]);
Разрыв страницы
В шаблоне:
<div>Первая страница</div>
<pagebreak />
<div>Вторая страница</div>
Или через CSS:
h1 {
page-break-after: avoid; /* Не разрывать после заголовка */
}
table.invoice_items {
page-break-inside: avoid; /* Не разрывать таблицу */
}
Водяной знак
$mpdf->SetWatermarkText('КОПИЯ');
$mpdf->showWatermarkText = true;
$mpdf->watermarkTextAlpha = 0.1; // Прозрачность 0-1
Защита паролем
$mpdf->SetProtection([
'copy', // Запретить копирование
'print' // Запретить печать
], 'user_password', 'owner_password');
Безопасность: защита от SQL-инъекций
Важно! При работе с данными всегда используйте подготовленные запросы через метод setParams(). Это защищает от SQL-инъекций.
❌ Небезопасно (не делайте так):
// Прямая подстановка переменной в условие
$obj = new Data("ServiceActs");
$act = $obj->fetch("Id='{$id}'")[0];
// Уязвимость: если $id = "1' OR '1'='1", то можно получить все записи
✅ Безопасно (правильный способ):
// Используйте плейсхолдеры ? и setParams()
$obj = new Data("ServiceActs");
$obj->setParams([$id]); // Параметры передаются отдельно
$act = $obj->fetch("Id=?")[0]; // ? заменяется безопасно
// Для нескольких параметров
$obj = new Data("Orders");
$obj->setParams([$userId, $status]);
$orders = $obj->fetch("UserId=? AND Status=?");
Как это работает:
- Метод
setParams()принимает массив параметров - В условии
fetch()используются плейсхолдеры? - Платформа автоматически экранирует значения через PDO
- SQL-инъекции становятся невозможными
Все примеры ниже используют этот безопасный подход.
Оптимизация: один запрос вместо трех
В примере выше мы делали три отдельных запроса к БД. Это наглядно, но неэффективно. Если можно сделать одним запросом — делайте одним!
❌ Три запроса (медленнее):
// Запрос 1: Акт
$obj = new Data("ServiceActs");
$obj->setParams([$id]);
$act = $obj->fetch("Id=?")[0];
// Запрос 2: Позиции акта
$obj = new Data("ServiceActItems");
$obj->setParams([$id]);
$actItems = $obj->fetch("ActId=?");
// Запрос 3: Клиент
$obj = new Data("Clients");
$obj->setParams([$act['ClientId']]);
$client = $obj->fetch("Id=?")[0];
✅ Один запрос через JOIN (быстрее):
use WeppsCore\Connect;
// Получаем акт с клиентом одним запросом
$sql = "
SELECT
a.*,
c.Name as ClientName,
c.Address as ClientAddress,
c.Email as ClientEmail
FROM ServiceActs a
LEFT JOIN Clients c ON c.Id = a.ClientId
WHERE a.Id = ?
";
$act = Connect::$instance->fetch($sql, [$id])[0];
// Позиции акта отдельно (связь many-to-one)
$sql = "SELECT * FROM ServiceActItems WHERE ActId = ? ORDER BY Priority";
$actItems = Connect::$instance->fetch($sql, [$id]);
// Теперь $act содержит и данные акта, и данные клиента
$smarty->assign('act', $act);
$smarty->assign('actItems', $actItems);
Когда использовать Data, а когда Connect::$instance:
| Ситуация | Используйте | Причина |
|---|---|---|
| Простая выборка из одной таблицы | Data |
Удобно, читаемо, автоматическая пагинация |
| JOIN двух таблиц | Connect::$instance |
Быстрее, один запрос вместо нескольких |
| Сложные агрегации (SUM, COUNT, GROUP BY) | Connect::$instance |
Больше контроля над SQL |
| Массовые операции (обновление 1000+ записей) | Connect::$instance |
Меньше overhead |
| Нужна схема таблицы и автоматические связи | Data |
Работает через s_ConfigFields |
Золотое правило: если запрос можно написать одним JOIN — пишите через Connect::$instance->fetch($sql, $params). Это дешевле по производительности и надёжнее (меньше обращений к БД).
Практические сценарии
Сценарий 1: Генерация счёта для клиента
Задача: При оформлении заказа клиент должен получить счёт на оплату.
Решение:
-
В момент создания заказа формируем PDF:
$obj = new Pdf(['action' => 'Invoice', 'id' => $orderId]); $mpdf = new \Mpdf\Mpdf(); $mpdf->WriteHTML($obj->css, 1); $mpdf->WriteHTML($obj->output); $pdfFile = "/files/invoices/invoice_{$orderId}.pdf"; $mpdf->Output($pdfFile, 'F'); // Сохраняем на диск -
Отправляем письмо с вложением:
$mail = new Mail('html'); $mail->setAttach([$pdfFile]); $mail->mail( $order['Email'], "Счёт на оплату #{$order['Id']}", "<p>Ваш счёт во вложении. Оплатите в течение 3 дней.</p>" ); -
Добавляем кнопку "Скачать счёт" в личном кабинете:
{* ID хешируется для безопасности *} <a href="/ext/Docs/Pdf/Request.php?action=Invoice&id={$order.IdHash}" target="_blank"> Скачать счёт </a> {* Или через красивый URL, если настроен .htaccess: /pdf/invoice/{$order.IdHash} *}
Сценарий 2: Автоматическая отправка актов в конце месяца
Задача: Каждый месяц формировать акты для всех клиентов и отправлять.
Решение: Создайте задачу в Cron (или используйте системное расширение):
// packages/WeppsAdmin/ConfigExtensions/Processing/ActsGeneration.php
$obj = new Data("ServiceActs");
$acts = $obj->fetch("DATE_FORMAT(ActDate, '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m')");
foreach ($acts as $act) {
// Загружаем клиента для каждого акта
$objClient = new Data("Clients");
$objClient->setParams([$act['ClientId']]);
$client = $objClient->fetch("Id=?")[0];
// Генерируем PDF
$pdfObj = new Pdf(['action' => 'Act', 'id' => $act['Id']]);
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML($pdfObj->css, 1);
$mpdf->WriteHTML($pdfObj->output);
// Сохраняем
$file = "/files/acts/act_{$act['Id']}.pdf";
$mpdf->Output($file, 'F');
// Отправляем клиенту
$mail = new Mail('html');
$mail->setAttach([$file]);
$mail->mail(
$client['Email'],
"Акт выполненных работ за " . date('m.Y'),
"<p>Направляем акт выполненных работ.</p>"
);
}
Сценарий 3: Массовая печать накладных
Задача: Сформировать один PDF-файл со всеми накладными для печати.
Решение:
$mpdf = new \Mpdf\Mpdf();
// Загружаем заказы для печати
$objOrders = new Data("Orders");
$orders = $objOrders->fetch("Status='ready_to_print'", 1000, 1, "Id");
foreach ($orders as $key => $order) {
$obj = new Pdf(['action' => 'Order', 'id' => $order['Id']]);
if ($key > 0) {
$mpdf->AddPage(); // Новая страница для каждой накладной
}
$mpdf->WriteHTML($obj->css, 1);
$mpdf->WriteHTML($obj->output);
}
$mpdf->Output('Накладные_' . date('Y-m-d') . '.pdf', 'D');
Особенности вёрстки для PDF
Поддерживаемые CSS-свойства
✅ Работает:
width,height,margin,paddingborder,border-collapsefont-family,font-size,font-weight,colortext-align,vertical-alignbackground-color(для ячеек таблиц)page-break-before,page-break-after
❌ Не работает:
position: absolute/fixed(ограниченная поддержка)float(частично работает)flexbox,grid- CSS-анимации
- Сложные селекторы
Рекомендации по вёрстке
-
Используйте таблицы для макета:
<table width="100%"> <tr> <td width="50%">Левая колонка</td> <td width="50%">Правая колонка</td> </tr> </table> -
Указывайте единицы измерения в мм:
.header { height: 30mm; margin-bottom: 5mm; } -
Избегайте внешних ресурсов:
{* Плохо: внешняя картинка *} <img src="https://cdn.example.com/logo.png" />
{ Хорошо: локальная картинка с полным путём } <img src="{$projectDev.protocol}{$projectDev.host}/pic/logo.png" />
4. **Проверяйте разрывы страниц:**
```css
table.important {
page-break-inside: avoid; /* Не разрывать таблицу */
}
Работа с многоязычным контентом (UTF-8)
mPDF автоматически работает с UTF-8 кодировкой, что позволяет использовать любые языки мира:
// По умолчанию используется DejaVuSansCondensed с UTF-8
body {
font-family: DejaVuSansCondensed, sans-serif;
}
Важно: Платформа Wepps автоматически использует UTF-8 для всех документов:
- Шаблоны
.tplсохраняйте в UTF-8 - Данные из БД передаются в UTF-8
- mPDF корректно обрабатывает UTF-8 без дополнительных настроек
- Шрифт DejaVu поддерживает большинство языков: кириллица (русский, украинский, белорусский), латиница с диакритикой, греческий, арабский, иврит, китайские иероглифы и многие другие
Примеры многоязычных документов:
<p>Русский: Счёт на оплату №123</p>
<p>English: Invoice #123</p>
<p>Deutsch: Rechnung Nr. 123</p>
<p>中文: 发票 #123</p>
<p>العربية: فاتورة #123</p>
Всё работает из коробки — просто пишите текст на нужном языке в шаблоне.
Преобразование чисел прописью
Для финансовых документов используйте класс TextTransforms:
use WeppsCore\TextTransforms;
// 15432.50 → "пятнадцать тысяч четыреста тридцать два рубля 50 копеек"
$sumInWords = TextTransforms::num2str(15432.50);
// В шаблоне
$smarty->assign('totalInWords', $sumInWords);
<p>Итого к оплате: {$order.Total|money} руб.</p>
<p>Сумма прописью: {$totalInWords}</p>
Форматирование дат
// В шаблоне Smarty
{$order.Date|date_format:"%d.%m.%Y"} // 29.01.2026
{$order.Date|date_format:"%d %B %Y г."} // 29 января 2026 г.
Отладка и тестирование
Сохранение HTML для проверки
Перед генерацией PDF проверьте, как выглядит HTML:
$obj = new Pdf(['action' => 'Invoice', 'id' => 15]);
// Сохраните HTML в файл
file_put_contents('/tmp/invoice_debug.html', $obj->output);
// Откройте в браузере и проверьте вёрстку
Включение отладки ошибок
$mpdf = new \Mpdf\Mpdf();
$mpdf->showImageErrors = true; // Показывать ошибки картинок
$mpdf->debug = true; // Режим отладки
Проверка размера файла
Слишком большие PDF могут быть проблемой:
$mpdf->Output($filename, 'F');
$filesize = filesize($filename);
if ($filesize > 2 * 1024 * 1024) { // Более 2 МБ
// Оптимизируйте изображения или уменьшите качество
}
Лучшие практики
✅ Делайте так:
-
Используйте переменные проекта:
{$shopInfo.Name} {$projectInfo.email} {$projectDev.host} -
Создавайте общие компоненты:
{* Переиспользуемая таблица реквизитов *} {include file="PdfCompanyDetails.tpl"} -
Проверяйте данные перед генерацией:
if (!$order || empty($order)) { Exception::error404(); } -
Кэшируйте PDF если данные не меняются:
$pdfFile = "/files/invoices/invoice_{$orderId}.pdf"; if (!file_exists($pdfFile)) { // Генерируем только если файла нет $mpdf->Output($pdfFile, 'F'); }
❌ Не делайте так:
- Не генерируйте PDF на лету для больших объёмов:
// Плохо: генерация 1000 PDF в цикле foreach ($orders as $order) { $obj = new Pdf(['action' => 'Order', 'id' => $order['Id']]); $obj->output(); }
// Хорошо: генерируйте асинхронно и сохраняйте
2. **Не забывайте про права доступа:**
```php
// Проверяйте, что пользователь может видеть документ
if ($order['UserId'] != Connect::$projectData['user']['Id']) {
Exception::error(403);
}
- Не используйте сложную вёрстку:
/* Плохо */ .layout { display: grid; grid-template-columns: 1fr 1fr; }
/ Хорошо / <table width="100%"> <tr> <td width="50%">...</td> <td width="50%">...</td> </tr> </table>
## Интеграция с другими модулями
### Отправка через Telegram
```php
use WeppsExtensions\Addons\Messages\Telegram\Telegram;
// Генерируем PDF
$obj = new Pdf(['action' => 'Invoice', 'id' => $orderId]);
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML($obj->css, 1);
$mpdf->WriteHTML($obj->output);
$pdfFile = "/tmp/invoice_{$orderId}.pdf";
$mpdf->Output($pdfFile, 'F');
// Отправляем в Telegram
$tg = new Telegram();
$tg->sendDocument($chatId, $pdfFile, "Ваш счёт №{$orderId}");
unlink($pdfFile);
Архивирование документов
// Создайте системное расширение для архива документов
function archivePdf($type, $id, $pdfContent) {
$dir = "/files/archive/{$type}/" . date('Y/m');
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$filename = "{$dir}/{$type}_{$id}_" . date('Y-m-d_H-i-s') . ".pdf";
file_put_contents($filename, $pdfContent);
// Записываем в БД для индексации
Connect::$instance->insert('DocumentsArchive', [
'DocType' => $type,
'DocId' => $id,
'FilePath' => $filename,
'CreateDate' => date('Y-m-d H:i:s')
]);
}
Выводы
Генерация PDF в Wepps — это:
- 🚀 Быстро — создаёте шаблон как обычную страницу
- 🎨 Просто — верстаете в Smarty+CSS, без сложных библиотек
- 💪 Мощно — вывод в браузер, скачивание, отправка по почте
- 🔧 Гибко — полный контроль над структурой и стилями
- 🌍 Надёжно — отличная поддержка кириллицы
Начните с простого: скопируйте один из существующих шаблонов (Order, Invoice, Receipt), измените под свои данные, добавьте case в Pdf.php — и у вас готов собственный генератор документов!
Типичный путь разработки:
- Создайте
.tplфайл — скопируйте структуру из PdfOrder.tpl - Добавьте
.cssфайл — оформите таблицы и отступы - Добавьте case в конструктор Pdf.php — загрузите данные из БД
- Протестируйте через
/ext/Docs/Pdf/Request.php?action=YourDoc&id=1 - Интегрируйте в свой функционал — отправка писем, кнопки скачивания
Вперёд, создавайте красивые документы! 📄✨