Очень часто внутри CRM/ERP-систем используется генератор DOC/PDF-файлов по шаблону, будь то счета-фактуры, акты приёма-передачи или целиком договоры с подписью и печатью. Чтобы дать клиенту возможность самому делать шаблоны документов и вставлять туда всякое (например, скан печати или подписи), я давно привык использовать PHPOffice/PHPWord вместе с его замечательным механизмом TemplateProcessor.
Суть его в том, что в DOCX-файле мы настраиваем переменные вида ${Variable}, а в PHP-скрипте пишем конструкцию типа
$templateProcessor->setValue('Variable', 'Value');
Чуть сложнее дела обстоят с табличными данными, но можно при желании разобраться. Вот цикл:
$i=1; foreach($jobs as $job) { $outputjobs[]=Array( 'Number' => $i, 'Name' => html_entity_decode($job['name']), 'Qty' => $job['qty'], 'Units' => $job['units'], 'Price' => $job['price'], 'Total' => $job['total'], ); $i++; } $templateProcessor->cloneRowAndSetValues('Line1_Number', $outputjobs);
А в DOC-файле размечаем следующим образом:
Обратите внимание на название первой переменной (№), оно такое же, как и значение в cloneRowAndSetValues.
Если у нас таблица пустая, то нет смысла её выводить. В таком случае заключим в DOC-файле таблицу в тэг-контейнер ${isTable} … ${/isTable}, а в PHP-скрипте проверим на пустоту переменную, и получится следующий кусок кода:
if(!empty($jobs)) { // таблица не пустая $templateProcessor->cloneBlock('isTable', 1); $templateProcessor->cloneRowAndSetValues('Line1_Number', $outputjobs); } else { // таблица пустая, не показываем $templateProcessor->cloneBlock('isTable',0,true,true); }
Это всё и многое другое есть в официальной документации PHPOffice. Чего там нет, так это подробной информации о создании PDF. Сам PHPOffice исповедует подход создания PDF-файлов через промежуточную генерацию в HTML, т.е. переводим DOC в HTML, а затем в PDF. Идея не самая плохая, особенно когда мы имеем возможность указать стили (css stylesheet) для PDF-файлов. Но поскольку у нас шаблоны, которые вообще проходят мимо нас, такая идея не совсем нам подходит.
Ещё один вариант — использовать dompdf в паре с PHPOffice, они отлично спарены и выдают нормальные документы. Но, к сожалению, вопрос с кириллицей там я решить не смог. В комплекте с dompdf идёт юникод-шрифт DejaVu, и можно конечно попросить клиентов писать документы именно в нём, но это ламерство. Можно попробовать доставить и привязать все возможные варианты шрифтов (Arial, Tahoma и т.п.) внутри dompdf, но у меня не вышло. Необходимо переводить их в формат afm и ешё что-то, я забил.
Вместо этого, поскольку у меня есть root-доступ и сервер мой, я решил передать генерацию PDF-файлов на сторону нормального ПО, а именно — компоненту unoconv внутри LibreOffice. Всё нижеперечисленное работает в Ubuntu 12.
Идём в консоль сервера. Если есть ISPManager, в нём есть Shell-клиент. Если вы под Windows 10, запустите Bash для Ubuntu.
Ставим unoconv:
apt-get install unoconv
Запускаем по инструкции и видим ошибку типа Office probably died. Unsupported URL: «type detection failed». Ставим LibreOffice:
apt-get install libreoffice
Переходим в директорию и запускаем:
doc2pdf word.docx
Видим массу каких-то ошибок, но — ура! — файл PDF создался в той же директории и с тем же названием, что и DOC!
Теперь нужно это всё настроить через PHP:
shell_exec('doc2pdf '.$the_file);
Ничего не работает. Скорее всего, LibreOffice стоит для root-пользователя, а PHP запускается из-под другого. Нужно запустить команду через sudo, для этого добавим в sudo пользователя, из-под которого работает PHP. Я использовал достаточно примитивный путь: открыл файл etc/sudoers и дописал вниз такое:
www-data ALL=(ALL) NOPASSWD: ALL
Разумеется, нормальный юникс-админ сделал бы по-другому, но у меня не было ни знаний, ни времени.
Также мы заметили, что наш PDF-файл генерируется с владельцем root, что в дальнейшем осложнит манипуляции с ним (удаление через PHP-скрипт, например). Поэтому в нашу команду запихаем ещё и изменение пользователя файла. Итоговая команда в PHP выглядит так:
chdir($dir); // эта команда нужна, если файлы создаются в другой директории, нежели PHP-скрипт shell_exec('sudo doc2pdf '.$the_name.'.docx'; sudo chown www-data '.$the_name.'.pdf');
Надеюсь, это сэкономит кому-то время.