update dockerfiles based on latest streamline image

The latest images incorporate all changes previously added by the dockerfiles.
The arm image however is still missing/unreliable from the registry and must be
built independently.
This commit is contained in:
2024-04-25 10:04:06 -07:00
parent 6896f639bd
commit d26252fc11
10177 changed files with 1212770 additions and 194 deletions
@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
@@ -0,0 +1,127 @@
name: Build
on:
pull_request: ~
push: ~
jobs:
check:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
- name: Validate composer.json
run: composer validate --strict --no-check-lock
cs-fixer:
runs-on: ubuntu-20.04
name: PHP-CS-Fixer
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- run: composer install --prefer-dist --no-interaction --no-progress --ansi
- run: vendor/bin/php-cs-fixer fix --diff --dry-run --verbose
tests:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
include:
- description: 'Symfony 7.1 DEV'
php: '8.2'
symfony: '7.1.*@dev'
- description: 'Symfony 7.0'
php: '8.3'
symfony: '7.0.*'
- description: 'Symfony 6.4'
php: '8.1'
symfony: '6.4.*'
- description: 'Symfony 6.0'
php: '8.3'
symfony: '6.0.*'
- description: 'Symfony 5.4'
php: '8.1'
symfony: '5.4.*'
- description: 'Symfony 5.0'
php: '8.3'
symfony: '5.0.*'
- description: 'Beta deps'
php: '8.1'
beta: true
name: PHP ${{ matrix.php }} tests (${{ matrix.description }})
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: ~/.composer/cache/files
key: composer-${{ matrix.php }}-${{ matrix.symfony }}-${{ matrix.composer_option }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- run: |
sed -ri 's/"symfony\/(.+)": "(.+)"/"symfony\/\1": "'${{ matrix.symfony }}'"/' composer.json;
if: matrix.symfony
- run: |
composer config minimum-stability dev
composer config prefer-stable true
if: matrix.beta
- name: remove cs-fixer for Symfony 7 (temporary as not-supported yet)
if: contains(matrix.symfony, '7.1.*@dev') || contains(matrix.symfony, '7.0.*')
run: |
composer remove --dev friendsofphp/php-cs-fixer pedrotroller/php-cs-custom-fixer --no-update
- run: composer update --prefer-dist --no-interaction --no-progress --ansi ${{ matrix.composer_option }}
- run: vendor/bin/phpunit
- run: vendor/bin/phpstan analyse --ansi --no-progress
tests-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
include:
- description: 'Symfony 7.1 DEV'
php: '8.2'
symfony: '7.1.*@dev'
- description: 'Symfony 7.0'
php: '8.3'
symfony: '7.0.*'
- description: 'Symfony 6.4'
php: '8.1'
symfony: '6.4.*'
- description: 'Symfony 5.4'
php: '8.1'
symfony: '5.4.*'
name: "[WINDOWS] PHP ${{ matrix.php }} tests (${{ matrix.description }})"
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: ~/.composer/cache/files
key: composer-${{ matrix.php }}-${{ matrix.symfony }}-${{ matrix.composer_option }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- run: |
(Get-Content composer.json) -replace '("symfony/[^"]+": )"[^"]+"', '$1"${{ matrix.symfony }}"' | Out-File -encoding ASCII composer.json
if: matrix.symfony
- run: |
composer config minimum-stability dev
composer config prefer-stable true
if: matrix.beta
- name: remove cs-fixer for Symfony 7 (temporary as not-supported yet)
if: contains(matrix.symfony, '7.1.*@dev') || contains(matrix.symfony, '7.0.*')
run: |
composer remove --dev friendsofphp/php-cs-fixer pedrotroller/php-cs-custom-fixer --no-update
- run: composer update --prefer-dist --no-interaction --no-progress --ansi ${{ matrix.composer_option }}
- run: vendor/bin/phpunit
- run: vendor/bin/phpstan analyse --ansi --no-progress
@@ -0,0 +1,45 @@
## 1.1.0
* Add bypass-proxy-for option added in 0.12.3 (see [#302](https://github.com/KnpLabs/snappy/pull/302))
* Fix symfony/process 4.2 deprecation notice (see [#331](https://github.com/KnpLabs/snappy/pull/331))
* Drop suppor for unmaintained PHP versions (5.6 and 7.0, see [#337](https://github.com/KnpLabs/snappy/pull/337)
* Drop support for unmaintained symfony/process versions (see [#337](https://github.com/KnpLabs/snappy/pull/337))
* Pass on error code in checkProcessStatus (see [#328](https://github.com/KnpLabs/snappy/pull/328))
Thanks to @joshpme, @drigani, @fbourigault, @NiR- and @leimd for their work.
## 1.0.4
* Support cache-dir for Image generation (see [#297](https://github.com/KnpLabs/snappy/pull/297)).
Thank you @dimitrilahaye for their work.
## 1.0.3
* Add support to Symfony 4 ([#290](https://github.com/KnpLabs/snappy/pull/290))
* Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase ([#287](https://github.com/KnpLabs/snappy/pull/287))
Credits go to @michaelperrin and @carusogabriel.
## 1.0.2
*A BC break was introduced in v1.0.0: using objects castable to string with a cyclic dependency to the generator
as option value would break `setOption()` / `setOptions()` methods.*
* Use logger context rather than `var_export` to log option values (see [#283](https://github.com/KnpLabs/snappy/pull/283))
Credits go to: @barryvdh.
## 1.0.1
* Fix `Call to a member function debug() on null` logger (see [#270](https://github.com/KnpLabs/snappy/pull/270))
## 1.0.0
* Don't check if it's a file when the path is bigger than `PHP_MAXPATHLEN` (see [#224](https://github.com/KnpLabs/snappy/pull/224))
* Pass `image-dpi` and `image-quality` options as integer (see [#251](https://github.com/KnpLabs/snappy/pull/251))
* Improve documentation readability (see [#255](https://github.com/KnpLabs/snappy/pull/255))
* Add logging capabilities to generators (see [#264](https://github.com/KnpLabs/snappy/pull/264))
* Add some more frequent questions/issues to the FAQ (see [#263](https://github.com/KnpLabs/snappy/pull/263), [#265](https://github.com/KnpLabs/snappy/pull/265), [#266](https://github.com/KnpLabs/snappy/pull/266))
Credits go to: @wouterbulten, @martinssipenko, @Herz3h, @akovalyov, @NiR-.
@@ -0,0 +1,64 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
## Contact
If you have any questions or feedback, [please ping us](https://twitter.com/KNPLabs) !
@@ -0,0 +1,85 @@
# Thanks for contributing!
:+1: First of all, thanks for contributing! The team is happy to help if you
have any questions. Have a look to this contributing guide and also to the
[FAQ section](https://github.com/KnpLabs/snappy/blob/master/doc/faq.md). :feet:
The following is a set of guidelines for contributing to Snappy, which is hosted
by the [KNP Labs Organization](https://github.com/KnpLabs) on GitHub. These are
mostly guidelines, not rules. Use your best judgment, and feel free to propose
changes to this document opening a pull request. :shipit:
## Code of Conduct
This project and everyone participating in it is governed by the following
[Code of Conduct](https://github.com/KnpLabs/snappy/blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code.
## Reporting a bug
#### Before submitting a bug
- Verify that you are using the latest Snappy version;
- Double-check the [documentation](https://github.com/KnpLabs/snappy/blob/master/README.md)
and the [FAQ section](https://github.com/KnpLabs/snappy/blob/master/doc/faq.md)
to see if you're not misusing the library;
- Check if the issue is a Snappy issue and not a wkhtmltopdf issue (see [how to](#how-to-verify-if-the-issue-is-a-snappy-issue));
- Check if the issue has already been reported. If it has and the issue is still
open, add a comment to the existing issue instead of opening a new one.
##### How to verify if the issue is a Snappy issue
In order to verify that the issue is a Snappy issue and not a wkhtmltopdf issue,
simply copy paste the command displayed in the error message in your command prompt.
If the same error appears on the command line, then it's a wkhtmltopdf issue and
you'll have more chance to resolve your issue [there](https://github.com/wkhtmltopdf/wkhtmltopdf/issues).
#### How to submit a (good) bug report
To report a Snappy bug please open a [GitHub issue](https://github.com/KnpLabs/snappy/issues)
following the rules below.
- Use a clear and descriptive title for the issue to identify the problem;
- Describe the steps needed to reproduce the bug including a code example when
possible;
- Give as much detail as possible about your environment (OS, PHP version,
Snappy configuration, ...);
## Suggesting enhancements
To suggest Snappy enhancements please open a [GitHub issue](https://github.com/KnpLabs/snappy/issues)
following the rules below.
- Use a clear and descriptive title for the issue to identify the problem;
- Provide a step-by-step description of the suggested enhancement in as many
details as possible;
- Explain why this enhancement would be useful with one or more use cases;
## Contributing to the code
A pull request, is the best way to provide a bug fix or to propose enhancements to Snappy.
When submitting a pull request please be sure to follow the same rules described
above in [Reporting a bug](#reporting-a-bug) and [Suggesting enhancements](suggesting-enhancements)
sections depending on the nature of your change.
> Before starting to work on a large change please open an issue to ask the
maintainers if they are fine with it (no one likes to work for nothing!).
1. Fork the repository
2. Once the repository has been forked clone it locally
```
git clone git@github.com:USERNAME/snappy.git
```
3. Create a new branch
```
git checkout -b BRANCH_NAME master
```
4. Code!!!
5. Add/Update tests (if needed)
6. Update documentation (if needed)
7. Run the tests and make sure that they are passing
```
composer unit-tests
composer static-analysis
```
8. Squash your commits
9. Rebase your branch on master and fix merge conflicts
10. Open the pull request
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010 Matthieu Bontemps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,169 @@
# Snappy
![Build Status](https://github.com/KnpLabs/snappy/actions/workflows/build.yaml/badge.svg)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/github/KnpLabs/snappy?branch=master&svg=true)](https://ci.appveyor.com/project/NiR-/snappy)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/KnpLabs/Gaufrette/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/KnpLabs/Gaufrette/?branch=master)
Snappy is a PHP library allowing thumbnail, snapshot or PDF generation from a url or a html page.
It uses the excellent webkit-based [wkhtmltopdf and wkhtmltoimage](http://wkhtmltopdf.org/)
available on OSX, linux, windows.
You will have to download wkhtmltopdf `0.12.x` in order to use Snappy.
Please, check [FAQ](doc/faq.md) before opening a new issue. Snappy is a tiny wrapper around wkhtmltox, so lots of issues are already answered, resolved or wkhtmltox ones.
Following integrations are available:
* [`knplabs/knp-snappy-bundle`](https://github.com/KnpLabs/KnpSnappyBundle), for Symfony
* [`barryvdh/laravel-snappy`](https://github.com/barryvdh/laravel-snappy), for Laravel
* [`mvlabs/mvlabs-snappy`](https://github.com/mvlabs/MvlabsSnappy), for Zend Framework
## Installation using [Composer](http://getcomposer.org/)
```bash
composer require knplabs/knp-snappy
```
## Usage
### Initialization
```php
<?php
require __DIR__ . '/vendor/autoload.php';
use Knp\Snappy\Pdf;
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
// or you can do it in two steps
$snappy = new Pdf();
$snappy->setBinary('/usr/local/bin/wkhtmltopdf');
```
### Display the pdf in the browser
```php
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
header('Content-Type: application/pdf');
echo $snappy->getOutput('http://www.github.com');
```
### Download the pdf from the browser
```php
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="file.pdf"');
echo $snappy->getOutput('http://www.github.com');
```
### Merge multiple urls into one pdf
```php
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="file.pdf"');
echo $snappy->getOutput(array('http://www.github.com','http://www.knplabs.com','http://www.php.net'));
```
### Generate local pdf file
```php
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
$snappy->generateFromHtml('<h1>Bill</h1><p>You owe me money, dude.</p>', '/tmp/bill-123.pdf');
```
### Pass options to snappy
```php
// Type wkhtmltopdf -H to see the list of options
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
$snappy->setOption('disable-javascript', true);
$snappy->setOption('no-background', true);
$snappy->setOption('allow', array('/path1', '/path2'));
$snappy->setOption('cookie', array('key' => 'value', 'key2' => 'value2'));
$snappy->setOption('post', array('key' => 'value'));
$snappy->setOption('cover', 'pathToCover.html');
// .. or pass a cover as html
$snappy->setOption('cover', '<h1>Bill cover</h1>');
$snappy->setOption('toc', true);
$snappy->setOption('cache-dir', '/path/to/cache/dir');
```
### Reset options
Options can be reset to their initial values with `resetOptions()` method.
```php
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
// Set some options
$snappy->setOption('copies' => 4);
// ..
// Reset options
$snappy->resetOptions();
```
## wkhtmltopdf binary as composer dependencies
If you want to download wkhtmltopdf and wkhtmltoimage with composer you add to `composer.json`:
```bash
composer require h4cc/wkhtmltopdf-i386 0.12.x
composer require h4cc/wkhtmltoimage-i386 0.12.x
```
or this if you are in 64 bit based system:
```bash
composer require h4cc/wkhtmltopdf-amd64 0.12.x
composer require h4cc/wkhtmltoimage-amd64 0.12.x
```
And then you can use it
```php
<?php
use Knp\Snappy\Pdf;
$myProjectDirectory = '/path/to/my/project';
$snappy = new Pdf($myProjectDirectory . '/vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386');
// or
$snappy = new Pdf($myProjectDirectory . '/vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64');
```
*N.B.* These static binaries are extracted from [Debian7 packages](https://github.com/h4cc/wkhtmltopdf-amd64/issues/13#issuecomment-150948179), so it might not be compatible with non-debian based linux distros
## Some use cases
If you want to generate table of contents and you want to use custom XSL stylesheet, do the following:
```php
<?php
$snappy = new Pdf('/path/to/binary');
$snappy->setOption('toc', true);
$snappy->setOption('xsl-style-sheet', 'http://path/to/stylesheet.xsl') //or local file;
$snappy->generateFromHtml('<p>Some content</p>', 'test.pdf');
```
## Bugs & Support
If you found a bug please fill a detailed issue with all the following points.
If you need some help, please at least provide a complete reproducer so we could help you based on facts rather than assumptions.
* OS and its version
* Wkhtmltopdf, its version and how you installed it
* A complete reproducer with relevant php and html/css/js code
If your reproducer is big, please try to shrink it. It will help everyone to narrow the bug.
## Maintainers
KNPLabs is looking for maintainers ([see why](https://knplabs.com/en/blog/news-for-our-foss-projects-maintenance)).
If you are interested, feel free to open a PR to ask to be added as a maintainer.
Well be glad to hear from you :)
## Credits
Snappy has been originally developed by the [KnpLabs](http://knplabs.com) team.
@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.4.x | :white_check_mark: |
| 1.3.x | :x: |
## Reporting a Vulnerability
You can send an email to <security@knplabs.com> if you spot any security issue in this library.
@@ -0,0 +1,53 @@
build: false
platform: x64
clone_folder: c:\projects\snappy
environment:
matrix:
- dependencies: lowest
php_ver_target: 7.1
- dependencies: current
php_ver_target: 7.2
- dependencies: highest
php_ver_target: 7.3
cache:
- composer.phar
- c:\ProgramData\chocolatey\bin -> appveyor.yml
- c:\ProgramData\chocolatey\lib -> appveyor.yml
- c:\php -> appveyor.yml
init:
- SET PATH=c:\Program Files\OpenSSL;c:\tools\php;%PATH%
- SET COMPOSER_NO_INTERACTION=1
- SET PHP=1
- SET ANSICON=121x90 (121x90)
install:
- IF EXIST c:\tools\php (SET PHP=0)
- ps: Set-Service wuauserv -StartupType Manual
# In order to be able to list all the avialable PHP packages we have to
# downgrade Chocolatey to version 0.10.13.
# See https://github.com/chocolatey/choco/issues/1843
- ps: choco install chocolatey -y --version 0.10.13 --allow-downgrade --force
- ps: choco search php --exact --all-versions -r
- ps: echo ((choco search php --exact --all-versions -r | select-string -pattern $env:php_ver_target | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','')
- ps: appveyor-retry cinst php --params '""/InstallDir:c:\tools\php""' --ignore-checksums -y --force --version ((choco search php --exact --all-versions -r | select-string -pattern $env:php_ver_target | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','')
- cd c:\tools\php
- IF %PHP%==1 copy php.ini-production php.ini /Y
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
- IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
- IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat
- appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar
- cd c:\projects\snappy
- IF %dependencies%==lowest appveyor-retry composer update --prefer-lowest --no-progress --profile -n
- IF %dependencies%==current appveyor-retry composer install --no-progress --profile
- IF %dependencies%==highest appveyor-retry composer update --no-progress --profile -n
- composer show
test_script:
- cd c:\projects\snappy
- vendor/bin/phpunit
@@ -0,0 +1,51 @@
{
"name": "knplabs/knp-snappy",
"type": "library",
"description": "PHP library allowing thumbnail, snapshot or PDF generation from a url or a html page. Wrapper for wkhtmltopdf/wkhtmltoimage.",
"keywords": ["pdf", "thumbnail", "snapshot", "knplabs", "knp", "wkhtmltopdf"],
"homepage": "http://github.com/KnpLabs/snappy",
"license": "MIT",
"authors": [
{
"name": "KNP Labs Team",
"homepage": "http://knplabs.com"
},
{
"name": "Symfony Community",
"homepage": "http://github.com/KnpLabs/snappy/contributors"
}
],
"require": {
"php": ">=8.1",
"symfony/process": "^5.0||^6.0||^7.0",
"psr/log": "^2.0||^3.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phpstan/phpstan": "^1.0.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"friendsofphp/php-cs-fixer": "^3.0",
"pedrotroller/php-cs-custom-fixer": "^2.19"
},
"autoload": {
"psr-4": {
"Knp\\Snappy\\": "src/Knp/Snappy"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"unit-tests": "vendor/bin/phpunit",
"static-analysis": "vendor/bin/phpstan analyse --ansi",
"check-cs": "vendor/bin/php-cs-fixer fix --diff --dry-run --verbose",
"fix-cs": "vendor/bin/php-cs-fixer fix --verbose"
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}
@@ -0,0 +1,179 @@
# Frequently asked questions
###### *Q*: It does not work and everything is broken.
*A*: Please, try to execute the command manually in your shell. Snappy is a thin PHP wrapper and most likely your issue is with wkhtmltopdf itself or is already described in this FAQ. If not, feel free to open the issue in Snappy issue tracker.
Please, note that wkhtmltopdf takes only input URL(s) or file name(s) as source.
###### *Q*: How to get the command executed by Snappy?
*A*: You need to install any PSR-3 compliant logging library and call `setLogger()` method on the generator. It will
log every command executed, its env vars and timeout. It will also log stdout and stderr whenever a command finishes, even if it fails.
###### *Q*: My tables are broken when it is rendered on multiple pages with break.
*A*: Add ```thead``` and ```tbody``` tags. Add the following css
```css
table { page-break-inside:auto; }
tr { page-break-inside:avoid; page-break-after:auto; }
thead { display:table-header-group; }
tfoot { display:table-footer-group; }
```
###### *Q*: I have a PNG with a transparent background. When generating a PDF, the background turns black.
*A*: It is wkhtmltopdf bug as described in https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2214. You should update wkhtmltopdf to at least 0.12.3-dev
###### *Q*: Is there a way to secure the pdf so it can't be edited?
*A*: There is no way to add a password via wkhtmltopdf, but there is a way via other linux tools like pdftk
###### *Q*: We are using wkhtmltopdf to export html to pdf. It breaks the HTML in two pages of pdf. Can we add a break?
*A*: It is known problem of `wkhtmltopdf`. You can use css `page-break-after`, like:
```html
<style type="text/css">
.page {
overflow: hidden;
page-break-after: always;
}
</style>
<div class="page">
new page
</div>
```
###### *Q*: It says `wkhtmltopdf: cannot connect to X server` or `xvfb-run: error: Xvfb failed to start.`
*A*: Please, check your `wkhtmltopdf` version. It is recommended to use at least `0.12.2.1` and what is important - starting from `wkhtmltopdf >= 0.12.2` it doesn't require X server or emulation anymore. You can download new version from http://wkhtmltopdf.org/downloads.html or install via composer for Linux servers as stated in [README](https://github.com/KnpLabs/snappy#wkhtmltopdf-binary-as-composer-dependencies). If there is no possibility to update `wkhtmltopdf`, please check http://stackoverflow.com/questions/9604625/wkhtmltopdf-cannot-connect-to-x-server
###### *Q*: PDF generation failed with wkhtmltopdf returning error code 1 due to ContentNotFoundError, how do I deal with that?
*A*: This is a known problem with wkhtmltopdf. Several issues has been raised: [issue 1855](https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1855), [issue 2051](https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2051). To catch that error, `generate` method will throw a `RuntimeException` with error code equals to the error code returned with wkhtmltopdf, catch this exception and check for the error code and then deal with this exception in appropriate ways.
###### *Q*: My PDF is always generated for a small screen resolution\I always receive a mobile version.
*A*: It is well-known issue of wkhtmltopdf, you can check https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1508. One of solutions is to use xvfb and to setup xvfb resolution to desired one though a simple bit of css such as `zoom: .75;` would be sufficient in most cases.
###### *Q*: My chars with accents in HTML document are not correctly rendered.
*A*: Make sure that you have set `<meta charset="UTF-8" />` in your HTML document, and you used the option `"encoding" => "utf-8"`.
###### *Q*: My document text is not correctly rendered, it is just black squares
*A*: Make sure you have installed `xfonts-base`, `xfonts-75dpi` and `urw-fonts`
###### *Q*: How to convert page with relative links from stdin / How to use relative media URLs
*A*: When you convert an HTML file with relative links/media URLs into PDF, you need to either:
* Switch to absolute links/media URLs
* Or use `<base></base>` tag to specify what's the base URL of those relative links.
###### *Q*: How to generate a single PDF from multiple sources?
*A*: Snappy and wkhtmltopdf both support generating a single PDF from multiple sources. To do so, you need to provide an array of input rather than a string.
```php
<?php
$pdf = new \Knp\Snappy\Pdf(__DIR__ . '/vendor/bin/wkhtmltopdf-amd64');
$pdf->generate(['https://google.com', 'https://google.jp'], '/tmp/out/test.pdf');
// or
$pdf->generateFromHtml(['<html><body>Doc 1</body></html>', '<html><body>Doc 2</body></html>'], '/tmp/out/test.pdf');
```
###### *Q*: My chars with accents passed to wkhtmltopdf options are not correctly rendered, i.e. `footer-right => 'Página [page] de [toPage]'` is converted to 'Página 1 de 1'.
*A*: The answer is long here. We use `escapeshellarg` function to escape all the option value passed to `wkhtmltox`. `escapeshellarg` makes its escape based on server locale, so if you are experiencing this issue - you can set
```php
setlocale(LC_CTYPE, 'es_ES.UTF-8')
```
or any locale which is suitable for you. You should take into account that if given locale is not configured on the server - you will still have an issue. Check your locales installed via running
```bash
locale -a
```
If the needed locale is missing on the server - you should install/configure it.
###### *Q*: How to put an header/footer on every page of the PDF?
*A*: You need to provide either a valid file path or some HTML content. Note that your HTML document(s) needs to start with a valid doctype and have html, head and body tags, or wkhtmltopdf will fail to render the PDF properly.
*Note that this feature does not work with wkhtmltopdf compiled against unpatched Qt. Most of the time, wkhtmltopdf packages from Linux distributions are not fine. You should rather rely on the
official version available on [wkhtmltopdf.org](https://wkhtmltopdf.org) or the version available from `h4cc/wkhtmltopdf` package.*
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$header = <<<HTML
<!DOCTYPE html>
<html>
<head><style type="text/css">p { color: #FF0000; }</style></head>
<body><p>Lorem ipsum</p></body>
</html>
HTML;
$footer = <<<HTML
<!DOCTYPE html>
<html>
<head><style type="text/css">p { color: #0000FF; }</style></head>
<body><p>Lorem ipsum</p></body>
</html>
HTML;
// Without html extension you might face following error:
// Exit with code 1, due to unknown error.
$footerPath = tempnam('/tmp', 'footer') . '.html';
file_put_contents($footerPath, $footer);
$pdf = new \Knp\Snappy\Pdf(__DIR__ . '/vendor/bin/wkhtmltopdf-amd64');
$pdf->generateFromHtml('', '/tmp/out/test.pdf', ['header-html' => $header, 'footer-html' => $footerPath], true);
```
###### *Q*: Is it possible to include an header and/or footer only on some specific pages?
*A*: No, wkhtmtopdf does not allow this.
###### *Q*: When running wkhtmltopdf through Snappy, I got an exit code 5 or 6
*A*: It's usually due to bad environment variables. For example, on MacOS, you need to check the value of `DYLD_LIBRARY_PATH` (see [#27](https://github.com/KnpLabs/snappy/issues/27#issuecomment-7199659)).
On Linux, you should check the value of `LD_LIBRARY_PATH`. Also note that, depending on the way you execute PHP, your environment variables might be reset for security reasons (for instance, look at `clear_env` on php-fpm).
###### *Q*: On Windows, when I generate a PDF nothing happens (there's no PDF file written)
*A*: You should check with sysinternals procmon if you experience `ACCESS_DENIED` error. If that's the case, you need to give execution permission to IIS users on wkhtmltopdf binary. Also, your user(s) should have write permissions on the temporary folder.
For more details see [#123](https://github.com/KnpLabs/snappy/issues/123).
###### *Q*: Snappy takes an endless amount of time to generate a PDF and eventually fails due to timeout
*A*: This is generally indicating some networking issues. It might be bad DNS record(s), some sporadic packet losses, unresponsive HTTP server ...
Note that if you use the PHP embedded server, you can't generate a PDF from an HTML page accessible from the same embedded server.
Indeed, the embedded server never forks and does not use threads. That means it's not able to process two requests
at the same time: it processes the first one, send the first response and only then starts to process the second one.
###### *Q*: How to proceed when experiencing `ContentNotFound`, `ConnectionRefusedError` or timeouts?
*A*: When you experience errors like `ContentNotFound` or `ConnectionRefusedError`, try to turn off `quiet` option and
look at Snappy logs (you have to set up a logger first).
If you experience timeouts, it might be hard to know what is failing. The best you can do to narrow the scope of the bug
is to slightly change your HTML code until you found the culprit. Start by removing whole parts, like document body to
know if it comes from something in the body or something in the head. If that's now working, re-add it but now remove
one half of its content. And repeat again and again until you find which URLs is buggy.
There's one more (better) way though: fire up tcpdump or wireshark and listen for http requests. You should see which
request(s) is failing, and you can even check the content of the request/response.
###### *Q*: My custom fonts aren't smooth
According to #326, you shall prefer using SVG versions of your custom fonts to have a better font smoothing.
@@ -0,0 +1,14 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
parameters:
level: 8
paths:
- src/
- tests/
inferPrivatePropertyTypeFromConstructor: true
reportUnmatchedIgnoredErrors: false
checkMissingIterableValueType: false
ignoreErrors:
- "#^Call to an undefined static method #"
- "#^Parameter \\#1 \\$command of class Symfony\\\\Component\\\\Process\\\\Process constructor expects array, string given\\.$#"
@@ -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);
}
}
@@ -0,0 +1,9 @@
<?php
namespace Knp\Snappy\Exception;
use InvalidArgumentException;
class FileAlreadyExistsException extends InvalidArgumentException
{
}
@@ -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;
}
}