View source
<?php
namespace Drupal\wkhtmltopdf\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Config\ConfigFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
class WkhtmltopdfController extends ControllerBase {
public $settings;
public $request;
public $filesystem;
public $logger;
public function __construct(ConfigFactory $config, Request $request, FileSystemInterface $filesystem, LoggerChannelInterface $logger) {
$this->settings = $config
->get('wkhtmltopdf.settings');
$this->request = $request;
$this->filesystem = $filesystem;
$this->logger = $logger;
}
public static function create(ContainerInterface $container) {
return new static($container
->get('config.factory'), $container
->get('request_stack')
->getCurrentRequest(), $container
->get('file_system'), $container
->get('logger.factory')
->get('wkhtmltopdf'));
}
public function generatePdf() {
$url = $this->request->query
->get('url');
$drupal_file_path = $this
->createFile($url);
$filename = $this
->generateFilename($url);
$file_response = $this
->getFileResponse($drupal_file_path, $filename);
return $file_response;
}
protected function getFileResponse($drupal_file_path, $filename) {
$force_download = $this->settings
->get('wkhtmltopdf_download');
$headers = [
'Content-Type' => 'application/pdf',
];
if ($force_download) {
$headers['Content-Disposition'] = 'attachment;filename="' . $filename . '"';
}
$fileResponse = new BinaryFileResponse($drupal_file_path, 200, $headers, true);
$fileResponse
->deleteFileAfterSend(TRUE);
return $fileResponse;
}
protected function createFile($url) {
$wkhtmltopdf_directory = 'public://wkhtmltopdf';
if (!$this->filesystem
->prepareDirectory($wkhtmltopdf_directory, FileSystemInterface::CREATE_DIRECTORY)) {
$this->logger
->error('wkhtmltopdf subdirectory could not be created in the public filesystem');
throw new HttpException(500, 'An error occurred generating the pdf.');
}
$filename = uniqid('wkhtmltopdf_', TRUE) . '.pdf';
$drupal_file_path = $wkhtmltopdf_directory . '/' . $filename;
$command = $this
->buildShellCommand($url, $drupal_file_path);
$lock = \Drupal::lock();
if ($lock
->acquire(__FILE__) !== FALSE) {
shell_exec($command);
$lock
->release(__FILE__);
}
else {
while ($lock
->acquire(__FILE__) === FALSE) {
$lock
->wait(__FILE__, 3);
}
if ($lock
->acquire(__FILE__) !== FALSE) {
shell_exec($command);
$lock
->release(__FILE__);
}
}
return $drupal_file_path;
}
protected function buildShellCommand($url, $drupal_file_path) {
$binary = $this->settings
->get('wkhtmltopdf_bin');
if (empty($binary)) {
$this->logger
->error('wkhtmltopdf binary path has not been set in the configuration');
throw new HttpException(500, 'An error occurred generating the pdf.');
}
$physical_file_path = $this->filesystem
->realpath($drupal_file_path);
$arguments = $this
->getCommandLineArguments();
$command = $binary;
foreach ($arguments as $argument => $argument_value) {
if (empty($argument_value)) {
$command .= ' --' . $argument;
}
else {
$command .= ' --' . $argument . ' ' . escapeshellarg($argument_value);
}
}
$command .= ' ' . $url . ' ' . $physical_file_path;
return $command;
}
protected function getCommandLineArguments() {
$arguments = [];
if ($this->settings
->get('wkhtmltopdf_zoom')) {
$arguments['zoom'] = $this->settings
->get('wkhtmltopdf_zoom');
}
if ($this->settings
->get('wkhtmltopdf_printmedia')) {
$arguments['print-media-type'] = '';
}
$this
->validateArguments($arguments);
return $arguments;
}
private function validateArguments($arguments) {
foreach ($arguments as $argument_name => $argument_value) {
$this
->validateArgument($argument_name, $argument_value);
}
}
private function validateArgument($argument_name, $argument_value) {
$validArguments = [
'allow',
'background',
'bypass-proxy-for',
'cache-dir',
'checkbox-checked-svg',
'checkbox-svg',
'collate',
'cookie',
'cookie-jar',
'copies',
'custom-header',
'custom-header-propagation',
'debug-javascript',
'default-header',
'disable-dotted-lines',
'disable-external-links',
'disable-forms',
'disable-internal-links',
'disable-javascript',
'disable-local-file-access',
'disable-plugins',
'disable-smart-shrinking',
'disable-toc-back-links',
'disable-toc-links',
'dpi',
'dump-default-toc-xsl',
'dump-outline',
'enable-external-links',
'enable-forms',
'enable-internal-links',
'enable-javascript',
'enable-local-file-access',
'enable-plugins',
'enable-smart-shrinking',
'enable-toc-back-links',
'encoding',
'exclude-from-outline',
'extended-help',
'footer-center',
'footer-font-name',
'footer-font-size',
'footer-html',
'footer-left',
'footer-line',
'footer-right',
'footer-spacing',
'grayscale',
'header-center',
'header-font-name',
'header-font-size',
'header-html',
'header-left',
'header-line',
'header-right',
'header-spacing',
'help',
'htmldoc',
'image-dpi',
'image-quality',
'images',
'include-in-outline',
'javascript-delay',
'keep-relative-links',
'license',
'load-error-handling',
'load-media-error-handling',
'log-level',
'lowquality',
'manpage',
'margin-bottom',
'margin-left',
'margin-right',
'margin-top',
'minimum-font-size',
'no-background',
'no-collate',
'no-custom-header-propagation',
'no-debug-javascript',
'no-footer-line',
'no-header-line',
'no-images',
'no-outline',
'no-pdf-compression',
'no-print-media-type',
'no-stop-slow-scripts',
'orientation',
'outline',
'outline-depth',
'page-height',
'page-offset',
'page-size',
'page-width',
'password',
'post',
'post-file',
'print-media-type',
'proxy',
'proxy-hostname-lookup',
'quiet',
'radiobutton-checked-svg',
'radiobutton-svg',
'read-args-from-stdin',
'readme',
'replace',
'resolve-relative-links',
'run-script',
'ssl-crt-path',
'ssl-key-password',
'ssl-key-path',
'stop-slow-scripts',
'title',
'toc-header-text',
'toc-level-indentation',
'toc-text-size-shrink',
'top',
'username',
'user-style-sheet',
'use-xserver',
'version',
'viewport-size',
'window-status',
'xsl-style-sheet',
'zoom',
];
if (!in_array($argument_name, $validArguments, TRUE)) {
$this->logger
->error('An invalid argument was passed to the wkhtmltopdf binary: %argument_name', [
'%argument_name' => $argument_name,
]);
throw new HttpException(500, 'An error occurred generating the pdf.');
}
}
protected function generateFilename($url) {
$filename = trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $url), '_') . '.pdf';
if (strlen($filename) > 255) {
$filename = substr($filename, -255, 255);
}
return $filename;
}
}