mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-12 11:11:31 +00:00
Build modified streamline images
The official image from streamline does not currently work for the arm64 platform. As a temporary measure, the source code and docker build scripts have been lifted from the official images and are used to build locally. Some additional modifications are made to reduce overall image size, these are documented in docker/README.md
This commit is contained in:
+776
@@ -0,0 +1,776 @@
|
||||
<?php
|
||||
|
||||
namespace Knp\Snappy;
|
||||
|
||||
use Knp\Snappy\Exception\FileAlreadyExistsException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Exception;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base generator class for medias.
|
||||
*
|
||||
* @author Matthieu Bontemps <matthieu.bontemps@knplabs.com>
|
||||
* @author Antoine Hérault <antoine.herault@knplabs.com>
|
||||
*/
|
||||
abstract class AbstractGenerator implements GeneratorInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
protected const ALLOWED_PROTOCOLS = ['file'];
|
||||
|
||||
protected const WINDOWS_LOCAL_FILENAME_REGEX = '/^[a-z]:(?:[\\\\\/]?(?:[\w\s!#()-]+|[\.]{1,2})+)*[\\\\\/]?/i';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $temporaryFiles = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $temporaryFolder;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private $binary;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* @var null|array
|
||||
*/
|
||||
private $env;
|
||||
|
||||
/**
|
||||
* @var null|int
|
||||
*/
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $defaultExtension;
|
||||
|
||||
/**
|
||||
* @param null|string $binary
|
||||
* @param array $options
|
||||
* @param null|array $env
|
||||
*/
|
||||
public function __construct($binary, array $options = [], array $env = null)
|
||||
{
|
||||
$this->configure();
|
||||
|
||||
$this->setBinary($binary);
|
||||
$this->setOptions($options);
|
||||
$this->env = empty($env) ? null : $env;
|
||||
|
||||
if (\is_callable([$this, 'removeTemporaryFiles'])) {
|
||||
\register_shutdown_function([$this, 'removeTemporaryFiles']);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->removeTemporaryFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default extension.
|
||||
* Useful when letting Snappy deal with file creation.
|
||||
*
|
||||
* @param string $defaultExtension
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefaultExtension($defaultExtension)
|
||||
{
|
||||
$this->defaultExtension = $defaultExtension;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultExtension(): string
|
||||
{
|
||||
return $this->defaultExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option. Be aware that option values are NOT validated and that
|
||||
* it is your responsibility to validate user inputs.
|
||||
*
|
||||
* @param string $name The option to set
|
||||
* @param mixed $value The value (NULL to unset)
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOption($name, $value)
|
||||
{
|
||||
if (!\array_key_exists($name, $this->options)) {
|
||||
throw new InvalidArgumentException(\sprintf('The option \'%s\' does not exist.', $name));
|
||||
}
|
||||
|
||||
$this->options[$name] = $value;
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug(\sprintf('Set option "%s".', $name), ['value' => $value]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout.
|
||||
*
|
||||
* @param null|int $timeout The timeout to set
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of options.
|
||||
*
|
||||
* @param array $options An associative array of options as name/value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
foreach ($options as $name => $value) {
|
||||
$this->setOption($name, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate($input, $output, array $options = [], $overwrite = false)
|
||||
{
|
||||
$this->prepareOutput($output, $overwrite);
|
||||
|
||||
$command = $this->getCommand($input, $output, $options);
|
||||
|
||||
$inputFiles = \is_array($input) ? \implode('", "', $input) : $input;
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info(\sprintf('Generate from file(s) "%s" to file "%s".', $inputFiles, $output), [
|
||||
'command' => $command,
|
||||
'env' => $this->env,
|
||||
'timeout' => $this->timeout,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
list($status, $stdout, $stderr) = $this->executeCommand($command);
|
||||
$this->checkProcessStatus($status, $stdout, $stderr, $command);
|
||||
$this->checkOutput($output, $command);
|
||||
} catch (Exception $e) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->error(\sprintf('An error happened while generating "%s".', $output), [
|
||||
'command' => $command,
|
||||
'status' => $status ?? null,
|
||||
'stdout' => $stdout ?? null,
|
||||
'stderr' => $stderr ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info(\sprintf('File "%s" has been successfully generated.', $output), [
|
||||
'command' => $command,
|
||||
'stdout' => $stdout,
|
||||
'stderr' => $stderr,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateFromHtml($html, $output, array $options = [], $overwrite = false)
|
||||
{
|
||||
$fileNames = [];
|
||||
if (\is_array($html)) {
|
||||
foreach ($html as $htmlInput) {
|
||||
$fileNames[] = $this->createTemporaryFile($htmlInput, 'html');
|
||||
}
|
||||
} else {
|
||||
$fileNames[] = $this->createTemporaryFile($html, 'html');
|
||||
}
|
||||
|
||||
$this->generate($fileNames, $output, $options, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOutput($input, array $options = [])
|
||||
{
|
||||
$filename = $this->createTemporaryFile(null, $this->getDefaultExtension());
|
||||
|
||||
$this->generate($input, $filename, $options);
|
||||
|
||||
return $this->getFileContents($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOutputFromHtml($html, array $options = [])
|
||||
{
|
||||
$fileNames = [];
|
||||
if (\is_array($html)) {
|
||||
foreach ($html as $htmlInput) {
|
||||
$fileNames[] = $this->createTemporaryFile($htmlInput, 'html');
|
||||
}
|
||||
} else {
|
||||
$fileNames[] = $this->createTemporaryFile($html, 'html');
|
||||
}
|
||||
|
||||
return $this->getOutput($fileNames, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the binary.
|
||||
*
|
||||
* @param null|string $binary The path/name of the binary
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBinary($binary)
|
||||
{
|
||||
$this->binary = $binary;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getBinary()
|
||||
{
|
||||
return $this->binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command for the given input and output files.
|
||||
*
|
||||
* @param array|string $input The input file
|
||||
* @param string $output The ouput file
|
||||
* @param array $options An optional array of options that will be used
|
||||
* only for this command
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand($input, $output, array $options = [])
|
||||
{
|
||||
if (null === $this->binary) {
|
||||
throw new LogicException('You must define a binary prior to conversion.');
|
||||
}
|
||||
|
||||
$options = $this->mergeOptions($options);
|
||||
|
||||
return $this->buildCommand($this->binary, $input, $output, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all temporary files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeTemporaryFiles()
|
||||
{
|
||||
foreach ($this->temporaryFiles as $file) {
|
||||
$this->unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TemporaryFolder.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemporaryFolder()
|
||||
{
|
||||
if ($this->temporaryFolder === null) {
|
||||
return \sys_get_temp_dir();
|
||||
}
|
||||
|
||||
return $this->temporaryFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set temporaryFolder.
|
||||
*
|
||||
* @param string $temporaryFolder
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTemporaryFolder($temporaryFolder)
|
||||
{
|
||||
$this->temporaryFolder = $temporaryFolder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all options to their initial values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetOptions()
|
||||
{
|
||||
$this->options = [];
|
||||
$this->configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must configure the media options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @see AbstractGenerator::addOption()
|
||||
*/
|
||||
abstract protected function configure();
|
||||
|
||||
/**
|
||||
* Adds an option.
|
||||
*
|
||||
* @param string $name The name
|
||||
* @param mixed $default An optional default value
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function addOption($name, $default = null)
|
||||
{
|
||||
if (\array_key_exists($name, $this->options)) {
|
||||
throw new InvalidArgumentException(\sprintf('The option \'%s\' already exists.', $name));
|
||||
}
|
||||
|
||||
$this->options[$name] = $default;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an array of options.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function addOptions(array $options)
|
||||
{
|
||||
foreach ($options as $name => $default) {
|
||||
$this->addOption($name, $default);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given array of options to the instance options and returns
|
||||
* the result options array. It does NOT change the instance options.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function mergeOptions(array $options)
|
||||
{
|
||||
$mergedOptions = $this->options;
|
||||
|
||||
foreach ($options as $name => $value) {
|
||||
if (!\array_key_exists($name, $mergedOptions)) {
|
||||
throw new InvalidArgumentException(\sprintf('The option \'%s\' does not exist.', $name));
|
||||
}
|
||||
|
||||
$mergedOptions[$name] = $value;
|
||||
}
|
||||
|
||||
return $mergedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the specified output.
|
||||
*
|
||||
* @param string $output The output filename
|
||||
* @param string $command The generation command
|
||||
*
|
||||
* @throws RuntimeException if the output file generation failed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkOutput($output, $command)
|
||||
{
|
||||
// the output file must exist
|
||||
if (!$this->fileExists($output)) {
|
||||
throw new RuntimeException(\sprintf('The file \'%s\' was not created (command: %s).', $output, $command));
|
||||
}
|
||||
|
||||
// the output file must not be empty
|
||||
if (0 === $this->filesize($output)) {
|
||||
throw new RuntimeException(\sprintf('The file \'%s\' was created but is empty (command: %s).', $output, $command));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the process return status.
|
||||
*
|
||||
* @param int $status The exit status code
|
||||
* @param string $stdout The stdout content
|
||||
* @param string $stderr The stderr content
|
||||
* @param string $command The run command
|
||||
*
|
||||
* @throws RuntimeException if the output file generation failed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkProcessStatus($status, $stdout, $stderr, $command)
|
||||
{
|
||||
if (0 !== $status && '' !== $stderr) {
|
||||
throw new RuntimeException(\sprintf('The exit status code \'%s\' says something went wrong:' . "\n" . 'stderr: "%s"' . "\n" . 'stdout: "%s"' . "\n" . 'command: %s.', $status, $stderr, $stdout, $command), $status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary file.
|
||||
* The file is not created if the $content argument is null.
|
||||
*
|
||||
* @param null|string $content Optional content for the temporary file
|
||||
* @param null|string $extension An optional extension for the filename
|
||||
*
|
||||
* @return string The filename
|
||||
*/
|
||||
protected function createTemporaryFile($content = null, $extension = null)
|
||||
{
|
||||
$dir = \rtrim($this->getTemporaryFolder(), \DIRECTORY_SEPARATOR);
|
||||
|
||||
if (!\is_dir($dir)) {
|
||||
if (false === @\mkdir($dir, 0777, true) && !\is_dir($dir)) {
|
||||
throw new RuntimeException(\sprintf("Unable to create directory: %s\n", $dir));
|
||||
}
|
||||
} elseif (!\is_writable($dir)) {
|
||||
throw new RuntimeException(\sprintf("Unable to write in directory: %s\n", $dir));
|
||||
}
|
||||
|
||||
$filename = $dir . \DIRECTORY_SEPARATOR . \uniqid('knp_snappy', true);
|
||||
|
||||
if (null !== $extension) {
|
||||
$filename .= '.' . $extension;
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
\file_put_contents($filename, $content);
|
||||
}
|
||||
|
||||
$this->temporaryFiles[] = $filename;
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the command string.
|
||||
*
|
||||
* @param string $binary The binary path/name
|
||||
* @param array|string $input Url(s) or file location(s) of the page(s) to process
|
||||
* @param string $output File location to the image-to-be
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildCommand($binary, $input, $output, array $options = [])
|
||||
{
|
||||
$command = $binary;
|
||||
$escapedBinary = \escapeshellarg($binary);
|
||||
if (\is_executable($escapedBinary)) {
|
||||
$command = $escapedBinary;
|
||||
}
|
||||
|
||||
foreach ($options as $key => $option) {
|
||||
if (null !== $option && false !== $option) {
|
||||
if (true === $option) {
|
||||
// Dont't put '--' if option is 'toc'.
|
||||
if ($key === 'toc') {
|
||||
$command .= ' ' . $key;
|
||||
} else {
|
||||
$command .= ' --' . $key;
|
||||
}
|
||||
} elseif (\is_array($option)) {
|
||||
if ($this->isAssociativeArray($option)) {
|
||||
foreach ($option as $k => $v) {
|
||||
$command .= ' --' . $key . ' ' . \escapeshellarg($k) . ' ' . \escapeshellarg($v);
|
||||
}
|
||||
} else {
|
||||
foreach ($option as $v) {
|
||||
$command .= ' --' . $key . ' ' . \escapeshellarg($v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Dont't add '--' if option is "cover" or "toc".
|
||||
if (\in_array($key, ['toc', 'cover'])) {
|
||||
$command .= ' ' . $key . ' ' . \escapeshellarg($option);
|
||||
} elseif (\in_array($key, ['image-dpi', 'image-quality'])) {
|
||||
$command .= ' --' . $key . ' ' . (int) $option;
|
||||
} else {
|
||||
$command .= ' --' . $key . ' ' . \escapeshellarg($option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($input)) {
|
||||
foreach ($input as $i) {
|
||||
$command .= ' ' . \escapeshellarg($i) . ' ';
|
||||
}
|
||||
$command .= \escapeshellarg($output);
|
||||
} else {
|
||||
$command .= ' ' . \escapeshellarg($input) . ' ' . \escapeshellarg($output);
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the array is an associative array
|
||||
* and not an indexed array.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAssociativeArray(array $array)
|
||||
{
|
||||
return (bool) \count(\array_filter(\array_keys($array), 'is_string'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given command via shell and returns the complete output as
|
||||
* a string.
|
||||
*
|
||||
* @param string $command
|
||||
*
|
||||
* @return array [status, stdout, stderr]
|
||||
*/
|
||||
protected function executeCommand($command)
|
||||
{
|
||||
if (\method_exists(Process::class, 'fromShellCommandline')) {
|
||||
$process = Process::fromShellCommandline($command, null, $this->env);
|
||||
} else {
|
||||
$process = new Process($command, null, $this->env);
|
||||
}
|
||||
|
||||
if (null !== $this->timeout) {
|
||||
$process->setTimeout($this->timeout);
|
||||
}
|
||||
|
||||
$process->run();
|
||||
|
||||
return [
|
||||
$process->getExitCode(),
|
||||
$process->getOutput(),
|
||||
$process->getErrorOutput(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the specified output.
|
||||
*
|
||||
* @param string $filename The output filename
|
||||
* @param bool $overwrite Whether to overwrite the file if it already
|
||||
* exist
|
||||
*
|
||||
* @throws FileAlreadyExistsException
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareOutput($filename, $overwrite)
|
||||
{
|
||||
if (!$this->isProtocolAllowed($filename)) {
|
||||
throw new InvalidArgumentException(\sprintf('The output file scheme is not supported. Expected one of [\'%s\'].', \implode('\', \'', self::ALLOWED_PROTOCOLS)));
|
||||
}
|
||||
|
||||
$directory = \dirname($filename);
|
||||
|
||||
if ($this->fileExists($filename)) {
|
||||
if (!$this->isFile($filename)) {
|
||||
throw new InvalidArgumentException(\sprintf('The output file \'%s\' already exists and it is a %s.', $filename, $this->isDir($filename) ? 'directory' : 'link'));
|
||||
}
|
||||
if (false === $overwrite) {
|
||||
throw new FileAlreadyExistsException(\sprintf('The output file \'%s\' already exists.', $filename));
|
||||
}
|
||||
if (!$this->unlink($filename)) {
|
||||
throw new RuntimeException(\sprintf('Could not delete already existing output file \'%s\'.', $filename));
|
||||
}
|
||||
} elseif (!$this->isDir($directory) && !$this->mkdir($directory)) {
|
||||
throw new RuntimeException(\sprintf('The output file\'s directory \'%s\' could not be created.', $directory));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the given filename has a supported protocol.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isProtocolAllowed($filename)
|
||||
{
|
||||
if (false === $parsedFilename = \parse_url($filename)) {
|
||||
throw new InvalidArgumentException('The filename is not valid.');
|
||||
}
|
||||
|
||||
$protocol = isset($parsedFilename['scheme']) ? \mb_strtolower($parsedFilename['scheme']) : 'file';
|
||||
|
||||
if (
|
||||
\PHP_OS_FAMILY === 'Windows'
|
||||
&& \strlen($protocol) === 1
|
||||
&& \preg_match(self::WINDOWS_LOCAL_FILENAME_REGEX, $filename)
|
||||
) {
|
||||
$protocol = 'file';
|
||||
}
|
||||
|
||||
return \in_array($protocol, self::ALLOWED_PROTOCOLS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_get_contents" function.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFileContents($filename)
|
||||
{
|
||||
$fileContent = \file_get_contents($filename);
|
||||
|
||||
if (false === $fileContent) {
|
||||
throw new RuntimeException(\sprintf('Could not read file \'%s\' content.', $filename));
|
||||
}
|
||||
|
||||
return $fileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_exists" function.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function fileExists($filename)
|
||||
{
|
||||
return \file_exists($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_file" method.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFile($filename)
|
||||
{
|
||||
return \strlen($filename) <= \PHP_MAXPATHLEN && \is_file($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "filesize" function.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function filesize($filename)
|
||||
{
|
||||
$filesize = \filesize($filename);
|
||||
|
||||
if (false === $filesize) {
|
||||
throw new RuntimeException(\sprintf('Could not read file \'%s\' size.', $filename));
|
||||
}
|
||||
|
||||
return $filesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "unlink" function.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function unlink($filename)
|
||||
{
|
||||
return $this->fileExists($filename) ? \unlink($filename) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_dir" function.
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDir($filename)
|
||||
{
|
||||
return \is_dir($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the mkdir function.
|
||||
*
|
||||
* @param string $pathname
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function mkdir($pathname)
|
||||
{
|
||||
return \mkdir($pathname, 0777, true);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Knp\Snappy\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class FileAlreadyExistsException extends InvalidArgumentException
|
||||
{
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Knp\Snappy;
|
||||
|
||||
/**
|
||||
* Interface for the media generators.
|
||||
*
|
||||
* @author Matthieu Bontemps <matthieu.bontemps@knplabs.com>
|
||||
* @author Antoine Hérault <antoine.herault@knplabs.com>*
|
||||
*/
|
||||
interface GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generates the output media file from the specified input HTML file.
|
||||
*
|
||||
* @param array|string $input The input HTML filename or URL
|
||||
* @param string $output The output media filename
|
||||
* @param array $options An array of options for this generation only
|
||||
* @param bool $overwrite Overwrite the file if it exists. If not, throw a FileAlreadyExistsException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function generate($input, $output, array $options = [], $overwrite = false);
|
||||
|
||||
/**
|
||||
* Generates the output media file from the given HTML.
|
||||
*
|
||||
* @param array|string $html The HTML to be converted
|
||||
* @param string $output The output media filename
|
||||
* @param array $options An array of options for this generation only
|
||||
* @param bool $overwrite Overwrite the file if it exists. If not, throw a FileAlreadyExistsException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function generateFromHtml($html, $output, array $options = [], $overwrite = false);
|
||||
|
||||
/**
|
||||
* Returns the output of the media generated from the specified input HTML
|
||||
* file.
|
||||
*
|
||||
* @param array|string $input The input HTML filename or URL
|
||||
* @param array $options An array of options for this output only
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput($input, array $options = []);
|
||||
|
||||
/**
|
||||
* Returns the output of the media generated from the given HTML.
|
||||
*
|
||||
* @param array|string $html The HTML to be converted
|
||||
* @param array $options An array of options for this output only
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutputFromHtml($html, array $options = []);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Knp\Snappy;
|
||||
|
||||
/**
|
||||
* Use this class to create a snapshot / thumbnail from a HTML page.
|
||||
*
|
||||
* @author Matthieu Bontemps <matthieu.bontemps@knplabs.com>
|
||||
* @author Antoine Hérault <antoine.herault@knplabs.com>
|
||||
*/
|
||||
class Image extends AbstractGenerator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($binary = null, array $options = [], array $env = null)
|
||||
{
|
||||
$this->setDefaultExtension('jpg');
|
||||
|
||||
parent::__construct($binary, $options, $env);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOptions([
|
||||
'allow' => null, // Allow the file or files from the specified folder to be loaded (repeatable)
|
||||
'bypass-proxy-for' => null, // Bypass proxy for host (repeatable)
|
||||
'cache-dir' => null, // Web cache directory
|
||||
'checkbox-checked-svg' => null, // Use this SVG file when rendering checked checkboxes
|
||||
'checked-svg' => null, // Use this SVG file when rendering unchecked checkboxes
|
||||
'cookie' => [], // Set an additional cookie (repeatable)
|
||||
'cookie-jar' => null, // Read and write cookies from and to the supplied cookie jar file
|
||||
'crop-h' => null, // Set height for cropping
|
||||
'crop-w' => null, // Set width for cropping
|
||||
'crop-x' => null, // Set x coordinate for cropping (default 0)
|
||||
'crop-y' => null, // Set y coordinate for cropping (default 0)
|
||||
'custom-header' => [], // Set an additional HTTP header (repeatable)
|
||||
'custom-header-propagation' => null, // Add HTTP headers specified by --custom-header for each resource request.
|
||||
'no-custom-header-propagation' => null, // Do not add HTTP headers specified by --custom-header for each resource request.
|
||||
'debug-javascript' => null, // Show javascript debugging output
|
||||
'no-debug-javascript' => null, // Do not show javascript debugging output (default)
|
||||
'encoding' => null, // Set the default text encoding, for input
|
||||
'format' => $this->getDefaultExtension(), // Output format
|
||||
'height' => null, // Set screen height (default is calculated from page content) (default 0)
|
||||
'images' => null, // Do load or print images (default)
|
||||
'no-images' => null, // Do not load or print images
|
||||
'disable-javascript' => null, // Do not allow web pages to run javascript
|
||||
'enable-javascript' => null, // Do allow web pages to run javascript (default)
|
||||
'javascript-delay' => null, // Wait some milliseconds for javascript finish (default 200)
|
||||
'load-error-handling' => null, // Specify how to handle pages that fail to load: abort, ignore or skip (default abort)
|
||||
'load-media-error-handling' => null, // Specify how to handle media files that fail to load: abort, ignore or skip (default ignore)
|
||||
'disable-local-file-access' => null, // Do not allowed conversion of a local file to read in other local files, unless explicitly allowed with allow
|
||||
'enable-local-file-access' => null, // Allowed conversion of a local file to read in other local files. (default)
|
||||
'minimum-font-size' => null, // Minimum font size
|
||||
'password' => null, // HTTP Authentication password
|
||||
'disable-plugins' => null, // Disable installed plugins (default)
|
||||
'enable-plugins' => null, // Enable installed plugins (plugins will likely not work)
|
||||
'post' => [], // Add an additional post field
|
||||
'post-file' => [], // Post an additional file
|
||||
'proxy' => null, // Use a proxy
|
||||
'quality' => null, // Output image quality (between 0 and 100) (default 94)
|
||||
'quiet' => null, // Be less verbose
|
||||
'radiobutton-checked-svg' => null, // Use this SVG file when rendering checked radio-buttons
|
||||
'radiobutton-svg' => null, // Use this SVG file when rendering unchecked radio-buttons
|
||||
'run-script' => null, // Run this additional javascript after the page is done loading (repeatable)
|
||||
'disable-smart-width' => null, // Use the specified width even if it is not large enough for the content
|
||||
'enable-smart-width' => null, // Extend --width to fit unbreakable content (default)
|
||||
'stop-slow-scripts' => null, // Stop slow running javascript
|
||||
'no-stop-slow-scripts' => null, // Do not stop slow running javascript (default)
|
||||
'transparent' => null, // Make the background transparent in pngs *
|
||||
'use-xserver' => null, // Use the X server (some plugins and other stuff might not work without X11)
|
||||
'user-style-sheet' => null, // Specify a user style sheet, to load with every page
|
||||
'username' => null, // HTTP Authentication username
|
||||
'width' => null, // Set screen width (default is 1024)
|
||||
'window-status' => null, // Wait until window.status is equal to this string before rendering page
|
||||
'zoom' => null, // Use this zoom factor (default 1)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Knp\Snappy;
|
||||
|
||||
/**
|
||||
* Use this class to transform a html/a url to a pdf.
|
||||
*
|
||||
* @author Matthieu Bontemps <matthieu.bontemps@knplabs.com>
|
||||
* @author Antoine Hérault <antoine.herault@knplabs.com>
|
||||
*/
|
||||
class Pdf extends AbstractGenerator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $optionsWithContentCheck = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($binary = null, array $options = [], array $env = null)
|
||||
{
|
||||
$this->setDefaultExtension('pdf');
|
||||
$this->setOptionsWithContentCheck();
|
||||
|
||||
parent::__construct($binary, $options, $env);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate($input, $output, array $options = [], $overwrite = false)
|
||||
{
|
||||
$options = $this->handleOptions($this->mergeOptions($options));
|
||||
|
||||
parent::generate($input, $output, $options, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle options to transform HTML strings into temporary files containing HTML.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array $options Transformed options
|
||||
*/
|
||||
protected function handleOptions(array $options = [])
|
||||
{
|
||||
foreach ($options as $option => $value) {
|
||||
if (null === $value) {
|
||||
unset($options[$option]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($value) && \array_key_exists($option, $this->optionsWithContentCheck)) {
|
||||
$saveToTempFile = !$this->isFile($value) && !$this->isOptionUrl($value);
|
||||
$fetchUrlContent = $option === 'xsl-style-sheet' && $this->isOptionUrl($value);
|
||||
|
||||
if ($saveToTempFile || $fetchUrlContent) {
|
||||
$fileContent = $fetchUrlContent ? \file_get_contents($value) : $value;
|
||||
$options[$option] = $this->createTemporaryFile($fileContent, $this->optionsWithContentCheck[$option]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert option content or url to file if it is needed.
|
||||
*
|
||||
* @param mixed $option
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isOptionUrl($option)
|
||||
{
|
||||
return (bool) \filter_var($option, \FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOptions([
|
||||
// Global options
|
||||
'collate' => null,
|
||||
'no-collate' => null,
|
||||
'cookie-jar' => null,
|
||||
'copies' => null,
|
||||
'dpi' => null,
|
||||
'extended-help' => null,
|
||||
'grayscale' => null,
|
||||
'help' => null,
|
||||
'htmldoc' => null,
|
||||
'ignore-load-errors' => null, // old v0.9
|
||||
'image-dpi' => null,
|
||||
'image-quality' => null,
|
||||
'license' => null,
|
||||
'log-level' => null,
|
||||
'lowquality' => true,
|
||||
'manpage' => null,
|
||||
'margin-bottom' => null,
|
||||
'margin-left' => null,
|
||||
'margin-right' => null,
|
||||
'margin-top' => null,
|
||||
'orientation' => null,
|
||||
'page-height' => null,
|
||||
'page-size' => null,
|
||||
'page-width' => null,
|
||||
'no-pdf-compression' => null,
|
||||
'quiet' => null,
|
||||
'read-args-from-stdin' => null,
|
||||
'readme' => null,
|
||||
'title' => null,
|
||||
'use-xserver' => null,
|
||||
'version' => null,
|
||||
// Outline options
|
||||
'dump-default-toc-xsl' => null,
|
||||
'dump-outline' => null,
|
||||
'outline' => null,
|
||||
'no-outline' => null,
|
||||
'outline-depth' => null,
|
||||
'output-format' => null,
|
||||
// Page options
|
||||
'allow' => null,
|
||||
'background' => null,
|
||||
'no-background' => null,
|
||||
'bypass-proxy-for' => null,
|
||||
'cache-dir' => null,
|
||||
'checkbox-checked-svg' => null,
|
||||
'checkbox-svg' => null,
|
||||
'cookie' => null,
|
||||
'custom-header' => null,
|
||||
'custom-header-propagation' => null,
|
||||
'no-custom-header-propagation' => null,
|
||||
'debug-javascript' => null,
|
||||
'no-debug-javascript' => null,
|
||||
'default-header' => null,
|
||||
'encoding' => null,
|
||||
'disable-external-links' => null,
|
||||
'enable-external-links' => null,
|
||||
'disable-forms' => null,
|
||||
'enable-forms' => null,
|
||||
'images' => null,
|
||||
'no-images' => null,
|
||||
'disable-internal-links' => null,
|
||||
'enable-internal-links' => null,
|
||||
'disable-javascript' => null,
|
||||
'enable-javascript' => null,
|
||||
'javascript-delay' => null,
|
||||
'keep-relative-links' => null,
|
||||
'load-error-handling' => null,
|
||||
'load-media-error-handling' => null,
|
||||
'disable-local-file-access' => null,
|
||||
'enable-local-file-access' => null,
|
||||
'minimum-font-size' => null,
|
||||
'exclude-from-outline' => null,
|
||||
'include-in-outline' => null,
|
||||
'page-offset' => null,
|
||||
'password' => null,
|
||||
'disable-plugins' => null,
|
||||
'enable-plugins' => null,
|
||||
'post' => null,
|
||||
'post-file' => null,
|
||||
'print-media-type' => null,
|
||||
'no-print-media-type' => null,
|
||||
'proxy' => null,
|
||||
'proxy-hostname-lookup' => null,
|
||||
'radiobutton-checked-svg' => null,
|
||||
'radiobutton-svg' => null,
|
||||
'redirect-delay' => null, // old v0.9
|
||||
'resolve-relative-links' => null,
|
||||
'run-script' => null,
|
||||
'disable-smart-shrinking' => null,
|
||||
'enable-smart-shrinking' => null,
|
||||
'ssl-crt-path' => null,
|
||||
'ssl-key-password' => null,
|
||||
'ssl-key-path' => null,
|
||||
'stop-slow-scripts' => null,
|
||||
'no-stop-slow-scripts' => null,
|
||||
'disable-toc-back-links' => null,
|
||||
'enable-toc-back-links' => null,
|
||||
'user-style-sheet' => null,
|
||||
'username' => null,
|
||||
'viewport-size' => null,
|
||||
'window-status' => null,
|
||||
'zoom' => null,
|
||||
// Headers and footer options
|
||||
'footer-center' => null,
|
||||
'footer-font-name' => null,
|
||||
'footer-font-size' => null,
|
||||
'footer-html' => null,
|
||||
'footer-left' => null,
|
||||
'footer-line' => null,
|
||||
'no-footer-line' => null,
|
||||
'footer-right' => null,
|
||||
'footer-spacing' => null,
|
||||
'header-center' => null,
|
||||
'header-font-name' => null,
|
||||
'header-font-size' => null,
|
||||
'header-html' => null,
|
||||
'header-left' => null,
|
||||
'header-line' => null,
|
||||
'no-header-line' => null,
|
||||
'header-right' => null,
|
||||
'header-spacing' => null,
|
||||
'replace' => null,
|
||||
// Cover object
|
||||
'cover' => null,
|
||||
// TOC object
|
||||
'toc' => null,
|
||||
// TOC options
|
||||
'disable-dotted-lines' => null,
|
||||
'toc-depth' => null, // old v0.9
|
||||
'toc-font-name' => null, // old v0.9
|
||||
'toc-l1-font-size' => null, // old v0.9
|
||||
'toc-header-text' => null,
|
||||
'toc-header-font-name' => null, // old v0.9
|
||||
'toc-header-font-size' => null, // old v0.9
|
||||
'toc-level-indentation' => null,
|
||||
'disable-toc-links' => null,
|
||||
'toc-text-size-shrink' => null,
|
||||
'xsl-style-sheet' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array with options which require to store the content of the option before passing it to wkhtmltopdf.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setOptionsWithContentCheck()
|
||||
{
|
||||
$this->optionsWithContentCheck = [
|
||||
'header-html' => 'html',
|
||||
'footer-html' => 'html',
|
||||
'cover' => 'html',
|
||||
'xsl-style-sheet' => 'xsl',
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user