├── tmp
└── .gitignore
├── .github
├── CODEOWNERS
├── config.yml
├── dependabot.yml
└── workflows
│ ├── lint-php-cs.yml
│ ├── psalm.yml
│ └── phpunit.yml
├── tests
├── resources
│ ├── pdf-test.pdf
│ ├── certificado.pfx
│ └── pdf-error-test.pdf
├── psalm-baseline.xml
├── Builder
│ └── JSignParamBuilder.php
├── Runtime
│ └── JavaRuntimeServiceTest.php
└── JSignPDFTest.php
├── .gitignore
├── vendor-bin
├── coding-standard
│ └── composer.json
├── psalm
│ └── composer.json
└── phpunit
│ ├── composer.json
│ └── composer.lock
├── phpunit.xml
├── .editorconfig
├── .php-cs-fixer.dist.php
├── example
└── index.php
├── psalm.xml
├── src
├── JSignPDF.php
├── JSignFileService.php
├── Runtime
│ ├── JSignPdfRuntimeService.php
│ └── JavaRuntimeService.php
└── Sign
│ ├── JSignParam.php
│ └── JSignService.php
├── README.md
├── composer.json
└── composer.lock
/tmp/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @vitormattos @jeidison
2 |
--------------------------------------------------------------------------------
/tests/resources/pdf-test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSignPdf/jsignpdf-php/HEAD/tests/resources/pdf-test.pdf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | .phpunit.result.cache
4 | .php-cs-fixer.cache
5 | vendor
6 | /vendor-bin/**/vendor/
7 |
--------------------------------------------------------------------------------
/tests/resources/certificado.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSignPdf/jsignpdf-php/HEAD/tests/resources/certificado.pfx
--------------------------------------------------------------------------------
/tests/resources/pdf-error-test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JSignPdf/jsignpdf-php/HEAD/tests/resources/pdf-error-test.pdf
--------------------------------------------------------------------------------
/tests/psalm-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | # Comment to be posted to on PRs from first time contributors in your repository
2 | newPRWelcomeComment: "Thanks for opening your first pull request in this repository! :v:"
3 |
--------------------------------------------------------------------------------
/vendor-bin/coding-standard/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require-dev": {
3 | "friendsofphp/php-cs-fixer": "^3.59"
4 | },
5 | "config": {
6 | "platform": {
7 | "php": "8.1"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/vendor-bin/psalm/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "config": {
3 | "platform": {
4 | "php": "8.1.17"
5 | },
6 | "sort-packages": true
7 | },
8 | "require-dev": {
9 | "vimeo/psalm": "^6.5.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/vendor-bin/phpunit/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "config": {
3 | "platform": {
4 | "php": "8.1"
5 | }
6 | },
7 | "require-dev": {
8 | "phpunit/phpunit": "^10.5",
9 | "mikey179/vfsstream": "^1.6",
10 | "donatj/mock-webserver": "^2.8"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 | ./tests
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for Composer
4 | - package-ecosystem: "composer" # See documentation for possible values
5 | directory: "/" # Location of package manifests
6 | schedule:
7 | interval: "daily"
8 |
9 | - package-ecosystem: "github-actions"
10 | directory: "/"
11 | schedule:
12 | interval: daily
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 4
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
16 | [composer.json]
17 | indent_size = 2
18 | indent_style = space
19 |
20 | [.github/**.yml]
21 | indent_size = 2
22 | indent_style = space
23 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | setParallelConfig(ParallelConfigFactory::detect())
12 | ->getFinder()
13 | ->ignoreVCSIgnored(true)
14 | ->notPath('vendor')
15 | ->notPath('vendor-bin')
16 | ->in(__DIR__);
17 | return $config;
18 |
--------------------------------------------------------------------------------
/tests/Builder/JSignParamBuilder.php:
--------------------------------------------------------------------------------
1 | params = JSignParam::instance();
14 | }
15 |
16 | public static function instance()
17 | {
18 | return new self();
19 | }
20 |
21 | public function getParams()
22 | {
23 | return $this->params;
24 | }
25 |
26 | public function withDefault()
27 | {
28 | $params = JSignParam::instance();
29 | $params->setCertificate(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR. '..' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'certificado.pfx'));
30 | $params->setPdf(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR. '..' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'pdf-test.pdf'));
31 | $params->setPassword('123');
32 | return $params;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/example/index.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | require_once __DIR__ . '/../vendor/autoload.php';
7 |
8 | use Jeidison\JSignPDF\JSignPDF;
9 | use Jeidison\JSignPDF\Sign\JSignParam;
10 |
11 | $password = '123';
12 |
13 | $privateKey = openssl_pkey_new([
14 | 'private_key_bits' => 2048,
15 | 'private_key_type' => OPENSSL_KEYTYPE_RSA,
16 | ]);
17 |
18 | $csr = openssl_csr_new(['commonName' => 'John Doe'], $privateKey, ['digest_alg' => 'sha256']);
19 | $x509 = openssl_csr_sign($csr, null, $privateKey, 365);
20 |
21 | openssl_pkcs12_export(
22 | $x509,
23 | $pfxCertificateContent,
24 | $privateKey,
25 | $password,
26 | );
27 |
28 | $param = JSignParam::instance();
29 |
30 | $param->setCertificate($pfxCertificateContent);
31 | $param->setPdf(file_get_contents(__DIR__ . '/../tests/resources/pdf-test.pdf'));
32 | $param->setPassword($password);
33 |
34 | $jSignPdf = new JSignPDF($param);
35 | $fileSigned = $jSignPdf->sign();
36 | file_put_contents(__DIR__ . '/../tmp/file_signed.pdf', $fileSigned);
37 |
--------------------------------------------------------------------------------
/.github/workflows/lint-php-cs.yml:
--------------------------------------------------------------------------------
1 | name: Lint php-cs
2 |
3 | on: pull_request
4 |
5 | permissions:
6 | contents: read
7 |
8 | concurrency:
9 | group: lint-php-cs-${{ github.head_ref || github.run_id }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | lint:
14 | runs-on: ubuntu-latest
15 |
16 | name: php-cs
17 |
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
21 | with:
22 | persist-credentials: false
23 |
24 | - name: Set up php8.1
25 | uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
26 | with:
27 | php-version: 8.1
28 | extensions: json, openssl
29 | coverage: none
30 | ini-file: development
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Install dependencies
35 | run: composer i
36 |
37 | - name: Lint
38 | run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
39 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/psalm.yml:
--------------------------------------------------------------------------------
1 | name: Static analysis
2 |
3 | on: pull_request
4 |
5 | concurrency:
6 | group: psalm-${{ github.head_ref || github.run_id }}
7 | cancel-in-progress: true
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | static-analysis:
14 | runs-on: ubuntu-latest
15 |
16 | name: static-psalm-analysis
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
20 | with:
21 | persist-credentials: false
22 |
23 | - name: Set up php8.1
24 | uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
25 | with:
26 | php-version: 8.1
27 | extensions: json, openssl
28 | coverage: none
29 | ini-file: development
30 | # Temporary workaround for missing pcntl_* in PHP 8.3
31 | ini-values: disable_functions=
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 |
35 | - name: Install dependencies
36 | run: composer i
37 |
38 | - name: Run coding standards check
39 | run: composer run psalm -- --output-format=github
40 |
--------------------------------------------------------------------------------
/src/JSignPDF.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class JSignPDF
13 | {
14 | private JSignService $service;
15 | private ?JSignParam $param = null;
16 |
17 | public function __construct(?JSignParam $param = null)
18 | {
19 | $this->service = new JSignService();
20 | $this->param = $param;
21 | }
22 |
23 | public static function instance(?JSignParam $param = null): self
24 | {
25 | return new self($param);
26 | }
27 |
28 | public function sign(): string
29 | {
30 | if (!$this->param instanceof JSignParam) {
31 | throw new Exception('Invalid JSignParam instance');
32 | }
33 | return $this->service->sign($this->param);
34 | }
35 |
36 | public function getVersion(): string
37 | {
38 | if (!$this->param instanceof JSignParam) {
39 | throw new Exception('Invalid JSignParam instance');
40 | }
41 | return $this->service->getVersion($this->param);
42 | }
43 |
44 | public function setParam(JSignParam $param): void
45 | {
46 | $this->param = $param;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/JSignFileService.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class JSignFileService
9 | {
10 | public static function instance(): self
11 | {
12 | return new self();
13 | }
14 |
15 | public function contentFile(string $path, bool $isInBase64 = false): string
16 | {
17 | $content = file_get_contents($path);
18 | if ($content === false) {
19 | return '';
20 | }
21 | return $isInBase64 ? base64_encode($content) : $content;
22 | }
23 |
24 | public function storeFile(string $path, string $name, string $content): string
25 | {
26 | $filename = $path . $name;
27 | file_put_contents($filename, $content);
28 | return $filename;
29 | }
30 |
31 | public function deleteFile(string $path): void
32 | {
33 | if (is_file($path)) {
34 | unlink($path);
35 | }
36 | }
37 |
38 | public function deleteTempFiles(string $pathTemp, string $name): void
39 | {
40 | $pathPfxFile = "$pathTemp$name.pfx";
41 | $pathPdfFile = "$pathTemp$name.pdf";
42 | $pathPdfSignedFile = "{$pathTemp}{$name}_signed.pdf";
43 | $tempFiles = [$pathPfxFile, $pathPdfFile, $pathPdfSignedFile];
44 | foreach ($tempFiles as $path) {
45 | $this->deleteFile($path);
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsignpdf-php
2 |
3 | This package is only wrapper of [JSignPdf](http://jsignpdf.sourceforge.net/) to use in PHP
4 |
5 | ## Installation:
6 |
7 | ```bash
8 | $ composer require jeidison/jsignpdf-php
9 | ```
10 |
11 | ## Examples
12 |
13 | ```php
14 | use Jeidison\JSignPDF\JSignPDF;
15 | use Jeidison\JSignPDF\Sign\JSignParam;
16 |
17 | $param = JSignParam::instance();
18 | $param->setCertificate(file_get_contents('/path/to/file/certificate.pfx'));
19 | $param->setPdf(file_get_contents('/path/to/file/pdf_to_sign.pdf'));
20 | $param->setPassword('certificate_password');
21 |
22 | $jSignPdf = new JSignPDF($param);
23 | $fileSigned = $jSignPdf->sign();
24 | file_put_contents('/path/to/file/file_signed.pdf', $fileSigned);
25 | ```
26 |
27 | With Java Installed:
28 | ```php
29 | $param->setIsUseJavaInstalled(true);
30 | ```
31 |
32 | With standalone Java:
33 | ```php
34 | $param->setJavaPath('/path/to/bin/java');
35 | ```
36 |
37 | With JSignPDF bin:
38 | ```php
39 | $param->setjSignPdfJarPath('/path/to/jsignpdf');
40 | ```
41 | With specific Java or JSignPdf version:
42 | ```php
43 | $params->getJSignPdfDownloadUrl('the url to download the zip here');
44 | $params->setJavaDownloadUrl('the url to download the .tar.gz here');
45 | ```
46 |
47 | Without JSignPDF bin:
48 | ```bash
49 | composer require jsignpdf/jsignpdf-bin
50 | ```
51 |
52 | File signed as base64:
53 | ```php
54 | $param->setIsOutputTypeBase64(true);
55 | ```
56 |
57 | Change temp directory:
58 | ```php
59 | $param->setTempPath('/path/temp/to/sign/files/');
60 | ```
61 |
62 | Change parameters of JSignPDF:
63 | ```php
64 | $param->setJSignParameters("-a -kst PKCS12 -ts https://freetsa.org/tsr");
65 | ```
66 |
67 | ## Credits
68 | - [Jeidison Farias](https://github.com/jeidison)
69 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsignpdf/jsignpdf-php",
3 | "description": "jsignpdf-php",
4 | "keywords": [
5 | "JSignPdf",
6 | "PHP",
7 | "Signature",
8 | "PHP Signature",
9 | "PDF",
10 | "PDF Sign PHP"
11 | ],
12 | "type": "library",
13 | "license": "MIT",
14 | "authors": [
15 | {
16 | "name": "Jeidison Farias",
17 | "email": "jeidison.farias@gmail.com"
18 | }
19 | ],
20 | "minimum-stability": "stable",
21 | "require": {
22 | "php": "^8.1",
23 | "ext-openssl": "*",
24 | "ext-json": "*"
25 | },
26 | "require-dev": {
27 | "bamarni/composer-bin-plugin": "^1.8"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Jeidison\\JSignPDF\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Jeidison\\JSignPDF\\Tests\\": "tests/"
37 | }
38 | },
39 | "scripts": {
40 | "cs:check": "php-cs-fixer fix --dry-run --diff",
41 | "cs:fix": "php-cs-fixer fix",
42 | "test:unit": "vendor/bin/phpunit --no-coverage --colors=always --fail-on-warning --fail-on-risky --display-deprecations --display-phpunit-deprecations",
43 | "test:coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit",
44 | "psalm": "psalm --no-cache --threads=$(nproc)",
45 | "psalm:update-baseline": "psalm --threads=$(nproc) --update-baseline --set-baseline=tests/psalm-baseline.xml",
46 | "post-install-cmd": [
47 | "@composer bin all install --ansi",
48 | "composer dump-autoload"
49 | ],
50 | "post-update-cmd": [
51 | "composer dump-autoload"
52 | ]
53 | },
54 | "extra": {
55 | "bamarni-bin": {
56 | "bin-links": true,
57 | "forward-command": true
58 | }
59 | },
60 | "config": {
61 | "allow-plugins": {
62 | "bamarni/composer-bin-plugin": true
63 | },
64 | "platform": {
65 | "php": "8.1"
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/.github/workflows/phpunit.yml:
--------------------------------------------------------------------------------
1 | name: PHPUnit
2 |
3 | on: pull_request
4 |
5 | permissions:
6 | contents: read
7 |
8 | concurrency:
9 | group: phpunit
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | changes:
14 | runs-on: ubuntu-latest
15 | permissions:
16 | contents: read
17 | pull-requests: read
18 |
19 | outputs:
20 | src: ${{ steps.changes.outputs.src }}
21 |
22 | steps:
23 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
24 | id: changes
25 | continue-on-error: true
26 | with:
27 | filters: |
28 | src:
29 | - '.github/workflows/**'
30 | - 'src/**'
31 | - 'tests/**'
32 | - 'vendor/**'
33 | - 'composer.json'
34 | - 'composer.lock'
35 |
36 | phpunit:
37 | runs-on: ubuntu-latest
38 |
39 | strategy:
40 | matrix:
41 | php-versions: [8.1, 8.2, 8.3, 8.4]
42 |
43 | name: PHP ${{ matrix.php-versions }}
44 |
45 | steps:
46 | - name: Checkout
47 | uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
48 | with:
49 | persist-credentials: false
50 |
51 | - name: Set up php ${{ matrix.php-versions }}
52 | uses: shivammathur/setup-php@ec406be512d7077f68eed36e63f4d91bc006edc4 # v2.35.4
53 | with:
54 | php-version: ${{ matrix.php-versions }}
55 | extensions: json, openssl
56 | coverage: xdebug
57 | ini-file: development
58 | env:
59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60 |
61 | - name: Set up dependencies
62 | run: composer i
63 |
64 | - name: PHPUnit
65 | run: composer run test:unit
66 |
67 | summary:
68 | permissions:
69 | contents: none
70 | runs-on: ubuntu-latest
71 | needs: [changes, phpunit]
72 |
73 | if: always()
74 |
75 | name: phpunit-summary
76 |
77 | steps:
78 | - name: Summary status
79 | run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit.result != 'success' }}; then exit 1; fi
80 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "094ab91c68ebc8fa01ccdc2e4364dce4",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "bamarni/composer-bin-plugin",
12 | "version": "1.8.2",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/bamarni/composer-bin-plugin.git",
16 | "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
21 | "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "composer-plugin-api": "^2.0",
26 | "php": "^7.2.5 || ^8.0"
27 | },
28 | "require-dev": {
29 | "composer/composer": "^2.0",
30 | "ext-json": "*",
31 | "phpstan/extension-installer": "^1.1",
32 | "phpstan/phpstan": "^1.8",
33 | "phpstan/phpstan-phpunit": "^1.1",
34 | "phpunit/phpunit": "^8.5 || ^9.5",
35 | "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
36 | "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
37 | "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
38 | },
39 | "type": "composer-plugin",
40 | "extra": {
41 | "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "Bamarni\\Composer\\Bin\\": "src"
46 | }
47 | },
48 | "notification-url": "https://packagist.org/downloads/",
49 | "license": [
50 | "MIT"
51 | ],
52 | "description": "No conflicts for your bin dependencies",
53 | "keywords": [
54 | "composer",
55 | "conflict",
56 | "dependency",
57 | "executable",
58 | "isolation",
59 | "tool"
60 | ],
61 | "support": {
62 | "issues": "https://github.com/bamarni/composer-bin-plugin/issues",
63 | "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
64 | },
65 | "time": "2022-10-31T08:38:03+00:00"
66 | }
67 | ],
68 | "aliases": [],
69 | "minimum-stability": "stable",
70 | "stability-flags": {},
71 | "prefer-stable": false,
72 | "prefer-lowest": false,
73 | "platform": {
74 | "php": "^8.1",
75 | "ext-openssl": "*",
76 | "ext-json": "*"
77 | },
78 | "platform-dev": {},
79 | "platform-overrides": {
80 | "php": "8.1"
81 | },
82 | "plugin-api-version": "2.6.0"
83 | }
84 |
--------------------------------------------------------------------------------
/src/Runtime/JSignPdfRuntimeService.php:
--------------------------------------------------------------------------------
1 | getjSignPdfJarPath();
17 | $downloadUrl = $params->getJSignPdfDownloadUrl();
18 |
19 | if ($jsignPdfPath && !$downloadUrl) {
20 | if (file_exists($jsignPdfPath)) {
21 | return $jsignPdfPath;
22 | }
23 | throw new InvalidArgumentException('Jar of JSignPDF not found on path: '. $jsignPdfPath);
24 | }
25 |
26 | if ($downloadUrl && $jsignPdfPath) {
27 | $baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath);
28 | if (!is_string($baseDir)) {
29 | throw new InvalidArgumentException('Invalid JsignParamPath');
30 | }
31 | if (!is_dir($baseDir)) {
32 | $ok = mkdir($baseDir, 0755, true);
33 | if ($ok === false) {
34 | throw new InvalidArgumentException('The JSignPdf base dir cannot be created: '. $baseDir);
35 | }
36 | }
37 | if (!file_exists($jsignPdfPath) || !self::validateVersion($params)) {
38 | self::downloadAndExtract($params);
39 | }
40 | return $jsignPdfPath;
41 | }
42 |
43 | throw new InvalidArgumentException('Java not found.');
44 | }
45 |
46 | private function validateVersion(JSignParam $params): bool
47 | {
48 | $jsignPdfPath = $params->getjSignPdfJarPath();
49 | $versionCacheFile = $jsignPdfPath . '/.jsignpdf_version_' . basename($params->getJSignPdfDownloadUrl());
50 | return file_exists($versionCacheFile);
51 | }
52 |
53 | private function downloadAndExtract(JSignParam $params): void
54 | {
55 | $jsignPdfPath = $params->getjSignPdfJarPath();
56 | $url = $params->getJSignPdfDownloadUrl();
57 |
58 | $baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath);
59 | if (!is_string($baseDir)) {
60 | throw new InvalidArgumentException('Invalid JsignParamPath');
61 | }
62 | if (!is_dir($baseDir)) {
63 | $ok = mkdir($baseDir, 0755, true);
64 | if (!$ok) {
65 | throw new RuntimeException('Failure to create the folder: ' . $baseDir);
66 | }
67 | }
68 | if (!filter_var($url, FILTER_VALIDATE_URL)) {
69 | throw new InvalidArgumentException('The url to download Java is invalid: ' . $url);
70 | }
71 | $this->chunkDownload($url, $baseDir . '/jsignpdf.zip');
72 | $z = new ZipArchive();
73 | $ok = $z->open($baseDir . '/jsignpdf.zip');
74 | if ($ok !== true) {
75 | throw new InvalidArgumentException('The file ' . $baseDir . '/jsignpdf.zip cannot be extracted');
76 | }
77 | $ok = $z->extractTo(pathto: $baseDir, files: [$z->getNameIndex(0) . 'JSignPdf.jar']);
78 | if ($ok !== true) {
79 | throw new InvalidArgumentException('JSignPdf.jar not found inside path: ' . $z->getNameIndex(0) . 'JSignPdf.jar');
80 | }
81 | @exec('mv ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)) . '/JSignPdf.jar ' . escapeshellarg($baseDir));
82 | @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)));
83 | @exec('rm -f ' . escapeshellarg($baseDir) . '/.jsignpdf_version_*');
84 | unlink($baseDir . '/jsignpdf.zip');
85 | if (!file_exists($baseDir . '/JSignPdf.jar')) {
86 | throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java');
87 | }
88 | touch($baseDir . '/.jsignpdf_version_' . basename($url));
89 | }
90 |
91 | private function chunkDownload(string $url, string $destination): void
92 | {
93 | $fp = fopen($destination, 'w');
94 |
95 | if ($fp) {
96 | $ch = curl_init($url);
97 | if ($ch === false) {
98 | throw new InvalidArgumentException('Failure to download file using the url ' . $url);
99 | }
100 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
101 | curl_setopt($ch, CURLOPT_FILE, $fp);
102 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
103 | $response = curl_exec($ch);
104 | if ($response === false) {
105 | throw new InvalidArgumentException('Failure to download file using the url ' . $url);
106 | }
107 | curl_close($ch);
108 | fclose($fp);
109 | } else {
110 | throw new InvalidArgumentException("Failute to download file using the url $url");
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Sign/JSignParam.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | class JSignParam
9 | {
10 | private string $pdf = '';
11 | private string $certificate = '';
12 | private string $password = '';
13 | private string $pathPdfSigned = '';
14 | private string $JSignParameters = "-a -kst PKCS12";
15 | private bool $isUseJavaInstalled = false;
16 | private string $javaPath = '';
17 | private string $tempPath = '';
18 | private string $tempName = '';
19 | private bool $isOutputTypeBase64 = false;
20 | private string $jSignPdfJarPath = '';
21 | private string $javaDownloadUrl = 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz';
22 | private string $jSignPdfDownloadUrl = 'https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip';
23 |
24 | public function __construct()
25 | {
26 | $this->tempName = md5(time() . uniqid() . mt_rand());
27 | $this->tempPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
28 | $this->javaPath = $this->tempPath . 'java' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'java';
29 | $this->jSignPdfJarPath = $this->tempPath . 'jsignpdf' . DIRECTORY_SEPARATOR . 'JSignPdf.jar';
30 | }
31 |
32 | public static function instance(): self
33 | {
34 | return new self();
35 | }
36 |
37 | public function getPdf(): string
38 | {
39 | return $this->pdf;
40 | }
41 |
42 | public function setPdf(string $pdf): self
43 | {
44 | $this->pdf = $pdf;
45 | return $this;
46 | }
47 |
48 | public function getCertificate(): string
49 | {
50 | return $this->certificate;
51 | }
52 |
53 | public function setCertificate(string $certificate): self
54 | {
55 | $this->certificate = $certificate;
56 | return $this;
57 | }
58 |
59 | public function getPassword(): string
60 | {
61 | return $this->password;
62 | }
63 |
64 | public function setPassword(string $password): self
65 | {
66 | $this->password = $password;
67 | return $this;
68 | }
69 |
70 | public function getPathPdfSigned(): string
71 | {
72 | return $this->pathPdfSigned != null ? $this->pathPdfSigned : $this->getTempPath();
73 | }
74 |
75 | public function setPathPdfSigned(string $pathPdfSigned): self
76 | {
77 | $this->pathPdfSigned = $pathPdfSigned;
78 | return $this;
79 | }
80 |
81 | public function getJSignParameters(): string
82 | {
83 | return $this->JSignParameters;
84 | }
85 |
86 | public function setJSignParameters(string $JSignParameters): self
87 | {
88 | $this->JSignParameters = $JSignParameters;
89 | return $this;
90 | }
91 |
92 | public function getTempPath(): string
93 | {
94 | return $this->tempPath;
95 | }
96 |
97 | public function setTempPath(string $tempPath): self
98 | {
99 | $this->tempPath = $tempPath;
100 | return $this;
101 | }
102 |
103 | public function getTempName(string $extension = ''): string
104 | {
105 | return $this->tempName.$extension;
106 | }
107 |
108 | public function isUseJavaInstalled(): bool
109 | {
110 | return $this->isUseJavaInstalled;
111 | }
112 |
113 | public function setIsUseJavaInstalled(bool $isUseJavaInstalled): self
114 | {
115 | $this->isUseJavaInstalled = $isUseJavaInstalled;
116 | return $this;
117 | }
118 |
119 | public function setJavaPath(string $javaPath): self
120 | {
121 | $this->javaPath = $javaPath;
122 | return $this;
123 | }
124 |
125 | public function getJavaPath(): string
126 | {
127 | return $this->javaPath;
128 | }
129 |
130 | public function setjSignPdfJarPath(string $jSignPdfJarPath): self
131 | {
132 | $this->jSignPdfJarPath = $jSignPdfJarPath;
133 | return $this;
134 | }
135 |
136 | public function getjSignPdfJarPath(): string
137 | {
138 | return $this->jSignPdfJarPath;
139 | }
140 |
141 | public function isOutputTypeBase64(): bool
142 | {
143 | return $this->isOutputTypeBase64;
144 | }
145 |
146 | public function setIsOutputTypeBase64(bool $isOutputTypeBase64): self
147 | {
148 | $this->isOutputTypeBase64 = $isOutputTypeBase64;
149 | return $this;
150 | }
151 |
152 | public function getTempPdfPath(): string
153 | {
154 | return $this->getTempPath() . $this->getTempName('.pdf');
155 | }
156 |
157 | public function getTempPdfSignedPath(): string
158 | {
159 | return $this->getPathPdfSigned() . $this->getTempName('_signed.pdf');
160 | }
161 |
162 | public function getTempCertificatePath(): string
163 | {
164 | return $this->getTempPath() . $this->getTempName('.pfx');
165 | }
166 |
167 | public function setJavaDownloadUrl(string $url): self
168 | {
169 | $this->javaDownloadUrl = $url;
170 | return $this;
171 | }
172 |
173 | public function getJavaDownloadUrl(): string
174 | {
175 | return $this->javaDownloadUrl;
176 | }
177 |
178 | public function setJSignPdfDownloadUrl(string $url): self
179 | {
180 | $this->jSignPdfDownloadUrl = $url;
181 | return $this;
182 | }
183 |
184 | public function getJSignPdfDownloadUrl(): string
185 | {
186 | return $this->jSignPdfDownloadUrl;
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/Runtime/JavaRuntimeService.php:
--------------------------------------------------------------------------------
1 | isUseJavaInstalled()) {
19 | return 'java';
20 | }
21 |
22 | $javaPath = $params->getJavaPath();
23 | $downloadUrl = $params->getJavaDownloadUrl();
24 |
25 | if ($javaPath && !$downloadUrl) {
26 | return $javaPath;
27 | }
28 |
29 | if ($downloadUrl && $javaPath) {
30 | $baseDir = preg_replace('/\/bin\/java$/', '', $javaPath);
31 | if (!is_string($baseDir)) {
32 | throw new InvalidArgumentException('Invalid JsignParamPath');
33 | }
34 | if (!is_dir($baseDir)) {
35 | $ok = mkdir($baseDir, 0755, true);
36 | if ($ok === false) {
37 | throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir);
38 | }
39 | }
40 | if (!self::validateVersion($params)) {
41 | self::downloadAndExtract($downloadUrl, $javaPath);
42 | }
43 | $params->setJavaDownloadUrl('');
44 | $params->setJavaPath($javaPath);
45 | return $javaPath;
46 | }
47 |
48 | throw new InvalidArgumentException('Java not found.');
49 | }
50 |
51 | private function validateVersion(JSignParam $params): bool
52 | {
53 | $javaPath = $params->getJavaPath();
54 | $baseDir = preg_replace('/\/bin\/java$/', '', $javaPath);
55 | if (!is_string($baseDir)) {
56 | throw new InvalidArgumentException('Invalid JsignParamPath');
57 | }
58 | $lastVersion = $baseDir . '/.java_version_' . basename($params->getJavaDownloadUrl());
59 | return file_exists($lastVersion);
60 | }
61 |
62 | private function downloadAndExtract(string $url, string $baseDir): void
63 | {
64 | $baseDir = preg_replace('/\/bin\/java$/', '', $baseDir);
65 | if (!is_string($baseDir)) {
66 | throw new InvalidArgumentException('Invalid JsignParamPath');
67 | }
68 |
69 | if (!is_dir($baseDir)) {
70 | $ok = mkdir($baseDir, 0755, true);
71 | if (!$ok) {
72 | throw new RuntimeException('Failure to create the folder: ' . $baseDir);
73 | }
74 | }
75 | if (!filter_var($url, FILTER_VALIDATE_URL)) {
76 | throw new InvalidArgumentException('The url to download Java is invalid: ' . $url);
77 | }
78 | $this->chunkDownload($url, $baseDir . '/java.tar.gz');
79 | try {
80 | $tar = new PharData($baseDir . '/java.tar.gz');
81 | } catch (PharException|UnexpectedValueException $e) {
82 | throw new InvalidArgumentException('The file ' . $baseDir . '/java.tar.gz cannot be extracted');
83 | }
84 | $rootDirInsideTar = $this->findRootDir($tar, $baseDir . '/java.tar.gz');
85 | if (empty($rootDirInsideTar)) {
86 | throw new InvalidArgumentException('Invalid tar content.');
87 | }
88 | $tar->extractTo(directory: $baseDir, overwrite: true);
89 | @exec('mv ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar) . '/* ' . escapeshellarg($baseDir));
90 | @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar));
91 | @exec('rm -f ' . escapeshellarg($baseDir) . '/.java_version_*');
92 | unlink($baseDir . '/java.tar.gz');
93 | touch($baseDir . '/.java_version_' . basename($url));
94 | if (!file_exists($baseDir . '/bin/java')) {
95 | throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java');
96 | }
97 | chmod($baseDir . '/bin/java', 0700);
98 | }
99 |
100 | private function findRootDir(PharData $phar, string $rootDir): string
101 | {
102 | $files = new \RecursiveIteratorIterator($phar, \RecursiveIteratorIterator::CHILD_FIRST);
103 | $rootDir = realpath($rootDir);
104 | if (!is_string($rootDir) || empty($rootDir)) {
105 | throw new InvalidArgumentException('Invalid tar content.');
106 | }
107 |
108 | foreach ($files as $file) {
109 | $pathName = $file->getPathname();
110 | if (str_contains($pathName, '/bin/') || str_contains($pathName, '/bin/')) {
111 | $parts = explode($rootDir, $pathName);
112 | $internalFullPath = end($parts);
113 | $parts = explode('/bin/', $internalFullPath);
114 | return trim($parts[0], '/');
115 | }
116 | }
117 | return '';
118 | }
119 |
120 | private function chunkDownload(string $url, string $destination): void
121 | {
122 | $fp = fopen($destination, 'w');
123 |
124 | if ($fp) {
125 | $ch = curl_init($url);
126 | if ($ch === false) {
127 | throw new InvalidArgumentException('Failure to download file using the url ' . $url);
128 | }
129 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
130 | curl_setopt($ch, CURLOPT_FILE, $fp);
131 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
132 | $response = curl_exec($ch);
133 | if ($response === false) {
134 | throw new InvalidArgumentException('Failure to download file using the url ' . $url);
135 | }
136 | curl_close($ch);
137 | fclose($fp);
138 | } else {
139 | throw new InvalidArgumentException("Failute to download file using the url $url");
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/tests/Runtime/JavaRuntimeServiceTest.php:
--------------------------------------------------------------------------------
1 | testTmpDir = sys_get_temp_dir() . '/jsignpdf_temp_dir_' . uniqid();
20 | mkdir(directory: $this->testTmpDir, recursive: true);
21 | }
22 |
23 | public function testGetPathWhenJavaIsInstalled(): void
24 | {
25 | $jsignParam = new JSignParam();
26 | $service = new JavaRuntimeService();
27 | $jsignParam->setIsUseJavaInstalled(true);
28 | $path = $service->getPath($jsignParam);
29 | $this->assertEquals('java', $path);
30 | }
31 |
32 | public function testGetPathWithCustomAndValidJavaPath(): void
33 | {
34 | $jsignParam = new JSignParam();
35 | $service = new JavaRuntimeService();
36 | vfsStream::setup('download');
37 | mkdir('vfs://download/bin');
38 | touch('vfs://download/bin/java');
39 | chmod('vfs://download/bin/java', 0755);
40 | $jsignParam->setJavaPath('vfs://download/bin/java');
41 | $jsignParam->setJavaDownloadUrl('');
42 | $path = $service->getPath($jsignParam);
43 | $this->assertEquals('vfs://download/bin/java', $path);
44 | }
45 |
46 | public function testGetPathWithCustomJavaPath(): void
47 | {
48 | $jsignParam = new JSignParam();
49 | $service = new JavaRuntimeService();
50 | $expectedPath = __FILE__;
51 | $jsignParam->setJavaPath($expectedPath);
52 | $jsignParam->setJavaDownloadUrl('');
53 | $actual = $service->getPath($jsignParam);
54 | $this->assertEquals($expectedPath, $actual);
55 | }
56 |
57 | public function testGetPathWithDownloadUrlAndNotRealDirectory(): void
58 | {
59 | $jsignParam = new JSignParam();
60 | $service = new JavaRuntimeService();
61 | $root = vfsStream::setup('download');
62 | $root->chmod(0770)
63 | ->chgrp(vfsStream::GROUP_USER_1)
64 | ->chown(vfsStream::OWNER_USER_1);
65 | chgrp('vfs://download', 44);
66 | $jsignParam->setJavaPath('vfs://download/not_real_directory');
67 | $jsignParam->setJavaDownloadUrl('https://fake.url');
68 | $this->expectException(InvalidArgumentException::class);
69 | $this->expectExceptionMessageMatches('/not a real directory/');
70 | $service->getPath($jsignParam);
71 | }
72 |
73 | public function testGetPathWithDownloadUrlWithInvalidUrl(): void
74 | {
75 | vfsStream::setup('download');
76 | mkdir('vfs://download/bin');
77 | touch('vfs://download/bin/java');
78 |
79 | $jsignParam = new JSignParam();
80 | $service = new JavaRuntimeService();
81 | $jsignParam->setJavaPath('vfs://download/bin/java');
82 | $jsignParam->setJavaDownloadUrl('invalid_url');
83 | $this->expectException(InvalidArgumentException::class);
84 | $this->expectExceptionMessageMatches('/url.*invalid/');
85 | $service->getPath($jsignParam);
86 | }
87 |
88 | public function testGetPathWithDownloadUrlWith4xxError(): void
89 | {
90 | vfsStream::setup('download');
91 | mkdir('vfs://download/bin');
92 | touch('vfs://download/bin/java');
93 |
94 | $jsignParam = new JSignParam();
95 | $service = new JavaRuntimeService();
96 | $jsignParam->setJavaPath('vfs://download/bin/java');
97 | $jsignParam->setJavaDownloadUrl('https://404.domain');
98 | $this->expectException(InvalidArgumentException::class);
99 | $this->expectExceptionMessageMatches('/Failure to download/');
100 | $service->getPath($jsignParam);
101 | }
102 |
103 | public function testGetPathWithDownloadUrlWithInvalidGzipedFile(): void
104 | {
105 | vfsStream::setup('download');
106 | mkdir('vfs://download/bin');
107 | touch('vfs://download/bin/java');
108 |
109 |
110 | $jsignParam = new JSignParam();
111 | $service = new JavaRuntimeService();
112 | $jsignParam->setJavaPath('vfs://download/bin/java');
113 |
114 | $server = new MockWebServer();
115 | $server->start();
116 | $server->setResponseOfPath(
117 | '/',
118 | new Response(
119 | 'invalid body response',
120 | )
121 | );
122 | $url = $server->getServerRoot();
123 | $jsignParam->setJavaDownloadUrl($url);
124 |
125 | $this->expectException(InvalidArgumentException::class);
126 | $this->expectExceptionMessageMatches('/cannot be extracted/');
127 | $service->getPath($jsignParam);
128 | }
129 |
130 | public function testGetPathWithDownloadUrlWithInvalidJavaPackage(): void
131 | {
132 | mkdir($this->testTmpDir . '/bin', 0755, true);
133 |
134 | $jsignParam = new JSignParam();
135 | $service = new JavaRuntimeService();
136 | $jsignParam->setJavaPath($this->testTmpDir . '/bin/java');
137 |
138 | $tar = new PharData($this->testTmpDir . '/temp.tar.gz');
139 | $tar->addFromString('file.txt', 'invalid file');
140 |
141 | $server = new MockWebServer();
142 | $server->start();
143 | $server->setResponseOfPath(
144 | '/',
145 | new Response(
146 | gzencode(file_get_contents($this->testTmpDir . '/temp.tar.gz')),
147 | )
148 | );
149 | unlink($this->testTmpDir . '/temp.tar.gz');
150 | $url = $server->getServerRoot();
151 | $jsignParam->setJavaDownloadUrl($url);
152 |
153 | $this->expectException(InvalidArgumentException::class);
154 | $this->expectExceptionMessageMatches('/Invalid tar content/');
155 | $service->getPath($jsignParam);
156 | }
157 |
158 | public function testGetPathWithDownloadUrlWithInvalidVersion(): void
159 | {
160 | $jsignParam = new JSignParam();
161 | $service = new JavaRuntimeService();
162 |
163 | // When the version is invalid, will try to download the package
164 | $server = new MockWebServer();
165 | $server->start();
166 | $server->setResponseOfPath(
167 | '/',
168 | new Response('invalid response'),
169 | );
170 | $baseUrl = $server->getServerRoot();
171 | $url = $baseUrl . '/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz';
172 | $jsignParam->setJavaDownloadUrl($url);
173 |
174 | $this->expectException(InvalidArgumentException::class);
175 | $this->expectExceptionMessageMatches('/cannot be extracted/');
176 | $service->getPath($jsignParam);
177 | }
178 |
179 | public function testGetPathWithDownloadUrlWithValidVersion(): void
180 | {
181 | $jsignParam = new JSignParam();
182 | $service = new JavaRuntimeService();
183 |
184 | $tarGzFilename = 'OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz';
185 | $url = 'https://fake.url/' . $tarGzFilename;
186 | $jsignParam->setJavaDownloadUrl($url);
187 |
188 | // When have a file with an expected name, will consider that the
189 | // downloaded java version is right
190 | touch($this->testTmpDir . '/.java_version_' . $tarGzFilename);
191 | $jsignParam->setJavaPath($this->testTmpDir . '/bin/java');
192 |
193 | $javaPath = $service->getPath($jsignParam);
194 | $this->assertEquals($jsignParam->getJavaPath(), $javaPath);
195 | }
196 |
197 | public function testGetPathWithoutJavaFallback(): void
198 | {
199 | mkdir($this->testTmpDir . '/bin', 0755, true);
200 |
201 | $jsignParam = new JSignParam();
202 | $service = new JavaRuntimeService();
203 |
204 | $jsignParam->setJavaPath('');
205 | $this->expectException(InvalidArgumentException::class);
206 | $this->expectExceptionMessageMatches('/Java not found/');
207 | $service->getPath($jsignParam);
208 | }
209 |
210 | protected function tearDown(): void
211 | {
212 | $dirs = glob(sys_get_temp_dir() . '/jsignpdf_temp_*', GLOB_ONLYDIR);
213 |
214 | foreach ($dirs as $dir) {
215 | if (is_dir($dir)) {
216 | $this->removeDirectoryContents($dir);
217 | rmdir($dir);
218 | }
219 | }
220 | }
221 |
222 | private function removeDirectoryContents($dir): void
223 | {
224 | $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS);
225 | $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);
226 |
227 | foreach ($files as $file) {
228 | if ($file->isDir()) {
229 | rmdir($file->getRealPath());
230 | } else {
231 | unlink($file->getRealPath());
232 | }
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/tests/JSignPDFTest.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | class JSignPDFTest extends TestCase
28 | {
29 | private JSignService $service;
30 |
31 | protected function setUp(): void
32 | {
33 | global $mockExec;
34 | $mockExec = null;
35 | $this->service = new JSignService();
36 | }
37 |
38 | private function getNewCert($password, $expireDays = 365)
39 | {
40 | $privateKey = openssl_pkey_new([
41 | 'private_key_bits' => 2048,
42 | 'private_key_type' => OPENSSL_KEYTYPE_RSA,
43 | ]);
44 |
45 | $csrNames = ['commonName' => 'Jhon Doe'];
46 |
47 | $csr = openssl_csr_new($csrNames, $privateKey, ['digest_alg' => 'sha256']);
48 | $x509 = openssl_csr_sign($csr, null, $privateKey, $expireDays);
49 |
50 | openssl_pkcs12_export(
51 | $x509,
52 | $pfxCertificateContent,
53 | $privateKey,
54 | $password,
55 | );
56 | return $pfxCertificateContent;
57 | }
58 |
59 | public function testSignSuccess()
60 | {
61 | global $mockExec;
62 | $mockExec = ['Finished: Signature succesfully created.'];
63 | $params = JSignParamBuilder::instance()->withDefault();
64 | vfsStream::setup('download');
65 | mkdir('vfs://download/jvava/bin', 0755, true);
66 | touch('vfs://download/jvava/bin/java');
67 | chmod('vfs://download/jvava/bin/java', 0755);
68 | $params->setJavaPath('vfs://download/jvava/bin/java');
69 | $params->setJavaDownloadUrl('');
70 | mkdir('vfs://download/jsignpdf', 0755, true);
71 | $params->setjSignPdfJarPath('vfs://download/jsignpdf');
72 | $params->setJSignPdfDownloadUrl('');
73 | $params->setCertificate($this->getNewCert($params->getPassword()));
74 | $params->setPathPdfSigned('vfs://download/temp');
75 | $signedFilePath = $params->getTempPdfSignedPath();
76 | file_put_contents($signedFilePath, 'signed file content');
77 | $fileSignedContent = $this->service->sign($params);
78 | $this->assertEquals('signed file content', $fileSignedContent);
79 | }
80 |
81 | #[DataProvider('providerSignUsingDifferentPasswords')]
82 | public function testSignUsingDifferentPasswords(string $password): void
83 | {
84 | global $mockExec;
85 | $mockExec = ['Finished: Signature succesfully created.'];
86 | $params = JSignParamBuilder::instance()->withDefault();
87 | vfsStream::setup('download');
88 | mkdir('vfs://download/jvava/bin', 0755, true);
89 | touch('vfs://download/jvava/bin/java');
90 | chmod('vfs://download/jvava/bin/java', 0755);
91 | $params->setJavaPath('vfs://download/jvava/bin/java');
92 | $params->setJavaDownloadUrl('');
93 | mkdir('vfs://download/jsignpdf', 0755, true);
94 | $params->setjSignPdfJarPath('vfs://download/jsignpdf');
95 | $params->setJSignPdfDownloadUrl('');
96 | $params->setCertificate($this->getNewCert($password));
97 | $params->setPassword($password);
98 | $params->setPathPdfSigned('vfs://download/temp');
99 | $signedFilePath = $params->getTempPdfSignedPath();
100 | file_put_contents($signedFilePath, 'signed file content');
101 | $fileSignedContent = $this->service->sign($params);
102 | $this->assertEquals('signed file content', $fileSignedContent);
103 | }
104 |
105 | public static function providerSignUsingDifferentPasswords(): array
106 | {
107 | return [
108 | ["with ' quote"],
109 | ['with ( parentheis )'],
110 | ['with $ dollar'],
111 | ['with 😃 unicode'],
112 | ];
113 | }
114 |
115 | public function testCertificateExpired()
116 | {
117 | $this->expectExceptionMessage('Certificate expired.');
118 | $params = JSignParamBuilder::instance()->withDefault();
119 | vfsStream::setup('download');
120 | mkdir('vfs://download/jvava/bin', 0755, true);
121 | touch('vfs://download/jvava/bin/java');
122 | chmod('vfs://download/jvava/bin/java', 0755);
123 | $params->setJavaPath('vfs://download/jvava/bin/java');
124 | $params->setJavaDownloadUrl('');
125 | mkdir('vfs://download/jsignpdf', 0755, true);
126 | $params->setjSignPdfJarPath('vfs://download/jsignpdf');
127 | $params->setJSignPdfDownloadUrl('');
128 | $params->setCertificate($this->getNewCert('123', 0));
129 | $params->setPassword('123');
130 | $signedFilePath = $params->getTempPdfSignedPath();
131 | file_put_contents($signedFilePath, 'signed file content');
132 | $this->service->sign($params);
133 | }
134 |
135 | public function testSignError()
136 | {
137 | $this->expectException(Exception::class);
138 | $params = JSignParamBuilder::instance();
139 | $this->service->sign($params->getParams());
140 | }
141 |
142 |
143 | public function testWithWhenResponseIsBase64()
144 | {
145 | global $mockExec;
146 | $mockExec = ['Finished: Signature succesfully created.'];
147 | $params = JSignParamBuilder::instance()->withDefault();
148 | vfsStream::setup('download');
149 | mkdir('vfs://download/jvava/bin', 0755, true);
150 | touch('vfs://download/jvava/bin/java');
151 | chmod('vfs://download/jvava/bin/java', 0755);
152 | $params->setJavaPath('vfs://download/jvava/bin/java');
153 | $params->setJavaDownloadUrl('');
154 | mkdir('vfs://download/jsignpdf', 0755, true);
155 | $params->setjSignPdfJarPath('vfs://download/jsignpdf');
156 | $params->setJSignPdfDownloadUrl('');
157 | $params->setCertificate($this->getNewCert('123'));
158 | $params->setPassword('123');
159 | $signedFilePath = $params->getTempPdfSignedPath();
160 | file_put_contents($signedFilePath, 'signed file content');
161 | $params->setIsOutputTypeBase64(true);
162 | $signedContent = $this->service->sign($params);
163 | $this->assertEquals(base64_encode('signed file content'), $signedContent);
164 | }
165 |
166 | public function testSignWhenCertificateIsEmpty()
167 | {
168 | $this->expectExceptionMessage('Certificate is Empty or Invalid.');
169 | $params = JSignParamBuilder::instance()->withDefault()->setCertificate('');
170 | $this->service->sign($params);
171 | }
172 |
173 | public function testSignWhenPdfIsEmpty()
174 | {
175 | $this->expectExceptionMessage('PDF is Empty or Invalid.');
176 | $params = JSignParamBuilder::instance()->withDefault()->setPdf('');
177 | $this->service->sign($params);
178 | }
179 |
180 | public function testSignWhenPasswordIsEmpty()
181 | {
182 | $this->expectExceptionMessage('Certificate Password is Empty.');
183 | $params = JSignParamBuilder::instance()->withDefault()->setPassword('');
184 | $this->service->sign($params);
185 | }
186 |
187 | public function testSignWhenTempPathIsInvalid()
188 | {
189 | $this->expectExceptionMessage('Temp Path is invalid or has not permission to writable.');
190 | $params = JSignParamBuilder::instance()->withDefault()->setTempPath('');
191 | $this->service->sign($params);
192 | }
193 |
194 | public function testSignWhenPasswordIsInvalid()
195 | {
196 | $this->expectExceptionMessage('Certificate Password Invalid.');
197 | $params = JSignParamBuilder::instance()->withDefault()->setPassword('123456');
198 | $this->service->sign($params);
199 | }
200 |
201 | public function testJSignPDFNotFound()
202 | {
203 | $this->expectExceptionMessageMatches('/JSignPDF not found/');
204 | $params = JSignParamBuilder::instance()->withDefault();
205 | $params->setJSignPdfDownloadUrl('');
206 | $params->setjSignPdfJarPath('invalid_path');
207 | $params->setCertificate($this->getNewCert($params->getPassword()));
208 | $params->setIsUseJavaInstalled(true);
209 | $this->service->getVersion($params);
210 | }
211 |
212 | public function testGetVersion()
213 | {
214 | global $mockExec;
215 | $mockExec = ['JSignPdf version 2.3.0'];
216 |
217 | $params = JSignParamBuilder::instance()->withDefault();
218 | vfsStream::setup('download');
219 | mkdir('vfs://download/bin');
220 | touch('vfs://download/bin/java');
221 | chmod('vfs://download/bin/java', 0755);
222 | mkdir('vfs://download/jsignpdf_fake_path/');
223 | touch('vfs://download/jsignpdf_fake_path/.jsignpdf_version_fake_url');
224 | $params->setJavaPath('vfs://download/bin/java');
225 | $params->setJSignPdfDownloadUrl('fake_url');
226 | $params->setIsUseJavaInstalled(true);
227 | $params->setjSignPdfJarPath('vfs://download/jsignpdf_fake_path');
228 | $version = $this->service->getVersion($params);
229 | $this->assertNotEmpty($version);
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/Sign/JSignService.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class JSignService
16 | {
17 | private JSignFileService $fileService;
18 |
19 | public function __construct()
20 | {
21 | $this->fileService = JSignFileService::instance();
22 | }
23 |
24 | public function sign(JSignParam $params): string
25 | {
26 | try {
27 | $this->validation($params);
28 |
29 | $commandSign = $this->commandSign($params);
30 | exec($commandSign, $output);
31 |
32 | $out = json_encode($output);
33 | if ($out === false) {
34 | throw new Exception('Error to sign PDF.');
35 | }
36 | $messageSuccess = "Finished: Signature succesfully created.";
37 | $isSigned = strpos($out, $messageSuccess) !== false;
38 |
39 | $this->throwIf(!$isSigned, "Error to sign PDF. $out");
40 |
41 | $fileSigned = $this->fileService->contentFile(
42 | $params->getTempPdfSignedPath(),
43 | $params->isOutputTypeBase64()
44 | );
45 |
46 | $this->fileService->deleteTempFiles(
47 | $params->getTempPath(),
48 | $params->getTempName()
49 | );
50 |
51 | return $fileSigned;
52 | } catch (Throwable $e) {
53 | if ($params->getTempPath()) {
54 | $this->fileService->deleteTempFiles($params->getTempPath(), $params->getTempName());
55 | }
56 |
57 | throw new Exception($e->getMessage());
58 | }
59 | }
60 |
61 | /**
62 | * JSignPdf don't works as well at CLI interfaceif the password have
63 | * unicode chars. As workaround, I changed the password certificate in
64 | * memory.
65 | */
66 | private function repackCertificateIfPasswordIsUnicode(
67 | JSignParam $params,
68 | \OpenSSLCertificate|string $cert,
69 | \OpenSSLAsymmetricKey|\OpenSSLCertificate|string $pkey,
70 | ): void {
71 | $detectedEncodingString = mb_detect_encoding($params->getPassword(), 'ASCII', true);
72 | if ($detectedEncodingString === false) {
73 | $password = md5(microtime());
74 | $newCert = $this->exportToPkcs12($cert, $pkey, $password);
75 | $params->setPassword($password);
76 | $params->setCertificate($newCert);
77 | }
78 | }
79 |
80 | public function getVersion(JSignParam $params): string
81 | {
82 | $java = $this->javaCommand($params);
83 | $jSignPdf = $this->getjSignPdfJarPath($params);
84 | $jSignPdf = $params->getjSignPdfJarPath();
85 |
86 | $command = "$java -jar $jSignPdf --version 2>&1";
87 | exec($command, $output);
88 | $lastRow = end($output);
89 | if (empty($output) || strpos($lastRow, 'version') === false) {
90 | return '';
91 | }
92 | return explode('version ', $lastRow)[1];
93 | }
94 |
95 | private function validation(JSignParam $params): void
96 | {
97 | $this->throwIf(empty($params->getTempPath()) || !is_writable($params->getTempPath()), 'Temp Path is invalid or has not permission to writable.');
98 | $this->throwIf(empty($params->getPdf()), 'PDF is Empty or Invalid.');
99 | $this->throwIf(empty($params->getCertificate()), 'Certificate is Empty or Invalid.');
100 | $this->throwIf(empty($params->getPassword()), 'Certificate Password is Empty.');
101 | $this->throwIf(!$this->isPasswordCertificateValid($params), 'Certificate Password Invalid.');
102 | $this->throwIf($this->isExpiredCertificate($params), 'Certificate expired.');
103 | if ($params->isUseJavaInstalled()) {
104 | $javaVersion = exec("java -version 2>&1");
105 | if ($javaVersion === false) {
106 | throw new Exception('Java not installed, set the flag "isUseJavaInstalled" as false or install java.');
107 | }
108 | $hasJavaVersion = strpos($javaVersion, 'not found') === false;
109 | $this->throwIf(!$hasJavaVersion, 'Java not installed, set the flag "isUseJavaInstalled" as false or install java.');
110 | }
111 | }
112 |
113 | /**
114 | * @psalm-return list{mixed, mixed}
115 | */
116 | private function storeTempFiles(JSignParam $params): array
117 | {
118 | $pdf = $this->fileService->storeFile(
119 | $params->getTempPath(),
120 | $params->getTempName('.pdf'),
121 | $params->getPdf()
122 | );
123 |
124 | $certificate = $this->fileService->storeFile(
125 | $params->getTempPath(),
126 | $params->getTempName('.pfx'),
127 | $params->getCertificate()
128 | );
129 |
130 | return [$pdf, $certificate];
131 | }
132 |
133 | private function commandSign(JSignParam $params): string
134 | {
135 | list($pdf, $certificate) = $this->storeTempFiles($params);
136 | $java = $this->javaCommand($params);
137 | $jSignPdf = $this->getjSignPdfJarPath($params);
138 |
139 | $password = escapeshellarg($params->getPassword());
140 | return "$java -Duser.language=en -jar $jSignPdf $pdf -ksf $certificate -ksp {$password} {$params->getJSignParameters()} -d {$params->getPathPdfSigned()} 2>&1";
141 | }
142 |
143 | private function javaCommand(JSignParam $params): string
144 | {
145 | $javaRuntimeService = new JavaRuntimeService();
146 | return $javaRuntimeService->getPath($params);
147 | }
148 |
149 | private function getjSignPdfJarPath(JSignParam $params): string
150 | {
151 | $JsignPdfRuntimeService = new JSignPdfRuntimeService();
152 | return $JsignPdfRuntimeService->getPath($params);
153 | }
154 |
155 | private function throwIf(bool $condition, string $message): void
156 | {
157 | if ($condition) {
158 | throw new Exception($message);
159 | }
160 | }
161 |
162 | private function isPasswordCertificateValid(JSignParam $params): bool
163 | {
164 | return $this->pkcs12Read($params) ? true : false;
165 | }
166 |
167 | /**
168 | * Prevent error to read certificate generated with old version of
169 | * openssl and using a newest version of openssl.
170 | *
171 | * To check the password is necessary to repack the certificate using
172 | * openssl command. If the command don't exists, will consider that
173 | * the password is invalid.
174 | *
175 | * Reference:
176 | *
177 | * https://github.com/php/php-src/issues/12128
178 | * https://www.php.net/manual/en/function.openssl-pkcs12-read.php#128992
179 | */
180 | private function pkcs12Read(JSignParam $params): array
181 | {
182 | $certificate = $params->getCertificate();
183 | $password = $params->getPassword();
184 | if (openssl_pkcs12_read($certificate, $certInfo, $password)) {
185 | $this->repackCertificateIfPasswordIsUnicode($params, $certInfo['cert'], $certInfo['pkey']);
186 | return $certInfo;
187 | }
188 | $msg = openssl_error_string();
189 | if ($msg === 'error:0308010C:digital envelope routines::unsupported') {
190 | $opensslVersion = exec('openssl version');
191 | if ($opensslVersion === false) {
192 | return [];
193 | }
194 | $tempPassword = tempnam(sys_get_temp_dir(), 'pfx');
195 | $tempEncriptedOriginal = tempnam(sys_get_temp_dir(), 'original');
196 | $tempEncriptedRepacked = tempnam(sys_get_temp_dir(), 'repacked');
197 | $tempDecrypted = tempnam(sys_get_temp_dir(), 'decripted');
198 | if ($tempDecrypted === false || $tempPassword === false || $tempEncriptedOriginal === false || $tempEncriptedRepacked === false) {
199 | return [];
200 | }
201 | file_put_contents($tempPassword, $password);
202 | file_put_contents($tempEncriptedOriginal, $certificate);
203 | $this->safeExec($tempPassword, $tempEncriptedOriginal, $tempDecrypted, $tempEncriptedRepacked);
204 | $certificateRepacked = file_get_contents($tempEncriptedRepacked);
205 | if ($certificateRepacked === false) {
206 | return [];
207 | }
208 | $params->setCertificate($certificateRepacked);
209 | unlink($tempPassword);
210 | unlink($tempEncriptedOriginal);
211 | unlink($tempEncriptedRepacked);
212 | unlink($tempDecrypted);
213 | openssl_pkcs12_read($certificateRepacked, $certInfo, $password);
214 | $this->repackCertificateIfPasswordIsUnicode($params, $certInfo['cert'], $certInfo['pkey']);
215 | return $certInfo;
216 | }
217 | return [];
218 | }
219 |
220 | private function safeExec(
221 | string $tempPassword,
222 | string $tempEncriptedOriginal,
223 | string $tempDecrypted,
224 | string $tempEncriptedRepacked,
225 | ): void {
226 | $tempPassword = escapeshellarg($tempPassword);
227 | $tempEncriptedOriginal = escapeshellarg($tempEncriptedOriginal);
228 | $tempDecrypted = escapeshellarg($tempDecrypted);
229 | $tempEncriptedRepacked = escapeshellarg($tempEncriptedRepacked);
230 |
231 | exec(
232 | <<pkcs12Read($params);
254 | $certificate = openssl_x509_parse($certInfo['cert']);
255 | if (!is_array($certificate)) {
256 | throw new Exception('Invalid certificate');
257 | }
258 | $currentDate = new DateTime();
259 | $dateCert = (clone $currentDate)->setTimestamp($certificate['validTo_time_t']);
260 | return $dateCert <= $currentDate;
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/vendor-bin/phpunit/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "a599f9439564eae0c324bd8f816485da",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "donatj/mock-webserver",
12 | "version": "v2.8.0",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/donatj/mock-webserver.git",
16 | "reference": "73b5d53a8f1285674ff36b2474a4cb9132d81177"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/donatj/mock-webserver/zipball/73b5d53a8f1285674ff36b2474a4cb9132d81177",
21 | "reference": "73b5d53a8f1285674ff36b2474a4cb9132d81177",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "ext-json": "*",
26 | "ext-sockets": "*",
27 | "php": ">=7.1",
28 | "ralouphie/getallheaders": "~2.0 || ~3.0"
29 | },
30 | "require-dev": {
31 | "corpus/coding-standard": "^0.6.0 || ^0.9.0",
32 | "donatj/drop": "^1.0",
33 | "ext-curl": "*",
34 | "friendsofphp/php-cs-fixer": "^3.1",
35 | "phpunit/phpunit": "~7|~9",
36 | "squizlabs/php_codesniffer": "^3.6"
37 | },
38 | "type": "library",
39 | "autoload": {
40 | "psr-4": {
41 | "donatj\\MockWebServer\\": "src/"
42 | }
43 | },
44 | "notification-url": "https://packagist.org/downloads/",
45 | "license": [
46 | "MIT"
47 | ],
48 | "authors": [
49 | {
50 | "name": "Jesse G. Donat",
51 | "email": "donatj@gmail.com",
52 | "homepage": "https://donatstudios.com",
53 | "role": "Lead"
54 | }
55 | ],
56 | "description": "Simple mock web server for unit testing",
57 | "keywords": [
58 | "dev",
59 | "http",
60 | "mock",
61 | "phpunit",
62 | "testing",
63 | "unit testing",
64 | "webserver"
65 | ],
66 | "support": {
67 | "issues": "https://github.com/donatj/mock-webserver/issues",
68 | "source": "https://github.com/donatj/mock-webserver/tree/v2.8.0"
69 | },
70 | "funding": [
71 | {
72 | "url": "https://www.paypal.me/donatj/15",
73 | "type": "custom"
74 | },
75 | {
76 | "url": "https://github.com/donatj",
77 | "type": "github"
78 | },
79 | {
80 | "url": "https://ko-fi.com/donatj",
81 | "type": "ko_fi"
82 | }
83 | ],
84 | "time": "2025-06-26T22:24:11+00:00"
85 | },
86 | {
87 | "name": "mikey179/vfsstream",
88 | "version": "v1.6.12",
89 | "source": {
90 | "type": "git",
91 | "url": "https://github.com/bovigo/vfsStream.git",
92 | "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0"
93 | },
94 | "dist": {
95 | "type": "zip",
96 | "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0",
97 | "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0",
98 | "shasum": ""
99 | },
100 | "require": {
101 | "php": ">=7.1.0"
102 | },
103 | "require-dev": {
104 | "phpunit/phpunit": "^7.5||^8.5||^9.6",
105 | "yoast/phpunit-polyfills": "^2.0"
106 | },
107 | "type": "library",
108 | "extra": {
109 | "branch-alias": {
110 | "dev-master": "1.6.x-dev"
111 | }
112 | },
113 | "autoload": {
114 | "psr-0": {
115 | "org\\bovigo\\vfs\\": "src/main/php"
116 | }
117 | },
118 | "notification-url": "https://packagist.org/downloads/",
119 | "license": [
120 | "BSD-3-Clause"
121 | ],
122 | "authors": [
123 | {
124 | "name": "Frank Kleine",
125 | "homepage": "http://frankkleine.de/",
126 | "role": "Developer"
127 | }
128 | ],
129 | "description": "Virtual file system to mock the real file system in unit tests.",
130 | "homepage": "http://vfs.bovigo.org/",
131 | "support": {
132 | "issues": "https://github.com/bovigo/vfsStream/issues",
133 | "source": "https://github.com/bovigo/vfsStream/tree/master",
134 | "wiki": "https://github.com/bovigo/vfsStream/wiki"
135 | },
136 | "time": "2024-08-29T18:43:31+00:00"
137 | },
138 | {
139 | "name": "myclabs/deep-copy",
140 | "version": "1.13.4",
141 | "source": {
142 | "type": "git",
143 | "url": "https://github.com/myclabs/DeepCopy.git",
144 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
145 | },
146 | "dist": {
147 | "type": "zip",
148 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
149 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
150 | "shasum": ""
151 | },
152 | "require": {
153 | "php": "^7.1 || ^8.0"
154 | },
155 | "conflict": {
156 | "doctrine/collections": "<1.6.8",
157 | "doctrine/common": "<2.13.3 || >=3 <3.2.2"
158 | },
159 | "require-dev": {
160 | "doctrine/collections": "^1.6.8",
161 | "doctrine/common": "^2.13.3 || ^3.2.2",
162 | "phpspec/prophecy": "^1.10",
163 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
164 | },
165 | "type": "library",
166 | "autoload": {
167 | "files": [
168 | "src/DeepCopy/deep_copy.php"
169 | ],
170 | "psr-4": {
171 | "DeepCopy\\": "src/DeepCopy/"
172 | }
173 | },
174 | "notification-url": "https://packagist.org/downloads/",
175 | "license": [
176 | "MIT"
177 | ],
178 | "description": "Create deep copies (clones) of your objects",
179 | "keywords": [
180 | "clone",
181 | "copy",
182 | "duplicate",
183 | "object",
184 | "object graph"
185 | ],
186 | "support": {
187 | "issues": "https://github.com/myclabs/DeepCopy/issues",
188 | "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
189 | },
190 | "funding": [
191 | {
192 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
193 | "type": "tidelift"
194 | }
195 | ],
196 | "time": "2025-08-01T08:46:24+00:00"
197 | },
198 | {
199 | "name": "nikic/php-parser",
200 | "version": "v5.6.0",
201 | "source": {
202 | "type": "git",
203 | "url": "https://github.com/nikic/PHP-Parser.git",
204 | "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
205 | },
206 | "dist": {
207 | "type": "zip",
208 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
209 | "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
210 | "shasum": ""
211 | },
212 | "require": {
213 | "ext-ctype": "*",
214 | "ext-json": "*",
215 | "ext-tokenizer": "*",
216 | "php": ">=7.4"
217 | },
218 | "require-dev": {
219 | "ircmaxell/php-yacc": "^0.0.7",
220 | "phpunit/phpunit": "^9.0"
221 | },
222 | "bin": [
223 | "bin/php-parse"
224 | ],
225 | "type": "library",
226 | "extra": {
227 | "branch-alias": {
228 | "dev-master": "5.0-dev"
229 | }
230 | },
231 | "autoload": {
232 | "psr-4": {
233 | "PhpParser\\": "lib/PhpParser"
234 | }
235 | },
236 | "notification-url": "https://packagist.org/downloads/",
237 | "license": [
238 | "BSD-3-Clause"
239 | ],
240 | "authors": [
241 | {
242 | "name": "Nikita Popov"
243 | }
244 | ],
245 | "description": "A PHP parser written in PHP",
246 | "keywords": [
247 | "parser",
248 | "php"
249 | ],
250 | "support": {
251 | "issues": "https://github.com/nikic/PHP-Parser/issues",
252 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
253 | },
254 | "time": "2025-07-27T20:03:57+00:00"
255 | },
256 | {
257 | "name": "phar-io/manifest",
258 | "version": "2.0.4",
259 | "source": {
260 | "type": "git",
261 | "url": "https://github.com/phar-io/manifest.git",
262 | "reference": "54750ef60c58e43759730615a392c31c80e23176"
263 | },
264 | "dist": {
265 | "type": "zip",
266 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
267 | "reference": "54750ef60c58e43759730615a392c31c80e23176",
268 | "shasum": ""
269 | },
270 | "require": {
271 | "ext-dom": "*",
272 | "ext-libxml": "*",
273 | "ext-phar": "*",
274 | "ext-xmlwriter": "*",
275 | "phar-io/version": "^3.0.1",
276 | "php": "^7.2 || ^8.0"
277 | },
278 | "type": "library",
279 | "extra": {
280 | "branch-alias": {
281 | "dev-master": "2.0.x-dev"
282 | }
283 | },
284 | "autoload": {
285 | "classmap": [
286 | "src/"
287 | ]
288 | },
289 | "notification-url": "https://packagist.org/downloads/",
290 | "license": [
291 | "BSD-3-Clause"
292 | ],
293 | "authors": [
294 | {
295 | "name": "Arne Blankerts",
296 | "email": "arne@blankerts.de",
297 | "role": "Developer"
298 | },
299 | {
300 | "name": "Sebastian Heuer",
301 | "email": "sebastian@phpeople.de",
302 | "role": "Developer"
303 | },
304 | {
305 | "name": "Sebastian Bergmann",
306 | "email": "sebastian@phpunit.de",
307 | "role": "Developer"
308 | }
309 | ],
310 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
311 | "support": {
312 | "issues": "https://github.com/phar-io/manifest/issues",
313 | "source": "https://github.com/phar-io/manifest/tree/2.0.4"
314 | },
315 | "funding": [
316 | {
317 | "url": "https://github.com/theseer",
318 | "type": "github"
319 | }
320 | ],
321 | "time": "2024-03-03T12:33:53+00:00"
322 | },
323 | {
324 | "name": "phar-io/version",
325 | "version": "3.2.1",
326 | "source": {
327 | "type": "git",
328 | "url": "https://github.com/phar-io/version.git",
329 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
330 | },
331 | "dist": {
332 | "type": "zip",
333 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
334 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
335 | "shasum": ""
336 | },
337 | "require": {
338 | "php": "^7.2 || ^8.0"
339 | },
340 | "type": "library",
341 | "autoload": {
342 | "classmap": [
343 | "src/"
344 | ]
345 | },
346 | "notification-url": "https://packagist.org/downloads/",
347 | "license": [
348 | "BSD-3-Clause"
349 | ],
350 | "authors": [
351 | {
352 | "name": "Arne Blankerts",
353 | "email": "arne@blankerts.de",
354 | "role": "Developer"
355 | },
356 | {
357 | "name": "Sebastian Heuer",
358 | "email": "sebastian@phpeople.de",
359 | "role": "Developer"
360 | },
361 | {
362 | "name": "Sebastian Bergmann",
363 | "email": "sebastian@phpunit.de",
364 | "role": "Developer"
365 | }
366 | ],
367 | "description": "Library for handling version information and constraints",
368 | "support": {
369 | "issues": "https://github.com/phar-io/version/issues",
370 | "source": "https://github.com/phar-io/version/tree/3.2.1"
371 | },
372 | "time": "2022-02-21T01:04:05+00:00"
373 | },
374 | {
375 | "name": "phpunit/php-code-coverage",
376 | "version": "10.1.16",
377 | "source": {
378 | "type": "git",
379 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
380 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
381 | },
382 | "dist": {
383 | "type": "zip",
384 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
385 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
386 | "shasum": ""
387 | },
388 | "require": {
389 | "ext-dom": "*",
390 | "ext-libxml": "*",
391 | "ext-xmlwriter": "*",
392 | "nikic/php-parser": "^4.19.1 || ^5.1.0",
393 | "php": ">=8.1",
394 | "phpunit/php-file-iterator": "^4.1.0",
395 | "phpunit/php-text-template": "^3.0.1",
396 | "sebastian/code-unit-reverse-lookup": "^3.0.0",
397 | "sebastian/complexity": "^3.2.0",
398 | "sebastian/environment": "^6.1.0",
399 | "sebastian/lines-of-code": "^2.0.2",
400 | "sebastian/version": "^4.0.1",
401 | "theseer/tokenizer": "^1.2.3"
402 | },
403 | "require-dev": {
404 | "phpunit/phpunit": "^10.1"
405 | },
406 | "suggest": {
407 | "ext-pcov": "PHP extension that provides line coverage",
408 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
409 | },
410 | "type": "library",
411 | "extra": {
412 | "branch-alias": {
413 | "dev-main": "10.1.x-dev"
414 | }
415 | },
416 | "autoload": {
417 | "classmap": [
418 | "src/"
419 | ]
420 | },
421 | "notification-url": "https://packagist.org/downloads/",
422 | "license": [
423 | "BSD-3-Clause"
424 | ],
425 | "authors": [
426 | {
427 | "name": "Sebastian Bergmann",
428 | "email": "sebastian@phpunit.de",
429 | "role": "lead"
430 | }
431 | ],
432 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
433 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
434 | "keywords": [
435 | "coverage",
436 | "testing",
437 | "xunit"
438 | ],
439 | "support": {
440 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
441 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
442 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
443 | },
444 | "funding": [
445 | {
446 | "url": "https://github.com/sebastianbergmann",
447 | "type": "github"
448 | }
449 | ],
450 | "time": "2024-08-22T04:31:57+00:00"
451 | },
452 | {
453 | "name": "phpunit/php-file-iterator",
454 | "version": "4.1.0",
455 | "source": {
456 | "type": "git",
457 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
458 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
459 | },
460 | "dist": {
461 | "type": "zip",
462 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
463 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
464 | "shasum": ""
465 | },
466 | "require": {
467 | "php": ">=8.1"
468 | },
469 | "require-dev": {
470 | "phpunit/phpunit": "^10.0"
471 | },
472 | "type": "library",
473 | "extra": {
474 | "branch-alias": {
475 | "dev-main": "4.0-dev"
476 | }
477 | },
478 | "autoload": {
479 | "classmap": [
480 | "src/"
481 | ]
482 | },
483 | "notification-url": "https://packagist.org/downloads/",
484 | "license": [
485 | "BSD-3-Clause"
486 | ],
487 | "authors": [
488 | {
489 | "name": "Sebastian Bergmann",
490 | "email": "sebastian@phpunit.de",
491 | "role": "lead"
492 | }
493 | ],
494 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
495 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
496 | "keywords": [
497 | "filesystem",
498 | "iterator"
499 | ],
500 | "support": {
501 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
502 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
503 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
504 | },
505 | "funding": [
506 | {
507 | "url": "https://github.com/sebastianbergmann",
508 | "type": "github"
509 | }
510 | ],
511 | "time": "2023-08-31T06:24:48+00:00"
512 | },
513 | {
514 | "name": "phpunit/php-invoker",
515 | "version": "4.0.0",
516 | "source": {
517 | "type": "git",
518 | "url": "https://github.com/sebastianbergmann/php-invoker.git",
519 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
520 | },
521 | "dist": {
522 | "type": "zip",
523 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
524 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
525 | "shasum": ""
526 | },
527 | "require": {
528 | "php": ">=8.1"
529 | },
530 | "require-dev": {
531 | "ext-pcntl": "*",
532 | "phpunit/phpunit": "^10.0"
533 | },
534 | "suggest": {
535 | "ext-pcntl": "*"
536 | },
537 | "type": "library",
538 | "extra": {
539 | "branch-alias": {
540 | "dev-main": "4.0-dev"
541 | }
542 | },
543 | "autoload": {
544 | "classmap": [
545 | "src/"
546 | ]
547 | },
548 | "notification-url": "https://packagist.org/downloads/",
549 | "license": [
550 | "BSD-3-Clause"
551 | ],
552 | "authors": [
553 | {
554 | "name": "Sebastian Bergmann",
555 | "email": "sebastian@phpunit.de",
556 | "role": "lead"
557 | }
558 | ],
559 | "description": "Invoke callables with a timeout",
560 | "homepage": "https://github.com/sebastianbergmann/php-invoker/",
561 | "keywords": [
562 | "process"
563 | ],
564 | "support": {
565 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
566 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
567 | },
568 | "funding": [
569 | {
570 | "url": "https://github.com/sebastianbergmann",
571 | "type": "github"
572 | }
573 | ],
574 | "time": "2023-02-03T06:56:09+00:00"
575 | },
576 | {
577 | "name": "phpunit/php-text-template",
578 | "version": "3.0.1",
579 | "source": {
580 | "type": "git",
581 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
582 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
583 | },
584 | "dist": {
585 | "type": "zip",
586 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
587 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
588 | "shasum": ""
589 | },
590 | "require": {
591 | "php": ">=8.1"
592 | },
593 | "require-dev": {
594 | "phpunit/phpunit": "^10.0"
595 | },
596 | "type": "library",
597 | "extra": {
598 | "branch-alias": {
599 | "dev-main": "3.0-dev"
600 | }
601 | },
602 | "autoload": {
603 | "classmap": [
604 | "src/"
605 | ]
606 | },
607 | "notification-url": "https://packagist.org/downloads/",
608 | "license": [
609 | "BSD-3-Clause"
610 | ],
611 | "authors": [
612 | {
613 | "name": "Sebastian Bergmann",
614 | "email": "sebastian@phpunit.de",
615 | "role": "lead"
616 | }
617 | ],
618 | "description": "Simple template engine.",
619 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
620 | "keywords": [
621 | "template"
622 | ],
623 | "support": {
624 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
625 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
626 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
627 | },
628 | "funding": [
629 | {
630 | "url": "https://github.com/sebastianbergmann",
631 | "type": "github"
632 | }
633 | ],
634 | "time": "2023-08-31T14:07:24+00:00"
635 | },
636 | {
637 | "name": "phpunit/php-timer",
638 | "version": "6.0.0",
639 | "source": {
640 | "type": "git",
641 | "url": "https://github.com/sebastianbergmann/php-timer.git",
642 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
643 | },
644 | "dist": {
645 | "type": "zip",
646 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
647 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
648 | "shasum": ""
649 | },
650 | "require": {
651 | "php": ">=8.1"
652 | },
653 | "require-dev": {
654 | "phpunit/phpunit": "^10.0"
655 | },
656 | "type": "library",
657 | "extra": {
658 | "branch-alias": {
659 | "dev-main": "6.0-dev"
660 | }
661 | },
662 | "autoload": {
663 | "classmap": [
664 | "src/"
665 | ]
666 | },
667 | "notification-url": "https://packagist.org/downloads/",
668 | "license": [
669 | "BSD-3-Clause"
670 | ],
671 | "authors": [
672 | {
673 | "name": "Sebastian Bergmann",
674 | "email": "sebastian@phpunit.de",
675 | "role": "lead"
676 | }
677 | ],
678 | "description": "Utility class for timing",
679 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
680 | "keywords": [
681 | "timer"
682 | ],
683 | "support": {
684 | "issues": "https://github.com/sebastianbergmann/php-timer/issues",
685 | "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
686 | },
687 | "funding": [
688 | {
689 | "url": "https://github.com/sebastianbergmann",
690 | "type": "github"
691 | }
692 | ],
693 | "time": "2023-02-03T06:57:52+00:00"
694 | },
695 | {
696 | "name": "phpunit/phpunit",
697 | "version": "10.5.48",
698 | "source": {
699 | "type": "git",
700 | "url": "https://github.com/sebastianbergmann/phpunit.git",
701 | "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541"
702 | },
703 | "dist": {
704 | "type": "zip",
705 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e0a2bc39f6fae7617989d690d76c48e6d2eb541",
706 | "reference": "6e0a2bc39f6fae7617989d690d76c48e6d2eb541",
707 | "shasum": ""
708 | },
709 | "require": {
710 | "ext-dom": "*",
711 | "ext-json": "*",
712 | "ext-libxml": "*",
713 | "ext-mbstring": "*",
714 | "ext-xml": "*",
715 | "ext-xmlwriter": "*",
716 | "myclabs/deep-copy": "^1.13.3",
717 | "phar-io/manifest": "^2.0.4",
718 | "phar-io/version": "^3.2.1",
719 | "php": ">=8.1",
720 | "phpunit/php-code-coverage": "^10.1.16",
721 | "phpunit/php-file-iterator": "^4.1.0",
722 | "phpunit/php-invoker": "^4.0.0",
723 | "phpunit/php-text-template": "^3.0.1",
724 | "phpunit/php-timer": "^6.0.0",
725 | "sebastian/cli-parser": "^2.0.1",
726 | "sebastian/code-unit": "^2.0.0",
727 | "sebastian/comparator": "^5.0.3",
728 | "sebastian/diff": "^5.1.1",
729 | "sebastian/environment": "^6.1.0",
730 | "sebastian/exporter": "^5.1.2",
731 | "sebastian/global-state": "^6.0.2",
732 | "sebastian/object-enumerator": "^5.0.0",
733 | "sebastian/recursion-context": "^5.0.0",
734 | "sebastian/type": "^4.0.0",
735 | "sebastian/version": "^4.0.1"
736 | },
737 | "suggest": {
738 | "ext-soap": "To be able to generate mocks based on WSDL files"
739 | },
740 | "bin": [
741 | "phpunit"
742 | ],
743 | "type": "library",
744 | "extra": {
745 | "branch-alias": {
746 | "dev-main": "10.5-dev"
747 | }
748 | },
749 | "autoload": {
750 | "files": [
751 | "src/Framework/Assert/Functions.php"
752 | ],
753 | "classmap": [
754 | "src/"
755 | ]
756 | },
757 | "notification-url": "https://packagist.org/downloads/",
758 | "license": [
759 | "BSD-3-Clause"
760 | ],
761 | "authors": [
762 | {
763 | "name": "Sebastian Bergmann",
764 | "email": "sebastian@phpunit.de",
765 | "role": "lead"
766 | }
767 | ],
768 | "description": "The PHP Unit Testing framework.",
769 | "homepage": "https://phpunit.de/",
770 | "keywords": [
771 | "phpunit",
772 | "testing",
773 | "xunit"
774 | ],
775 | "support": {
776 | "issues": "https://github.com/sebastianbergmann/phpunit/issues",
777 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
778 | "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.48"
779 | },
780 | "funding": [
781 | {
782 | "url": "https://phpunit.de/sponsors.html",
783 | "type": "custom"
784 | },
785 | {
786 | "url": "https://github.com/sebastianbergmann",
787 | "type": "github"
788 | },
789 | {
790 | "url": "https://liberapay.com/sebastianbergmann",
791 | "type": "liberapay"
792 | },
793 | {
794 | "url": "https://thanks.dev/u/gh/sebastianbergmann",
795 | "type": "thanks_dev"
796 | },
797 | {
798 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
799 | "type": "tidelift"
800 | }
801 | ],
802 | "time": "2025-07-11T04:07:17+00:00"
803 | },
804 | {
805 | "name": "ralouphie/getallheaders",
806 | "version": "3.0.3",
807 | "source": {
808 | "type": "git",
809 | "url": "https://github.com/ralouphie/getallheaders.git",
810 | "reference": "120b605dfeb996808c31b6477290a714d356e822"
811 | },
812 | "dist": {
813 | "type": "zip",
814 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
815 | "reference": "120b605dfeb996808c31b6477290a714d356e822",
816 | "shasum": ""
817 | },
818 | "require": {
819 | "php": ">=5.6"
820 | },
821 | "require-dev": {
822 | "php-coveralls/php-coveralls": "^2.1",
823 | "phpunit/phpunit": "^5 || ^6.5"
824 | },
825 | "type": "library",
826 | "autoload": {
827 | "files": [
828 | "src/getallheaders.php"
829 | ]
830 | },
831 | "notification-url": "https://packagist.org/downloads/",
832 | "license": [
833 | "MIT"
834 | ],
835 | "authors": [
836 | {
837 | "name": "Ralph Khattar",
838 | "email": "ralph.khattar@gmail.com"
839 | }
840 | ],
841 | "description": "A polyfill for getallheaders.",
842 | "support": {
843 | "issues": "https://github.com/ralouphie/getallheaders/issues",
844 | "source": "https://github.com/ralouphie/getallheaders/tree/develop"
845 | },
846 | "time": "2019-03-08T08:55:37+00:00"
847 | },
848 | {
849 | "name": "sebastian/cli-parser",
850 | "version": "2.0.1",
851 | "source": {
852 | "type": "git",
853 | "url": "https://github.com/sebastianbergmann/cli-parser.git",
854 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
855 | },
856 | "dist": {
857 | "type": "zip",
858 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
859 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
860 | "shasum": ""
861 | },
862 | "require": {
863 | "php": ">=8.1"
864 | },
865 | "require-dev": {
866 | "phpunit/phpunit": "^10.0"
867 | },
868 | "type": "library",
869 | "extra": {
870 | "branch-alias": {
871 | "dev-main": "2.0-dev"
872 | }
873 | },
874 | "autoload": {
875 | "classmap": [
876 | "src/"
877 | ]
878 | },
879 | "notification-url": "https://packagist.org/downloads/",
880 | "license": [
881 | "BSD-3-Clause"
882 | ],
883 | "authors": [
884 | {
885 | "name": "Sebastian Bergmann",
886 | "email": "sebastian@phpunit.de",
887 | "role": "lead"
888 | }
889 | ],
890 | "description": "Library for parsing CLI options",
891 | "homepage": "https://github.com/sebastianbergmann/cli-parser",
892 | "support": {
893 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
894 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
895 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
896 | },
897 | "funding": [
898 | {
899 | "url": "https://github.com/sebastianbergmann",
900 | "type": "github"
901 | }
902 | ],
903 | "time": "2024-03-02T07:12:49+00:00"
904 | },
905 | {
906 | "name": "sebastian/code-unit",
907 | "version": "2.0.0",
908 | "source": {
909 | "type": "git",
910 | "url": "https://github.com/sebastianbergmann/code-unit.git",
911 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
912 | },
913 | "dist": {
914 | "type": "zip",
915 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
916 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
917 | "shasum": ""
918 | },
919 | "require": {
920 | "php": ">=8.1"
921 | },
922 | "require-dev": {
923 | "phpunit/phpunit": "^10.0"
924 | },
925 | "type": "library",
926 | "extra": {
927 | "branch-alias": {
928 | "dev-main": "2.0-dev"
929 | }
930 | },
931 | "autoload": {
932 | "classmap": [
933 | "src/"
934 | ]
935 | },
936 | "notification-url": "https://packagist.org/downloads/",
937 | "license": [
938 | "BSD-3-Clause"
939 | ],
940 | "authors": [
941 | {
942 | "name": "Sebastian Bergmann",
943 | "email": "sebastian@phpunit.de",
944 | "role": "lead"
945 | }
946 | ],
947 | "description": "Collection of value objects that represent the PHP code units",
948 | "homepage": "https://github.com/sebastianbergmann/code-unit",
949 | "support": {
950 | "issues": "https://github.com/sebastianbergmann/code-unit/issues",
951 | "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
952 | },
953 | "funding": [
954 | {
955 | "url": "https://github.com/sebastianbergmann",
956 | "type": "github"
957 | }
958 | ],
959 | "time": "2023-02-03T06:58:43+00:00"
960 | },
961 | {
962 | "name": "sebastian/code-unit-reverse-lookup",
963 | "version": "3.0.0",
964 | "source": {
965 | "type": "git",
966 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
967 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
968 | },
969 | "dist": {
970 | "type": "zip",
971 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
972 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
973 | "shasum": ""
974 | },
975 | "require": {
976 | "php": ">=8.1"
977 | },
978 | "require-dev": {
979 | "phpunit/phpunit": "^10.0"
980 | },
981 | "type": "library",
982 | "extra": {
983 | "branch-alias": {
984 | "dev-main": "3.0-dev"
985 | }
986 | },
987 | "autoload": {
988 | "classmap": [
989 | "src/"
990 | ]
991 | },
992 | "notification-url": "https://packagist.org/downloads/",
993 | "license": [
994 | "BSD-3-Clause"
995 | ],
996 | "authors": [
997 | {
998 | "name": "Sebastian Bergmann",
999 | "email": "sebastian@phpunit.de"
1000 | }
1001 | ],
1002 | "description": "Looks up which function or method a line of code belongs to",
1003 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
1004 | "support": {
1005 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
1006 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
1007 | },
1008 | "funding": [
1009 | {
1010 | "url": "https://github.com/sebastianbergmann",
1011 | "type": "github"
1012 | }
1013 | ],
1014 | "time": "2023-02-03T06:59:15+00:00"
1015 | },
1016 | {
1017 | "name": "sebastian/comparator",
1018 | "version": "5.0.3",
1019 | "source": {
1020 | "type": "git",
1021 | "url": "https://github.com/sebastianbergmann/comparator.git",
1022 | "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
1023 | },
1024 | "dist": {
1025 | "type": "zip",
1026 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
1027 | "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
1028 | "shasum": ""
1029 | },
1030 | "require": {
1031 | "ext-dom": "*",
1032 | "ext-mbstring": "*",
1033 | "php": ">=8.1",
1034 | "sebastian/diff": "^5.0",
1035 | "sebastian/exporter": "^5.0"
1036 | },
1037 | "require-dev": {
1038 | "phpunit/phpunit": "^10.5"
1039 | },
1040 | "type": "library",
1041 | "extra": {
1042 | "branch-alias": {
1043 | "dev-main": "5.0-dev"
1044 | }
1045 | },
1046 | "autoload": {
1047 | "classmap": [
1048 | "src/"
1049 | ]
1050 | },
1051 | "notification-url": "https://packagist.org/downloads/",
1052 | "license": [
1053 | "BSD-3-Clause"
1054 | ],
1055 | "authors": [
1056 | {
1057 | "name": "Sebastian Bergmann",
1058 | "email": "sebastian@phpunit.de"
1059 | },
1060 | {
1061 | "name": "Jeff Welch",
1062 | "email": "whatthejeff@gmail.com"
1063 | },
1064 | {
1065 | "name": "Volker Dusch",
1066 | "email": "github@wallbash.com"
1067 | },
1068 | {
1069 | "name": "Bernhard Schussek",
1070 | "email": "bschussek@2bepublished.at"
1071 | }
1072 | ],
1073 | "description": "Provides the functionality to compare PHP values for equality",
1074 | "homepage": "https://github.com/sebastianbergmann/comparator",
1075 | "keywords": [
1076 | "comparator",
1077 | "compare",
1078 | "equality"
1079 | ],
1080 | "support": {
1081 | "issues": "https://github.com/sebastianbergmann/comparator/issues",
1082 | "security": "https://github.com/sebastianbergmann/comparator/security/policy",
1083 | "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
1084 | },
1085 | "funding": [
1086 | {
1087 | "url": "https://github.com/sebastianbergmann",
1088 | "type": "github"
1089 | }
1090 | ],
1091 | "time": "2024-10-18T14:56:07+00:00"
1092 | },
1093 | {
1094 | "name": "sebastian/complexity",
1095 | "version": "3.2.0",
1096 | "source": {
1097 | "type": "git",
1098 | "url": "https://github.com/sebastianbergmann/complexity.git",
1099 | "reference": "68ff824baeae169ec9f2137158ee529584553799"
1100 | },
1101 | "dist": {
1102 | "type": "zip",
1103 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
1104 | "reference": "68ff824baeae169ec9f2137158ee529584553799",
1105 | "shasum": ""
1106 | },
1107 | "require": {
1108 | "nikic/php-parser": "^4.18 || ^5.0",
1109 | "php": ">=8.1"
1110 | },
1111 | "require-dev": {
1112 | "phpunit/phpunit": "^10.0"
1113 | },
1114 | "type": "library",
1115 | "extra": {
1116 | "branch-alias": {
1117 | "dev-main": "3.2-dev"
1118 | }
1119 | },
1120 | "autoload": {
1121 | "classmap": [
1122 | "src/"
1123 | ]
1124 | },
1125 | "notification-url": "https://packagist.org/downloads/",
1126 | "license": [
1127 | "BSD-3-Clause"
1128 | ],
1129 | "authors": [
1130 | {
1131 | "name": "Sebastian Bergmann",
1132 | "email": "sebastian@phpunit.de",
1133 | "role": "lead"
1134 | }
1135 | ],
1136 | "description": "Library for calculating the complexity of PHP code units",
1137 | "homepage": "https://github.com/sebastianbergmann/complexity",
1138 | "support": {
1139 | "issues": "https://github.com/sebastianbergmann/complexity/issues",
1140 | "security": "https://github.com/sebastianbergmann/complexity/security/policy",
1141 | "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
1142 | },
1143 | "funding": [
1144 | {
1145 | "url": "https://github.com/sebastianbergmann",
1146 | "type": "github"
1147 | }
1148 | ],
1149 | "time": "2023-12-21T08:37:17+00:00"
1150 | },
1151 | {
1152 | "name": "sebastian/diff",
1153 | "version": "5.1.1",
1154 | "source": {
1155 | "type": "git",
1156 | "url": "https://github.com/sebastianbergmann/diff.git",
1157 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
1158 | },
1159 | "dist": {
1160 | "type": "zip",
1161 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
1162 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
1163 | "shasum": ""
1164 | },
1165 | "require": {
1166 | "php": ">=8.1"
1167 | },
1168 | "require-dev": {
1169 | "phpunit/phpunit": "^10.0",
1170 | "symfony/process": "^6.4"
1171 | },
1172 | "type": "library",
1173 | "extra": {
1174 | "branch-alias": {
1175 | "dev-main": "5.1-dev"
1176 | }
1177 | },
1178 | "autoload": {
1179 | "classmap": [
1180 | "src/"
1181 | ]
1182 | },
1183 | "notification-url": "https://packagist.org/downloads/",
1184 | "license": [
1185 | "BSD-3-Clause"
1186 | ],
1187 | "authors": [
1188 | {
1189 | "name": "Sebastian Bergmann",
1190 | "email": "sebastian@phpunit.de"
1191 | },
1192 | {
1193 | "name": "Kore Nordmann",
1194 | "email": "mail@kore-nordmann.de"
1195 | }
1196 | ],
1197 | "description": "Diff implementation",
1198 | "homepage": "https://github.com/sebastianbergmann/diff",
1199 | "keywords": [
1200 | "diff",
1201 | "udiff",
1202 | "unidiff",
1203 | "unified diff"
1204 | ],
1205 | "support": {
1206 | "issues": "https://github.com/sebastianbergmann/diff/issues",
1207 | "security": "https://github.com/sebastianbergmann/diff/security/policy",
1208 | "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
1209 | },
1210 | "funding": [
1211 | {
1212 | "url": "https://github.com/sebastianbergmann",
1213 | "type": "github"
1214 | }
1215 | ],
1216 | "time": "2024-03-02T07:15:17+00:00"
1217 | },
1218 | {
1219 | "name": "sebastian/environment",
1220 | "version": "6.1.0",
1221 | "source": {
1222 | "type": "git",
1223 | "url": "https://github.com/sebastianbergmann/environment.git",
1224 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
1225 | },
1226 | "dist": {
1227 | "type": "zip",
1228 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
1229 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
1230 | "shasum": ""
1231 | },
1232 | "require": {
1233 | "php": ">=8.1"
1234 | },
1235 | "require-dev": {
1236 | "phpunit/phpunit": "^10.0"
1237 | },
1238 | "suggest": {
1239 | "ext-posix": "*"
1240 | },
1241 | "type": "library",
1242 | "extra": {
1243 | "branch-alias": {
1244 | "dev-main": "6.1-dev"
1245 | }
1246 | },
1247 | "autoload": {
1248 | "classmap": [
1249 | "src/"
1250 | ]
1251 | },
1252 | "notification-url": "https://packagist.org/downloads/",
1253 | "license": [
1254 | "BSD-3-Clause"
1255 | ],
1256 | "authors": [
1257 | {
1258 | "name": "Sebastian Bergmann",
1259 | "email": "sebastian@phpunit.de"
1260 | }
1261 | ],
1262 | "description": "Provides functionality to handle HHVM/PHP environments",
1263 | "homepage": "https://github.com/sebastianbergmann/environment",
1264 | "keywords": [
1265 | "Xdebug",
1266 | "environment",
1267 | "hhvm"
1268 | ],
1269 | "support": {
1270 | "issues": "https://github.com/sebastianbergmann/environment/issues",
1271 | "security": "https://github.com/sebastianbergmann/environment/security/policy",
1272 | "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
1273 | },
1274 | "funding": [
1275 | {
1276 | "url": "https://github.com/sebastianbergmann",
1277 | "type": "github"
1278 | }
1279 | ],
1280 | "time": "2024-03-23T08:47:14+00:00"
1281 | },
1282 | {
1283 | "name": "sebastian/exporter",
1284 | "version": "5.1.2",
1285 | "source": {
1286 | "type": "git",
1287 | "url": "https://github.com/sebastianbergmann/exporter.git",
1288 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
1289 | },
1290 | "dist": {
1291 | "type": "zip",
1292 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
1293 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
1294 | "shasum": ""
1295 | },
1296 | "require": {
1297 | "ext-mbstring": "*",
1298 | "php": ">=8.1",
1299 | "sebastian/recursion-context": "^5.0"
1300 | },
1301 | "require-dev": {
1302 | "phpunit/phpunit": "^10.0"
1303 | },
1304 | "type": "library",
1305 | "extra": {
1306 | "branch-alias": {
1307 | "dev-main": "5.1-dev"
1308 | }
1309 | },
1310 | "autoload": {
1311 | "classmap": [
1312 | "src/"
1313 | ]
1314 | },
1315 | "notification-url": "https://packagist.org/downloads/",
1316 | "license": [
1317 | "BSD-3-Clause"
1318 | ],
1319 | "authors": [
1320 | {
1321 | "name": "Sebastian Bergmann",
1322 | "email": "sebastian@phpunit.de"
1323 | },
1324 | {
1325 | "name": "Jeff Welch",
1326 | "email": "whatthejeff@gmail.com"
1327 | },
1328 | {
1329 | "name": "Volker Dusch",
1330 | "email": "github@wallbash.com"
1331 | },
1332 | {
1333 | "name": "Adam Harvey",
1334 | "email": "aharvey@php.net"
1335 | },
1336 | {
1337 | "name": "Bernhard Schussek",
1338 | "email": "bschussek@gmail.com"
1339 | }
1340 | ],
1341 | "description": "Provides the functionality to export PHP variables for visualization",
1342 | "homepage": "https://www.github.com/sebastianbergmann/exporter",
1343 | "keywords": [
1344 | "export",
1345 | "exporter"
1346 | ],
1347 | "support": {
1348 | "issues": "https://github.com/sebastianbergmann/exporter/issues",
1349 | "security": "https://github.com/sebastianbergmann/exporter/security/policy",
1350 | "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
1351 | },
1352 | "funding": [
1353 | {
1354 | "url": "https://github.com/sebastianbergmann",
1355 | "type": "github"
1356 | }
1357 | ],
1358 | "time": "2024-03-02T07:17:12+00:00"
1359 | },
1360 | {
1361 | "name": "sebastian/global-state",
1362 | "version": "6.0.2",
1363 | "source": {
1364 | "type": "git",
1365 | "url": "https://github.com/sebastianbergmann/global-state.git",
1366 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
1367 | },
1368 | "dist": {
1369 | "type": "zip",
1370 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
1371 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
1372 | "shasum": ""
1373 | },
1374 | "require": {
1375 | "php": ">=8.1",
1376 | "sebastian/object-reflector": "^3.0",
1377 | "sebastian/recursion-context": "^5.0"
1378 | },
1379 | "require-dev": {
1380 | "ext-dom": "*",
1381 | "phpunit/phpunit": "^10.0"
1382 | },
1383 | "type": "library",
1384 | "extra": {
1385 | "branch-alias": {
1386 | "dev-main": "6.0-dev"
1387 | }
1388 | },
1389 | "autoload": {
1390 | "classmap": [
1391 | "src/"
1392 | ]
1393 | },
1394 | "notification-url": "https://packagist.org/downloads/",
1395 | "license": [
1396 | "BSD-3-Clause"
1397 | ],
1398 | "authors": [
1399 | {
1400 | "name": "Sebastian Bergmann",
1401 | "email": "sebastian@phpunit.de"
1402 | }
1403 | ],
1404 | "description": "Snapshotting of global state",
1405 | "homepage": "https://www.github.com/sebastianbergmann/global-state",
1406 | "keywords": [
1407 | "global state"
1408 | ],
1409 | "support": {
1410 | "issues": "https://github.com/sebastianbergmann/global-state/issues",
1411 | "security": "https://github.com/sebastianbergmann/global-state/security/policy",
1412 | "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
1413 | },
1414 | "funding": [
1415 | {
1416 | "url": "https://github.com/sebastianbergmann",
1417 | "type": "github"
1418 | }
1419 | ],
1420 | "time": "2024-03-02T07:19:19+00:00"
1421 | },
1422 | {
1423 | "name": "sebastian/lines-of-code",
1424 | "version": "2.0.2",
1425 | "source": {
1426 | "type": "git",
1427 | "url": "https://github.com/sebastianbergmann/lines-of-code.git",
1428 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
1429 | },
1430 | "dist": {
1431 | "type": "zip",
1432 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
1433 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
1434 | "shasum": ""
1435 | },
1436 | "require": {
1437 | "nikic/php-parser": "^4.18 || ^5.0",
1438 | "php": ">=8.1"
1439 | },
1440 | "require-dev": {
1441 | "phpunit/phpunit": "^10.0"
1442 | },
1443 | "type": "library",
1444 | "extra": {
1445 | "branch-alias": {
1446 | "dev-main": "2.0-dev"
1447 | }
1448 | },
1449 | "autoload": {
1450 | "classmap": [
1451 | "src/"
1452 | ]
1453 | },
1454 | "notification-url": "https://packagist.org/downloads/",
1455 | "license": [
1456 | "BSD-3-Clause"
1457 | ],
1458 | "authors": [
1459 | {
1460 | "name": "Sebastian Bergmann",
1461 | "email": "sebastian@phpunit.de",
1462 | "role": "lead"
1463 | }
1464 | ],
1465 | "description": "Library for counting the lines of code in PHP source code",
1466 | "homepage": "https://github.com/sebastianbergmann/lines-of-code",
1467 | "support": {
1468 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
1469 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
1470 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
1471 | },
1472 | "funding": [
1473 | {
1474 | "url": "https://github.com/sebastianbergmann",
1475 | "type": "github"
1476 | }
1477 | ],
1478 | "time": "2023-12-21T08:38:20+00:00"
1479 | },
1480 | {
1481 | "name": "sebastian/object-enumerator",
1482 | "version": "5.0.0",
1483 | "source": {
1484 | "type": "git",
1485 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1486 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
1487 | },
1488 | "dist": {
1489 | "type": "zip",
1490 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
1491 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
1492 | "shasum": ""
1493 | },
1494 | "require": {
1495 | "php": ">=8.1",
1496 | "sebastian/object-reflector": "^3.0",
1497 | "sebastian/recursion-context": "^5.0"
1498 | },
1499 | "require-dev": {
1500 | "phpunit/phpunit": "^10.0"
1501 | },
1502 | "type": "library",
1503 | "extra": {
1504 | "branch-alias": {
1505 | "dev-main": "5.0-dev"
1506 | }
1507 | },
1508 | "autoload": {
1509 | "classmap": [
1510 | "src/"
1511 | ]
1512 | },
1513 | "notification-url": "https://packagist.org/downloads/",
1514 | "license": [
1515 | "BSD-3-Clause"
1516 | ],
1517 | "authors": [
1518 | {
1519 | "name": "Sebastian Bergmann",
1520 | "email": "sebastian@phpunit.de"
1521 | }
1522 | ],
1523 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1524 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1525 | "support": {
1526 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
1527 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
1528 | },
1529 | "funding": [
1530 | {
1531 | "url": "https://github.com/sebastianbergmann",
1532 | "type": "github"
1533 | }
1534 | ],
1535 | "time": "2023-02-03T07:08:32+00:00"
1536 | },
1537 | {
1538 | "name": "sebastian/object-reflector",
1539 | "version": "3.0.0",
1540 | "source": {
1541 | "type": "git",
1542 | "url": "https://github.com/sebastianbergmann/object-reflector.git",
1543 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
1544 | },
1545 | "dist": {
1546 | "type": "zip",
1547 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
1548 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
1549 | "shasum": ""
1550 | },
1551 | "require": {
1552 | "php": ">=8.1"
1553 | },
1554 | "require-dev": {
1555 | "phpunit/phpunit": "^10.0"
1556 | },
1557 | "type": "library",
1558 | "extra": {
1559 | "branch-alias": {
1560 | "dev-main": "3.0-dev"
1561 | }
1562 | },
1563 | "autoload": {
1564 | "classmap": [
1565 | "src/"
1566 | ]
1567 | },
1568 | "notification-url": "https://packagist.org/downloads/",
1569 | "license": [
1570 | "BSD-3-Clause"
1571 | ],
1572 | "authors": [
1573 | {
1574 | "name": "Sebastian Bergmann",
1575 | "email": "sebastian@phpunit.de"
1576 | }
1577 | ],
1578 | "description": "Allows reflection of object attributes, including inherited and non-public ones",
1579 | "homepage": "https://github.com/sebastianbergmann/object-reflector/",
1580 | "support": {
1581 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
1582 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
1583 | },
1584 | "funding": [
1585 | {
1586 | "url": "https://github.com/sebastianbergmann",
1587 | "type": "github"
1588 | }
1589 | ],
1590 | "time": "2023-02-03T07:06:18+00:00"
1591 | },
1592 | {
1593 | "name": "sebastian/recursion-context",
1594 | "version": "5.0.0",
1595 | "source": {
1596 | "type": "git",
1597 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1598 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
1599 | },
1600 | "dist": {
1601 | "type": "zip",
1602 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
1603 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
1604 | "shasum": ""
1605 | },
1606 | "require": {
1607 | "php": ">=8.1"
1608 | },
1609 | "require-dev": {
1610 | "phpunit/phpunit": "^10.0"
1611 | },
1612 | "type": "library",
1613 | "extra": {
1614 | "branch-alias": {
1615 | "dev-main": "5.0-dev"
1616 | }
1617 | },
1618 | "autoload": {
1619 | "classmap": [
1620 | "src/"
1621 | ]
1622 | },
1623 | "notification-url": "https://packagist.org/downloads/",
1624 | "license": [
1625 | "BSD-3-Clause"
1626 | ],
1627 | "authors": [
1628 | {
1629 | "name": "Sebastian Bergmann",
1630 | "email": "sebastian@phpunit.de"
1631 | },
1632 | {
1633 | "name": "Jeff Welch",
1634 | "email": "whatthejeff@gmail.com"
1635 | },
1636 | {
1637 | "name": "Adam Harvey",
1638 | "email": "aharvey@php.net"
1639 | }
1640 | ],
1641 | "description": "Provides functionality to recursively process PHP variables",
1642 | "homepage": "https://github.com/sebastianbergmann/recursion-context",
1643 | "support": {
1644 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1645 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
1646 | },
1647 | "funding": [
1648 | {
1649 | "url": "https://github.com/sebastianbergmann",
1650 | "type": "github"
1651 | }
1652 | ],
1653 | "time": "2023-02-03T07:05:40+00:00"
1654 | },
1655 | {
1656 | "name": "sebastian/type",
1657 | "version": "4.0.0",
1658 | "source": {
1659 | "type": "git",
1660 | "url": "https://github.com/sebastianbergmann/type.git",
1661 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
1662 | },
1663 | "dist": {
1664 | "type": "zip",
1665 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
1666 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
1667 | "shasum": ""
1668 | },
1669 | "require": {
1670 | "php": ">=8.1"
1671 | },
1672 | "require-dev": {
1673 | "phpunit/phpunit": "^10.0"
1674 | },
1675 | "type": "library",
1676 | "extra": {
1677 | "branch-alias": {
1678 | "dev-main": "4.0-dev"
1679 | }
1680 | },
1681 | "autoload": {
1682 | "classmap": [
1683 | "src/"
1684 | ]
1685 | },
1686 | "notification-url": "https://packagist.org/downloads/",
1687 | "license": [
1688 | "BSD-3-Clause"
1689 | ],
1690 | "authors": [
1691 | {
1692 | "name": "Sebastian Bergmann",
1693 | "email": "sebastian@phpunit.de",
1694 | "role": "lead"
1695 | }
1696 | ],
1697 | "description": "Collection of value objects that represent the types of the PHP type system",
1698 | "homepage": "https://github.com/sebastianbergmann/type",
1699 | "support": {
1700 | "issues": "https://github.com/sebastianbergmann/type/issues",
1701 | "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
1702 | },
1703 | "funding": [
1704 | {
1705 | "url": "https://github.com/sebastianbergmann",
1706 | "type": "github"
1707 | }
1708 | ],
1709 | "time": "2023-02-03T07:10:45+00:00"
1710 | },
1711 | {
1712 | "name": "sebastian/version",
1713 | "version": "4.0.1",
1714 | "source": {
1715 | "type": "git",
1716 | "url": "https://github.com/sebastianbergmann/version.git",
1717 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
1718 | },
1719 | "dist": {
1720 | "type": "zip",
1721 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
1722 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
1723 | "shasum": ""
1724 | },
1725 | "require": {
1726 | "php": ">=8.1"
1727 | },
1728 | "type": "library",
1729 | "extra": {
1730 | "branch-alias": {
1731 | "dev-main": "4.0-dev"
1732 | }
1733 | },
1734 | "autoload": {
1735 | "classmap": [
1736 | "src/"
1737 | ]
1738 | },
1739 | "notification-url": "https://packagist.org/downloads/",
1740 | "license": [
1741 | "BSD-3-Clause"
1742 | ],
1743 | "authors": [
1744 | {
1745 | "name": "Sebastian Bergmann",
1746 | "email": "sebastian@phpunit.de",
1747 | "role": "lead"
1748 | }
1749 | ],
1750 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1751 | "homepage": "https://github.com/sebastianbergmann/version",
1752 | "support": {
1753 | "issues": "https://github.com/sebastianbergmann/version/issues",
1754 | "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
1755 | },
1756 | "funding": [
1757 | {
1758 | "url": "https://github.com/sebastianbergmann",
1759 | "type": "github"
1760 | }
1761 | ],
1762 | "time": "2023-02-07T11:34:05+00:00"
1763 | },
1764 | {
1765 | "name": "theseer/tokenizer",
1766 | "version": "1.2.3",
1767 | "source": {
1768 | "type": "git",
1769 | "url": "https://github.com/theseer/tokenizer.git",
1770 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
1771 | },
1772 | "dist": {
1773 | "type": "zip",
1774 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1775 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
1776 | "shasum": ""
1777 | },
1778 | "require": {
1779 | "ext-dom": "*",
1780 | "ext-tokenizer": "*",
1781 | "ext-xmlwriter": "*",
1782 | "php": "^7.2 || ^8.0"
1783 | },
1784 | "type": "library",
1785 | "autoload": {
1786 | "classmap": [
1787 | "src/"
1788 | ]
1789 | },
1790 | "notification-url": "https://packagist.org/downloads/",
1791 | "license": [
1792 | "BSD-3-Clause"
1793 | ],
1794 | "authors": [
1795 | {
1796 | "name": "Arne Blankerts",
1797 | "email": "arne@blankerts.de",
1798 | "role": "Developer"
1799 | }
1800 | ],
1801 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
1802 | "support": {
1803 | "issues": "https://github.com/theseer/tokenizer/issues",
1804 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
1805 | },
1806 | "funding": [
1807 | {
1808 | "url": "https://github.com/theseer",
1809 | "type": "github"
1810 | }
1811 | ],
1812 | "time": "2024-03-03T12:36:25+00:00"
1813 | }
1814 | ],
1815 | "aliases": [],
1816 | "minimum-stability": "stable",
1817 | "stability-flags": {},
1818 | "prefer-stable": false,
1819 | "prefer-lowest": false,
1820 | "platform": {},
1821 | "platform-dev": {},
1822 | "platform-overrides": {
1823 | "php": "8.1"
1824 | },
1825 | "plugin-api-version": "2.6.0"
1826 | }
1827 |
--------------------------------------------------------------------------------