├── 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 | --------------------------------------------------------------------------------