mirror of
https://github.com/vdm-io/tcpdf.git
synced 2024-11-25 05:57:32 +00:00
Update to v6.6.5
This commit is contained in:
parent
2e41063eba
commit
f815fbe5fc
6
.gitattributes
vendored
6
.gitattributes
vendored
@ -1,6 +0,0 @@
|
|||||||
/.github export-ignore
|
|
||||||
/.gitignore export-ignore
|
|
||||||
/.gitattributes export-ignore
|
|
||||||
/tests export-ignore
|
|
||||||
/scripts export-ignore
|
|
||||||
/phpstan.neon.dist export-ignore
|
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
.idea
|
|
||||||
.phpdoc
|
|
||||||
*.bak
|
|
||||||
/vendor/
|
|
||||||
/composer.lock
|
|
||||||
/composer.phar
|
|
@ -1,3 +1,23 @@
|
|||||||
|
6.6.5 (2023-09-06)
|
||||||
|
- Fix corrupted file.
|
||||||
|
|
||||||
|
6.6.4 (2023-09-06)
|
||||||
|
- Fix GitHub automation tests.
|
||||||
|
|
||||||
|
6.6.3 (2023-09-06)
|
||||||
|
- Fix SPDX license ID (#591)
|
||||||
|
- Fix warning "array offset on value of type null" (#620)
|
||||||
|
- Improve the README about the status of this library (#589)
|
||||||
|
- Fix deprecation warning with PHP 8.1 (#614)
|
||||||
|
- Fixes for PHP 8.2 in tcpdf_fonts.php (#632)
|
||||||
|
- Fix some php 8+ edge cases (#630)
|
||||||
|
- Fix composite glyph output (#581)
|
||||||
|
- Fix "access array offset on value of type bool" with PDF/A (#583)
|
||||||
|
- Fix non-numeric value warning (#627)
|
||||||
|
- Fix issues with S25 barcode (#611)
|
||||||
|
- Fix return type annotations (#613)
|
||||||
|
- Fix some inconsistencies in type hints (#598)
|
||||||
|
|
||||||
6.6.2 (2022-12-17)
|
6.6.2 (2022-12-17)
|
||||||
- Ensure pregSplit return type is always array.
|
- Ensure pregSplit return type is always array.
|
||||||
- Add ability to run tests on various operating systems (#566)
|
- Add ability to run tests on various operating systems (#566)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
published by the Free Software Foundation, either version 3 of the
|
published by the Free Software Foundation, either version 3 of the
|
||||||
License, or (at your option) any later version.
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
2002-2022 Nicola Asuni - Tecnick.com LTD
|
2002-2023 Nicola Asuni - Tecnick.com LTD
|
||||||
|
|
||||||
**********************************************************************
|
**********************************************************************
|
||||||
**********************************************************************
|
**********************************************************************
|
||||||
|
@ -5,6 +5,9 @@ __If you already know how to use TCPDF and you need it for a Joomla! project, th
|
|||||||
TCPDF is a PHP class for generating PDF files on-the-fly without requiring external extensions.
|
TCPDF is a PHP class for generating PDF files on-the-fly without requiring external extensions.
|
||||||
This library includes also a class to extract data from existing PDF documents and classes to generate 1D and 2D barcodes in various formats.
|
This library includes also a class to extract data from existing PDF documents and classes to generate 1D and 2D barcodes in various formats.
|
||||||
|
|
||||||
|
## NOTE
|
||||||
|
A new version of this library is under development at https://github.com/tecnickcom/tc-lib-pdf and as a consequence this library is in support only mode.
|
||||||
|
|
||||||
## Main Features:
|
## Main Features:
|
||||||
* no external libraries are required for the basic functions;
|
* no external libraries are required for the basic functions;
|
||||||
* all standard page formats, custom page formats, custom margins and units of measure;
|
* all standard page formats, custom page formats, custom margins and units of measure;
|
||||||
@ -63,7 +66,7 @@ https://packages.debian.org/source/stable/icc-profiles-free
|
|||||||
|
|
||||||
* **category** Library
|
* **category** Library
|
||||||
* **author** Nicola Asuni <info@tecnick.com>
|
* **author** Nicola Asuni <info@tecnick.com>
|
||||||
* **copyright** 2002-2022 Nicola Asuni - Tecnick.com LTD
|
* **copyright** 2002-2023 Nicola Asuni - Tecnick.com LTD
|
||||||
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
||||||
* **link** http://www.tcpdf.org
|
* **link** http://www.tcpdf.org
|
||||||
* **source** https://github.com/tecnickcom/TCPDF
|
* **source** https://github.com/tecnickcom/TCPDF
|
||||||
@ -72,7 +75,7 @@ https://packages.debian.org/source/stable/icc-profiles-free
|
|||||||
|
|
||||||
* Nicola Asuni <info@tecnick.com>
|
* Nicola Asuni <info@tecnick.com>
|
||||||
|
|
||||||
> Compiled into an installer for Joomla 3 by [Llewellyn van der Merwe](mailto:joomla@vdm.io) at [Vast Development Method](https://www.vdm.io/)
|
> Compiled into an installer for Joomla 3|4|5 by [Llewellyn van der Merwe](mailto:joomla@vdm.io) at [Vast Development Method](https://www.vdm.io/)
|
||||||
|
|
||||||
# Usage in Joomla (PHP)
|
# Usage in Joomla (PHP)
|
||||||
```php
|
```php
|
||||||
|
@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
* **category** Library
|
* **category** Library
|
||||||
* **author** Nicola Asuni <info@tecnick.com>
|
* **author** Nicola Asuni <info@tecnick.com>
|
||||||
* **copyright** 2002-2022 Nicola Asuni - Tecnick.com LTD
|
* **copyright** 2002-2023 Nicola Asuni - Tecnick.com LTD
|
||||||
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
||||||
* **link** http://www.tcpdf.org
|
* **link** http://www.tcpdf.org
|
||||||
* **source** https://github.com/tecnickcom/TCPDF
|
* **source** https://github.com/tecnickcom/TCPDF
|
||||||
|
|
||||||
|
|
||||||
## IMPORTANT
|
## NOTE
|
||||||
A new version of this library is under development at https://github.com/tecnickcom/tc-lib-pdf and as a consequence this version will not receive any additional development or support.
|
A new version of this library is under development at https://github.com/tecnickcom/tc-lib-pdf and as a consequence this library is in support only mode.
|
||||||
This version should be considered obsolete, new projects should use the new version as soon it will become stable.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
"barcodes"
|
"barcodes"
|
||||||
],
|
],
|
||||||
"homepage": "http://www.tcpdf.org/",
|
"homepage": "http://www.tcpdf.org/",
|
||||||
"version": "6.6.2",
|
"version": "6.6.5",
|
||||||
"license": "LGPL-3.0-only",
|
"license": "LGPL-3.0-or-later",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Nicola Asuni",
|
"name": "Nicola Asuni",
|
||||||
|
@ -888,6 +888,7 @@ class QRcode {
|
|||||||
if ($col >= $this->rsblocks[0]['dataLength']) {
|
if ($col >= $this->rsblocks[0]['dataLength']) {
|
||||||
$row += $this->b1;
|
$row += $this->b1;
|
||||||
}
|
}
|
||||||
|
$row = (int) $row;
|
||||||
$ret = $this->rsblocks[$row]['data'][$col];
|
$ret = $this->rsblocks[$row]['data'][$col];
|
||||||
} elseif ($this->count < $this->dataLength + $this->eccLength) {
|
} elseif ($this->count < $this->dataLength + $this->eccLength) {
|
||||||
$row = ($this->count - $this->dataLength) % $this->blocks;
|
$row = ($this->count - $this->dataLength) % $this->blocks;
|
||||||
|
@ -1323,43 +1323,43 @@ class TCPDF_FONTS {
|
|||||||
// set the checkSumAdjustment to 0
|
// set the checkSumAdjustment to 0
|
||||||
$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
|
$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
|
||||||
}
|
}
|
||||||
$pad = 4 - ($table[$tag]['length'] % 4);
|
|
||||||
if ($pad != 4) {
|
|
||||||
// the length of a table must be a multiple of four bytes
|
|
||||||
$table[$tag]['length'] += $pad;
|
|
||||||
$table[$tag]['data'] .= str_repeat("\x0", $pad);
|
|
||||||
}
|
|
||||||
$table[$tag]['offset'] = $offset;
|
$table[$tag]['offset'] = $offset;
|
||||||
$offset += $table[$tag]['length'];
|
$offset += $table[$tag]['length'];
|
||||||
|
$numPad = ($offset + 3 & ~3) - $offset;
|
||||||
|
if($numPad > 0) {
|
||||||
|
$table[$tag]['data'] .= str_repeat("\x0", $numPad);
|
||||||
|
$offset += $numPad;
|
||||||
|
}
|
||||||
// check sum is not changed (so keep the following line commented)
|
// check sum is not changed (so keep the following line commented)
|
||||||
//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
|
//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length'] + $numPad);
|
||||||
} else {
|
} else {
|
||||||
unset($table[$tag]);
|
unset($table[$tag]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add loca
|
// add loca
|
||||||
|
$table['loca'] = array();
|
||||||
$table['loca']['data'] = $loca;
|
$table['loca']['data'] = $loca;
|
||||||
$table['loca']['length'] = strlen($loca);
|
$table['loca']['length'] = strlen($loca);
|
||||||
$pad = 4 - ($table['loca']['length'] % 4);
|
|
||||||
if ($pad != 4) {
|
|
||||||
// the length of a table must be a multiple of four bytes
|
|
||||||
$table['loca']['length'] += $pad;
|
|
||||||
$table['loca']['data'] .= str_repeat("\x0", $pad);
|
|
||||||
}
|
|
||||||
$table['loca']['offset'] = $offset;
|
$table['loca']['offset'] = $offset;
|
||||||
$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
|
|
||||||
$offset += $table['loca']['length'];
|
$offset += $table['loca']['length'];
|
||||||
|
$numPad = ($offset + 3 & ~3) - $offset;
|
||||||
|
if($numPad > 0) {
|
||||||
|
$table['loca']['data'] .= str_repeat("\x0", $numPad);
|
||||||
|
$offset += $numPad;
|
||||||
|
}
|
||||||
|
$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length'] + $numPad);
|
||||||
// add glyf
|
// add glyf
|
||||||
|
$table['glyf'] = array();
|
||||||
$table['glyf']['data'] = $glyf;
|
$table['glyf']['data'] = $glyf;
|
||||||
$table['glyf']['length'] = strlen($glyf);
|
$table['glyf']['length'] = strlen($glyf);
|
||||||
$pad = 4 - ($table['glyf']['length'] % 4);
|
|
||||||
if ($pad != 4) {
|
|
||||||
// the length of a table must be a multiple of four bytes
|
|
||||||
$table['glyf']['length'] += $pad;
|
|
||||||
$table['glyf']['data'] .= str_repeat("\x0", $pad);
|
|
||||||
}
|
|
||||||
$table['glyf']['offset'] = $offset;
|
$table['glyf']['offset'] = $offset;
|
||||||
$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
|
$offset += $table['glyf']['length'];
|
||||||
|
$numPad = ($offset + 3 & ~3) - $offset;
|
||||||
|
if($numPad > 0) {
|
||||||
|
$table['glyf']['data'] .= str_repeat("\x0", $numPad);
|
||||||
|
$offset += $numPad;
|
||||||
|
}
|
||||||
|
$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length'] + $numPad);
|
||||||
// rebuild font
|
// rebuild font
|
||||||
$font = '';
|
$font = '';
|
||||||
$font .= pack('N', 0x10000); // sfnt version
|
$font .= pack('N', 0x10000); // sfnt version
|
||||||
@ -1383,7 +1383,7 @@ class TCPDF_FONTS {
|
|||||||
}
|
}
|
||||||
// set checkSumAdjustment on head table
|
// set checkSumAdjustment on head table
|
||||||
$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
|
$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
|
||||||
$font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
|
$font = substr($font, 0, $table['head']['offset'] + $offset + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + $offset + 12);
|
||||||
return $font;
|
return $font;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1780,9 +1780,9 @@ class TCPDF_FONTS {
|
|||||||
*/
|
*/
|
||||||
public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
|
public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
|
||||||
if ($isunicode) {
|
if ($isunicode) {
|
||||||
return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta);
|
return array_map(get_called_class().'::unichrUnicode', $ta);
|
||||||
}
|
}
|
||||||
return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta);
|
return array_map(get_called_class().'::unichrASCII', $ta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2002,7 +2002,7 @@ class TCPDF_FONTS {
|
|||||||
if ($isunicode) {
|
if ($isunicode) {
|
||||||
// requires PCRE unicode support turned on
|
// requires PCRE unicode support turned on
|
||||||
$chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
|
$chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
$carr = array_map(array('TCPDF_FONTS', 'uniord'), $chars);
|
$carr = array_map(get_called_class().'::uniord', $chars);
|
||||||
} else {
|
} else {
|
||||||
$chars = str_split($str);
|
$chars = str_split($str);
|
||||||
$carr = array_map('ord', $chars);
|
$carr = array_map('ord', $chars);
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
// File name : tcpdf_static.php
|
// File name : tcpdf_static.php
|
||||||
// Version : 1.1.4
|
// Version : 1.1.4
|
||||||
// Begin : 2002-08-03
|
// Begin : 2002-08-03
|
||||||
// Last Update : 2022-08-12
|
// Last Update : 2023-09-06
|
||||||
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
|
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
|
||||||
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
|
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Copyright (C) 2002-2022 Nicola Asuni - Tecnick.com LTD
|
// Copyright (C) 2002-2023 Nicola Asuni - Tecnick.com LTD
|
||||||
//
|
//
|
||||||
// This file is part of TCPDF software library.
|
// This file is part of TCPDF software library.
|
||||||
//
|
//
|
||||||
@ -55,7 +55,7 @@ class TCPDF_STATIC {
|
|||||||
* Current TCPDF version.
|
* Current TCPDF version.
|
||||||
* @private static
|
* @private static
|
||||||
*/
|
*/
|
||||||
private static $tcpdf_version = '6.6.2';
|
private static $tcpdf_version = '6.6.5';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String alias for total number of pages.
|
* String alias for total number of pages.
|
||||||
@ -1780,7 +1780,7 @@ class TCPDF_STATIC {
|
|||||||
if ($ret === false) {
|
if ($ret === false) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
return $ret;
|
return is_array($ret) ? $ret : array();
|
||||||
}
|
}
|
||||||
// preg_split is bugged - try alternative solution
|
// preg_split is bugged - try alternative solution
|
||||||
$ret = array();
|
$ret = array();
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
parameters:
|
|
||||||
level: 1
|
|
||||||
paths:
|
|
||||||
- ./
|
|
||||||
excludePaths:
|
|
||||||
- vendor/
|
|
||||||
# remove once https://github.com/phpstan/phpstan/issues/7955 is fixed
|
|
||||||
- fonts/
|
|
||||||
- tests/
|
|
||||||
|
|
||||||
scanFiles:
|
|
||||||
- tcpdf_autoconfig.php
|
|
||||||
|
|
||||||
ignoreErrors:
|
|
||||||
- '~^Constant (PDF_HEADER_LOGO|K_PATH_CACHE|K_PATH_FONTS|K_PATH_IMAGES|K_PATH_URL) not found\.$~'
|
|
||||||
- '~^Constructor of class TCPDF has an unused parameter \$diskcache\.$~'
|
|
||||||
- '~^Variable \$\w+ might not be defined\.$~'
|
|
||||||
- '~^Method TCPDF(_FILTERS)?::\w+\(\) should return .+ but return statement is missing\.$~'
|
|
||||||
|
|
||||||
# mcrypt does not support PHP 7.2 or later
|
|
||||||
-
|
|
||||||
message: '~^(Constant MCRYPT_RIJNDAEL_128 not found\.|Function mcrypt_get_cipher_name not found\.)$~'
|
|
||||||
path: tcpdf.php
|
|
||||||
count: 2
|
|
||||||
-
|
|
||||||
message: '~^(Constant (MCRYPT_ARCFOUR|MCRYPT_MODE_CBC|MCRYPT_MODE_STREAM|MCRYPT_RAND|MCRYPT_RIJNDAEL_128) not found\.|Function (mcrypt_create_iv|mcrypt_encrypt|mcrypt_get_iv_size) not found\.)$~'
|
|
||||||
path: include/tcpdf_static.php
|
|
||||||
count: 16
|
|
||||||
|
|
||||||
-
|
|
||||||
message: '~^(Call to static method create\(\) on an unknown class Symfony\\Component\\Finder\\Finder\.|Instantiated class Doctum\\(Doctum|RemoteRepository\\GitHubRemoteRepository) not found\.)$~'
|
|
||||||
path: scripts/doctum.php
|
|
||||||
count: 3
|
|
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Doctum\Doctum;
|
|
||||||
use Doctum\RemoteRepository\GitHubRemoteRepository;
|
|
||||||
use Symfony\Component\Finder\Finder;
|
|
||||||
|
|
||||||
$rootDir = __DIR__ . '/../';
|
|
||||||
|
|
||||||
$iterator = Finder::create()
|
|
||||||
->files()
|
|
||||||
->name('*.php')
|
|
||||||
->notPath('cache')
|
|
||||||
->notPath('build')
|
|
||||||
->notPath('fonts')
|
|
||||||
->notPath('vendor')
|
|
||||||
->notPath('tests')
|
|
||||||
->notPath('examples')
|
|
||||||
->in($rootDir);
|
|
||||||
|
|
||||||
return new Doctum($iterator, [
|
|
||||||
'title' => 'TCPDF',
|
|
||||||
'build_dir' => $rootDir . '/build',
|
|
||||||
'cache_dir' => $rootDir . '/cache',
|
|
||||||
'source_dir' => $rootDir,
|
|
||||||
'remote_repository' => new GitHubRemoteRepository('tecnickcom/TCPDF', $rootDir),
|
|
||||||
'footer_link' => [
|
|
||||||
'href' => 'https://github.com/tecnickcom/TCPDF#readme',
|
|
||||||
'rel' => 'noreferrer noopener',
|
|
||||||
'target' => '_blank',
|
|
||||||
'before_text' => 'This documentation is for',
|
|
||||||
'link_text' => 'TCPDF',
|
|
||||||
'after_text' => 'the PHP library to build PDFs.',
|
|
||||||
],
|
|
||||||
]);
|
|
28
tcpdf.php
28
tcpdf.php
@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
//============================================================+
|
//============================================================+
|
||||||
// File name : tcpdf.php
|
// File name : tcpdf.php
|
||||||
// Version : 6.6.2
|
// Version : 6.6.5
|
||||||
// Begin : 2002-08-03
|
// Begin : 2002-08-03
|
||||||
// Last Update : 2022-12-06
|
// Last Update : 2023-09-06
|
||||||
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
|
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
|
||||||
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
|
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Copyright (C) 2002-2022 Nicola Asuni - Tecnick.com LTD
|
// Copyright (C) 2002-2023 Nicola Asuni - Tecnick.com LTD
|
||||||
//
|
//
|
||||||
// This file is part of TCPDF software library.
|
// This file is part of TCPDF software library.
|
||||||
//
|
//
|
||||||
@ -104,7 +104,7 @@
|
|||||||
* Tools to encode your unicode fonts are on fonts/utils directory.</p>
|
* Tools to encode your unicode fonts are on fonts/utils directory.</p>
|
||||||
* @package com.tecnick.tcpdf
|
* @package com.tecnick.tcpdf
|
||||||
* @author Nicola Asuni
|
* @author Nicola Asuni
|
||||||
* @version 6.6.2
|
* @version 6.6.5
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TCPDF configuration
|
// TCPDF configuration
|
||||||
@ -128,7 +128,7 @@ require_once(dirname(__FILE__).'/include/tcpdf_static.php');
|
|||||||
* TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
|
* TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
|
||||||
* @package com.tecnick.tcpdf
|
* @package com.tecnick.tcpdf
|
||||||
* @brief PHP class for generating PDF documents without requiring external extensions.
|
* @brief PHP class for generating PDF documents without requiring external extensions.
|
||||||
* @version 6.6.2
|
* @version 6.6.5
|
||||||
* @author Nicola Asuni - info@tecnick.com
|
* @author Nicola Asuni - info@tecnick.com
|
||||||
* @IgnoreAnnotation("protected")
|
* @IgnoreAnnotation("protected")
|
||||||
* @IgnoreAnnotation("public")
|
* @IgnoreAnnotation("public")
|
||||||
@ -574,12 +574,14 @@ class TCPDF {
|
|||||||
/**
|
/**
|
||||||
* Minimum distance between header and top page margin.
|
* Minimum distance between header and top page margin.
|
||||||
* @protected
|
* @protected
|
||||||
|
* @var float
|
||||||
*/
|
*/
|
||||||
protected $header_margin;
|
protected $header_margin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum distance between footer and bottom page margin.
|
* Minimum distance between footer and bottom page margin.
|
||||||
* @protected
|
* @protected
|
||||||
|
* @var float
|
||||||
*/
|
*/
|
||||||
protected $footer_margin;
|
protected $footer_margin;
|
||||||
|
|
||||||
@ -2461,7 +2463,7 @@ class TCPDF {
|
|||||||
*/
|
*/
|
||||||
public function getCellHeight($fontsize, $padding=TRUE) {
|
public function getCellHeight($fontsize, $padding=TRUE) {
|
||||||
$height = ($fontsize * $this->cell_height_ratio);
|
$height = ($fontsize * $this->cell_height_ratio);
|
||||||
if ($padding) {
|
if ($padding && !empty($this->cell_padding)) {
|
||||||
$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
|
$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
|
||||||
}
|
}
|
||||||
return round($height, 6);
|
return round($height, 6);
|
||||||
@ -3372,7 +3374,7 @@ class TCPDF {
|
|||||||
/**
|
/**
|
||||||
* Set header margin.
|
* Set header margin.
|
||||||
* (minimum distance between header and top page margin)
|
* (minimum distance between header and top page margin)
|
||||||
* @param int $hm distance in user units
|
* @param float $hm distance in user units
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
public function setHeaderMargin($hm=10) {
|
public function setHeaderMargin($hm=10) {
|
||||||
@ -3392,7 +3394,7 @@ class TCPDF {
|
|||||||
/**
|
/**
|
||||||
* Set footer margin.
|
* Set footer margin.
|
||||||
* (minimum distance between footer and bottom page margin)
|
* (minimum distance between footer and bottom page margin)
|
||||||
* @param int $fm distance in user units
|
* @param float $fm distance in user units
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
public function setFooterMargin($fm=10) {
|
public function setFooterMargin($fm=10) {
|
||||||
@ -4102,6 +4104,7 @@ class TCPDF {
|
|||||||
* @param float $fontsize Font size in points. The default value is the current size.
|
* @param float $fontsize Font size in points. The default value is the current size.
|
||||||
* @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
|
* @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
|
||||||
* @return float[]|float total string length or array of characted widths
|
* @return float[]|float total string length or array of characted widths
|
||||||
|
* @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
|
||||||
* @author Nicola Asuni
|
* @author Nicola Asuni
|
||||||
* @public
|
* @public
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
@ -4118,6 +4121,7 @@ class TCPDF {
|
|||||||
* @param float $fontsize Font size in points. The default value is the current size.
|
* @param float $fontsize Font size in points. The default value is the current size.
|
||||||
* @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
|
* @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
|
||||||
* @return float[]|float total string length or array of characted widths
|
* @return float[]|float total string length or array of characted widths
|
||||||
|
* @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
|
||||||
* @author Nicola Asuni
|
* @author Nicola Asuni
|
||||||
* @public
|
* @public
|
||||||
* @since 2.4.000 (2008-03-06)
|
* @since 2.4.000 (2008-03-06)
|
||||||
@ -6409,7 +6413,7 @@ class TCPDF {
|
|||||||
// calculate maximum width for a single character on string
|
// calculate maximum width for a single character on string
|
||||||
$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
|
$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
|
||||||
array_walk($chrw, array($this, 'getRawCharWidth'));
|
array_walk($chrw, array($this, 'getRawCharWidth'));
|
||||||
$maxchwidth = max($chrw);
|
$maxchwidth = ((is_array($chrw) || $chrw instanceof Countable) && count($chrw) > 0) ? max($chrw) : 0;
|
||||||
// get array of chars
|
// get array of chars
|
||||||
$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
|
$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
|
||||||
// get the number of characters
|
// get the number of characters
|
||||||
@ -6872,6 +6876,8 @@ class TCPDF {
|
|||||||
}
|
}
|
||||||
// resize the block to be contained on the remaining available page or column space
|
// resize the block to be contained on the remaining available page or column space
|
||||||
if ($fitonpage) {
|
if ($fitonpage) {
|
||||||
|
// fallback to avoid division by zero
|
||||||
|
$h = $h == 0 ? 1 : $h;
|
||||||
$ratio_wh = ($w / $h);
|
$ratio_wh = ($w / $h);
|
||||||
if (($y + $h) > $this->PageBreakTrigger) {
|
if (($y + $h) > $this->PageBreakTrigger) {
|
||||||
$h = $this->PageBreakTrigger - $y;
|
$h = $this->PageBreakTrigger - $y;
|
||||||
@ -9925,7 +9931,7 @@ class TCPDF {
|
|||||||
}
|
}
|
||||||
$out .= ' >> >>';
|
$out .= ' >> >>';
|
||||||
}
|
}
|
||||||
$font = $this->getFontBuffer('helvetica');
|
$font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
|
||||||
$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
|
$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
|
||||||
$out .= ' /Q '.(($this->rtl)?'2':'0');
|
$out .= ' /Q '.(($this->rtl)?'2':'0');
|
||||||
//$out .= ' /XFA ';
|
//$out .= ' /XFA ';
|
||||||
@ -22055,7 +22061,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
|
|||||||
public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
|
public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
|
||||||
// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
|
// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
|
||||||
// convert text rendering parameters
|
// convert text rendering parameters
|
||||||
if ($stroke < 0) {
|
if ($stroke < 0 || !is_numeric($stroke)) {
|
||||||
$stroke = 0;
|
$stroke = 0;
|
||||||
}
|
}
|
||||||
if ($fill === true) {
|
if ($fill === true) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<extension type="library" method="upgrade" version="3.1">
|
<extension type="library" method="upgrade" version="4.0">
|
||||||
<name>TCPDF</name>
|
<name>TCPDF</name>
|
||||||
<libraryname>tcpdf</libraryname>
|
<libraryname>tcpdf</libraryname>
|
||||||
<author>Nicola Asuni</author>
|
<author>Nicola Asuni</author>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
<license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPL</license>
|
<license>http://www.gnu.org/licenses/gpl-3.0.html GNU/GPL</license>
|
||||||
<authorEmail>info@tecnick.com</authorEmail>
|
<authorEmail>info@tecnick.com</authorEmail>
|
||||||
<authorUrl>www.tecnick.com</authorUrl>
|
<authorUrl>www.tecnick.com</authorUrl>
|
||||||
<version>6.6.2</version>
|
<version>6.6.5</version>
|
||||||
<description>This is a PHP class for generating PDF documents without requiring external extensions.</description>
|
<description>This is a PHP class for generating PDF documents without requiring external extensions.</description>
|
||||||
|
|
||||||
<files>
|
<files>
|
||||||
|
@ -828,7 +828,7 @@ class TCPDFBarcode {
|
|||||||
$chr['5'] = '11101011101010';
|
$chr['5'] = '11101011101010';
|
||||||
$chr['6'] = '10111011101010';
|
$chr['6'] = '10111011101010';
|
||||||
$chr['7'] = '10101011101110';
|
$chr['7'] = '10101011101110';
|
||||||
$chr['8'] = '10101110111010';
|
$chr['8'] = '11101010111010';
|
||||||
$chr['9'] = '10111010111010';
|
$chr['9'] = '10111010111010';
|
||||||
if ($checksum) {
|
if ($checksum) {
|
||||||
// add checksum
|
// add checksum
|
||||||
@ -838,7 +838,7 @@ class TCPDFBarcode {
|
|||||||
// add leading zero if code-length is odd
|
// add leading zero if code-length is odd
|
||||||
$code = '0'.$code;
|
$code = '0'.$code;
|
||||||
}
|
}
|
||||||
$seq = '11011010';
|
$seq = '1110111010';
|
||||||
$clen = strlen($code);
|
$clen = strlen($code);
|
||||||
for ($i = 0; $i < $clen; ++$i) {
|
for ($i = 0; $i < $clen; ++$i) {
|
||||||
$digit = $code[$i];
|
$digit = $code[$i];
|
||||||
@ -848,7 +848,7 @@ class TCPDFBarcode {
|
|||||||
}
|
}
|
||||||
$seq .= $chr[$digit];
|
$seq .= $chr[$digit];
|
||||||
}
|
}
|
||||||
$seq .= '1101011';
|
$seq .= '111010111';
|
||||||
$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
|
$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
|
||||||
return $this->binseq_to_array($seq, $bararray);
|
return $this->binseq_to_array($seq, $bararray);
|
||||||
}
|
}
|
||||||
|
5
tests/.gitignore
vendored
5
tests/.gitignore
vendored
@ -1,5 +0,0 @@
|
|||||||
/composer.lock
|
|
||||||
/composer.phar
|
|
||||||
/coverage.lcov
|
|
||||||
/runs
|
|
||||||
/vendor
|
|
@ -1,471 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* This script compares two runs of the test suite.
|
|
||||||
*
|
|
||||||
* The comparison is not bit by bit, as most of the generated
|
|
||||||
* files have some time-induced differences.
|
|
||||||
*
|
|
||||||
* This script will however point out gross discrepancies.
|
|
||||||
*
|
|
||||||
* PDF documents will be compared as PNG images.
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
use LocateBinaries\LocateBinaries;
|
|
||||||
|
|
||||||
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
|
|
||||||
echo 'Run `composer install` in the tests/ directory first.' . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
$options = getopt('o:vhr:', array(
|
|
||||||
'group:',
|
|
||||||
'output-dir:',
|
|
||||||
'reference-dir:',
|
|
||||||
'stop-on-defect',
|
|
||||||
'verbose',
|
|
||||||
'help'
|
|
||||||
));
|
|
||||||
|
|
||||||
function printCompareRunsHelp()
|
|
||||||
{
|
|
||||||
echo 'Usage:' . PHP_EOL;
|
|
||||||
echo ' php compare_runs.php [-chv] [-o <path>] -r referenceDir compareDir' . PHP_EOL;
|
|
||||||
echo 'Description:' . PHP_EOL;
|
|
||||||
echo ' Compares 2 runs of the TCPDF test suite.' . PHP_EOL;
|
|
||||||
echo " PDF files are first converted to PNG images using XpdfReader's `pdftopng` tool" . PHP_EOL;
|
|
||||||
echo " or using Poppler's `pdftoppm`." . PHP_EOL;
|
|
||||||
echo " PNG images are compared using ImageMagick's `magick` tool." . PHP_EOL;
|
|
||||||
echo 'Supported environment variables:' . PHP_EOL;
|
|
||||||
echo ' PDFINFO_BINARY Path to pdfinfo executable to use.' . PHP_EOL;
|
|
||||||
echo ' For more information on pdfinfo, visit https://www.xpdfreader.com/' . PHP_EOL;
|
|
||||||
echo ' or https://poppler.freedesktop.org/' . PHP_EOL;
|
|
||||||
echo ' PDFTOPNG_BINARY Path to pdftopng executable to use.' . PHP_EOL;
|
|
||||||
echo ' For more information on pdftopng, visit https://www.xpdfreader.com/' . PHP_EOL;
|
|
||||||
echo ' PDFTOPPM_BINARY Path to pdftoppm executable to use.' . PHP_EOL;
|
|
||||||
echo ' For more information on pdftoppm, visit https://poppler.freedesktop.org/' . PHP_EOL;
|
|
||||||
echo ' MAGICK_BINARY Path to magick executable to use.' . PHP_EOL;
|
|
||||||
echo ' For more information on magick, visit https://imagemagick.org/' . PHP_EOL;
|
|
||||||
echo 'Arguments:' . PHP_EOL;
|
|
||||||
echo ' compareDir' . PHP_EOL;
|
|
||||||
echo ' Path to the folder containing the results of test run to compare' . PHP_EOL;
|
|
||||||
echo ' with the reference.' . PHP_EOL;
|
|
||||||
echo 'Options:' . PHP_EOL;
|
|
||||||
echo ' -c, --clean-up' . PHP_EOL;
|
|
||||||
echo ' Clean up generated files. NOT IMPLEMENTED YET.' . PHP_EOL;
|
|
||||||
echo ' -o <path>, --output-dir=<path>' . PHP_EOL;
|
|
||||||
echo ' The folder in which files should be generated.' . PHP_EOL;
|
|
||||||
echo ' Default is to create a folder in the system\'s temporary folder.' . PHP_EOL;
|
|
||||||
echo ' -r <path>, --reference-dir=<path>' . PHP_EOL;
|
|
||||||
echo ' Path to the folder containing the results of the test run of reference.' . PHP_EOL;
|
|
||||||
echo ' If no reference folder is provided, a set will be automatically downloaded.' . PHP_EOL;
|
|
||||||
echo ' --group=<name>' . PHP_EOL;
|
|
||||||
echo ' Filter the tests to compare based on the @group annotation present in the file.' . PHP_EOL;
|
|
||||||
echo ' --stop-on-defect' . PHP_EOL;
|
|
||||||
echo ' Stop execution upon first difference.' . PHP_EOL;
|
|
||||||
echo ' -v, --verbose' . PHP_EOL;
|
|
||||||
echo ' Outputs more information.' . PHP_EOL;
|
|
||||||
echo ' -h, --help' . PHP_EOL;
|
|
||||||
echo ' Prints this message.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $options
|
|
||||||
|| array_key_exists('h', $options)
|
|
||||||
|| array_key_exists('help', $options)) {
|
|
||||||
printCompareRunsHelp();
|
|
||||||
exit(false === $options ? -1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($options['o'])) {
|
|
||||||
$outputDir = $options['o'];
|
|
||||||
}
|
|
||||||
if (!empty($options['output-dir'])) {
|
|
||||||
$outputDir = $options['output-dir'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($options['r'])) {
|
|
||||||
$referenceDirArg = $options['r'];
|
|
||||||
}
|
|
||||||
if (!empty($options['reference-dir'])) {
|
|
||||||
$referenceDirArg = $options['reference-dir'];
|
|
||||||
}
|
|
||||||
if (!isset($referenceDirArg)) {
|
|
||||||
echo "Please provide the reference folder using the -r or --reference-dir argument." . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Automate the cleanup of generated files (PNG images from PDF conversion)
|
|
||||||
if (array_key_exists('c', $options) || array_key_exists('clean-up', $options)) {
|
|
||||||
$preserveOutputFiles = false;
|
|
||||||
} elseif (isset($outputDir)) {
|
|
||||||
$preserveOutputFiles = true;
|
|
||||||
} else {
|
|
||||||
$preserveOutputFiles = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stopOnDefect = array_key_exists('stop-on-defect', $options);
|
|
||||||
|
|
||||||
$verbose = array_key_exists('v', $options) || array_key_exists('verbose', $options);
|
|
||||||
|
|
||||||
$groups = array();
|
|
||||||
if (!empty($options['group'])) {
|
|
||||||
if (is_array($options['group'])) {
|
|
||||||
$groups = $options['group'];
|
|
||||||
} else {
|
|
||||||
$groups = explode(',', $options['group']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yes, this is a very basic check.
|
|
||||||
if ($argc < 2) {
|
|
||||||
echo "Please provide folder path" . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$compareDirArg = $argv[$argc - 1];
|
|
||||||
|
|
||||||
if (!is_dir(realpath($compareDirArg))) {
|
|
||||||
echo "Could not find folder to compare: $compareDirArg" . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
// Normalize the folder path to end with a slash
|
|
||||||
$compareDir = rtrim(realpath($compareDirArg), '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
if (!is_dir(realpath($referenceDirArg))) {
|
|
||||||
echo "Could not find reference folder: $referenceDirArg" . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
// Normalize the folder path to end with a slash
|
|
||||||
$referenceDir = trim(realpath($referenceDirArg), '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
$rootDir = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
|
|
||||||
$exampleDir = $rootDir . 'examples' . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
$isBinaryLocatorAvailable = class_exists('\LocateBinaries\LocateBinaries');
|
|
||||||
|
|
||||||
$pdfinfo = getenv('PDFINFO_BINARY');
|
|
||||||
if (empty($pdfinfo)) {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('pdfinfo')
|
|
||||||
: array();
|
|
||||||
if (empty($paths)) {
|
|
||||||
echo 'pdfinfo could not be located.' . PHP_EOL;
|
|
||||||
echo 'Please set the PDFINFO_BINARY environment variable.' . PHP_EOL;
|
|
||||||
if (!$isBinaryLocatorAvailable) {
|
|
||||||
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$pdfinfo = reset($paths);
|
|
||||||
}
|
|
||||||
$pdftopng = getenv('PDFTOPNG_BINARY');
|
|
||||||
if (empty($pdftopng)) {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('pdftopng')
|
|
||||||
: array();
|
|
||||||
if (!empty($paths)) {
|
|
||||||
$pdftopng = reset($paths);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$pdftoppm = getenv('PDFTOPPM_BINARY');
|
|
||||||
if (empty($pdftoppm)) {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('pdftoppm')
|
|
||||||
: array();
|
|
||||||
if (!empty($paths)) {
|
|
||||||
$pdftoppm = reset($paths);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (empty($pdftopng) && empty($pdftoppm)) {
|
|
||||||
echo 'pdftopng nor pdftoppm could not be located.' . PHP_EOL;
|
|
||||||
echo 'Please set the PDFTOPNG_BINARY or PDFTOPPM_BINARY environment variable.' . PHP_EOL;
|
|
||||||
if (!$isBinaryLocatorAvailable) {
|
|
||||||
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tools = array(
|
|
||||||
'pdfinfo' => $pdfinfo,
|
|
||||||
'pdftopng' => $pdftopng,
|
|
||||||
'pdftoppm' => $pdftoppm,
|
|
||||||
);
|
|
||||||
$pdfTools = new PdfTools($tools, $verbose);
|
|
||||||
echo 'pdfinfo: ' . $pdfinfo . PHP_EOL;
|
|
||||||
echo 'pdfinfo version: ' . $pdfTools->getPdfinfoVersionInfo() . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
echo 'pdftopng: ' . $pdftopng . PHP_EOL;
|
|
||||||
echo 'pdftopng version: ' . $pdfTools->getPdftopngVersionInfo() . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
$magick = getenv('MAGICK_BINARY');
|
|
||||||
if (empty($magick)) {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('magick')
|
|
||||||
: array();
|
|
||||||
if (empty($paths)) {
|
|
||||||
echo 'magick could not be located.' . PHP_EOL;
|
|
||||||
echo 'Please set the MAGICK_BINARY environment variable.' . PHP_EOL;
|
|
||||||
if (!$isBinaryLocatorAvailable) {
|
|
||||||
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$magick = reset($paths);
|
|
||||||
}
|
|
||||||
$imagemagick = new ImageMagick($magick, $verbose);
|
|
||||||
echo 'magick: ' . $magick . PHP_EOL;
|
|
||||||
echo 'magick version: ' . $imagemagick->getMagickVersionInfo() . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
$isGeneratedTempDir = false;
|
|
||||||
if (!isset($outputDir)) {
|
|
||||||
echo PHP_EOL;
|
|
||||||
echo "The --output-dir option was not used, a temporary folder will be necessary." . PHP_EOL;
|
|
||||||
try {
|
|
||||||
$outputDir = \Cs278\Mktemp\temporaryDir('TCPDF-tests.XXXXXXXXX') . DIRECTORY_SEPARATOR;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
echo $e->getMessage();
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$isGeneratedTempDir = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_dir(realpath($outputDir))) {
|
|
||||||
echo "Could not find output folder: $outputDir" . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Example folder: $exampleDir" . PHP_EOL;
|
|
||||||
|
|
||||||
$outputDir = realpath($outputDir);
|
|
||||||
$outputDir = rtrim($outputDir, '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
echo "Output folder: $outputDir" . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
echo 'Comparing folders:' . PHP_EOL;
|
|
||||||
echo "< $referenceDir" . PHP_EOL;
|
|
||||||
echo "> $compareDir" . PHP_EOL;
|
|
||||||
|
|
||||||
$separator = '----------------------------------';
|
|
||||||
|
|
||||||
$testRunner = new TestRunner($exampleDir);
|
|
||||||
|
|
||||||
$differences = array();
|
|
||||||
$ignored = array();
|
|
||||||
$comparisons = 0;
|
|
||||||
$bitByBitComparisons = 0;
|
|
||||||
|
|
||||||
$testFiles = $testRunner
|
|
||||||
->filterByGroup($groups)
|
|
||||||
->getTestFiles()
|
|
||||||
;
|
|
||||||
foreach ($testFiles as $file => $type) {
|
|
||||||
++$comparisons;
|
|
||||||
$outputFile = basename($file, '.php') . '.output.' . strtolower($type);
|
|
||||||
|
|
||||||
$outputFile1 = $referenceDir . $outputFile;
|
|
||||||
$outputFile2 = $compareDir . $outputFile;
|
|
||||||
|
|
||||||
$exists1 = file_exists($outputFile1);
|
|
||||||
$exists2 = file_exists($outputFile2);
|
|
||||||
|
|
||||||
if (!$exists1 && !$exists2) {
|
|
||||||
$ignored[] = $file;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($exists1 && !$exists2) {
|
|
||||||
$differences[] = $file . ' (FILE PRESENCE)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< PRESENT' . PHP_EOL;
|
|
||||||
echo '> MISSING' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$exists1 && $exists2) {
|
|
||||||
$differences[] = $file . ' (FILE PRESENCE)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< ABSENT' . PHP_EOL;
|
|
||||||
echo '> PRESENT' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exact size comparison is not workable for most of the files
|
|
||||||
$size1 = filesize($outputFile1);
|
|
||||||
$size2 = filesize($outputFile2);
|
|
||||||
if ($size1 === 0 && $size2 > 0) {
|
|
||||||
$differences[] = $file . ' (FILE SIZE)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< EMPTY' . PHP_EOL;
|
|
||||||
echo '> HAS CONTENT' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($size1 > 0 && $size2 === 0) {
|
|
||||||
$differences[] = $file . ' (FILE SIZE)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< HAS CONTENT' . PHP_EOL;
|
|
||||||
echo '> EMPTY' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($size1 === 0 && $size2 === 0) {
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< EMPTY' . PHP_EOL;
|
|
||||||
echo '> EMPTY' . PHP_EOL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can we compare files?
|
|
||||||
// For now, just some example files are easily comparable.
|
|
||||||
// (See the @group comparable docBlock annotation)
|
|
||||||
$examplePHPSource = file_get_contents($exampleDir . $file);
|
|
||||||
if (strpos($examplePHPSource, '* @group comparable') !== false) {
|
|
||||||
++$bitByBitComparisons;
|
|
||||||
|
|
||||||
$pngFiles1 = null;
|
|
||||||
$pngFiles2 = null;
|
|
||||||
|
|
||||||
if ('PDF' !== $type) {
|
|
||||||
$hash1 = sha1_file($outputFile1);
|
|
||||||
$hash2 = sha1_file($outputFile2);
|
|
||||||
if ($hash1 !== $hash2) {
|
|
||||||
$differences[] = $file . ' (CONTENT)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< ORIGIN' . PHP_EOL;
|
|
||||||
echo '> DIFFERENT' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('PNG' === $type) {
|
|
||||||
$pngFiles1 = array($outputFile1);
|
|
||||||
$pngFiles2 = array($outputFile2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For PDF files, we generate PNG files (one per page) with pdftopng
|
|
||||||
if ('PDF' === $type) {
|
|
||||||
// Let's first check there are both valid PDF files
|
|
||||||
$isPdf1 = $pdfTools->isPdf($outputFile1);
|
|
||||||
$isPdf2 = $pdfTools->isPdf($outputFile2);
|
|
||||||
if ($isPdf1 && !$isPdf2) {
|
|
||||||
$differences[] = $file . ' (CONTENT)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< PDF' . PHP_EOL;
|
|
||||||
echo '> NOT PDF' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$isPdf1 && $isPdf2) {
|
|
||||||
$differences[] = $file . ' (CONTENT)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< NOT PDF' . PHP_EOL;
|
|
||||||
echo '> PDF' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$isPdf1 && !$isPdf2) {
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< NOT PDF' . PHP_EOL;
|
|
||||||
echo '> NOT PDF' . PHP_EOL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we convert each page of the PDF into PNG
|
|
||||||
$conversionDir = $outputDir . basename($file, '.php') . DIRECTORY_SEPARATOR;
|
|
||||||
$pngFiles1 = $pdfTools->convertToPng($outputFile1, $conversionDir . 'ref');
|
|
||||||
$pngFiles2 = $pdfTools->convertToPng($outputFile2, $conversionDir . 'cmp');
|
|
||||||
|
|
||||||
if (count($pngFiles1) !== count($pngFiles2)) {
|
|
||||||
$differences[] = $file . ' (CONTENT)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< ' . count($pngFiles1) . ' page(s)' . PHP_EOL;
|
|
||||||
echo '> ' . count($pngFiles2) . ' page(s)' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($pngFiles1, $pngFiles2)) {
|
|
||||||
$pngCount = count($pngFiles1);
|
|
||||||
for ($i = 0; $i < $pngCount; ++$i) {
|
|
||||||
if (!$imagemagick->areSimilar($pngFiles1[$i], $pngFiles2[$i])) {
|
|
||||||
$differences[] = $file . ' (CONTENT)';
|
|
||||||
echo $separator . PHP_EOL;
|
|
||||||
echo $outputFile . PHP_EOL;
|
|
||||||
echo '< ORIGIN' . PHP_EOL;
|
|
||||||
echo '> DIFFERENT' . PHP_EOL;
|
|
||||||
if ($stopOnDefect) {
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
continue 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$passed = empty($differences);
|
|
||||||
echo PHP_EOL;
|
|
||||||
if ($passed) {
|
|
||||||
echo 'Comparison: IDENTICAL' . PHP_EOL;
|
|
||||||
} else {
|
|
||||||
echo 'Comparison: DIFFERENCES EXIST' . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo " Total tests: $comparisons" . PHP_EOL;
|
|
||||||
|
|
||||||
$ignoredCount = count($ignored);
|
|
||||||
if ($ignoredCount > 0) {
|
|
||||||
echo ' Probably skipped tests: ' . $ignoredCount . PHP_EOL;
|
|
||||||
foreach ($ignored as $ignoredFile) {
|
|
||||||
echo ' ' . $ignoredFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo " Exact content comparisons: " . $bitByBitComparisons . PHP_EOL;
|
|
||||||
|
|
||||||
if ($differences) {
|
|
||||||
echo ' Differences: ' . count($differences) . PHP_EOL;
|
|
||||||
echo ' Files:' . PHP_EOL;
|
|
||||||
foreach ($differences as $file) {
|
|
||||||
echo " $file" . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$exitCode = (!$passed) ? 1 : 0;
|
|
||||||
echo 'Exit code: ' . $exitCode . PHP_EOL;
|
|
||||||
exit($exitCode);
|
|
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "tecnickcom/tcpdf-tests",
|
|
||||||
"type": "metapackage",
|
|
||||||
"description": "Dependencies for the test suite",
|
|
||||||
"keywords": [
|
|
||||||
"PDF",
|
|
||||||
"tcpdf",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"homepage": "http://www.tcpdf.org/",
|
|
||||||
"license": "LGPL-3.0-only",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Philippe Jausions",
|
|
||||||
"email": "jausions@11abacus.com",
|
|
||||||
"role": "contributor"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"PHP": ">=5.3.0",
|
|
||||||
"cs278/mktemp": "*"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"rosell-dk/locate-binaries": "Allows to detect executables such as pdfinfo"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Tecnickcom\\TCPDF\\Tests\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"archive": {
|
|
||||||
"exclude": [
|
|
||||||
"/examples"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if (extension_loaded('pcov')) {
|
|
||||||
\pcov\start();
|
|
||||||
|
|
||||||
|
|
||||||
class CoverageObjectPcov
|
|
||||||
{
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
\pcov\stop();
|
|
||||||
$rootDir = realpath(__DIR__ . '/../') . '/';
|
|
||||||
$coverageFile = $rootDir . 'tests/coverage.lcov';
|
|
||||||
$covData = \pcov\collect(
|
|
||||||
\pcov\exclusive,
|
|
||||||
array(
|
|
||||||
__FILE__
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$coverageData = '';
|
|
||||||
foreach ($covData as $file => $coverageForFile) {
|
|
||||||
$coverageData .= 'SF:' . $file . "\n";
|
|
||||||
$coverageData .= 'TN:' . str_replace($rootDir, '', $_SERVER['PHP_SELF']) . "\n";
|
|
||||||
foreach ($coverageForFile as $line => $coverageValue) {
|
|
||||||
$coverageValue = $coverageValue === -1 ? 0 : $coverageValue;
|
|
||||||
$coverageData .= 'DA:' . $line . ',' . $coverageValue . "\n";
|
|
||||||
}
|
|
||||||
$coverageData .= 'end_of_record' . "\n";
|
|
||||||
}
|
|
||||||
file_put_contents($coverageFile, $coverageData, LOCK_EX | FILE_APPEND);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$a = new CoverageObjectPcov();
|
|
||||||
}
|
|
||||||
if (extension_loaded('xdebug')) {
|
|
||||||
\xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
|
|
||||||
|
|
||||||
class CoverageObjectXdebug
|
|
||||||
{
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
$rootDir = realpath(__DIR__ . '/../') . '/';
|
|
||||||
$coverageFile = $rootDir . 'tests/coverage.lcov';
|
|
||||||
$covData = xdebug_get_code_coverage();
|
|
||||||
$coverageData = '';
|
|
||||||
foreach ($covData as $file => $coverageForFile) {
|
|
||||||
$coverageData .= 'SF:' . $file . "\n";
|
|
||||||
$coverageData .= 'TN:' . str_replace($rootDir, '', $_SERVER['PHP_SELF']) . "\n";
|
|
||||||
foreach ($coverageForFile as $line => $coverageValue) {
|
|
||||||
$coverageValue = $coverageValue > 0 ? $coverageValue : 0;
|
|
||||||
$coverageData .= 'DA:' . $line . ',' . $coverageValue . "\n";
|
|
||||||
}
|
|
||||||
$coverageData .= 'end_of_record' . "\n";
|
|
||||||
}
|
|
||||||
file_put_contents($coverageFile, $coverageData, LOCK_EX | FILE_APPEND);
|
|
||||||
\xdebug_stop_code_coverage(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$a = new CoverageObjectXdebug();
|
|
||||||
}
|
|
279
tests/launch.php
279
tests/launch.php
@ -1,279 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test runner
|
|
||||||
*
|
|
||||||
* Usage: php launch.php --help
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
use LocateBinaries\LocateBinaries;
|
|
||||||
|
|
||||||
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
|
|
||||||
echo 'Run `composer install` in the tests/ directory first.' . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
$options = getopt('o:vh', array(
|
|
||||||
'group:',
|
|
||||||
'output-dir:',
|
|
||||||
'stop-on-defect',
|
|
||||||
'verbose',
|
|
||||||
'help'
|
|
||||||
));
|
|
||||||
|
|
||||||
function printLaunchHelp()
|
|
||||||
{
|
|
||||||
echo 'Usage:' . PHP_EOL;
|
|
||||||
echo ' php launch.php [-chv] [-o <path>] [file...]' . PHP_EOL;
|
|
||||||
echo 'Description:' . PHP_EOL;
|
|
||||||
echo ' Launches the test suite for Tecnickcom\'s TCPDF.' . PHP_EOL;
|
|
||||||
echo 'Supported environment variables:' . PHP_EOL;
|
|
||||||
echo ' PHP_BINARY Path to php executable to use.' . PHP_EOL;
|
|
||||||
echo ' PDFINFO_BINARY Path to pdfinfo executable to use.' . PHP_EOL;
|
|
||||||
echo ' For more information on pdfinfo, visit https://www.xpdfreader.com/' . PHP_EOL;
|
|
||||||
echo 'Arguments:' . PHP_EOL;
|
|
||||||
echo ' file' . PHP_EOL;
|
|
||||||
echo ' Test file(s) to run. If not provided all the tests are considered for the run.' . PHP_EOL;
|
|
||||||
echo ' Usage example:' . PHP_EOL;
|
|
||||||
echo ' php launch.php example_001.php barcodes/example_1d_html.php' . PHP_EOL;
|
|
||||||
echo 'Options:' . PHP_EOL;
|
|
||||||
echo ' -c, --clean-up' . PHP_EOL;
|
|
||||||
echo ' Clean up generated files.' . PHP_EOL;
|
|
||||||
echo ' The default is to NOT clean up if the -o option is provided,' . PHP_EOL;
|
|
||||||
echo ' and to clean up if the -o option is NOT provided.' . PHP_EOL;
|
|
||||||
echo ' -o <path>, --output-dir=<path>' . PHP_EOL;
|
|
||||||
echo ' The folder in which files should be generated.' . PHP_EOL;
|
|
||||||
echo ' Default is to create a folder in the system\'s temporary folder.' . PHP_EOL;
|
|
||||||
echo ' --group=<name>' . PHP_EOL;
|
|
||||||
echo ' Filter the tests to run based on the @group annotation present in the file.' . PHP_EOL;
|
|
||||||
echo ' --stop-on-defect' . PHP_EOL;
|
|
||||||
echo ' Stop execution upon first not-passed test.' . PHP_EOL;
|
|
||||||
echo ' -v, --verbose' . PHP_EOL;
|
|
||||||
echo ' Outputs more information.' . PHP_EOL;
|
|
||||||
echo ' -h, --help' . PHP_EOL;
|
|
||||||
echo ' Prints this message.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $options
|
|
||||||
|| array_key_exists('h', $options)
|
|
||||||
|| array_key_exists('help', $options)) {
|
|
||||||
printLaunchHelp();
|
|
||||||
exit(false === $options ? -1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($options['o'])) {
|
|
||||||
$outputDir = $options['o'];
|
|
||||||
}
|
|
||||||
if (!empty($options['output-dir'])) {
|
|
||||||
$outputDir = $options['output-dir'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('c', $options) || array_key_exists('clean-up', $options)) {
|
|
||||||
$preserveOutputFiles = false;
|
|
||||||
} elseif (isset($outputDir)) {
|
|
||||||
$preserveOutputFiles = true;
|
|
||||||
} else {
|
|
||||||
$preserveOutputFiles = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stopOn = array();
|
|
||||||
if (array_key_exists('stop-on-defect', $options)) {
|
|
||||||
$stopOn[] = 'defect';
|
|
||||||
}
|
|
||||||
|
|
||||||
$verbose = array_key_exists('v', $options) || array_key_exists('verbose', $options);
|
|
||||||
|
|
||||||
$groups = array();
|
|
||||||
if (!empty($options['group'])) {
|
|
||||||
if (is_array($options['group'])) {
|
|
||||||
$groups = $options['group'];
|
|
||||||
} else {
|
|
||||||
$groups = explode(',', $options['group']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$isBinaryLocatorAvailable = class_exists('\LocateBinaries\LocateBinaries');
|
|
||||||
|
|
||||||
$pdfinfo = getenv('PDFINFO_BINARY');
|
|
||||||
if (empty($pdfinfo)) {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('pdfinfo')
|
|
||||||
: array();
|
|
||||||
if (empty($paths)) {
|
|
||||||
echo 'pdfinfo could not be located.' . PHP_EOL;
|
|
||||||
echo 'Please set the PDFINFO_BINARY environment variable.' . PHP_EOL;
|
|
||||||
if (!$isBinaryLocatorAvailable) {
|
|
||||||
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$pdfinfo = reset($paths);
|
|
||||||
}
|
|
||||||
$pdfTools = new PdfTools(array('pdfinfo' => $pdfinfo), $verbose);
|
|
||||||
echo 'pdfinfo: ' . $pdfinfo . PHP_EOL;
|
|
||||||
echo 'pdfinfo version: ' . $pdfTools->getPdfinfoVersionInfo() . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
// Allows you to use PHP_BINARY=/usr/bin/php5.3 php ./tests/launch.php
|
|
||||||
$phpBinary = getenv('PHP_BINARY');
|
|
||||||
if (empty($phpBinary)) {
|
|
||||||
// PHP_BINARY only exists since PHP 5.4
|
|
||||||
if (defined('PHP_BINARY')) {
|
|
||||||
$phpBinary = PHP_BINARY;
|
|
||||||
} else {
|
|
||||||
$paths = ($isBinaryLocatorAvailable)
|
|
||||||
? LocateBinaries::locateInstalledBinaries('php')
|
|
||||||
: array();
|
|
||||||
if (empty($paths)) {
|
|
||||||
echo 'php could not be located. Please set PHP_BINARY environment variable.' . PHP_EOL;
|
|
||||||
if (!$isBinaryLocatorAvailable) {
|
|
||||||
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
|
|
||||||
}
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$phpBinary = reset($paths);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$isWindows = (stripos(PHP_OS, 'WIN') === 0);
|
|
||||||
|
|
||||||
$phpExecutor = new PhpExecutor($phpBinary, $verbose);
|
|
||||||
|
|
||||||
echo 'PHP: ' . ((string)$phpExecutor) . PHP_EOL;
|
|
||||||
echo 'PHP version: ' . $phpExecutor->getPhpVersionInfo() . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of extension availability.
|
|
||||||
* Possible values:
|
|
||||||
* - true: built in PHP,
|
|
||||||
* - false: available,
|
|
||||||
* - null: not available (not detected).
|
|
||||||
*/
|
|
||||||
$phpExtensions = array(
|
|
||||||
'bcmath' => null,
|
|
||||||
'gd' => null,
|
|
||||||
'imagick' => null,
|
|
||||||
'json' => null,
|
|
||||||
'openssl' => null,
|
|
||||||
'xml' => null,
|
|
||||||
);
|
|
||||||
$phpExtensionDir = $phpExecutor->getPhpExtensionDir();
|
|
||||||
echo 'PHP extension folder: ' . $phpExtensionDir . PHP_EOL;
|
|
||||||
if (strpos($phpExtensionDir, ' ') !== false) {
|
|
||||||
echo "WARNING: Spaces in extension_dir might cause problems." . PHP_EOL;
|
|
||||||
if ($isWindows) {
|
|
||||||
echo " You should use `dir /x` to get the short name of the path," . PHP_EOL;
|
|
||||||
echo " then adjust the extension_dir option of your php.ini file." . PHP_EOL;
|
|
||||||
}
|
|
||||||
if (!in_array('defect', $stopOn, true)) {
|
|
||||||
$stopOn[] = 'defect';
|
|
||||||
echo " --stop-on-defect as been forced to avoid too many failing tests." . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo 'Extensions:' . PHP_EOL;
|
|
||||||
foreach ($phpExtensions as $extension => $_) {
|
|
||||||
$status = $phpExecutor->getExtensionStatus($extension);
|
|
||||||
$phpExtensions[$extension] = $status;
|
|
||||||
echo " $extension: ";
|
|
||||||
if (true === $status) {
|
|
||||||
echo 'BUILT-IN';
|
|
||||||
} elseif (false === $status) {
|
|
||||||
echo 'AVAILABLE';
|
|
||||||
} else {
|
|
||||||
echo 'NO';
|
|
||||||
}
|
|
||||||
echo PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $phpExtensions['gd'] && null === $phpExtensions['imagick']) {
|
|
||||||
echo 'gd or imagick extension required.' . PHP_EOL;
|
|
||||||
echo 'Exit code: 1' . PHP_EOL;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $phpExtensions['openssl']) {
|
|
||||||
echo 'openssl extension required.' . PHP_EOL;
|
|
||||||
echo 'Exit code: 1' . PHP_EOL;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
$rootDir = dirname(realpath(__DIR__)) . DIRECTORY_SEPARATOR;
|
|
||||||
echo "Root folder: $rootDir" . PHP_EOL;
|
|
||||||
|
|
||||||
$isGeneratedTempDir = false;
|
|
||||||
if (!isset($outputDir)) {
|
|
||||||
echo PHP_EOL;
|
|
||||||
echo "The --output-dir option was not used, a temporary folder will be necessary." . PHP_EOL;
|
|
||||||
try {
|
|
||||||
$outputDir = \Cs278\Mktemp\temporaryDir('TCPDF-tests.XXXXXXXXX') . DIRECTORY_SEPARATOR;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
echo $e->getMessage();
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$isGeneratedTempDir = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_dir(realpath($outputDir))) {
|
|
||||||
echo "Could not find output folder: $outputDir" . PHP_EOL;
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
$outputDir = realpath($outputDir);
|
|
||||||
echo "Output folder: $outputDir" . PHP_EOL;
|
|
||||||
echo PHP_EOL;
|
|
||||||
|
|
||||||
$testsDir = $rootDir . 'tests' . DIRECTORY_SEPARATOR;
|
|
||||||
echo "Test folder: $testsDir" . PHP_EOL;
|
|
||||||
|
|
||||||
$testExecutor = new TestExecutor(
|
|
||||||
$phpExecutor,
|
|
||||||
array_keys($phpExtensions),
|
|
||||||
$pdfTools,
|
|
||||||
$outputDir,
|
|
||||||
$testsDir,
|
|
||||||
$verbose
|
|
||||||
);
|
|
||||||
|
|
||||||
// Files that should be excluded from the test suite
|
|
||||||
$ignored = array(
|
|
||||||
'example_006.php',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if the script is run for specific test files
|
|
||||||
$requestedTests = array();
|
|
||||||
foreach (array_reverse($argv) as $value) {
|
|
||||||
// This is a crude way to work around how getopt() parses arguments to script
|
|
||||||
if (preg_match('~^(barcodes/)?example_\d[\d_a-z]+\.php$~', $value)) {
|
|
||||||
$requestedTests[] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$testRunner = new TestRunner($rootDir . 'examples');
|
|
||||||
$passed = $testRunner
|
|
||||||
->withTestExecutor($testExecutor)
|
|
||||||
->preserveOutputFiles($preserveOutputFiles)
|
|
||||||
->excludeTests($ignored)
|
|
||||||
->only($requestedTests)
|
|
||||||
->filterByGroup($groups)
|
|
||||||
->stopOn($stopOn)
|
|
||||||
->runTests($outputDir)
|
|
||||||
;
|
|
||||||
|
|
||||||
if (!$preserveOutputFiles && $isGeneratedTempDir) {
|
|
||||||
rmdir($outputDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final result
|
|
||||||
$testRunner->printSummary();
|
|
||||||
$exitCode = (!$passed) ? 1 : 0;
|
|
||||||
echo 'Exit code: ' . $exitCode . PHP_EOL;
|
|
||||||
exit($exitCode);
|
|
198
tests/launch.sh
198
tests/launch.sh
@ -1,198 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
|
|
||||||
command -v pdfinfo > /dev/null
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "pdfinfo could not be found"
|
|
||||||
echo "On Debian based systems you can run: apt install -y poppler-utils"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Only start here, the command checking can exit code > 0
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
EXAMPLE_FILES="$(find examples/ -type f -name 'example*.php' \
|
|
||||||
-not -path '*/barcodes/*' \
|
|
||||||
-not -wholename 'examples/example_006.php' \
|
|
||||||
| sort -df)"
|
|
||||||
|
|
||||||
EXAMPLE_BARCODE_FILES="$(find examples/barcodes -type f -name 'example*.php' \
|
|
||||||
| sort -df)"
|
|
||||||
|
|
||||||
TEMP_FOLDER="$(mktemp -d /tmp/TCPDF-tests.XXXXXXXXX)"
|
|
||||||
OUTPUT_FILE="${TEMP_FOLDER}/output.pdf"
|
|
||||||
OUTPUT_FILE_ERROR="${TEMP_FOLDER}/errors.txt"
|
|
||||||
# Allows you to use PHP_BINARY="php8.1" ./tests/launch.sh
|
|
||||||
PHP_BINARY="${PHP_BINARY:-php}"
|
|
||||||
ROOT_DIR="$(${PHP_BINARY} -r 'echo realpath(__DIR__);')"
|
|
||||||
TESTS_DIR="${ROOT_DIR}/tests/"
|
|
||||||
|
|
||||||
PHP_EXT_DIR="$(${PHP_BINARY} -r 'echo ini_get("extension_dir");')"
|
|
||||||
|
|
||||||
echo "php extension dir: ${PHP_EXT_DIR}"
|
|
||||||
|
|
||||||
BCMATH_EXT="-d extension=$(find ${PHP_EXT_DIR} -type f -name 'bcmath.so')"
|
|
||||||
echo "bcmath found at: ${BCMATH_EXT}"
|
|
||||||
|
|
||||||
COVERAGE_EXTENSION="-d extension=pcov.so"
|
|
||||||
IMAGICK_OR_GD="-dextension=gd.so"
|
|
||||||
JSON_EXT="-dextension=json.so"
|
|
||||||
XML_EXT="-dextension=xml.so"
|
|
||||||
if [ "$(${PHP_BINARY} -r 'echo PHP_MAJOR_VERSION;')" = "5" ];then
|
|
||||||
X_DEBUG_EXT="$(find ${PHP_EXT_DIR} -type f -name 'xdebug.so' || '')"
|
|
||||||
echo "Xdebug found at: ${X_DEBUG_EXT}"
|
|
||||||
# pcov does not exist for PHP 5
|
|
||||||
COVERAGE_EXTENSION="-d zend_extension=${X_DEBUG_EXT} -d xdebug.mode=coverage"
|
|
||||||
|
|
||||||
# 5.5, 5.4, 5.3
|
|
||||||
if [ "$(${PHP_BINARY} -r 'echo (PHP_MINOR_VERSION < 6) ? "true" : "false";')" = "true" ];then
|
|
||||||
IMAGICK_OR_GD="-dextension=imagick.so"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$(${PHP_BINARY} -r 'echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;')" = "70" ];then
|
|
||||||
X_DEBUG_EXT="$(find ${PHP_EXT_DIR} -type f -name 'xdebug.so' || '')"
|
|
||||||
echo "Xdebug found at: ${X_DEBUG_EXT}"
|
|
||||||
# pcov does not exist for PHP 7.0
|
|
||||||
COVERAGE_EXTENSION="-d zend_extension=${X_DEBUG_EXT} -d xdebug.mode=coverage"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# PHP >= 8.x.x
|
|
||||||
if [ "$(${PHP_BINARY} -r 'echo (PHP_MAJOR_VERSION >= 8) ? "true" : "false";')" = "true" ];then
|
|
||||||
# The json ext is bundled into PHP 8.0
|
|
||||||
JSON_EXT=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Root folder: ${ROOT_DIR}"
|
|
||||||
echo "Temporary folder: ${TEMP_FOLDER}"
|
|
||||||
echo "PHP version: $(${PHP_BINARY} -v)"
|
|
||||||
|
|
||||||
FAILED_FLAG=0
|
|
||||||
|
|
||||||
cd "${ROOT_DIR}/examples"
|
|
||||||
|
|
||||||
for file in $EXAMPLE_FILES; do
|
|
||||||
echo "File: $file"
|
|
||||||
${PHP_BINARY} -l "${ROOT_DIR}/$file" > /dev/null
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "File-lint-passed: $file"
|
|
||||||
fi
|
|
||||||
set +e
|
|
||||||
# Some examples load a bit more into memory (this is why the limit is set to 1G)
|
|
||||||
# Avoid side effects on classes installed on the system, set include_path to a folder wihout php classes (include_path)
|
|
||||||
${PHP_BINARY} -n \
|
|
||||||
-d include_path="${TEMP_FOLDER}" \
|
|
||||||
-d date.timezone=UTC \
|
|
||||||
${IMAGICK_OR_GD} ${COVERAGE_EXTENSION} \
|
|
||||||
${BCMATH_EXT} \
|
|
||||||
${JSON_EXT} \
|
|
||||||
${XML_EXT} \
|
|
||||||
-d display_errors=on \
|
|
||||||
-d error_reporting=-1 \
|
|
||||||
-d memory_limit=1G \
|
|
||||||
-d pcov.directory="${ROOT_DIR}" \
|
|
||||||
-d auto_prepend_file="${TESTS_DIR}/coverage.php" \
|
|
||||||
"${ROOT_DIR}/$file" 1> "${OUTPUT_FILE}" 2> "${OUTPUT_FILE_ERROR}"
|
|
||||||
set -e
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "File-run-passed: $file"
|
|
||||||
ERROR_LOGS="$(cat "${OUTPUT_FILE_ERROR}")"
|
|
||||||
if [ ! -z "${ERROR_LOGS}" ]; then
|
|
||||||
FAILED_FLAG=1
|
|
||||||
echo "Logs: $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${ERROR_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
fi
|
|
||||||
if [ $(head -c 4 "${OUTPUT_FILE}") != "%PDF" ]; then
|
|
||||||
FAILED_FLAG=1
|
|
||||||
# cut before the PDF output starts and destroys the final logs
|
|
||||||
OUT_LOGS="$(cat "${OUTPUT_FILE}" | sed '/%PDF/q')"
|
|
||||||
echo "Generated-not-a-pdf: $file"
|
|
||||||
echo "Logs (cut before PDF output eventually starts): $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${OUT_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
pdfinfo "${OUTPUT_FILE}" > /dev/null
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
FAILED_FLAG=1
|
|
||||||
echo "Generated-invalid-file: $file"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
FAILED_FLAG=1
|
|
||||||
echo "File-run-failed: $file"
|
|
||||||
ERROR_LOGS="$(cat "${OUTPUT_FILE_ERROR}")"
|
|
||||||
if [ ! -z "${ERROR_LOGS}" ]; then
|
|
||||||
echo "Logs: $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${ERROR_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
else
|
|
||||||
# cut before the PDF output starts and destroys the final logs
|
|
||||||
OUT_LOGS="$(cat "${OUTPUT_FILE}" | sed '/%PDF/q')"
|
|
||||||
echo "Logs: $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${OUT_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for file in $EXAMPLE_BARCODE_FILES; do
|
|
||||||
echo "File: $file"
|
|
||||||
${PHP_BINARY} -l "${ROOT_DIR}/$file" > /dev/null
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "File-lint-passed: $file"
|
|
||||||
fi
|
|
||||||
set +e
|
|
||||||
# Avoid side effects on classes installed on the system, set include_path to a folder wihout php classes (include_path)
|
|
||||||
${PHP_BINARY} -n \
|
|
||||||
-d include_path="${TEMP_FOLDER}" \
|
|
||||||
-d date.timezone=UTC \
|
|
||||||
${BCMATH_EXT} ${COVERAGE_EXTENSION} \
|
|
||||||
-d display_errors=on \
|
|
||||||
-d error_reporting=-1 \
|
|
||||||
-d pcov.directory="${ROOT_DIR}" \
|
|
||||||
-d auto_prepend_file="${TESTS_DIR}/coverage.php" \
|
|
||||||
"${ROOT_DIR}/$file" 1> "${OUTPUT_FILE}" 2> "${OUTPUT_FILE_ERROR}"
|
|
||||||
set -e
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "File-run-passed: $file"
|
|
||||||
ERROR_LOGS="$(cat "${OUTPUT_FILE_ERROR}")"
|
|
||||||
if [ ! -z "${ERROR_LOGS}" ]; then
|
|
||||||
FAILED_FLAG=1
|
|
||||||
echo "Logs: $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${ERROR_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
FAILED_FLAG=1
|
|
||||||
echo "File-run-failed: $file"
|
|
||||||
ERROR_LOGS="$(cat "${OUTPUT_FILE_ERROR}")"
|
|
||||||
if [ ! -z "${ERROR_LOGS}" ]; then
|
|
||||||
echo "Logs: $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${ERROR_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
fi
|
|
||||||
# cut before the PDF output starts and destroys the final logs
|
|
||||||
OUT_LOGS="$(cat "${OUTPUT_FILE}" | sed '/%PDF/q')"
|
|
||||||
if [ ! -z "${OUT_LOGS}" ]; then
|
|
||||||
echo "Logs (cut before PDF output eventually starts): $file"
|
|
||||||
echo "---------------------------"
|
|
||||||
echo "${OUT_LOGS}"
|
|
||||||
echo "---------------------------"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
cd - > /dev/null
|
|
||||||
|
|
||||||
rm -rf "${TEMP_FOLDER}"
|
|
||||||
|
|
||||||
echo "Exit code: ${FAILED_FLAG}"
|
|
||||||
exit "${FAILED_FLAG}"
|
|
@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to execute magick via shell
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
use LogicException;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class ImageMagick
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string|null Path to magick as shell argument
|
|
||||||
*/
|
|
||||||
private $magick = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $verbose;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $magickVersionInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $magick Path to ImageMagick `magick` executable
|
|
||||||
* @param bool $verbose
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
$magick,
|
|
||||||
$verbose = false
|
|
||||||
) {
|
|
||||||
$this->magick = escapeshellarg($magick);
|
|
||||||
$this->verbose = $verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string pdfinfo version information (multiline information)
|
|
||||||
* @throws LogicException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getMagickVersionInfo()
|
|
||||||
{
|
|
||||||
if (null === $this->magick) {
|
|
||||||
throw new LogicException('No path to magick. Provide it to ImageMagick PHP class constructor.');
|
|
||||||
}
|
|
||||||
if (null === $this->magickVersionInfo) {
|
|
||||||
$exec = sprintf('%s -version 2>&1', $this->magick);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if (0 !== $resultCode && 99 !== $resultCode) {
|
|
||||||
throw new RuntimeException('Execution failed: ' . $exec);
|
|
||||||
}
|
|
||||||
$this->magickVersionInfo = implode(PHP_EOL, $output);
|
|
||||||
}
|
|
||||||
return $this->magickVersionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file1 Path to image to compare
|
|
||||||
* @param string $file2 Path to image to compare
|
|
||||||
* @return bool
|
|
||||||
* @throws LogicException
|
|
||||||
*/
|
|
||||||
public function areSimilar($file1, $file2)
|
|
||||||
{
|
|
||||||
if (null === $this->magick) {
|
|
||||||
throw new LogicException('No path to magick. Provide it to ' . __CLASS__ . ' PHP class constructor.');
|
|
||||||
}
|
|
||||||
$exec = implode(' ', array(
|
|
||||||
$this->magick,
|
|
||||||
'compare',
|
|
||||||
'-metric MAE',
|
|
||||||
escapeshellarg($file1),
|
|
||||||
escapeshellarg($file2),
|
|
||||||
'null:',
|
|
||||||
' 2>&1',
|
|
||||||
));
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
$result = implode(PHP_EOL, $output);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $result . PHP_EOL;
|
|
||||||
}
|
|
||||||
if (0 !== $resultCode) {
|
|
||||||
if (!preg_match('/^[-0-9.e]+\s+\([-0-9.e]+\)$/', $result)) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
'An error occurred with magick compare command' . PHP_EOL
|
|
||||||
. $result,
|
|
||||||
$resultCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '0 (0)' === $result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to execute PDF-related commands via shell
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
use LogicException;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class PdfTools
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string|null Path to pdfinfo as shell argument
|
|
||||||
*/
|
|
||||||
private $pdfinfo = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null Path to pdftopng as shell argument
|
|
||||||
*/
|
|
||||||
private $pdftopng = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string|null Path to pdftoppm as shell argument
|
|
||||||
*/
|
|
||||||
private $pdftoppm = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $verbose;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $pdfinfoVersionInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $pdftopngVersionInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $tools Path to PDF tool executables (indexed by tool name)
|
|
||||||
* @param bool $verbose
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
array $tools,
|
|
||||||
$verbose = false
|
|
||||||
) {
|
|
||||||
if (!empty($tools['pdfinfo'])) {
|
|
||||||
$this->pdfinfo = escapeshellarg($tools['pdfinfo']);
|
|
||||||
}
|
|
||||||
if (!empty($tools['pdftopng'])) {
|
|
||||||
$this->pdftopng = escapeshellarg($tools['pdftopng']);
|
|
||||||
}
|
|
||||||
if (!empty($tools['pdftoppm'])) {
|
|
||||||
$this->pdftoppm = escapeshellarg($tools['pdftoppm']);
|
|
||||||
}
|
|
||||||
$this->verbose = $verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string pdfinfo version information (multiline information)
|
|
||||||
* @throws LogicException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getPdfinfoVersionInfo()
|
|
||||||
{
|
|
||||||
if (null === $this->pdfinfo) {
|
|
||||||
throw new LogicException('No path to pdfinfo. Provide it to ' . __CLASS__ . ' PHP class constructor.');
|
|
||||||
}
|
|
||||||
if (null === $this->pdfinfoVersionInfo) {
|
|
||||||
$exec = sprintf('%s -v 2>&1', $this->pdfinfo);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if (0 !== $resultCode && 99 !== $resultCode) {
|
|
||||||
throw new RuntimeException('Execution failed: ' . $exec);
|
|
||||||
}
|
|
||||||
$this->pdfinfoVersionInfo = implode(PHP_EOL, $output);
|
|
||||||
}
|
|
||||||
return $this->pdfinfoVersionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string pdftopng or pdftoppm version information (multiline information)
|
|
||||||
* @throws LogicException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getPdftopngVersionInfo()
|
|
||||||
{
|
|
||||||
$tool = $this->pdftopng ?: $this->pdftoppm;
|
|
||||||
if (null === $tool) {
|
|
||||||
throw new LogicException('No path to pdftopng not pdftoppm. Provide it to ' . __CLASS__ . ' PHP class constructor.');
|
|
||||||
}
|
|
||||||
if (null === $this->pdftopngVersionInfo) {
|
|
||||||
$exec = sprintf('%s -v 2>&1', $tool);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if (0 !== $resultCode && 99 !== $resultCode) {
|
|
||||||
throw new RuntimeException('Execution failed: ' . $exec);
|
|
||||||
}
|
|
||||||
$this->pdftopngVersionInfo = implode(PHP_EOL, $output);
|
|
||||||
}
|
|
||||||
return $this->pdftopngVersionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file Path of file to check
|
|
||||||
* @return bool
|
|
||||||
* @throws LogicException
|
|
||||||
*/
|
|
||||||
public function isPdf($file)
|
|
||||||
{
|
|
||||||
if (null === $this->pdfinfo) {
|
|
||||||
throw new LogicException('No path to pdfinfo. Provide it to ' . __CLASS__ . ' PHP class constructor.');
|
|
||||||
}
|
|
||||||
$exec = implode(' ', array(
|
|
||||||
$this->pdfinfo,
|
|
||||||
escapeshellarg($file)
|
|
||||||
));
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo implode(PHP_EOL, $output) . PHP_EOL;
|
|
||||||
}
|
|
||||||
return (0 === $resultCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureFolder($path)
|
|
||||||
{
|
|
||||||
if (file_exists($path) && is_dir($path) && is_writable($path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mkdir($path, 0775, true)) {
|
|
||||||
throw new RuntimeException('Could not create folder: ' . $path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file The path of the PDF document to convert into PNG
|
|
||||||
* @param string $pngRoot The root of the generated PNG file names.
|
|
||||||
* Example: if <code>$pngRoot = '/usr/home/TCPDF/compare_runs/my-root'</code>,
|
|
||||||
* the generated PNG files will be as follows:
|
|
||||||
* <ul>
|
|
||||||
* <li><code>/usr/home/TCPDF/compare_runs/my-root-0000001.png</code>,</li>
|
|
||||||
* <li><code>/usr/home/TCPDF/compare_runs/my-root-0000002.png</code>,</li>
|
|
||||||
* <li>...</li>
|
|
||||||
* </ul>
|
|
||||||
* @return string[] List of paths for generated PNG (one per page)
|
|
||||||
* @throws LogicException
|
|
||||||
*/
|
|
||||||
public function convertToPng($file, $pngRoot)
|
|
||||||
{
|
|
||||||
if ($this->pdftopng) {
|
|
||||||
$tool = $this->pdftopng;
|
|
||||||
} elseif ($this->pdftoppm) {
|
|
||||||
// When using pdftoppm, we specify the `-png` option to get PNG files
|
|
||||||
$tool = $this->pdftoppm . ' -png';
|
|
||||||
}
|
|
||||||
if (!isset($tool)) {
|
|
||||||
throw new LogicException('No path to pdftopng nor pdftoppm. Provide it to ' . __CLASS__ . ' PHP class constructor.');
|
|
||||||
}
|
|
||||||
$this->ensureFolder(dirname($pngRoot));
|
|
||||||
$exec = implode(' ', array(
|
|
||||||
$tool,
|
|
||||||
escapeshellarg($file),
|
|
||||||
escapeshellarg($pngRoot),
|
|
||||||
' 2>&1',
|
|
||||||
));
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo implode(PHP_EOL, $output) . PHP_EOL;
|
|
||||||
}
|
|
||||||
if (0 !== $resultCode) {
|
|
||||||
throw new RuntimeException(implode(PHP_EOL, $output));
|
|
||||||
}
|
|
||||||
$generatedFiles = glob($pngRoot . '*.png');
|
|
||||||
if (false === $generatedFiles) {
|
|
||||||
throw new RuntimeException('Could not get the list of generated PNG files.');
|
|
||||||
}
|
|
||||||
return $generatedFiles;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,281 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to execute PHP via shell
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class PhpExecutor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string PHP executable as a shell argument
|
|
||||||
*/
|
|
||||||
private $phpShell;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $extensionDir = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $phpVersion = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $phpVersionInfo = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool[]
|
|
||||||
*/
|
|
||||||
private $builtInExtensions = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var mixed[]
|
|
||||||
*/
|
|
||||||
private $extensionStatuses = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $verbose;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $php Path to PHP executable
|
|
||||||
* @param bool $verbose
|
|
||||||
*/
|
|
||||||
public function __construct($php, $verbose = false)
|
|
||||||
{
|
|
||||||
$this->phpShell = escapeshellarg($php);
|
|
||||||
$this->verbose = $verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return $this->phpShell;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $command PHP command to execute
|
|
||||||
* @param string|null $cliOptions MUST be properly escaped for the shell
|
|
||||||
* @return string Result of the command
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function executeCommand($command, $cliOptions = null)
|
|
||||||
{
|
|
||||||
$exec = sprintf(
|
|
||||||
'%s %s -r %s',
|
|
||||||
$this->phpShell,
|
|
||||||
$cliOptions,
|
|
||||||
escapeshellarg($command)
|
|
||||||
);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if (0 !== $resultCode) {
|
|
||||||
throw new RuntimeException('Execution failed: ' . $exec);
|
|
||||||
}
|
|
||||||
return $output[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string PHP version information (multiline information)
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getPhpVersionInfo()
|
|
||||||
{
|
|
||||||
if (null === $this->phpVersionInfo) {
|
|
||||||
$exec = sprintf('%s -n -v', $this->phpShell);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
if (0 !== $resultCode) {
|
|
||||||
throw new RuntimeException('Execution failed: ' . $exec);
|
|
||||||
}
|
|
||||||
$this->phpVersionInfo = implode(PHP_EOL, $output);
|
|
||||||
}
|
|
||||||
return $this->phpVersionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int PHP version as a integer
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getPhpVersion()
|
|
||||||
{
|
|
||||||
if (null === $this->phpVersion) {
|
|
||||||
$this->phpVersion = (int)$this->executeCommand('echo PHP_VERSION_ID;');
|
|
||||||
}
|
|
||||||
return $this->phpVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function getPhpExtensionDir()
|
|
||||||
{
|
|
||||||
if (null === $this->extensionDir) {
|
|
||||||
$this->extensionDir = $this->executeCommand("echo ini_get('extension_dir');");
|
|
||||||
}
|
|
||||||
return $this->extensionDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isWindows()
|
|
||||||
{
|
|
||||||
return stripos(PHP_OS, 'WIN') === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $extension Extension name (without .so, .dll, or "php_" prefix)
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function makePhpExtensionFileName($extension)
|
|
||||||
{
|
|
||||||
if ($this->isWindows()) {
|
|
||||||
if ('gd' === $extension && $this->getPhpVersion() < 80000) {
|
|
||||||
$extension = 'gd2';
|
|
||||||
}
|
|
||||||
return "php_$extension.dll";
|
|
||||||
}
|
|
||||||
return $extension . '.so';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $path
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function escapePath($path)
|
|
||||||
{
|
|
||||||
if ($this->isWindows()) {
|
|
||||||
// if ($this->getPhpVersion() < 50400) {
|
|
||||||
// $path = str_replace('\\', '/', $path);
|
|
||||||
// }
|
|
||||||
if (strpos($path, '~') !== false) {
|
|
||||||
return "'" . $path . "'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (strpos($path, ' ') !== false) {
|
|
||||||
return "'" . $path . "'";
|
|
||||||
}
|
|
||||||
return escapeshellarg((string)$path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $path
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function escapePathForCliOption($path)
|
|
||||||
{
|
|
||||||
if ($this->isWindows()) {
|
|
||||||
if ($this->getPhpVersion() < 50400) {
|
|
||||||
$path = str_replace('\\', '/', $path);
|
|
||||||
return "'" . $path . "'";
|
|
||||||
}
|
|
||||||
if (strpos($path, '~') !== false) {
|
|
||||||
return "'" . $path . "'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (strpos($path, ' ') !== false) {
|
|
||||||
return "'" . $path . "'";
|
|
||||||
}
|
|
||||||
return (string)$path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @param string $value
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function makeCliOption($name, $value)
|
|
||||||
{
|
|
||||||
switch ($name) {
|
|
||||||
case 'auto_prepend_file':
|
|
||||||
case 'extension_dir':
|
|
||||||
case 'include_path':
|
|
||||||
$arg = $this->escapePathForCliOption($value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$arg = escapeshellarg($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf('-d %s=%s', $name, $arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $extension Extension name (without .so, .dll, or "php_" prefix)
|
|
||||||
* @return bool Whether the extension is available for loading into PHP
|
|
||||||
*/
|
|
||||||
public function isExtensionAvailable($extension)
|
|
||||||
{
|
|
||||||
$extensionLib = $this->makePhpExtensionFileName($extension);
|
|
||||||
return file_exists($this->getPhpExtensionDir() . DIRECTORY_SEPARATOR . $extensionLib);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $extension Extension name (without .so, .dll, or "php_" prefix)
|
|
||||||
* @return bool Whether the extension is built in PHP
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function isExtensionBuiltIn($extension)
|
|
||||||
{
|
|
||||||
if (!isset($this->builtInExtensions[$extension])) {
|
|
||||||
$this->builtInExtensions[$extension] = (bool)$this->executeCommand("echo extension_loaded('$extension') ? 1 : 0;", '-n');
|
|
||||||
}
|
|
||||||
return $this->builtInExtensions[$extension];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $extension Extension name (without .so, .dll, or "php_" prefix)
|
|
||||||
* @return bool|null
|
|
||||||
* <ul>
|
|
||||||
* <li>true: built in PHP,</li>
|
|
||||||
* <li>false: available,</li>
|
|
||||||
* <li>null: not available (not detected).</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public function getExtensionStatus($extension)
|
|
||||||
{
|
|
||||||
if (!array_key_exists($extension, $this->extensionStatuses)) {
|
|
||||||
if ($this->isExtensionBuiltIn($extension)) {
|
|
||||||
$this->extensionStatuses[$extension] = true;
|
|
||||||
} elseif ($this->isExtensionAvailable($extension)) {
|
|
||||||
$this->extensionStatuses[$extension] = false;
|
|
||||||
} else {
|
|
||||||
$this->extensionStatuses[$extension] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->extensionStatuses[$extension];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file Path to file to lint
|
|
||||||
* @return bool Whether the PHP syntax of the file is okay
|
|
||||||
*/
|
|
||||||
public function isValidPhpFile($file)
|
|
||||||
{
|
|
||||||
$exec = sprintf(
|
|
||||||
'%s -l %s',
|
|
||||||
$this->phpShell,
|
|
||||||
$this->escapePath($file)
|
|
||||||
);
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
return (0 === $resultCode);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,255 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to execute TCPDF test
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
class TestExecutor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var PhpExecutor
|
|
||||||
*/
|
|
||||||
private $phpExecutor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $tempDir;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $testsDir;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $cliOptions = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $cliExtensionOptions = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
private $extensionsToLoad;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PdfTools
|
|
||||||
*/
|
|
||||||
private $pdfTools;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $verbose;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param PhpExecutor $phpExecutor
|
|
||||||
* @param array $extensionsToLoad
|
|
||||||
* @param PdfTools $pdfTools
|
|
||||||
* @param string $tempDir Path to tho temporary folder
|
|
||||||
* @param string $testsDir Path to this folder
|
|
||||||
* @param bool $verbose
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
PhpExecutor $phpExecutor,
|
|
||||||
array $extensionsToLoad,
|
|
||||||
PdfTools $pdfTools,
|
|
||||||
$tempDir,
|
|
||||||
$testsDir,
|
|
||||||
$verbose = false
|
|
||||||
) {
|
|
||||||
$this->phpExecutor = $phpExecutor;
|
|
||||||
$this->tempDir = $tempDir;
|
|
||||||
$this->testsDir = $testsDir;
|
|
||||||
$this->extensionsToLoad = $extensionsToLoad;
|
|
||||||
$this->pdfTools = $pdfTools;
|
|
||||||
$this->verbose = $verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
private function extensionsToLoad()
|
|
||||||
{
|
|
||||||
$toLoad = array();
|
|
||||||
foreach ($this->extensionsToLoad as $extension) {
|
|
||||||
// "false" means "not built-in but available"
|
|
||||||
if ($this->phpExecutor->getExtensionStatus($extension) === false) {
|
|
||||||
$toLoad[] = $extension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $toLoad;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file Path of PHP file to execute
|
|
||||||
* @param string $outputFile Path to output file
|
|
||||||
* @param string $outputFileError Path to output file error
|
|
||||||
* @return bool TRUE if successful, FALSE otherwise
|
|
||||||
*/
|
|
||||||
public function execute(
|
|
||||||
$file,
|
|
||||||
$outputFile,
|
|
||||||
$outputFileError
|
|
||||||
) {
|
|
||||||
$exec = implode(' ', array(
|
|
||||||
(string)$this->phpExecutor,
|
|
||||||
$this->getPhpCliOptions(),
|
|
||||||
'-f ' . escapeshellarg($file),
|
|
||||||
'1> ' . escapeshellarg($outputFile),
|
|
||||||
'2> ' . escapeshellarg($outputFileError)
|
|
||||||
));
|
|
||||||
if ($this->verbose) {
|
|
||||||
echo $exec . PHP_EOL;
|
|
||||||
}
|
|
||||||
exec($exec, $output, $resultCode);
|
|
||||||
return (0 === $resultCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $file
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function assertIsPhpValidFile($file)
|
|
||||||
{
|
|
||||||
return $this->phpExecutor->isValidPhpFile($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $outputFile Path to output file
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isPdfFile($outputFile)
|
|
||||||
{
|
|
||||||
return $this->pdfTools->isPdf($outputFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $outputFile Path to output file
|
|
||||||
* @param string $outputFileError Path to error file
|
|
||||||
* @param string $type Expected type of output
|
|
||||||
* @param bool $preservingFiles
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function assertIsFileType(
|
|
||||||
$outputFile,
|
|
||||||
$outputFileError,
|
|
||||||
$type,
|
|
||||||
$preservingFiles
|
|
||||||
) {
|
|
||||||
$valid = false;
|
|
||||||
|
|
||||||
$expectedHead = array(
|
|
||||||
'PDF' => '%PDF',
|
|
||||||
'PNG' => chr(0x89) . chr(0x50) . chr(0x4e) . chr(0x47),
|
|
||||||
'HTML' => '<div ',
|
|
||||||
'SVG' => '<?xml version="1.0" standalone="no"?>',
|
|
||||||
);
|
|
||||||
|
|
||||||
$error = file_get_contents($outputFileError);
|
|
||||||
$outputHead = file_get_contents($outputFile, false, null, 0, strlen($expectedHead[$type]));
|
|
||||||
|
|
||||||
if ($error || '' === $outputHead || false === $outputHead) {
|
|
||||||
echo " Output: NOT $type FILE" . PHP_EOL;
|
|
||||||
if ($preservingFiles) {
|
|
||||||
echo ' Output file: ' . $outputFile . PHP_EOL;
|
|
||||||
echo ' Output error file: ' . $outputFileError . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo ' Logs:' . PHP_EOL;
|
|
||||||
echo '---------------------------' . PHP_EOL;
|
|
||||||
echo $error . PHP_EOL;
|
|
||||||
echo '---------------------------' . PHP_EOL;
|
|
||||||
} elseif ($expectedHead[$type] !== $outputHead) {
|
|
||||||
echo " Output: NOT $type FILE" . PHP_EOL;
|
|
||||||
if ($preservingFiles) {
|
|
||||||
echo ' Output file: ' . $outputFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo ' Logs:' . PHP_EOL;
|
|
||||||
echo '---------------------------' . PHP_EOL;
|
|
||||||
// cut before the output starts and destroys the final logs
|
|
||||||
$output = file_get_contents($outputFile);
|
|
||||||
$headMarker = strpos($output, $expectedHead[$type]);
|
|
||||||
if (false !== $headMarker) {
|
|
||||||
echo substr($output, 0, $headMarker) . PHP_EOL;
|
|
||||||
} else {
|
|
||||||
echo $output . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo '---------------------------' . PHP_EOL;
|
|
||||||
} elseif ('PDF' === $type && !$this->isPdfFile($outputFile)) {
|
|
||||||
echo " Output: NOT PDF FILE" . PHP_EOL;
|
|
||||||
if ($preservingFiles) {
|
|
||||||
echo ' Output file: ' . $outputFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$valid = true;
|
|
||||||
echo " Output: $type" . PHP_EOL;
|
|
||||||
if ($preservingFiles) {
|
|
||||||
echo ' Output file: ' . $outputFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getPhpExtensionCliOptions()
|
|
||||||
{
|
|
||||||
if (null === $this->cliExtensionOptions) {
|
|
||||||
$extensions = array();
|
|
||||||
foreach ($this->extensionsToLoad() as $extension) {
|
|
||||||
$extensions[] = '-d extension=' . $this->phpExecutor->makePhpExtensionFileName($extension);
|
|
||||||
}
|
|
||||||
$this->cliExtensionOptions = implode(' ', $extensions);
|
|
||||||
}
|
|
||||||
return $this->cliExtensionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getPhpCliOptions()
|
|
||||||
{
|
|
||||||
if (null === $this->cliOptions) {
|
|
||||||
// Some examples load a bit more into memory (this is why the limit is set to 1G)
|
|
||||||
// Avoid side effects on classes installed on the system, set include_path to
|
|
||||||
// a folder without php classes (include_path)
|
|
||||||
|
|
||||||
$extensionDir = $this->phpExecutor->makeCliOption(
|
|
||||||
'extension_dir',
|
|
||||||
$this->phpExecutor->getPhpExtensionDir()
|
|
||||||
);
|
|
||||||
$includePath = $this->phpExecutor->makeCliOption(
|
|
||||||
'include_path',
|
|
||||||
$this->tempDir
|
|
||||||
);
|
|
||||||
$autoPrependFile = $this->phpExecutor->makeCliOption(
|
|
||||||
'auto_prepend_file',
|
|
||||||
$this->testsDir . 'coverage.php'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->cliOptions = implode(' ', array(
|
|
||||||
'-n',
|
|
||||||
'-d date.timezone=UTC',
|
|
||||||
'-d display_errors=on',
|
|
||||||
'-d error_reporting=-1',
|
|
||||||
'-d memory_limit=1G',
|
|
||||||
$includePath,
|
|
||||||
$extensionDir,
|
|
||||||
$this->getPhpExtensionCliOptions(),
|
|
||||||
$autoPrependFile,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return $this->cliOptions;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,379 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to execute the TCPDF test suite
|
|
||||||
*
|
|
||||||
* @author Philippe Jausions
|
|
||||||
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Tecnickcom\TCPDF\Tests;
|
|
||||||
|
|
||||||
class TestRunner
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $exampleDir;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var TestExecutor
|
|
||||||
*/
|
|
||||||
private $testExecutor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
private $excludedFiles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $preserveFiles = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $failed = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $generatedFiles = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $ignored = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array|null
|
|
||||||
*/
|
|
||||||
private $onlyFiles = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test count
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $count;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $stopOn = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int|null
|
|
||||||
*/
|
|
||||||
private $runTime = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array|null
|
|
||||||
*/
|
|
||||||
private $groups = null;
|
|
||||||
|
|
||||||
public function __construct($exampleDir)
|
|
||||||
{
|
|
||||||
$this->exampleDir = rtrim($exampleDir, '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TestExecutor $testExecutor
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function withTestExecutor(TestExecutor $testExecutor)
|
|
||||||
{
|
|
||||||
$this->testExecutor = $testExecutor;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param bool $preserve
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function preserveOutputFiles($preserve = true)
|
|
||||||
{
|
|
||||||
$this->preserveFiles = (bool)$preserve;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $excluded
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function excludeTests(array $excluded)
|
|
||||||
{
|
|
||||||
$this->excludedFiles = $excluded;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $files
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function only(array $files)
|
|
||||||
{
|
|
||||||
$this->onlyFiles = empty($files) ? null : $files;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $groups
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function filterByGroup(array $groups)
|
|
||||||
{
|
|
||||||
$this->groups = (empty($groups)) ? null : $groups;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string[] $conditions
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function stopOn(array $conditions)
|
|
||||||
{
|
|
||||||
$this->stopOn = $conditions;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $condition
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function shouldStopOn($condition)
|
|
||||||
{
|
|
||||||
if (in_array('defect', $this->stopOn, true)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (in_array($condition, $this->stopOn, true)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getTestFiles()
|
|
||||||
{
|
|
||||||
chdir($this->exampleDir);
|
|
||||||
$exampleFiles = array_flip(glob('example*.php'));
|
|
||||||
$exampleFiles = array_flip($exampleFiles);
|
|
||||||
|
|
||||||
$exampleBarcodeFiles = glob('barcodes/example*.php');
|
|
||||||
|
|
||||||
$files = array();
|
|
||||||
foreach ($exampleFiles as $exampleFile) {
|
|
||||||
$files[$exampleFile] = 'PDF';
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($exampleBarcodeFiles as $exampleFile) {
|
|
||||||
$type = preg_replace('/^.+(html|png|svgi?)$/', '\1', basename($exampleFile, '.php'));
|
|
||||||
if ('svgi' === $type) {
|
|
||||||
$files[$exampleFile] = 'SVG';
|
|
||||||
} else {
|
|
||||||
$files[$exampleFile] = strtoupper($type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $this->onlyFiles) {
|
|
||||||
foreach ($files as $file => $type) {
|
|
||||||
if (!in_array($file, $this->onlyFiles, true)) {
|
|
||||||
unset($files[$file]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $this->groups) {
|
|
||||||
$regExp = '/\*\s*@group\s+(' . implode('|', $this->groups) . ')\s/';
|
|
||||||
foreach ($files as $file => $type) {
|
|
||||||
$source = file_get_contents($file);
|
|
||||||
if (!preg_match($regExp, $source)) {
|
|
||||||
unset($files[$file]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $files;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $outputDir Path to output folder
|
|
||||||
* @return bool TRUE if all tests passed, FALSE otherwise
|
|
||||||
*/
|
|
||||||
public function runTests($outputDir)
|
|
||||||
{
|
|
||||||
if (!isset($this->testExecutor)) {
|
|
||||||
throw new \RuntimeException("Test executor is missing. Did you forget to call withTestExecutor()?");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->runTime = null;
|
|
||||||
$startTime = time();
|
|
||||||
|
|
||||||
$outputFolder = rtrim($outputDir, '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
chdir($this->exampleDir);
|
|
||||||
|
|
||||||
$this->failed = array();
|
|
||||||
$this->ignored = array();
|
|
||||||
$this->generatedFiles = array();
|
|
||||||
$this->count = 0;
|
|
||||||
foreach ($this->getTestFiles() as $file => $type) {
|
|
||||||
++$this->count;
|
|
||||||
echo 'File: ' . $file . PHP_EOL;
|
|
||||||
if (in_array($file, $this->excludedFiles, true)) {
|
|
||||||
echo ' Run: SKIPPED' . PHP_EOL;
|
|
||||||
$this->ignored[] = $file;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$this->testExecutor->assertIsPhpValidFile($this->exampleDir . $file)) {
|
|
||||||
echo ' Lint: FAILED' . PHP_EOL;
|
|
||||||
$this->failed[] = $file;
|
|
||||||
if ($this->shouldStopOn('failure')) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
echo ' Lint: PASSED' . PHP_EOL;
|
|
||||||
|
|
||||||
if (!$this->preserveFiles) {
|
|
||||||
$outputFile = $outputFolder . 'output.pdf';
|
|
||||||
$outputFileError = $outputFolder . 'errors.txt';
|
|
||||||
} else {
|
|
||||||
$baseName = $outputFolder . basename($file, '.php');
|
|
||||||
$outputFile = $baseName . '.output.' . strtolower($type);
|
|
||||||
$outputFileError = $baseName . '.errors.txt';
|
|
||||||
}
|
|
||||||
$this->generatedFiles[$outputFile] = $file;
|
|
||||||
$this->generatedFiles[$outputFileError] = $file;
|
|
||||||
|
|
||||||
$isSuccess = $this->testExecutor->execute(
|
|
||||||
$this->exampleDir . $file,
|
|
||||||
$outputFile,
|
|
||||||
$outputFileError
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$isSuccess) {
|
|
||||||
echo ' Run: FAILED' . PHP_EOL;
|
|
||||||
$this->failed[] = $file;
|
|
||||||
$this->testExecutor->assertIsFileType(
|
|
||||||
$outputFile,
|
|
||||||
$outputFileError,
|
|
||||||
$type,
|
|
||||||
$this->preserveFiles
|
|
||||||
);
|
|
||||||
if ($this->shouldStopOn('failure')) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo ' Run: PASSED' . PHP_EOL;
|
|
||||||
$isFileType = $this->testExecutor->assertIsFileType(
|
|
||||||
$outputFile,
|
|
||||||
$outputFileError,
|
|
||||||
$type,
|
|
||||||
$this->preserveFiles
|
|
||||||
);
|
|
||||||
if (!$isFileType) {
|
|
||||||
$this->failed[] = $file;
|
|
||||||
if ($this->shouldStopOn('failure')) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->runTime = time() - $startTime;
|
|
||||||
|
|
||||||
$this->cleanUp();
|
|
||||||
return empty($this->failed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getFailedTests()
|
|
||||||
{
|
|
||||||
return $this->failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getGeneratedFiles()
|
|
||||||
{
|
|
||||||
return array_keys($this->generatedFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getSkippedTests()
|
|
||||||
{
|
|
||||||
return $this->ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getTotalTestCount()
|
|
||||||
{
|
|
||||||
return $this->count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get last run time in seconds
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
public function getRunTime()
|
|
||||||
{
|
|
||||||
return $this->runTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function cleanUp()
|
|
||||||
{
|
|
||||||
if ($this->preserveFiles) {
|
|
||||||
echo 'Generated files remaining on disk: ' . count($this->generatedFiles) . PHP_EOL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->getGeneratedFiles() as $generatedFile) {
|
|
||||||
if (file_exists($generatedFile)) {
|
|
||||||
unlink($generatedFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputs a summary of the last test suite run
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function printSummary()
|
|
||||||
{
|
|
||||||
$failed = $this->getFailedTests();
|
|
||||||
$ignored = $this->getSkippedTests();
|
|
||||||
$failedCount = count($failed);
|
|
||||||
$ignoredCount = count($ignored);
|
|
||||||
if ($failedCount === 0) {
|
|
||||||
echo 'Test suite: PASSED' . PHP_EOL;
|
|
||||||
} else {
|
|
||||||
echo 'Test suite: FAILED' . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo ' Runtime: ' . $this->getRunTime() . 's' . PHP_EOL;
|
|
||||||
echo ' Total tests: ' . $this->getTotalTestCount() . PHP_EOL;
|
|
||||||
if ($ignoredCount > 0) {
|
|
||||||
echo ' SKipped tests: ' . $ignoredCount . PHP_EOL;
|
|
||||||
foreach ($ignored as $ignoredFile) {
|
|
||||||
echo ' ' . $ignoredFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($failedCount > 0) {
|
|
||||||
echo ' Failed tests: ' . $failedCount . PHP_EOL;
|
|
||||||
foreach ($failed as $failedFile) {
|
|
||||||
echo ' ' . $failedFile . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
@ -70,7 +70,7 @@
|
|||||||
<type>library</type>
|
<type>library</type>
|
||||||
<client>site</client>
|
<client>site</client>
|
||||||
<folder></folder>
|
<folder></folder>
|
||||||
<version>6.4.1</version>
|
<version>6.6.2</version>
|
||||||
<infourl title="TCPDF for Joomla!">https://github.com/vdm-io/tcpdf</infourl>
|
<infourl title="TCPDF for Joomla!">https://github.com/vdm-io/tcpdf</infourl>
|
||||||
<downloads>
|
<downloads>
|
||||||
<downloadurl type="full" format="zip">https://github.com/vdm-io/tcpdf/archive/v6.6.2.zip</downloadurl>
|
<downloadurl type="full" format="zip">https://github.com/vdm-io/tcpdf/archive/v6.6.2.zip</downloadurl>
|
||||||
@ -79,4 +79,20 @@
|
|||||||
<maintainerurl>https://github.com/llewellynvdm</maintainerurl>
|
<maintainerurl>https://github.com/llewellynvdm</maintainerurl>
|
||||||
<targetplatform name="joomla" version="3.*"/>
|
<targetplatform name="joomla" version="3.*"/>
|
||||||
</update>
|
</update>
|
||||||
|
<update>
|
||||||
|
<name>TCPDF</name>
|
||||||
|
<description>This is a PHP class for generating PDF documents without requiring external extensions.</description>
|
||||||
|
<element>tcpdf</element>
|
||||||
|
<type>library</type>
|
||||||
|
<client>site</client>
|
||||||
|
<folder></folder>
|
||||||
|
<version>6.6.5</version>
|
||||||
|
<infourl title="TCPDF for Joomla!">https://github.com/vdm-io/tcpdf</infourl>
|
||||||
|
<downloads>
|
||||||
|
<downloadurl type="full" format="zip">https://github.com/vdm-io/tcpdf/archive/v6.6.5.zip</downloadurl>
|
||||||
|
</downloads>
|
||||||
|
<maintainer>Llewellyn van der Merwe</maintainer>
|
||||||
|
<maintainerurl>https://github.com/llewellynvdm</maintainerurl>
|
||||||
|
<targetplatform name="joomla" version="3\.*|4\.[01234]|5\.0"/>
|
||||||
|
</update>
|
||||||
</updates>
|
</updates>
|
||||||
|
Loading…
Reference in New Issue
Block a user