├── .github ├── CODEOWNERS └── dependabot.yml ├── .gitignore ├── docker-compose.yml ├── src ├── DomainNameValidator.php ├── StringList.php ├── SucceedingDomainNameValidator.php ├── ArrayBasedStringList.php ├── Validators │ ├── IntegerValueValidator.php │ ├── SucceedingEmailValidator.php │ ├── RequiredFieldValidator.php │ ├── StringLengthValidator.php │ ├── AllowedValuesValidator.php │ ├── AmountPolicyValidator.php │ ├── EmailValidator.php │ ├── TextPolicyValidator.php │ └── AddressValidator.php ├── ConstraintViolation.php ├── ValidationResponse.php └── ValidationResult.php ├── .scrutinizer.yml ├── tests ├── bootstrap.php ├── Unit │ ├── Validators │ │ ├── IntegerValueValidatorTest.php │ │ ├── AllowedValuesValidatorTest.php │ │ ├── AmountPolicyValidatorTest.php │ │ ├── EmailValidatorTest.php │ │ └── AddressValidatorTest.php │ ├── ValidationResultTest.php │ └── ConstraintViolationTest.php └── System │ └── TextPolicyValidatorTest.php ├── phpcs.xml ├── phpunit.xml.dist ├── composer.json ├── .woodpecker └── ci.yml ├── Makefile ├── README.md ├── COPYING.txt └── composer.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This marks the default owner group of all files in this repository 2 | * @wmde/funtech-core 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | !.* 3 | 4 | composer.phar 5 | 6 | vendor/ 7 | coverage/ 8 | 9 | .idea/ 10 | 11 | .phpunit.result.cache 12 | .phpunit.cache 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | fun-validators: 3 | image: "registry.gitlab.com/fun-tech/fundraising-frontend-docker:latest" 4 | volumes: 5 | - ./:/usr/src/app 6 | working_dir: /usr/src/app 7 | -------------------------------------------------------------------------------- /src/DomainNameValidator.php: -------------------------------------------------------------------------------- 1 | arrayOfString; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | src/ 9 | tests/ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Validators/IntegerValueValidator.php: -------------------------------------------------------------------------------- 1 | $maxLength ) { 14 | return new ValidationResult( new ConstraintViolation( $value, 'incorrect_length' ) ); 15 | } 16 | 17 | return new ValidationResult(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | tests/Unit 16 | 17 | 18 | tests/System 19 | 20 | 21 | 22 | 23 | src 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Validators/AllowedValuesValidator.php: -------------------------------------------------------------------------------- 1 | allowedValues, true ) ) { 28 | return new ValidationResult(); 29 | } 30 | return new ValidationResult( new ConstraintViolation( $value, 'Not an allowed value' ) ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wmde/fun-validators", 3 | "license": "GPL-2.0-or-later", 4 | "description": "General and shared validation services created as part of the WMDE fundraising software", 5 | "require": { 6 | "php": ">=8.4" 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": "~12.0", 10 | "wmde/fundraising-phpcs": "~13.0", 11 | "phpstan/phpstan": "^2.1" 12 | }, 13 | "repositories": [ 14 | { 15 | "type": "vcs", 16 | "url": "https://github.com/wmde/fundraising-phpcs", 17 | "no-api": true 18 | } 19 | ], 20 | "autoload": { 21 | "psr-4": { 22 | "WMDE\\FunValidators\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "WMDE\\FunValidators\\Tests\\": "tests/" 28 | } 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "1.0.x-dev" 33 | } 34 | }, 35 | "config": { 36 | "allow-plugins": { 37 | "dealerdirect/phpcodesniffer-composer-installer": true 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Unit/Validators/IntegerValueValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( $validator->validate( '1234567890' )->isSuccessful() ); 17 | $this->assertTrue( $validator->validate( '000123456789' )->isSuccessful() ); 18 | } 19 | 20 | public function testGivenInvalidValues_validationFails(): void { 21 | $validator = new IntegerValueValidator(); 22 | $this->assertFalse( $validator->validate( '-1234567890' )->isSuccessful() ); 23 | $this->assertFalse( $validator->validate( '21391e213123' )->isSuccessful() ); 24 | $this->assertFalse( $validator->validate( '21391e213123' )->isSuccessful() ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ConstraintViolation.php: -------------------------------------------------------------------------------- 1 | value; 25 | } 26 | 27 | public function getMessageIdentifier(): string { 28 | return $this->messageIdentifier; 29 | } 30 | 31 | public function getSource(): string { 32 | return $this->source; 33 | } 34 | 35 | public function setSource( string $source ): void { 36 | if ( $this->source && $this->source !== $source ) { 37 | trigger_error( 'Source is already set, overwriting it will be a logic exception in the future', E_USER_DEPRECATED ); 38 | } 39 | $this->source = $source; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/ValidationResponse.php: -------------------------------------------------------------------------------- 1 | validationErrors; 39 | } 40 | 41 | public function isSuccessful(): bool { 42 | return count( $this->validationErrors ) == 0; 43 | } 44 | 45 | public function needsModeration(): bool { 46 | return $this->needsModerationValue; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Validators/AmountPolicyValidator.php: -------------------------------------------------------------------------------- 1 | isOneTimeAmountTooHigh( $amount, $interval ) || 22 | $this->isAnuallyRecurringAmountTooHigh( $amount, $interval ) ) { 23 | return new ValidationResult( new ConstraintViolation( $amount, self::VIOLATION_TOO_HIGH ) ); 24 | } 25 | 26 | return new ValidationResult(); 27 | } 28 | 29 | private function isOneTimeAmountTooHigh( float $amount, int $interval ): bool { 30 | if ( $interval === 0 ) { 31 | return $amount >= $this->maxAmountOneTime; 32 | } 33 | return false; 34 | } 35 | 36 | private function isAnuallyRecurringAmountTooHigh( float $amount, int $interval ): bool { 37 | if ( $interval > 0 ) { 38 | return ( 12 / $interval ) * $amount >= $this->maxAmountRecurringAnnually; 39 | } 40 | return false; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Validators/AllowedValuesValidatorTest.php: -------------------------------------------------------------------------------- 1 | expectException( UnexpectedValueException::class ); 17 | new AllowedValuesValidator( [] ); 18 | } 19 | 20 | public function testGivenAllowedValues_theyAreAccepted(): void { 21 | $validator = new AllowedValuesValidator( [ 'kittens', 'unicorns' ] ); 22 | $this->assertTrue( $validator->validate( 'kittens' )->isSuccessful() ); 23 | $this->assertTrue( $validator->validate( 'unicorns' )->isSuccessful() ); 24 | 25 | $this->assertFalse( $validator->validate( 'dragons' )->isSuccessful() ); 26 | } 27 | 28 | public function testAllowedValuesAreCheckedStrictly(): void { 29 | $validator = new AllowedValuesValidator( [ '1', '2' ] ); 30 | $this->assertTrue( $validator->validate( '1' )->isSuccessful() ); 31 | $this->assertFalse( $validator->validate( 1 )->isSuccessful() ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.woodpecker/ci.yml: -------------------------------------------------------------------------------- 1 | clone: 2 | git: 3 | image: woodpeckerci/plugin-git 4 | settings: 5 | # "lfs: false" disables downloading resources from LFS, which we don't use 6 | lfs: false 7 | 8 | matrix: 9 | CONTAINER_IMAGE: 10 | - registry.gitlab.com/fun-tech/fundraising-frontend-docker:latest 11 | # Use the following image in future (Jan/Feb 2026) 12 | # - registry.gitlab.com/fun-tech/fundraising-frontend-docker:php-8.5 13 | 14 | steps: 15 | - name: build 16 | when: 17 | - event: [ push, pull_request, cron, manual ] 18 | image: ${CONTAINER_IMAGE} 19 | environment: 20 | COMPOSER_CACHE_DIR: /composer_cache 21 | GITHUB_TOKEN: 22 | from_secret: github_token 23 | volumes: 24 | - /tmp/woodpeckerci/cache:/composer_cache 25 | commands: 26 | # Environment setup 27 | - echo -e "machine github.com\n login $GITHUB_TOKEN" > ~/.netrc 28 | - composer config -g github-oauth.github.com "$GITHUB_TOKEN" 29 | - composer install --prefer-dist --no-progress --no-interaction 30 | # CI 31 | - vendor/bin/phpcs 32 | - php -d xdebug.mode=coverage vendor/bin/phpunit --coverage-clover coverage.xml 33 | - php -d memory_limit=1G vendor/bin/phpstan analyse --level=9 --no-progress src/ tests/ 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | current_user := $(shell id -u) 2 | current_group := $(shell id -g) 3 | BUILD_DIR := $(PWD) 4 | DOCKER_FLAGS := --interactive --tty 5 | DOCKER_IMAGE := registry.gitlab.com/fun-tech/fundraising-frontend-docker 6 | COVERAGE_FLAGS := --coverage-html coverage 7 | 8 | install-php: 9 | docker run --rm $(DOCKER_FLAGS) --volume $(BUILD_DIR):/app -w /app --volume ~/.composer:/composer --user $(current_user):$(current_group) $(DOCKER_IMAGE) composer install $(COMPOSER_FLAGS) 10 | 11 | update-php: 12 | docker run --rm $(DOCKER_FLAGS) --volume $(BUILD_DIR):/app -w /app --volume ~/.composer:/composer --user $(current_user):$(current_group) $(DOCKER_IMAGE) composer update $(COMPOSER_FLAGS) 13 | 14 | ci: phpunit cs stan 15 | 16 | ci-with-coverage: phpunit-with-coverage cs stan 17 | 18 | phpunit: 19 | docker compose run --rm fun-validators ./vendor/bin/phpunit 20 | 21 | phpunit-with-coverage: 22 | docker compose run --rm -e XDEBUG_MODE=coverage fun-validators ./vendor/bin/phpunit $(COVERAGE_FLAGS) 23 | 24 | cs: 25 | docker compose run --rm fun-validators ./vendor/bin/phpcs 26 | 27 | fix-cs: 28 | docker compose run --rm fun-validators ./vendor/bin/phpcbf 29 | 30 | stan: 31 | docker compose run --rm fun-validators ./vendor/bin/phpstan analyse --level=9 --no-progress src/ tests/ 32 | 33 | .PHONY: install-php update-php ci ci-with-coverage phpunit phpunit-with-coverage cs fix-cs stan 34 | -------------------------------------------------------------------------------- /src/ValidationResult.php: -------------------------------------------------------------------------------- 1 | violations = $violations; 16 | } 17 | 18 | public function isSuccessful(): bool { 19 | return empty( $this->violations ); 20 | } 21 | 22 | public function hasViolations(): bool { 23 | return !empty( $this->violations ); 24 | } 25 | 26 | /** 27 | * @return ConstraintViolation[] 28 | */ 29 | public function getViolations(): array { 30 | return $this->violations; 31 | } 32 | 33 | /** 34 | * Set source of an error in all violations. 35 | * 36 | * This method is meant for validation results that come from one source, 37 | * e.g. a validator like {@see WMDE\FunValidators\Validators\RequiredFieldValidator} 38 | * or {@see WMDE\FunValidators\Validators\StringLengthValidator} 39 | * 40 | * DO NOT call it on validation results that come from validators 41 | * that validate several sources. In those cases, the responsibility to 42 | * set the source is with the validator and not its calling code. 43 | */ 44 | public function setSourceForAllViolations( string $sourceName ): self { 45 | foreach ( $this->violations as $violation ) { 46 | $violation->setSource( $sourceName ); 47 | } 48 | return $this; 49 | } 50 | 51 | public function getFirstViolation(): ?ConstraintViolation { 52 | return $this->violations[0] ?? null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Validators/EmailValidator.php: -------------------------------------------------------------------------------- 1 | domainValidator->isValid( $normalizedDomain ) ) { 39 | return new ValidationResult( 40 | new ConstraintViolation( $emailAddress, 'email_address_domain_record_not_found' ) 41 | ); 42 | } 43 | 44 | return new ValidationResult(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fun-validators 2 | 3 | [![Build Status](https://travis-ci.org/wmde/fun-validators.svg?branch=master)](https://travis-ci.org/wmde/fun-validators) 4 | [![Latest Stable Version](https://poser.pugx.org/wmde/fun-validators/version.png)](https://packagist.org/packages/wmde/fun-validators) 5 | [![Download count](https://poser.pugx.org/wmde/fun-validators/d/total.png)](https://packagist.org/packages/wmde/fun-validators) 6 | 7 | General and shared validation services created as part of the WMDE fundraising software. 8 | 9 | ## Installation 10 | 11 | To use the fun-validators library in your project, simply add a dependency on wmde/fun-validators 12 | to your project's `composer.json` file. Here is a minimal example of a `composer.json` 13 | file that just defines a dependency on fun-validators 1.x: 14 | 15 | ```json 16 | { 17 | "require": { 18 | "wmde/fun-validators": "~1.0" 19 | } 20 | } 21 | ``` 22 | 23 | ## Development 24 | 25 | For development you need to have Docker and compose Docker plugin installed. Local PHP and Composer are not needed. 26 | 27 | ### Installing dependencies 28 | 29 | To install the project dependencies via Composer, run: 30 | 31 | make install-php 32 | 33 | To update the dependencies, run 34 | 35 | make update-php 36 | 37 | 38 | To update a specific dependency, you can run 39 | 40 | 41 | make update-php COMPOSER_FLAGS=dependency-name 42 | 43 | 44 | ### Running the CI checks 45 | 46 | To run all CI checks, which includes PHPUnit tests, PHPCS style checks and coverage tag validation, run: 47 | 48 | make 49 | 50 | ### Running the tests 51 | 52 | To run just the PHPUnit tests run 53 | 54 | make test 55 | 56 | To run only a subset of PHPUnit tests or otherwise pass flags to PHPUnit, run 57 | 58 | docker compose run --rm fun-validators ./vendor/bin/phpunit --filter SomeClassNameOrFilter 59 | 60 | 61 | -------------------------------------------------------------------------------- /tests/Unit/ValidationResultTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( $result->isSuccessful() ); 18 | $this->assertFalse( $result->hasViolations() ); 19 | } 20 | 21 | public function testResultWithViolationsIsNotSuccessful(): void { 22 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' ); 23 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' ); 24 | $result = new ValidationResult( $violation1, $violation2 ); 25 | 26 | $this->assertFalse( $result->isSuccessful() ); 27 | $this->assertTrue( $result->hasViolations() ); 28 | $this->assertSame( [ $violation1, $violation2 ], $result->getViolations() ); 29 | } 30 | 31 | public function testSetSourceForAllViolationsChangesAllViolations(): void { 32 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' ); 33 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' ); 34 | $result = new ValidationResult( $violation1, $violation2 ); 35 | 36 | $result->setSourceForAllViolations( 'a_test_field' ); 37 | 38 | $this->assertSame( 'a_test_field', $violation1->getSource() ); 39 | $this->assertSame( 'a_test_field', $violation2->getSource() ); 40 | } 41 | 42 | public function testGetFirstViolationReturnsFirstViolation(): void { 43 | $violation1 = new ConstraintViolation( 'value?', 'test_message_1' ); 44 | $violation2 = new ConstraintViolation( 'value!', 'test_message_2' ); 45 | $result = new ValidationResult( $violation1, $violation2 ); 46 | 47 | $this->assertSame( $violation1, $result->getFirstViolation() ); 48 | } 49 | 50 | public function testGetFirstViolationReturnsNullWhenThereAreNoViolations(): void { 51 | $result = new ValidationResult(); 52 | 53 | $this->assertNull( $result->getFirstViolation() ); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tests/Unit/Validators/AmountPolicyValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( $this->newAmountValidator()->validate( $amount, $interval )->isSuccessful() ); 28 | } 29 | 30 | /** 31 | * @return array 32 | */ 33 | public static function smallAmountProvider(): array { 34 | return [ 35 | [ 750.0, self::INTERVAL_ONCE ], 36 | [ 20.0, self::INTERVAL_MONTHLY ], 37 | [ 100.5, self::INTERVAL_QUARTERLY ], 38 | [ 499.98, self::INTERVAL_SEMIANNUAL ], 39 | [ 999.99, self::INTERVAL_YEARLY ] 40 | ]; 41 | } 42 | 43 | /** 44 | * @param float $amount 45 | * @param int $interval 46 | */ 47 | #[DataProvider( 'offLimitAmountProvider' )] 48 | public function testGivenAmountTooHigh_validationFails( float $amount, int $interval ): void { 49 | $this->assertFalse( $this->newAmountValidator()->validate( $amount, $interval )->isSuccessful() ); 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public static function offLimitAmountProvider(): array { 56 | return [ 57 | [ 1750.0, self::INTERVAL_ONCE ], 58 | [ 101.0, self::INTERVAL_MONTHLY ], 59 | [ 250.5, self::INTERVAL_QUARTERLY ], 60 | [ 600, self::INTERVAL_SEMIANNUAL ], 61 | [ 1337, self::INTERVAL_YEARLY ] 62 | ]; 63 | } 64 | 65 | private function newAmountValidator(): AmountPolicyValidator { 66 | return new AmountPolicyValidator( 1000, 1000 ); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /tests/Unit/ConstraintViolationTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 'no_mix_of_numbers_and_letters', $constraintViolation->getMessageIdentifier() ); 17 | $this->assertSame( '1nval1d', $constraintViolation->getValue() ); 18 | $this->assertSame( 'username', $constraintViolation->getSource() ); 19 | } 20 | 21 | public function testSetSource(): void { 22 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters" ); 23 | 24 | $constraintViolation->setSource( 'access_code' ); 25 | 26 | $this->assertSame( 'access_code', $constraintViolation->getSource() ); 27 | } 28 | 29 | /** 30 | * We'll change this to an exception check when our other libraries don't trigger deprecations any more 31 | */ 32 | public function testSetSourceDeprecatesOverridingExistingSource(): void { 33 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters", 'username' ); 34 | $hasTriggeredDeprecation = false; 35 | // @phpstan-ignore-next-line argument.type 36 | set_error_handler( static function ()use( &$hasTriggeredDeprecation ){ 37 | $hasTriggeredDeprecation = true; 38 | }, E_USER_DEPRECATED ); 39 | 40 | $constraintViolation->setSource( 'access_code' ); 41 | 42 | restore_error_handler(); 43 | $this->assertTrue( $hasTriggeredDeprecation ); 44 | } 45 | 46 | /** 47 | * We'll change this to an exception check when our other libraries don't trigger deprecations any more 48 | */ 49 | public function testSetSourceAllowsOverridingWhenSourceNameMatches(): void { 50 | $constraintViolation = new ConstraintViolation( "1nval1d", "no_mix_of_numbers_and_letters", 'username' ); 51 | $hasTriggeredDeprecation = false; 52 | // @phpstan-ignore-next-line argument.type 53 | set_error_handler( static function ()use( &$hasTriggeredDeprecation ){ 54 | $hasTriggeredDeprecation = true; 55 | }, E_USER_DEPRECATED ); 56 | 57 | $constraintViolation->setSource( 'username' ); 58 | 59 | restore_error_handler(); 60 | $this->assertFalse( $hasTriggeredDeprecation ); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /tests/Unit/Validators/EmailValidatorTest.php: -------------------------------------------------------------------------------- 1 | newStubDomainValidator() ); 39 | 40 | $this->assertTrue( $mailValidator->validate( $validEmail )->isSuccessful() ); 41 | } 42 | 43 | /** 44 | * @return array> 45 | */ 46 | public static function fullyValidEmailProvider(): array { 47 | return [ 48 | [ 'christoph.fischer@wikimedia.de' ], 49 | [ 'test@nick.berlin' ], 50 | [ 'A-Za-z0-9.!#$%&\'*+-/=?^_`{|}~info@nick.berlin' ], 51 | [ 'info@triebwerk-grün.de' ], 52 | [ 'info@triebwerk-grün.de' ], 53 | [ 'info@موقع.وزارة-الاتصالات.مصر' ], 54 | ]; 55 | } 56 | 57 | /** 58 | * @param string $invalidEmail 59 | */ 60 | #[DataProvider( 'emailWithInvalidDomainProvider' )] 61 | public function testGivenMailWithInvalidDomain_validationWithDomainNameCheckFails( string $invalidEmail ): void { 62 | $mailValidator = new EmailValidator( $this->newStubDomainValidator() ); 63 | 64 | $this->assertFalse( $mailValidator->validate( $invalidEmail )->isSuccessful() ); 65 | } 66 | 67 | /** 68 | * @return array> 69 | */ 70 | public static function emailWithInvalidDomainProvider(): array { 71 | return [ 72 | [ 'chrifi.asfsfas.de ' ], 73 | [ ' ' ], 74 | [ 'fibor@fgagaadadfafasfasfasfasffasfsfe.com' ], 75 | [ 'hllo909a()_9a=f9@dsafadsff' ], 76 | [ 'christoph.fischer@wikimedia.de ' ], 77 | [ 'christoph.füscher@wikimedia.de ' ], 78 | [ 'ich@ort...' ] 79 | ]; 80 | } 81 | 82 | /** 83 | * @param string $invalidEmail 84 | */ 85 | #[DataProvider( 'emailWithInvalidFormatProvider' )] 86 | public function testGivenMailWithInvalidFormat_validationWithoutDomainCheckFails( string $invalidEmail ): void { 87 | $mailValidator = new EmailValidator( new SucceedingDomainNameValidator() ); 88 | 89 | $this->assertFalse( $mailValidator->validate( $invalidEmail )->isSuccessful() ); 90 | } 91 | 92 | /** 93 | * @return array> 94 | */ 95 | public static function emailWithInvalidFormatProvider(): array { 96 | return [ 97 | [ 'chrifi.asfsfas.de ' ], 98 | [ ' ' ], 99 | [ 'hllo909a()_9a=f9@dsafadsff' ], 100 | [ 'christoph.fischer@wikimedia.de ' ], 101 | [ 'christoph.füscher@wikimedia.de ' ], 102 | ]; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/Validators/TextPolicyValidator.php: -------------------------------------------------------------------------------- 1 | deniedWords = $deniedWords ?? new ArrayBasedStringList( [] ); 27 | $this->allowedWords = $allowedWords ?? new ArrayBasedStringList( [] ); 28 | } 29 | 30 | /** 31 | * @return string[] 32 | */ 33 | private function getDeniedWords(): array { 34 | return $this->deniedWords->toArray(); 35 | } 36 | 37 | /** 38 | * @return string[] 39 | */ 40 | private function getAllowedWords(): array { 41 | return $this->allowedWords->toArray(); 42 | } 43 | 44 | public function textIsHarmless( string $text ): bool { 45 | return $this->hasHarmlessContent( 46 | $text, 47 | self::CHECK_DENIED_WORDS 48 | | self::IGNORE_ALLOWED_WORDS 49 | | self::CHECK_URLS 50 | ); 51 | } 52 | 53 | public function hasHarmlessContent( string $text, int $flags ): bool { 54 | $ignoreAllowedWords = (bool)( $flags & self::IGNORE_ALLOWED_WORDS ); 55 | 56 | if ( $flags & self::CHECK_URLS ) { 57 | $testWithDNS = (bool)( $flags & self::CHECK_URLS_DNS ); 58 | 59 | if ( $this->hasUrls( $text, $testWithDNS, $ignoreAllowedWords ) ) { 60 | return false; 61 | } 62 | } 63 | 64 | if ( $flags & self::CHECK_DENIED_WORDS ) { 65 | if ( count( $this->getDeniedWords() ) > 0 && $this->hasDeniedWords( $text, $ignoreAllowedWords ) ) { 66 | return false; 67 | } 68 | } 69 | 70 | return true; 71 | } 72 | 73 | /** 74 | * @param string[] $newDeniedWordsArray 75 | */ 76 | public function addDeniedWordsFromArray( array $newDeniedWordsArray ): void { 77 | $this->deniedWords = new ArrayBasedStringList( array_merge( $this->getDeniedWords(), $newDeniedWordsArray ) ); 78 | } 79 | 80 | /** 81 | * @param string[] $newAllowedWordsArray 82 | */ 83 | public function addAllowedWordsFromArray( array $newAllowedWordsArray ): void { 84 | $this->allowedWords = new ArrayBasedStringList( array_merge( $this->getAllowedWords(), $newAllowedWordsArray ) ); 85 | } 86 | 87 | private function hasDeniedWords( string $text, bool $ignoreAllowedWords ): bool { 88 | $deniedMatches = $this->getMatches( $text, $this->getDeniedWords() ); 89 | 90 | if ( $ignoreAllowedWords ) { 91 | $allowedMatches = $this->getMatches( $text, $this->getAllowedWords() ); 92 | 93 | if ( count( $allowedMatches ) > 0 ) { 94 | return $this->hasDeniedWordNotMatchingAllowedWords( $deniedMatches, $allowedMatches ); 95 | } 96 | 97 | } 98 | 99 | return count( $deniedMatches ) > 0; 100 | } 101 | 102 | /** 103 | * @param string $text 104 | * @param string[] $wordArray 105 | * @return string[] 106 | */ 107 | private function getMatches( string $text, array $wordArray ): array { 108 | $matches = []; 109 | preg_match_all( $this->composeRegex( $wordArray ), $text, $matches ); 110 | return $matches[0]; 111 | } 112 | 113 | /** 114 | * @param string[] $deniedMatches 115 | * @param string[] $allowedMatches 116 | */ 117 | private function hasDeniedWordNotMatchingAllowedWords( array $deniedMatches, array $allowedMatches ): bool { 118 | return count( 119 | array_udiff( 120 | $deniedMatches, 121 | $allowedMatches, 122 | function ( $deniedMatch, $allowedMatch ) { 123 | return (int)!preg_match( $this->composeRegex( [ $deniedMatch ] ), $allowedMatch ); 124 | } 125 | ) 126 | ) > 0; 127 | } 128 | 129 | private function wordMatchesAllowedWords( string $word ): bool { 130 | return in_array( strtolower( $word ), array_map( 'strtolower', $this->getAllowedWords() ) ); 131 | } 132 | 133 | private function hasUrls( string $text, bool $testWithDNS, bool $ignoreAllowedWords ): bool { 134 | // check for obvious URLs 135 | if ( preg_match( '|https?://www\.[a-z\.0-9]+|i', $text ) || preg_match( '|www\.[a-z\.0-9]+|i', $text ) ) { 136 | return true; 137 | } 138 | 139 | // check for non-obvious URLs with dns lookup 140 | if ( $testWithDNS ) { 141 | $possibleDomainNames = $this->extractPossibleDomainNames( $text ); 142 | foreach ( $possibleDomainNames as $domainName ) { 143 | if ( !( $ignoreAllowedWords && $this->wordMatchesAllowedWords( $domainName ) ) && $this->isExistingDomain( 144 | $domainName 145 | ) ) { 146 | return true; 147 | } 148 | } 149 | } 150 | 151 | return false; 152 | } 153 | 154 | /** 155 | * @return string[] 156 | */ 157 | private function extractPossibleDomainNames( string $text ): array { 158 | preg_match_all( '|[a-z\.0-9]+\.[a-z]{2,6}|i', $text, $possibleDomainNames ); 159 | return $possibleDomainNames[0]; 160 | } 161 | 162 | private function isExistingDomain( string $domainName ): bool { 163 | if ( filter_var( 'http://' . $domainName, FILTER_VALIDATE_URL ) === false ) { 164 | return false; 165 | } 166 | return checkdnsrr( $domainName, 'A' ); 167 | } 168 | 169 | /** 170 | * @param string[] $wordArray 171 | */ 172 | private function composeRegex( array $wordArray ): string { 173 | $quotedWords = array_map( 174 | static function ( string $word ) { 175 | return str_replace( ' ', '\\s*', preg_quote( trim( $word ), '#' ) ); 176 | }, 177 | $wordArray 178 | ); 179 | return '#(.*?)(' . implode( '|', $quotedWords ) . ')#i'; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/Validators/AddressValidator.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | private array $maximumFieldLengths = [ 35 | self::SOURCE_COMPANY => 100, 36 | self::SOURCE_FIRST_NAME => 50, 37 | self::SOURCE_LAST_NAME => 50, 38 | self::SOURCE_SALUTATION => 16, 39 | self::SOURCE_TITLE => 16, 40 | self::SOURCE_STREET_ADDRESS => 100, 41 | self::SOURCE_CITY => 100, 42 | self::SOURCE_COUNTRY => 8, 43 | self::SOURCE_POSTAL_CODE => 16, 44 | ]; 45 | 46 | /** 47 | * @param string[] $countriesPostcodePatterns 48 | * @param string[] $addressPatterns 49 | */ 50 | public function __construct( 51 | private array $countriesPostcodePatterns, 52 | private array $addressPatterns 53 | ) { 54 | } 55 | 56 | public function validatePostalAddress( string $streetAddress, string $postalCode, string $city, string $countryCode ): ValidationResult { 57 | $violations = []; 58 | 59 | if ( $streetAddress === '' ) { 60 | $violations[] = new ConstraintViolation( 61 | $streetAddress, 62 | self::VIOLATION_MISSING, 63 | self::SOURCE_STREET_ADDRESS 64 | ); 65 | } else { 66 | $violations[] = $this->validateFieldLength( $streetAddress, self::SOURCE_STREET_ADDRESS ); 67 | } 68 | 69 | $violations[] = $postalCodeLengthViolation = $this->validateFieldLength( $postalCode, self::SOURCE_POSTAL_CODE ); 70 | if ( $postalCodeLengthViolation === null ) { 71 | if ( isset( $this->countriesPostcodePatterns[$countryCode] ) ) { 72 | $violations[] = $this->validatePostalCode( $this->countriesPostcodePatterns[$countryCode], $postalCode ); 73 | } else { 74 | $violations[] = $this->validatePostalCode( $this->addressPatterns['postcode'], $postalCode ); 75 | } 76 | } 77 | 78 | if ( $city === '' ) { 79 | $violations[] = new ConstraintViolation( 80 | $city, 81 | self::VIOLATION_MISSING, 82 | self::SOURCE_CITY 83 | ); 84 | } else { 85 | $violations[] = $this->validateFieldLength( $city, self::SOURCE_CITY ); 86 | } 87 | 88 | if ( $countryCode === '' ) { 89 | $violations[] = new ConstraintViolation( 90 | $countryCode, 91 | self::VIOLATION_MISSING, 92 | self::SOURCE_COUNTRY 93 | ); 94 | } else { 95 | $violations[] = $this->validateFieldLength( $countryCode, self::SOURCE_COUNTRY ); 96 | } 97 | 98 | return new ValidationResult( ...array_filter( $violations ) ); 99 | } 100 | 101 | private function validatePostalCode( string $pattern, string $postalCode ): ?ConstraintViolation { 102 | return $this->validateMatchPattern( 103 | $pattern, 104 | $postalCode, 105 | self::VIOLATION_NOT_POSTCODE, 106 | self::SOURCE_POSTAL_CODE 107 | ); 108 | } 109 | 110 | private function validateMatchPattern( string $pattern, string $value, string $messageIdentifier, string $source ): ?ConstraintViolation { 111 | if ( !preg_match( $pattern, $value ) ) { 112 | return new ConstraintViolation( $value, $messageIdentifier, $source ); 113 | } 114 | return null; 115 | } 116 | 117 | public function validatePersonName( string $salutation, string $title, string $firstname, string $lastname ): ValidationResult { 118 | $violations = []; 119 | 120 | if ( $salutation === '' ) { 121 | $violations[] = new ConstraintViolation( 122 | $salutation, 123 | self::VIOLATION_MISSING, 124 | self::SOURCE_SALUTATION 125 | ); 126 | } else { 127 | $violations[] = $this->validateFieldLength( $salutation, self::SOURCE_SALUTATION ); 128 | } 129 | 130 | $violations[] = $this->validateFieldLength( $title, self::SOURCE_TITLE ); 131 | 132 | if ( $firstname === '' ) { 133 | $violations[] = new ConstraintViolation( 134 | $firstname, 135 | self::VIOLATION_MISSING, 136 | self::SOURCE_FIRST_NAME 137 | ); 138 | } else { 139 | $violations[] = $this->validateFieldLength( $firstname, self::SOURCE_FIRST_NAME ); 140 | $violations[] = $this->validateMatchPattern( 141 | $this->addressPatterns['firstName'], 142 | $firstname, 143 | self::VIOLATION_PATTERN_MATCH, 144 | self::SOURCE_FIRST_NAME 145 | ); 146 | } 147 | 148 | if ( $lastname === '' ) { 149 | $violations[] = new ConstraintViolation( 150 | $lastname, 151 | self::VIOLATION_MISSING, 152 | self::SOURCE_LAST_NAME 153 | ); 154 | } else { 155 | $violations[] = $this->validateFieldLength( $lastname, self::SOURCE_LAST_NAME ); 156 | $violations[] = $this->validateMatchPattern( 157 | $this->addressPatterns['lastName'], 158 | $lastname, 159 | self::VIOLATION_PATTERN_MATCH, 160 | self::SOURCE_LAST_NAME 161 | ); 162 | } 163 | return new ValidationResult( ...array_filter( $violations ) ); 164 | } 165 | 166 | public function validateCompanyName( string $companyName ): ValidationResult { 167 | $violations = []; 168 | if ( $companyName === '' ) { 169 | $violations[] = new ConstraintViolation( 170 | $companyName, 171 | self::VIOLATION_MISSING, 172 | self::SOURCE_COMPANY 173 | ); 174 | } else { 175 | $violations[] = $this->validateFieldLength( $companyName, self::SOURCE_COMPANY ); 176 | } 177 | return new ValidationResult( ...array_filter( $violations ) ); 178 | } 179 | 180 | private function validateFieldLength( string $value, string $fieldName ): ?ConstraintViolation { 181 | if ( strlen( $value ) > $this->maximumFieldLengths[$fieldName] ) { 182 | return new ConstraintViolation( 183 | $value, 184 | self::VIOLATION_WRONG_LENGTH, 185 | $fieldName 186 | ); 187 | } 188 | return null; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/System/TextPolicyValidatorTest.php: -------------------------------------------------------------------------------- 1 | skipIfNoInternet(); 22 | 23 | $textPolicyValidator = new TextPolicyValidator(); 24 | 25 | $this->assertFalse( $textPolicyValidator->hasHarmlessContent( 26 | $commentToTest, 27 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS 28 | ) ); 29 | } 30 | 31 | private function skipIfNoInternet(): void { 32 | try { 33 | if ( !(bool)fsockopen( 'www.google.com', 80, $num, $error, 1 ) ) { 34 | $this->markTestSkipped( 'No internet connection' ); 35 | } 36 | } catch ( Exception $exception ) { 37 | $this->markTestSkipped( 'No internet connection' ); 38 | } 39 | } 40 | 41 | /** 42 | * @return array> 43 | */ 44 | public static function urlTestProvider(): array { 45 | return [ 46 | [ 'www.example.com' ], 47 | [ 'http://www.example.com' ], 48 | [ 'https://www.example.com' ], 49 | [ 'example.com' ], 50 | [ 'example.com/test' ], 51 | [ 'example.com/teKAst/index.php' ], 52 | [ 'Ich mag Wikipedia. Aber meine Seite ist auch toll:example.com/teKAst/index.php' ], 53 | [ 'inwx.berlin' ], 54 | [ 'wwwwwww.website.com' ], 55 | [ 'TriebWerk-Grün.de' ], 56 | ]; 57 | } 58 | 59 | /** 60 | * @param string $commentToTest 61 | */ 62 | #[DataProvider( 'harmlessTestProvider' )] 63 | public function testWhenGivenHarmlessComment_validatorReturnsTrue( string $commentToTest ): void { 64 | $this->skipIfNoInternet(); 65 | 66 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator(); 67 | 68 | $this->assertTrue( $textPolicyValidator->hasHarmlessContent( 69 | $commentToTest, 70 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS | TextPolicyValidator::CHECK_DENIED_WORDS 71 | ) ); 72 | } 73 | 74 | /** 75 | * @return array> 76 | */ 77 | public static function harmlessTestProvider(): array { 78 | return [ 79 | [ 'Wikipedia ist so super, meine Eltern sagen es ist eine toll Seite. Berlin ist auch Super.' ], 80 | [ 'Ich mag Wikipedia. Aber meine Seite ist auch toll. Googelt mal nach Bunsenbrenner!!!1' ], 81 | [ 'Bei Wikipedia kann man eine Menge zum Thema Hamster finden. Hamster fressen voll viel Zeug alter!' ], 82 | // this also tests the domain detection 83 | [ 'Manche Seiten haben keinen Inhalt, das finde ich sch...e' ], 84 | ]; 85 | } 86 | 87 | public function testHarmlessContentWithDns(): void { 88 | $this->skipIfNoInternet(); 89 | 90 | if ( checkdnsrr( 'some-non-existing-domain-drfeszrfdaesr.sdferdyerdhgty', 'A' ) ) { 91 | // https://www.youtube.com/watch?v=HGBOeLdm-1s 92 | $this->markTestSkipped( 'Your DNS/ISP provider gives results for impossible host names.' ); 93 | } 94 | 95 | $textPolicyValidator = new TextPolicyValidator(); 96 | 97 | $this->assertTrue( $textPolicyValidator->hasHarmlessContent( 98 | 'Ich mag Wikipedia.Wieso ? Weil ich es so toll finde!', 99 | TextPolicyValidator::CHECK_URLS | TextPolicyValidator::CHECK_URLS_DNS | TextPolicyValidator::CHECK_DENIED_WORDS 100 | ) ); 101 | } 102 | 103 | /** 104 | * @param string $commentToTest 105 | */ 106 | #[DataProvider( 'insultingTestProvider' )] 107 | public function testWhenGivenInsultingComment_validatorReturnsFalse( string $commentToTest ): void { 108 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator(); 109 | 110 | $this->assertFalse( $textPolicyValidator->hasHarmlessContent( 111 | $commentToTest, 112 | TextPolicyValidator::CHECK_DENIED_WORDS 113 | ) ); 114 | } 115 | 116 | /** 117 | * @return array> 118 | */ 119 | public static function insultingTestProvider(): array { 120 | return [ 121 | [ 'Alles Deppen!' ], 122 | [ 'Heil Hitler!' ], 123 | [ 'Duhamsterfresse!!!' ], 124 | [ 'Alles nur HAMSTERFRESSEN!!!!!!!!1111111111' ], 125 | [ 'SiegHeil' ], 126 | [ 'Sieg Heil' ], 127 | [ "Sieg \n\tHeil!" ] 128 | ]; 129 | } 130 | 131 | /** 132 | * @param string $commentToTest 133 | */ 134 | #[DataProvider( 'allowedWordsInsultingTestProvider' )] 135 | public function testWhenGivenInsultingCommentAndAllowedWords_validatorReturnsFalse( string $commentToTest ): void { 136 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator(); 137 | 138 | $this->assertFalse( 139 | $textPolicyValidator->hasHarmlessContent( 140 | $commentToTest, 141 | TextPolicyValidator::CHECK_URLS 142 | | TextPolicyValidator::CHECK_URLS_DNS 143 | | TextPolicyValidator::CHECK_DENIED_WORDS 144 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS 145 | ) 146 | ); 147 | } 148 | 149 | /** 150 | * @return array> 151 | */ 152 | public static function allowedWordsInsultingTestProvider(): array { 153 | return [ 154 | [ 'Ich heisse Deppendorf ihr Deppen und das ist auch gut so!' ], 155 | [ 'Ihr Arschgeigen, ich wohne in Marsch und das ist auch gut so!' ], 156 | [ 'Bei Wikipedia gibts echt tolle Arschkrampen!' ], 157 | ]; 158 | } 159 | 160 | /** 161 | * @param string $commentToTest 162 | */ 163 | #[DataProvider( 'allowedWordsHarmlessTestProvider' )] 164 | public function testWhenGivenHarmlessCommentAndAllowedWords_validatorReturnsTrue( string $commentToTest ): void { 165 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator(); 166 | 167 | $this->assertTrue( 168 | $textPolicyValidator->hasHarmlessContent( 169 | $commentToTest, 170 | TextPolicyValidator::CHECK_URLS 171 | | TextPolicyValidator::CHECK_URLS_DNS 172 | | TextPolicyValidator::CHECK_DENIED_WORDS 173 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS 174 | ) 175 | ); 176 | } 177 | 178 | /** 179 | * @return array> 180 | */ 181 | public static function allowedWordsHarmlessTestProvider(): array { 182 | return [ 183 | [ 'Wikipedia ist so super, meine Eltern sagen es ist eine toll Seite. Berlin ist auch Super.' ], 184 | [ 'Ich heisse Deppendorf ihr und das ist auch gut so!' ], 185 | [ 'Bei Wikipedia gibts echt tolle Dinge!' ], 186 | [ 'Ick spend richtig Kohle, denn ick hab ne GmbH & Co.KG' ], 187 | ]; 188 | } 189 | 190 | /** 191 | * @param string $commentToTest 192 | */ 193 | #[DataProvider( 'insultingTestProviderWithRegexChars' )] 194 | public function testGivenBadWordMatchContainingRegexChars_validatorReturnsFalse( string $commentToTest ): void { 195 | $textPolicyValidator = $this->getPreFilledTextPolicyValidator(); 196 | 197 | $this->assertFalse( 198 | $textPolicyValidator->hasHarmlessContent( 199 | $commentToTest, 200 | TextPolicyValidator::CHECK_URLS 201 | | TextPolicyValidator::CHECK_URLS_DNS 202 | | TextPolicyValidator::CHECK_DENIED_WORDS 203 | | TextPolicyValidator::IGNORE_ALLOWED_WORDS 204 | ) 205 | ); 206 | } 207 | 208 | /** 209 | * @return array> 210 | */ 211 | public static function insultingTestProviderWithRegexChars(): array { 212 | return [ 213 | [ 'Ich heisse Deppendorf (ihr Deppen und das ist auch gut so!' ], 214 | [ 'Ihr [Arschgeigen], ich wohne in //Marsch// und das ist auch gut so!' ], 215 | [ 'Bei #Wikipedia gibts echt tolle Arschkrampen!' ], 216 | ]; 217 | } 218 | 219 | private function getPreFilledTextPolicyValidator(): TextPolicyValidator { 220 | $textPolicyValidator = new TextPolicyValidator(); 221 | $textPolicyValidator->addDeniedWordsFromArray( 222 | [ 223 | 'deppen', 224 | 'hitler', 225 | 'hamsterfresse', 226 | 'arsch', 227 | 'sieg heil' 228 | ] ); 229 | $textPolicyValidator->addAllowedWordsFromArray( 230 | [ 231 | 'Deppendorf', 232 | 'Marsch', 233 | 'Co.KG', 234 | ] ); 235 | return $textPolicyValidator; 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /tests/Unit/Validators/AddressValidatorTest.php: -------------------------------------------------------------------------------- 1 | '/^[0-9]{5}$/', 16 | 'AT' => '/^[0-9]{4}$/', 17 | 'CH' => '/^[0-9]{4}$/', 18 | 'BE' => '/^[0-9]{4}$/', 19 | 'IT' => '/^[0-9]{5}$/', 20 | 'LI' => '/^[0-9]{4}$/', 21 | 'LU' => '/^[0-9]{4}$/', 22 | ]; 23 | 24 | private const ADDRESS_PATTERNS = [ 25 | 'firstName' => "/^[A-Za-z\x{00C0}-\x{00D6}\x{00D8}-\x{00f6}\x{00f8}-\x{00ff}\\s\\-\\.\\']+$/", 26 | 'lastName' => "/^[A-Za-z\x{00C0}-\x{00D6}\x{00D8}-\x{00f6}\x{00f8}-\x{00ff}\\s\\-\\.\\']+$/", 27 | 'postcode' => '/^.+$/', 28 | ]; 29 | 30 | public function testGivenValidPostalAddress_noViolationsAreReturned(): void { 31 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 32 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '12345', 'Test City', 'Germany' ); 33 | $this->assertTrue( $validationResult->isSuccessful() ); 34 | } 35 | 36 | public function testGivenValidPersonName_noViolationsAreReturned(): void { 37 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 38 | $validationResult = $validator->validatePersonName( 'Herr', 'Prof. Dr.', 'Tester', 'Testing' ); 39 | $this->assertTrue( $validationResult->isSuccessful() ); 40 | } 41 | 42 | public function testGivenValidCompany_noViolationsAreReturned(): void { 43 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 44 | $validationResult = $validator->validateCompanyName( 'Test Company GmbH & Co. KG' ); 45 | $this->assertTrue( $validationResult->isSuccessful() ); 46 | } 47 | 48 | public function testGivenTooLongPostalValues_correctViolationsAreReturned(): void { 49 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 50 | $validationResult = $validator->validatePostalAddress( 51 | str_repeat( 'a', 101 ), 52 | str_repeat( '1', 17 ), 53 | str_repeat( 'a', 101 ), 54 | str_repeat( 'a', 9 ) 55 | ); 56 | $this->assertFalse( $validationResult->isSuccessful() ); 57 | $this->assertCount( 4, $validationResult->getViolations() ); 58 | $this->assertSame( 'street', $validationResult->getViolations()[0]->getSource() ); 59 | $this->assertSame( 'postcode', $validationResult->getViolations()[1]->getSource() ); 60 | $this->assertSame( 'city', $validationResult->getViolations()[2]->getSource() ); 61 | $this->assertSame( 'country', $validationResult->getViolations()[3]->getSource() ); 62 | } 63 | 64 | public function testGivenTooLongNameValues_correctViolationsAreReturned(): void { 65 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 66 | $validationResult = $validator->validatePersonName( 67 | str_repeat( 'a', 17 ), 68 | str_repeat( 'a', 17 ), 69 | str_repeat( 'a', 51 ), 70 | str_repeat( 'a', 51 ) 71 | ); 72 | $this->assertFalse( $validationResult->isSuccessful() ); 73 | $this->assertCount( 4, $validationResult->getViolations() ); 74 | $this->assertSame( 'salutation', $validationResult->getViolations()[0]->getSource() ); 75 | $this->assertSame( 'title', $validationResult->getViolations()[1]->getSource() ); 76 | $this->assertSame( 'firstName', $validationResult->getViolations()[2]->getSource() ); 77 | $this->assertSame( 'lastName', $validationResult->getViolations()[3]->getSource() ); 78 | } 79 | 80 | public function testGivenTooLongCompanyValues_correctViolationsAreReturned(): void { 81 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 82 | $validationResult = $validator->validateCompanyName( 83 | str_repeat( 'a', 101 ) 84 | ); 85 | $this->assertFalse( $validationResult->isSuccessful() ); 86 | $this->assertCount( 1, $validationResult->getViolations() ); 87 | $this->assertSame( 'companyName', $validationResult->getViolations()[0]->getSource() ); 88 | } 89 | 90 | public function testGivenEmptyPostalValues_correctViolationsAreReturned(): void { 91 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 92 | $validationResult = $validator->validatePostalAddress( 93 | '', 94 | '', 95 | '', 96 | '' 97 | ); 98 | $this->assertFalse( $validationResult->isSuccessful() ); 99 | $violations = array_values( $validationResult->getViolations() ); 100 | $this->assertCount( 4, $violations ); 101 | $this->assertSame( 'street', $violations[0]->getSource() ); 102 | $this->assertSame( 'postcode', $violations[1]->getSource() ); 103 | $this->assertSame( 'city', $violations[2]->getSource() ); 104 | $this->assertSame( 'country', $violations[3]->getSource() ); 105 | } 106 | 107 | public function testGivenEmptyNameValues_correctViolationsAreReturned(): void { 108 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 109 | $validationResult = $validator->validatePersonName( 110 | '', 111 | '', 112 | '', 113 | '' 114 | ); 115 | $this->assertFalse( $validationResult->isSuccessful() ); 116 | // Title is optional, no violation expected here 117 | $this->assertCount( 3, $validationResult->getViolations() ); 118 | $this->assertSame( 'salutation', $validationResult->getViolations()[0]->getSource() ); 119 | $this->assertSame( 'firstName', $validationResult->getViolations()[1]->getSource() ); 120 | $this->assertSame( 'lastName', $validationResult->getViolations()[2]->getSource() ); 121 | } 122 | 123 | public function testGivenEmptyCompanyValues_correctViolationsAreReturned(): void { 124 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 125 | $validationResult = $validator->validateCompanyName( '' ); 126 | $this->assertFalse( $validationResult->isSuccessful() ); 127 | $this->assertCount( 1, $validationResult->getViolations() ); 128 | $this->assertSame( 'companyName', $validationResult->getViolations()[0]->getSource() ); 129 | } 130 | 131 | public function testGivenBadPostcodeForCountry_correctViolationsAreReturned(): void { 132 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 133 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '123', 'Test City', 'DE' ); 134 | $this->assertSame( 'postcode', $validationResult->getViolations()[0]->getSource() ); 135 | } 136 | 137 | public function testGivenBadPostcodeForCountryWithoutPatterns_addressPatternIsUsedViolationsAreReturned(): void { 138 | $addressPatterns = [ 139 | 'firstName' => "/.+$/", 140 | 'lastName' => "/.+$/", 141 | // Weird pattern to make numbers fail 142 | 'postcode' => '/^[bao]{5}$/', 143 | ]; 144 | $validator = new AddressValidator( [], $addressPatterns ); 145 | $validationResult = $validator->validatePostalAddress( 'Test 1234', '123', 'Test City', 'US' ); 146 | $this->assertSame( 'postcode', $validationResult->getViolations()[0]->getSource() ); 147 | } 148 | 149 | public function testGivenLengthValidationFailsForPostCode_violationsContainOnlyLengthViolationsAndNoPatternViolations(): void { 150 | $addressPatterns = [ 151 | 'firstName' => "/.+$/", 152 | 'lastName' => "/.+$/", 153 | 'postcode' => '/^[0-9]{10}$/', 154 | ]; 155 | $countryPatterns = [ 156 | 'DE' => '/^[0-9]{5}$/', 157 | ]; 158 | // has to be longer than maximum field length in AddressValidator 159 | $longPostalCode = '1234567890123456789'; 160 | $validator = new AddressValidator( $countryPatterns, $addressPatterns ); 161 | $validationResultForUnknownCountry = $validator->validatePostalAddress( 'Test 1234', $longPostalCode, 'Test City', 'US' ); 162 | $validationResultForKnownCountry = $validator->validatePostalAddress( 'Test 1234', $longPostalCode, 'Test City', 'DE' ); 163 | 164 | $this->assertCount( 1, $validationResultForUnknownCountry->getViolations() ); 165 | $this->assertSame( 'postcode', $validationResultForUnknownCountry->getViolations()[0]->getSource() ); 166 | $this->assertSame( 'wrong-length', $validationResultForUnknownCountry->getViolations()[0]->getMessageIdentifier() ); 167 | $this->assertCount( 1, $validationResultForKnownCountry->getViolations() ); 168 | $this->assertSame( 'postcode', $validationResultForKnownCountry->getViolations()[0]->getSource() ); 169 | $this->assertSame( 'wrong-length', $validationResultForKnownCountry->getViolations()[0]->getMessageIdentifier() ); 170 | } 171 | 172 | public function testGivenBadFirstAndLastName_correctViolationsAreReturned(): void { 173 | $validator = new AddressValidator( self::COUNTRY_POSTCODE_PATTERNS, self::ADDRESS_PATTERNS ); 174 | $validationResult = $validator->validatePersonName( 'Herr', '', '£$%^&*()', '£$%^&*()' ); 175 | $this->assertSame( 'firstName', $validationResult->getViolations()[0]->getSource() ); 176 | $this->assertSame( 'lastName', $validationResult->getViolations()[1]->getSource() ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | The license text below "----" applies to all files within this distribution, other 2 | than those that are in a directory which contains files named "LICENSE" or 3 | "COPYING", or a subdirectory thereof. For those files, the license text contained in 4 | said file overrides any license information contained in directories of smaller depth. 5 | Alternative licenses are typically used for software that is provided by external 6 | parties, and merely packaged with this software for convenience. 7 | ---- 8 | 9 | GNU GENERAL PUBLIC LICENSE 10 | Version 2, June 1991 11 | 12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 13 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 14 | Everyone is permitted to copy and distribute verbatim copies 15 | of this license document, but changing it is not allowed. 16 | 17 | Preamble 18 | 19 | The licenses for most software are designed to take away your 20 | freedom to share and change it. By contrast, the GNU General Public 21 | License is intended to guarantee your freedom to share and change free 22 | software--to make sure the software is free for all its users. This 23 | General Public License applies to most of the Free Software 24 | Foundation's software and to any other program whose authors commit to 25 | using it. (Some other Free Software Foundation software is covered by 26 | the GNU Lesser General Public License instead.) You can apply it to 27 | your programs, too. 28 | 29 | When we speak of free software, we are referring to freedom, not 30 | price. Our General Public Licenses are designed to make sure that you 31 | have the freedom to distribute copies of free software (and charge for 32 | this service if you wish), that you receive source code or can get it 33 | if you want it, that you can change the software or use pieces of it 34 | in new free programs; and that you know you can do these things. 35 | 36 | To protect your rights, we need to make restrictions that forbid 37 | anyone to deny you these rights or to ask you to surrender the rights. 38 | These restrictions translate to certain responsibilities for you if you 39 | distribute copies of the software, or if you modify it. 40 | 41 | For example, if you distribute copies of such a program, whether 42 | gratis or for a fee, you must give the recipients all the rights that 43 | you have. You must make sure that they, too, receive or can get the 44 | source code. And you must show them these terms so they know their 45 | rights. 46 | 47 | We protect your rights with two steps: (1) copyright the software, and 48 | (2) offer you this license which gives you legal permission to copy, 49 | distribute and/or modify the software. 50 | 51 | Also, for each author's protection and ours, we want to make certain 52 | that everyone understands that there is no warranty for this free 53 | software. If the software is modified by someone else and passed on, we 54 | want its recipients to know that what they have is not the original, so 55 | that any problems introduced by others will not reflect on the original 56 | authors' reputations. 57 | 58 | Finally, any free program is threatened constantly by software 59 | patents. We wish to avoid the danger that redistributors of a free 60 | program will individually obtain patent licenses, in effect making the 61 | program proprietary. To prevent this, we have made it clear that any 62 | patent must be licensed for everyone's free use or not licensed at all. 63 | 64 | The precise terms and conditions for copying, distribution and 65 | modification follow. 66 | 67 | GNU GENERAL PUBLIC LICENSE 68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 69 | 70 | 0. This License applies to any program or other work which contains 71 | a notice placed by the copyright holder saying it may be distributed 72 | under the terms of this General Public License. The "Program", below, 73 | refers to any such program or work, and a "work based on the Program" 74 | means either the Program or any derivative work under copyright law: 75 | that is to say, a work containing the Program or a portion of it, 76 | either verbatim or with modifications and/or translated into another 77 | language. (Hereinafter, translation is included without limitation in 78 | the term "modification".) Each licensee is addressed as "you". 79 | 80 | Activities other than copying, distribution and modification are not 81 | covered by this License; they are outside its scope. The act of 82 | running the Program is not restricted, and the output from the Program 83 | is covered only if its contents constitute a work based on the 84 | Program (independent of having been made by running the Program). 85 | Whether that is true depends on what the Program does. 86 | 87 | 1. You may copy and distribute verbatim copies of the Program's 88 | source code as you receive it, in any medium, provided that you 89 | conspicuously and appropriately publish on each copy an appropriate 90 | copyright notice and disclaimer of warranty; keep intact all the 91 | notices that refer to this License and to the absence of any warranty; 92 | and give any other recipients of the Program a copy of this License 93 | along with the Program. 94 | 95 | You may charge a fee for the physical act of transferring a copy, and 96 | you may at your option offer warranty protection in exchange for a fee. 97 | 98 | 2. You may modify your copy or copies of the Program or any portion 99 | of it, thus forming a work based on the Program, and copy and 100 | distribute such modifications or work under the terms of Section 1 101 | above, provided that you also meet all of these conditions: 102 | 103 | a) You must cause the modified files to carry prominent notices 104 | stating that you changed the files and the date of any change. 105 | 106 | b) You must cause any work that you distribute or publish, that in 107 | whole or in part contains or is derived from the Program or any 108 | part thereof, to be licensed as a whole at no charge to all third 109 | parties under the terms of this License. 110 | 111 | c) If the modified program normally reads commands interactively 112 | when run, you must cause it, when started running for such 113 | interactive use in the most ordinary way, to print or display an 114 | announcement including an appropriate copyright notice and a 115 | notice that there is no warranty (or else, saying that you provide 116 | a warranty) and that users may redistribute the program under 117 | these conditions, and telling the user how to view a copy of this 118 | License. (Exception: if the Program itself is interactive but 119 | does not normally print such an announcement, your work based on 120 | the Program is not required to print an announcement.) 121 | 122 | These requirements apply to the modified work as a whole. If 123 | identifiable sections of that work are not derived from the Program, 124 | and can be reasonably considered independent and separate works in 125 | themselves, then this License, and its terms, do not apply to those 126 | sections when you distribute them as separate works. But when you 127 | distribute the same sections as part of a whole which is a work based 128 | on the Program, the distribution of the whole must be on the terms of 129 | this License, whose permissions for other licensees extend to the 130 | entire whole, and thus to each and every part regardless of who wrote it. 131 | 132 | Thus, it is not the intent of this section to claim rights or contest 133 | your rights to work written entirely by you; rather, the intent is to 134 | exercise the right to control the distribution of derivative or 135 | collective works based on the Program. 136 | 137 | In addition, mere aggregation of another work not based on the Program 138 | with the Program (or with a work based on the Program) on a volume of 139 | a storage or distribution medium does not bring the other work under 140 | the scope of this License. 141 | 142 | 3. You may copy and distribute the Program (or a work based on it, 143 | under Section 2) in object code or executable form under the terms of 144 | Sections 1 and 2 above provided that you also do one of the following: 145 | 146 | a) Accompany it with the complete corresponding machine-readable 147 | source code, which must be distributed under the terms of Sections 148 | 1 and 2 above on a medium customarily used for software interchange; or, 149 | 150 | b) Accompany it with a written offer, valid for at least three 151 | years, to give any third party, for a charge no more than your 152 | cost of physically performing source distribution, a complete 153 | machine-readable copy of the corresponding source code, to be 154 | distributed under the terms of Sections 1 and 2 above on a medium 155 | customarily used for software interchange; or, 156 | 157 | c) Accompany it with the information you received as to the offer 158 | to distribute corresponding source code. (This alternative is 159 | allowed only for noncommercial distribution and only if you 160 | received the program in object code or executable form with such 161 | an offer, in accord with Subsection b above.) 162 | 163 | The source code for a work means the preferred form of the work for 164 | making modifications to it. For an executable work, complete source 165 | code means all the source code for all modules it contains, plus any 166 | associated interface definition files, plus the scripts used to 167 | control compilation and installation of the executable. However, as a 168 | special exception, the source code distributed need not include 169 | anything that is normally distributed (in either source or binary 170 | form) with the major components (compiler, kernel, and so on) of the 171 | operating system on which the executable runs, unless that component 172 | itself accompanies the executable. 173 | 174 | If distribution of executable or object code is made by offering 175 | access to copy from a designated place, then offering equivalent 176 | access to copy the source code from the same place counts as 177 | distribution of the source code, even though third parties are not 178 | compelled to copy the source along with the object code. 179 | 180 | 4. You may not copy, modify, sublicense, or distribute the Program 181 | except as expressly provided under this License. Any attempt 182 | otherwise to copy, modify, sublicense or distribute the Program is 183 | void, and will automatically terminate your rights under this License. 184 | However, parties who have received copies, or rights, from you under 185 | this License will not have their licenses terminated so long as such 186 | parties remain in full compliance. 187 | 188 | 5. You are not required to accept this License, since you have not 189 | signed it. However, nothing else grants you permission to modify or 190 | distribute the Program or its derivative works. These actions are 191 | prohibited by law if you do not accept this License. Therefore, by 192 | modifying or distributing the Program (or any work based on the 193 | Program), you indicate your acceptance of this License to do so, and 194 | all its terms and conditions for copying, distributing or modifying 195 | the Program or works based on it. 196 | 197 | 6. Each time you redistribute the Program (or any work based on the 198 | Program), the recipient automatically receives a license from the 199 | original licensor to copy, distribute or modify the Program subject to 200 | these terms and conditions. You may not impose any further 201 | restrictions on the recipients' exercise of the rights granted herein. 202 | You are not responsible for enforcing compliance by third parties to 203 | this License. 204 | 205 | 7. If, as a consequence of a court judgment or allegation of patent 206 | infringement or for any other reason (not limited to patent issues), 207 | conditions are imposed on you (whether by court order, agreement or 208 | otherwise) that contradict the conditions of this License, they do not 209 | excuse you from the conditions of this License. If you cannot 210 | distribute so as to satisfy simultaneously your obligations under this 211 | License and any other pertinent obligations, then as a consequence you 212 | may not distribute the Program at all. For example, if a patent 213 | license would not permit royalty-free redistribution of the Program by 214 | all those who receive copies directly or indirectly through you, then 215 | the only way you could satisfy both it and this License would be to 216 | refrain entirely from distribution of the Program. 217 | 218 | If any portion of this section is held invalid or unenforceable under 219 | any particular circumstance, the balance of the section is intended to 220 | apply and the section as a whole is intended to apply in other 221 | circumstances. 222 | 223 | It is not the purpose of this section to induce you to infringe any 224 | patents or other property right claims or to contest validity of any 225 | such claims; this section has the sole purpose of protecting the 226 | integrity of the free software distribution system, which is 227 | implemented by public license practices. Many people have made 228 | generous contributions to the wide range of software distributed 229 | through that system in reliance on consistent application of that 230 | system; it is up to the author/donor to decide if he or she is willing 231 | to distribute software through any other system and a licensee cannot 232 | impose that choice. 233 | 234 | This section is intended to make thoroughly clear what is believed to 235 | be a consequence of the rest of this License. 236 | 237 | 8. If the distribution and/or use of the Program is restricted in 238 | certain countries either by patents or by copyrighted interfaces, the 239 | original copyright holder who places the Program under this License 240 | may add an explicit geographical distribution limitation excluding 241 | those countries, so that distribution is permitted only in or among 242 | countries not thus excluded. In such case, this License incorporates 243 | the limitation as if written in the body of this License. 244 | 245 | 9. The Free Software Foundation may publish revised and/or new versions 246 | of the General Public License from time to time. Such new versions will 247 | be similar in spirit to the present version, but may differ in detail to 248 | address new problems or concerns. 249 | 250 | Each version is given a distinguishing version number. If the Program 251 | specifies a version number of this License which applies to it and "any 252 | later version", you have the option of following the terms and conditions 253 | either of that version or of any later version published by the Free 254 | Software Foundation. If the Program does not specify a version number of 255 | this License, you may choose any version ever published by the Free Software 256 | Foundation. 257 | 258 | 10. If you wish to incorporate parts of the Program into other free 259 | programs whose distribution conditions are different, write to the author 260 | to ask for permission. For software which is copyrighted by the Free 261 | Software Foundation, write to the Free Software Foundation; we sometimes 262 | make exceptions for this. Our decision will be guided by the two goals 263 | of preserving the free status of all derivatives of our free software and 264 | of promoting the sharing and reuse of software generally. 265 | 266 | NO WARRANTY 267 | 268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 276 | REPAIR OR CORRECTION. 277 | 278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 286 | POSSIBILITY OF SUCH DAMAGES. 287 | 288 | END OF TERMS AND CONDITIONS 289 | 290 | How to Apply These Terms to Your New Programs 291 | 292 | If you develop a new program, and you want it to be of the greatest 293 | possible use to the public, the best way to achieve this is to make it 294 | free software which everyone can redistribute and change under these terms. 295 | 296 | To do so, attach the following notices to the program. It is safest 297 | to attach them to the start of each source file to most effectively 298 | convey the exclusion of warranty; and each file should have at least 299 | the "copyright" line and a pointer to where the full notice is found. 300 | 301 | 302 | Copyright (C) 303 | 304 | This program is free software; you can redistribute it and/or modify 305 | it under the terms of the GNU General Public License as published by 306 | the Free Software Foundation; either version 2 of the License, or 307 | (at your option) any later version. 308 | 309 | This program is distributed in the hope that it will be useful, 310 | but WITHOUT ANY WARRANTY; without even the implied warranty of 311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 312 | GNU General Public License for more details. 313 | 314 | You should have received a copy of the GNU General Public License along 315 | with this program; if not, write to the Free Software Foundation, Inc., 316 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 317 | 318 | Also add information on how to contact you by electronic and paper mail. 319 | 320 | If the program is interactive, make it output a short notice like this 321 | when it starts in an interactive mode: 322 | 323 | Gnomovision version 69, Copyright (C) year name of author 324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 325 | This is free software, and you are welcome to redistribute it 326 | under certain conditions; type `show c' for details. 327 | 328 | The hypothetical commands `show w' and `show c' should show the appropriate 329 | parts of the General Public License. Of course, the commands you use may 330 | be called something other than `show w' and `show c'; they could even be 331 | mouse-clicks or menu items--whatever suits your program. 332 | 333 | You should also get your employer (if you work as a programmer) or your 334 | school, if any, to sign a "copyright disclaimer" for the program, if 335 | necessary. Here is a sample; alter the names: 336 | 337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 338 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 339 | 340 | , 1 April 1989 341 | Ty Coon, President of Vice 342 | 343 | This General Public License does not permit incorporating your program into 344 | proprietary programs. If your program is a subroutine library, you may 345 | consider it more useful to permit linking proprietary applications with the 346 | library. If this is what you want to do, use the GNU Lesser General 347 | Public License instead of this License. 348 | -------------------------------------------------------------------------------- /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": "8bda1b2a9a3dbd26d691a8a7e92699ea", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "composer/semver", 12 | "version": "3.4.4", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/composer/semver.git", 16 | "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", 21 | "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^5.3.2 || ^7.0 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "phpstan/phpstan": "^1.11", 29 | "symfony/phpunit-bridge": "^3 || ^7" 30 | }, 31 | "type": "library", 32 | "extra": { 33 | "branch-alias": { 34 | "dev-main": "3.x-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Composer\\Semver\\": "src" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Nils Adermann", 49 | "email": "naderman@naderman.de", 50 | "homepage": "http://www.naderman.de" 51 | }, 52 | { 53 | "name": "Jordi Boggiano", 54 | "email": "j.boggiano@seld.be", 55 | "homepage": "http://seld.be" 56 | }, 57 | { 58 | "name": "Rob Bast", 59 | "email": "rob.bast@gmail.com", 60 | "homepage": "http://robbast.nl" 61 | } 62 | ], 63 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 64 | "keywords": [ 65 | "semantic", 66 | "semver", 67 | "validation", 68 | "versioning" 69 | ], 70 | "support": { 71 | "irc": "ircs://irc.libera.chat:6697/composer", 72 | "issues": "https://github.com/composer/semver/issues", 73 | "source": "https://github.com/composer/semver/tree/3.4.4" 74 | }, 75 | "funding": [ 76 | { 77 | "url": "https://packagist.com", 78 | "type": "custom" 79 | }, 80 | { 81 | "url": "https://github.com/composer", 82 | "type": "github" 83 | } 84 | ], 85 | "time": "2025-08-20T19:15:30+00:00" 86 | }, 87 | { 88 | "name": "composer/spdx-licenses", 89 | "version": "1.5.9", 90 | "source": { 91 | "type": "git", 92 | "url": "https://github.com/composer/spdx-licenses.git", 93 | "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" 94 | }, 95 | "dist": { 96 | "type": "zip", 97 | "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", 98 | "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", 99 | "shasum": "" 100 | }, 101 | "require": { 102 | "php": "^5.3.2 || ^7.0 || ^8.0" 103 | }, 104 | "require-dev": { 105 | "phpstan/phpstan": "^1.11", 106 | "symfony/phpunit-bridge": "^3 || ^7" 107 | }, 108 | "type": "library", 109 | "extra": { 110 | "branch-alias": { 111 | "dev-main": "1.x-dev" 112 | } 113 | }, 114 | "autoload": { 115 | "psr-4": { 116 | "Composer\\Spdx\\": "src" 117 | } 118 | }, 119 | "notification-url": "https://packagist.org/downloads/", 120 | "license": [ 121 | "MIT" 122 | ], 123 | "authors": [ 124 | { 125 | "name": "Nils Adermann", 126 | "email": "naderman@naderman.de", 127 | "homepage": "http://www.naderman.de" 128 | }, 129 | { 130 | "name": "Jordi Boggiano", 131 | "email": "j.boggiano@seld.be", 132 | "homepage": "http://seld.be" 133 | }, 134 | { 135 | "name": "Rob Bast", 136 | "email": "rob.bast@gmail.com", 137 | "homepage": "http://robbast.nl" 138 | } 139 | ], 140 | "description": "SPDX licenses list and validation library.", 141 | "keywords": [ 142 | "license", 143 | "spdx", 144 | "validator" 145 | ], 146 | "support": { 147 | "irc": "ircs://irc.libera.chat:6697/composer", 148 | "issues": "https://github.com/composer/spdx-licenses/issues", 149 | "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" 150 | }, 151 | "funding": [ 152 | { 153 | "url": "https://packagist.com", 154 | "type": "custom" 155 | }, 156 | { 157 | "url": "https://github.com/composer", 158 | "type": "github" 159 | }, 160 | { 161 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 162 | "type": "tidelift" 163 | } 164 | ], 165 | "time": "2025-05-12T21:07:07+00:00" 166 | }, 167 | { 168 | "name": "dealerdirect/phpcodesniffer-composer-installer", 169 | "version": "v1.1.2", 170 | "source": { 171 | "type": "git", 172 | "url": "https://github.com/PHPCSStandards/composer-installer.git", 173 | "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1" 174 | }, 175 | "dist": { 176 | "type": "zip", 177 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", 178 | "reference": "e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1", 179 | "shasum": "" 180 | }, 181 | "require": { 182 | "composer-plugin-api": "^2.2", 183 | "php": ">=5.4", 184 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 185 | }, 186 | "require-dev": { 187 | "composer/composer": "^2.2", 188 | "ext-json": "*", 189 | "ext-zip": "*", 190 | "php-parallel-lint/php-parallel-lint": "^1.4.0", 191 | "phpcompatibility/php-compatibility": "^9.0", 192 | "yoast/phpunit-polyfills": "^1.0" 193 | }, 194 | "type": "composer-plugin", 195 | "extra": { 196 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 197 | }, 198 | "autoload": { 199 | "psr-4": { 200 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 201 | } 202 | }, 203 | "notification-url": "https://packagist.org/downloads/", 204 | "license": [ 205 | "MIT" 206 | ], 207 | "authors": [ 208 | { 209 | "name": "Franck Nijhof", 210 | "email": "opensource@frenck.dev", 211 | "homepage": "https://frenck.dev", 212 | "role": "Open source developer" 213 | }, 214 | { 215 | "name": "Contributors", 216 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" 217 | } 218 | ], 219 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 220 | "keywords": [ 221 | "PHPCodeSniffer", 222 | "PHP_CodeSniffer", 223 | "code quality", 224 | "codesniffer", 225 | "composer", 226 | "installer", 227 | "phpcbf", 228 | "phpcs", 229 | "plugin", 230 | "qa", 231 | "quality", 232 | "standard", 233 | "standards", 234 | "style guide", 235 | "stylecheck", 236 | "tests" 237 | ], 238 | "support": { 239 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues", 240 | "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", 241 | "source": "https://github.com/PHPCSStandards/composer-installer" 242 | }, 243 | "funding": [ 244 | { 245 | "url": "https://github.com/PHPCSStandards", 246 | "type": "github" 247 | }, 248 | { 249 | "url": "https://github.com/jrfnl", 250 | "type": "github" 251 | }, 252 | { 253 | "url": "https://opencollective.com/php_codesniffer", 254 | "type": "open_collective" 255 | }, 256 | { 257 | "url": "https://thanks.dev/u/gh/phpcsstandards", 258 | "type": "thanks_dev" 259 | } 260 | ], 261 | "time": "2025-07-17T20:45:56+00:00" 262 | }, 263 | { 264 | "name": "mediawiki/mediawiki-codesniffer", 265 | "version": "v48.0.0", 266 | "source": { 267 | "type": "git", 268 | "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git", 269 | "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212" 270 | }, 271 | "dist": { 272 | "type": "zip", 273 | "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/6d46ca2334d5e1c5be10bf28e01f6010cfbff212", 274 | "reference": "6d46ca2334d5e1c5be10bf28e01f6010cfbff212", 275 | "shasum": "" 276 | }, 277 | "require": { 278 | "composer/semver": "^3.4.2", 279 | "composer/spdx-licenses": "~1.5.2", 280 | "ext-json": "*", 281 | "ext-mbstring": "*", 282 | "php": ">=8.1.0", 283 | "phpcsstandards/phpcsextra": "1.4.0", 284 | "squizlabs/php_codesniffer": "3.13.2" 285 | }, 286 | "require-dev": { 287 | "ext-dom": "*", 288 | "mediawiki/mediawiki-phan-config": "0.17.0", 289 | "mediawiki/minus-x": "1.1.3", 290 | "php-parallel-lint/php-console-highlighter": "1.0.0", 291 | "php-parallel-lint/php-parallel-lint": "1.4.0", 292 | "phpunit/phpunit": "9.6.21" 293 | }, 294 | "type": "phpcodesniffer-standard", 295 | "autoload": { 296 | "psr-4": { 297 | "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/" 298 | } 299 | }, 300 | "notification-url": "https://packagist.org/downloads/", 301 | "license": [ 302 | "GPL-2.0-or-later" 303 | ], 304 | "description": "MediaWiki CodeSniffer Standards", 305 | "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP", 306 | "keywords": [ 307 | "codesniffer", 308 | "mediawiki" 309 | ], 310 | "support": { 311 | "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v48.0.0" 312 | }, 313 | "time": "2025-09-04T20:12:57+00:00" 314 | }, 315 | { 316 | "name": "myclabs/deep-copy", 317 | "version": "1.13.4", 318 | "source": { 319 | "type": "git", 320 | "url": "https://github.com/myclabs/DeepCopy.git", 321 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" 322 | }, 323 | "dist": { 324 | "type": "zip", 325 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", 326 | "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", 327 | "shasum": "" 328 | }, 329 | "require": { 330 | "php": "^7.1 || ^8.0" 331 | }, 332 | "conflict": { 333 | "doctrine/collections": "<1.6.8", 334 | "doctrine/common": "<2.13.3 || >=3 <3.2.2" 335 | }, 336 | "require-dev": { 337 | "doctrine/collections": "^1.6.8", 338 | "doctrine/common": "^2.13.3 || ^3.2.2", 339 | "phpspec/prophecy": "^1.10", 340 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 341 | }, 342 | "type": "library", 343 | "autoload": { 344 | "files": [ 345 | "src/DeepCopy/deep_copy.php" 346 | ], 347 | "psr-4": { 348 | "DeepCopy\\": "src/DeepCopy/" 349 | } 350 | }, 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "description": "Create deep copies (clones) of your objects", 356 | "keywords": [ 357 | "clone", 358 | "copy", 359 | "duplicate", 360 | "object", 361 | "object graph" 362 | ], 363 | "support": { 364 | "issues": "https://github.com/myclabs/DeepCopy/issues", 365 | "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" 366 | }, 367 | "funding": [ 368 | { 369 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 370 | "type": "tidelift" 371 | } 372 | ], 373 | "time": "2025-08-01T08:46:24+00:00" 374 | }, 375 | { 376 | "name": "nikic/php-parser", 377 | "version": "v5.6.2", 378 | "source": { 379 | "type": "git", 380 | "url": "https://github.com/nikic/PHP-Parser.git", 381 | "reference": "3a454ca033b9e06b63282ce19562e892747449bb" 382 | }, 383 | "dist": { 384 | "type": "zip", 385 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", 386 | "reference": "3a454ca033b9e06b63282ce19562e892747449bb", 387 | "shasum": "" 388 | }, 389 | "require": { 390 | "ext-ctype": "*", 391 | "ext-json": "*", 392 | "ext-tokenizer": "*", 393 | "php": ">=7.4" 394 | }, 395 | "require-dev": { 396 | "ircmaxell/php-yacc": "^0.0.7", 397 | "phpunit/phpunit": "^9.0" 398 | }, 399 | "bin": [ 400 | "bin/php-parse" 401 | ], 402 | "type": "library", 403 | "extra": { 404 | "branch-alias": { 405 | "dev-master": "5.x-dev" 406 | } 407 | }, 408 | "autoload": { 409 | "psr-4": { 410 | "PhpParser\\": "lib/PhpParser" 411 | } 412 | }, 413 | "notification-url": "https://packagist.org/downloads/", 414 | "license": [ 415 | "BSD-3-Clause" 416 | ], 417 | "authors": [ 418 | { 419 | "name": "Nikita Popov" 420 | } 421 | ], 422 | "description": "A PHP parser written in PHP", 423 | "keywords": [ 424 | "parser", 425 | "php" 426 | ], 427 | "support": { 428 | "issues": "https://github.com/nikic/PHP-Parser/issues", 429 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" 430 | }, 431 | "time": "2025-10-21T19:32:17+00:00" 432 | }, 433 | { 434 | "name": "phar-io/manifest", 435 | "version": "2.0.4", 436 | "source": { 437 | "type": "git", 438 | "url": "https://github.com/phar-io/manifest.git", 439 | "reference": "54750ef60c58e43759730615a392c31c80e23176" 440 | }, 441 | "dist": { 442 | "type": "zip", 443 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", 444 | "reference": "54750ef60c58e43759730615a392c31c80e23176", 445 | "shasum": "" 446 | }, 447 | "require": { 448 | "ext-dom": "*", 449 | "ext-libxml": "*", 450 | "ext-phar": "*", 451 | "ext-xmlwriter": "*", 452 | "phar-io/version": "^3.0.1", 453 | "php": "^7.2 || ^8.0" 454 | }, 455 | "type": "library", 456 | "extra": { 457 | "branch-alias": { 458 | "dev-master": "2.0.x-dev" 459 | } 460 | }, 461 | "autoload": { 462 | "classmap": [ 463 | "src/" 464 | ] 465 | }, 466 | "notification-url": "https://packagist.org/downloads/", 467 | "license": [ 468 | "BSD-3-Clause" 469 | ], 470 | "authors": [ 471 | { 472 | "name": "Arne Blankerts", 473 | "email": "arne@blankerts.de", 474 | "role": "Developer" 475 | }, 476 | { 477 | "name": "Sebastian Heuer", 478 | "email": "sebastian@phpeople.de", 479 | "role": "Developer" 480 | }, 481 | { 482 | "name": "Sebastian Bergmann", 483 | "email": "sebastian@phpunit.de", 484 | "role": "Developer" 485 | } 486 | ], 487 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 488 | "support": { 489 | "issues": "https://github.com/phar-io/manifest/issues", 490 | "source": "https://github.com/phar-io/manifest/tree/2.0.4" 491 | }, 492 | "funding": [ 493 | { 494 | "url": "https://github.com/theseer", 495 | "type": "github" 496 | } 497 | ], 498 | "time": "2024-03-03T12:33:53+00:00" 499 | }, 500 | { 501 | "name": "phar-io/version", 502 | "version": "3.2.1", 503 | "source": { 504 | "type": "git", 505 | "url": "https://github.com/phar-io/version.git", 506 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 507 | }, 508 | "dist": { 509 | "type": "zip", 510 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 511 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 512 | "shasum": "" 513 | }, 514 | "require": { 515 | "php": "^7.2 || ^8.0" 516 | }, 517 | "type": "library", 518 | "autoload": { 519 | "classmap": [ 520 | "src/" 521 | ] 522 | }, 523 | "notification-url": "https://packagist.org/downloads/", 524 | "license": [ 525 | "BSD-3-Clause" 526 | ], 527 | "authors": [ 528 | { 529 | "name": "Arne Blankerts", 530 | "email": "arne@blankerts.de", 531 | "role": "Developer" 532 | }, 533 | { 534 | "name": "Sebastian Heuer", 535 | "email": "sebastian@phpeople.de", 536 | "role": "Developer" 537 | }, 538 | { 539 | "name": "Sebastian Bergmann", 540 | "email": "sebastian@phpunit.de", 541 | "role": "Developer" 542 | } 543 | ], 544 | "description": "Library for handling version information and constraints", 545 | "support": { 546 | "issues": "https://github.com/phar-io/version/issues", 547 | "source": "https://github.com/phar-io/version/tree/3.2.1" 548 | }, 549 | "time": "2022-02-21T01:04:05+00:00" 550 | }, 551 | { 552 | "name": "phpcsstandards/phpcsextra", 553 | "version": "1.4.0", 554 | "source": { 555 | "type": "git", 556 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", 557 | "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca" 558 | }, 559 | "dist": { 560 | "type": "zip", 561 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca", 562 | "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca", 563 | "shasum": "" 564 | }, 565 | "require": { 566 | "php": ">=5.4", 567 | "phpcsstandards/phpcsutils": "^1.1.0", 568 | "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" 569 | }, 570 | "require-dev": { 571 | "php-parallel-lint/php-console-highlighter": "^1.0", 572 | "php-parallel-lint/php-parallel-lint": "^1.4.0", 573 | "phpcsstandards/phpcsdevcs": "^1.1.6", 574 | "phpcsstandards/phpcsdevtools": "^1.2.1", 575 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 576 | }, 577 | "type": "phpcodesniffer-standard", 578 | "extra": { 579 | "branch-alias": { 580 | "dev-stable": "1.x-dev", 581 | "dev-develop": "1.x-dev" 582 | } 583 | }, 584 | "notification-url": "https://packagist.org/downloads/", 585 | "license": [ 586 | "LGPL-3.0-or-later" 587 | ], 588 | "authors": [ 589 | { 590 | "name": "Juliette Reinders Folmer", 591 | "homepage": "https://github.com/jrfnl", 592 | "role": "lead" 593 | }, 594 | { 595 | "name": "Contributors", 596 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" 597 | } 598 | ], 599 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", 600 | "keywords": [ 601 | "PHP_CodeSniffer", 602 | "phpcbf", 603 | "phpcodesniffer-standard", 604 | "phpcs", 605 | "standards", 606 | "static analysis" 607 | ], 608 | "support": { 609 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", 610 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", 611 | "source": "https://github.com/PHPCSStandards/PHPCSExtra" 612 | }, 613 | "funding": [ 614 | { 615 | "url": "https://github.com/PHPCSStandards", 616 | "type": "github" 617 | }, 618 | { 619 | "url": "https://github.com/jrfnl", 620 | "type": "github" 621 | }, 622 | { 623 | "url": "https://opencollective.com/php_codesniffer", 624 | "type": "open_collective" 625 | }, 626 | { 627 | "url": "https://thanks.dev/u/gh/phpcsstandards", 628 | "type": "thanks_dev" 629 | } 630 | ], 631 | "time": "2025-06-14T07:40:39+00:00" 632 | }, 633 | { 634 | "name": "phpcsstandards/phpcsutils", 635 | "version": "1.1.1", 636 | "source": { 637 | "type": "git", 638 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", 639 | "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd" 640 | }, 641 | "dist": { 642 | "type": "zip", 643 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/f7eb16f2fa4237d5db9e8fed8050239bee17a9bd", 644 | "reference": "f7eb16f2fa4237d5db9e8fed8050239bee17a9bd", 645 | "shasum": "" 646 | }, 647 | "require": { 648 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", 649 | "php": ">=5.4", 650 | "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" 651 | }, 652 | "require-dev": { 653 | "ext-filter": "*", 654 | "php-parallel-lint/php-console-highlighter": "^1.0", 655 | "php-parallel-lint/php-parallel-lint": "^1.4.0", 656 | "phpcsstandards/phpcsdevcs": "^1.1.6", 657 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" 658 | }, 659 | "type": "phpcodesniffer-standard", 660 | "extra": { 661 | "branch-alias": { 662 | "dev-stable": "1.x-dev", 663 | "dev-develop": "1.x-dev" 664 | } 665 | }, 666 | "autoload": { 667 | "classmap": [ 668 | "PHPCSUtils/" 669 | ] 670 | }, 671 | "notification-url": "https://packagist.org/downloads/", 672 | "license": [ 673 | "LGPL-3.0-or-later" 674 | ], 675 | "authors": [ 676 | { 677 | "name": "Juliette Reinders Folmer", 678 | "homepage": "https://github.com/jrfnl", 679 | "role": "lead" 680 | }, 681 | { 682 | "name": "Contributors", 683 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" 684 | } 685 | ], 686 | "description": "A suite of utility functions for use with PHP_CodeSniffer", 687 | "homepage": "https://phpcsutils.com/", 688 | "keywords": [ 689 | "PHP_CodeSniffer", 690 | "phpcbf", 691 | "phpcodesniffer-standard", 692 | "phpcs", 693 | "phpcs3", 694 | "phpcs4", 695 | "standards", 696 | "static analysis", 697 | "tokens", 698 | "utility" 699 | ], 700 | "support": { 701 | "docs": "https://phpcsutils.com/", 702 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", 703 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", 704 | "source": "https://github.com/PHPCSStandards/PHPCSUtils" 705 | }, 706 | "funding": [ 707 | { 708 | "url": "https://github.com/PHPCSStandards", 709 | "type": "github" 710 | }, 711 | { 712 | "url": "https://github.com/jrfnl", 713 | "type": "github" 714 | }, 715 | { 716 | "url": "https://opencollective.com/php_codesniffer", 717 | "type": "open_collective" 718 | }, 719 | { 720 | "url": "https://thanks.dev/u/gh/phpcsstandards", 721 | "type": "thanks_dev" 722 | } 723 | ], 724 | "time": "2025-08-10T01:04:45+00:00" 725 | }, 726 | { 727 | "name": "phpstan/phpstan", 728 | "version": "2.1.32", 729 | "dist": { 730 | "type": "zip", 731 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", 732 | "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", 733 | "shasum": "" 734 | }, 735 | "require": { 736 | "php": "^7.4|^8.0" 737 | }, 738 | "conflict": { 739 | "phpstan/phpstan-shim": "*" 740 | }, 741 | "bin": [ 742 | "phpstan", 743 | "phpstan.phar" 744 | ], 745 | "type": "library", 746 | "autoload": { 747 | "files": [ 748 | "bootstrap.php" 749 | ] 750 | }, 751 | "notification-url": "https://packagist.org/downloads/", 752 | "license": [ 753 | "MIT" 754 | ], 755 | "description": "PHPStan - PHP Static Analysis Tool", 756 | "keywords": [ 757 | "dev", 758 | "static analysis" 759 | ], 760 | "support": { 761 | "docs": "https://phpstan.org/user-guide/getting-started", 762 | "forum": "https://github.com/phpstan/phpstan/discussions", 763 | "issues": "https://github.com/phpstan/phpstan/issues", 764 | "security": "https://github.com/phpstan/phpstan/security/policy", 765 | "source": "https://github.com/phpstan/phpstan-src" 766 | }, 767 | "funding": [ 768 | { 769 | "url": "https://github.com/ondrejmirtes", 770 | "type": "github" 771 | }, 772 | { 773 | "url": "https://github.com/phpstan", 774 | "type": "github" 775 | } 776 | ], 777 | "time": "2025-11-11T15:18:17+00:00" 778 | }, 779 | { 780 | "name": "phpunit/php-code-coverage", 781 | "version": "12.5.0", 782 | "source": { 783 | "type": "git", 784 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 785 | "reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a" 786 | }, 787 | "dist": { 788 | "type": "zip", 789 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bca180c050dd3ae15f87c26d25cabb34fe1a0a5a", 790 | "reference": "bca180c050dd3ae15f87c26d25cabb34fe1a0a5a", 791 | "shasum": "" 792 | }, 793 | "require": { 794 | "ext-dom": "*", 795 | "ext-libxml": "*", 796 | "ext-xmlwriter": "*", 797 | "nikic/php-parser": "^5.6.2", 798 | "php": ">=8.3", 799 | "phpunit/php-file-iterator": "^6.0", 800 | "phpunit/php-text-template": "^5.0", 801 | "sebastian/complexity": "^5.0", 802 | "sebastian/environment": "^8.0.3", 803 | "sebastian/lines-of-code": "^4.0", 804 | "sebastian/version": "^6.0", 805 | "theseer/tokenizer": "^1.3.1" 806 | }, 807 | "require-dev": { 808 | "phpunit/phpunit": "^12.4.4" 809 | }, 810 | "suggest": { 811 | "ext-pcov": "PHP extension that provides line coverage", 812 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 813 | }, 814 | "type": "library", 815 | "extra": { 816 | "branch-alias": { 817 | "dev-main": "12.5.x-dev" 818 | } 819 | }, 820 | "autoload": { 821 | "classmap": [ 822 | "src/" 823 | ] 824 | }, 825 | "notification-url": "https://packagist.org/downloads/", 826 | "license": [ 827 | "BSD-3-Clause" 828 | ], 829 | "authors": [ 830 | { 831 | "name": "Sebastian Bergmann", 832 | "email": "sebastian@phpunit.de", 833 | "role": "lead" 834 | } 835 | ], 836 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 837 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 838 | "keywords": [ 839 | "coverage", 840 | "testing", 841 | "xunit" 842 | ], 843 | "support": { 844 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 845 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 846 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.0" 847 | }, 848 | "funding": [ 849 | { 850 | "url": "https://github.com/sebastianbergmann", 851 | "type": "github" 852 | }, 853 | { 854 | "url": "https://liberapay.com/sebastianbergmann", 855 | "type": "liberapay" 856 | }, 857 | { 858 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 859 | "type": "thanks_dev" 860 | }, 861 | { 862 | "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", 863 | "type": "tidelift" 864 | } 865 | ], 866 | "time": "2025-11-29T07:15:54+00:00" 867 | }, 868 | { 869 | "name": "phpunit/php-file-iterator", 870 | "version": "6.0.0", 871 | "source": { 872 | "type": "git", 873 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 874 | "reference": "961bc913d42fe24a257bfff826a5068079ac7782" 875 | }, 876 | "dist": { 877 | "type": "zip", 878 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", 879 | "reference": "961bc913d42fe24a257bfff826a5068079ac7782", 880 | "shasum": "" 881 | }, 882 | "require": { 883 | "php": ">=8.3" 884 | }, 885 | "require-dev": { 886 | "phpunit/phpunit": "^12.0" 887 | }, 888 | "type": "library", 889 | "extra": { 890 | "branch-alias": { 891 | "dev-main": "6.0-dev" 892 | } 893 | }, 894 | "autoload": { 895 | "classmap": [ 896 | "src/" 897 | ] 898 | }, 899 | "notification-url": "https://packagist.org/downloads/", 900 | "license": [ 901 | "BSD-3-Clause" 902 | ], 903 | "authors": [ 904 | { 905 | "name": "Sebastian Bergmann", 906 | "email": "sebastian@phpunit.de", 907 | "role": "lead" 908 | } 909 | ], 910 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 911 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 912 | "keywords": [ 913 | "filesystem", 914 | "iterator" 915 | ], 916 | "support": { 917 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 918 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", 919 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" 920 | }, 921 | "funding": [ 922 | { 923 | "url": "https://github.com/sebastianbergmann", 924 | "type": "github" 925 | } 926 | ], 927 | "time": "2025-02-07T04:58:37+00:00" 928 | }, 929 | { 930 | "name": "phpunit/php-invoker", 931 | "version": "6.0.0", 932 | "source": { 933 | "type": "git", 934 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 935 | "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" 936 | }, 937 | "dist": { 938 | "type": "zip", 939 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", 940 | "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", 941 | "shasum": "" 942 | }, 943 | "require": { 944 | "php": ">=8.3" 945 | }, 946 | "require-dev": { 947 | "ext-pcntl": "*", 948 | "phpunit/phpunit": "^12.0" 949 | }, 950 | "suggest": { 951 | "ext-pcntl": "*" 952 | }, 953 | "type": "library", 954 | "extra": { 955 | "branch-alias": { 956 | "dev-main": "6.0-dev" 957 | } 958 | }, 959 | "autoload": { 960 | "classmap": [ 961 | "src/" 962 | ] 963 | }, 964 | "notification-url": "https://packagist.org/downloads/", 965 | "license": [ 966 | "BSD-3-Clause" 967 | ], 968 | "authors": [ 969 | { 970 | "name": "Sebastian Bergmann", 971 | "email": "sebastian@phpunit.de", 972 | "role": "lead" 973 | } 974 | ], 975 | "description": "Invoke callables with a timeout", 976 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 977 | "keywords": [ 978 | "process" 979 | ], 980 | "support": { 981 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 982 | "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", 983 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" 984 | }, 985 | "funding": [ 986 | { 987 | "url": "https://github.com/sebastianbergmann", 988 | "type": "github" 989 | } 990 | ], 991 | "time": "2025-02-07T04:58:58+00:00" 992 | }, 993 | { 994 | "name": "phpunit/php-text-template", 995 | "version": "5.0.0", 996 | "source": { 997 | "type": "git", 998 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 999 | "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" 1000 | }, 1001 | "dist": { 1002 | "type": "zip", 1003 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", 1004 | "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", 1005 | "shasum": "" 1006 | }, 1007 | "require": { 1008 | "php": ">=8.3" 1009 | }, 1010 | "require-dev": { 1011 | "phpunit/phpunit": "^12.0" 1012 | }, 1013 | "type": "library", 1014 | "extra": { 1015 | "branch-alias": { 1016 | "dev-main": "5.0-dev" 1017 | } 1018 | }, 1019 | "autoload": { 1020 | "classmap": [ 1021 | "src/" 1022 | ] 1023 | }, 1024 | "notification-url": "https://packagist.org/downloads/", 1025 | "license": [ 1026 | "BSD-3-Clause" 1027 | ], 1028 | "authors": [ 1029 | { 1030 | "name": "Sebastian Bergmann", 1031 | "email": "sebastian@phpunit.de", 1032 | "role": "lead" 1033 | } 1034 | ], 1035 | "description": "Simple template engine.", 1036 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1037 | "keywords": [ 1038 | "template" 1039 | ], 1040 | "support": { 1041 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 1042 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", 1043 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" 1044 | }, 1045 | "funding": [ 1046 | { 1047 | "url": "https://github.com/sebastianbergmann", 1048 | "type": "github" 1049 | } 1050 | ], 1051 | "time": "2025-02-07T04:59:16+00:00" 1052 | }, 1053 | { 1054 | "name": "phpunit/php-timer", 1055 | "version": "8.0.0", 1056 | "source": { 1057 | "type": "git", 1058 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1059 | "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" 1060 | }, 1061 | "dist": { 1062 | "type": "zip", 1063 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", 1064 | "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", 1065 | "shasum": "" 1066 | }, 1067 | "require": { 1068 | "php": ">=8.3" 1069 | }, 1070 | "require-dev": { 1071 | "phpunit/phpunit": "^12.0" 1072 | }, 1073 | "type": "library", 1074 | "extra": { 1075 | "branch-alias": { 1076 | "dev-main": "8.0-dev" 1077 | } 1078 | }, 1079 | "autoload": { 1080 | "classmap": [ 1081 | "src/" 1082 | ] 1083 | }, 1084 | "notification-url": "https://packagist.org/downloads/", 1085 | "license": [ 1086 | "BSD-3-Clause" 1087 | ], 1088 | "authors": [ 1089 | { 1090 | "name": "Sebastian Bergmann", 1091 | "email": "sebastian@phpunit.de", 1092 | "role": "lead" 1093 | } 1094 | ], 1095 | "description": "Utility class for timing", 1096 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1097 | "keywords": [ 1098 | "timer" 1099 | ], 1100 | "support": { 1101 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 1102 | "security": "https://github.com/sebastianbergmann/php-timer/security/policy", 1103 | "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" 1104 | }, 1105 | "funding": [ 1106 | { 1107 | "url": "https://github.com/sebastianbergmann", 1108 | "type": "github" 1109 | } 1110 | ], 1111 | "time": "2025-02-07T04:59:38+00:00" 1112 | }, 1113 | { 1114 | "name": "phpunit/phpunit", 1115 | "version": "12.4.5", 1116 | "source": { 1117 | "type": "git", 1118 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1119 | "reference": "5af317802efd27d5b9cbe048e3760d4a2f687f45" 1120 | }, 1121 | "dist": { 1122 | "type": "zip", 1123 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5af317802efd27d5b9cbe048e3760d4a2f687f45", 1124 | "reference": "5af317802efd27d5b9cbe048e3760d4a2f687f45", 1125 | "shasum": "" 1126 | }, 1127 | "require": { 1128 | "ext-dom": "*", 1129 | "ext-json": "*", 1130 | "ext-libxml": "*", 1131 | "ext-mbstring": "*", 1132 | "ext-xml": "*", 1133 | "ext-xmlwriter": "*", 1134 | "myclabs/deep-copy": "^1.13.4", 1135 | "phar-io/manifest": "^2.0.4", 1136 | "phar-io/version": "^3.2.1", 1137 | "php": ">=8.3", 1138 | "phpunit/php-code-coverage": "^12.4.0", 1139 | "phpunit/php-file-iterator": "^6.0.0", 1140 | "phpunit/php-invoker": "^6.0.0", 1141 | "phpunit/php-text-template": "^5.0.0", 1142 | "phpunit/php-timer": "^8.0.0", 1143 | "sebastian/cli-parser": "^4.2.0", 1144 | "sebastian/comparator": "^7.1.3", 1145 | "sebastian/diff": "^7.0.0", 1146 | "sebastian/environment": "^8.0.3", 1147 | "sebastian/exporter": "^7.0.2", 1148 | "sebastian/global-state": "^8.0.2", 1149 | "sebastian/object-enumerator": "^7.0.0", 1150 | "sebastian/type": "^6.0.3", 1151 | "sebastian/version": "^6.0.0", 1152 | "staabm/side-effects-detector": "^1.0.5" 1153 | }, 1154 | "bin": [ 1155 | "phpunit" 1156 | ], 1157 | "type": "library", 1158 | "extra": { 1159 | "branch-alias": { 1160 | "dev-main": "12.4-dev" 1161 | } 1162 | }, 1163 | "autoload": { 1164 | "files": [ 1165 | "src/Framework/Assert/Functions.php" 1166 | ], 1167 | "classmap": [ 1168 | "src/" 1169 | ] 1170 | }, 1171 | "notification-url": "https://packagist.org/downloads/", 1172 | "license": [ 1173 | "BSD-3-Clause" 1174 | ], 1175 | "authors": [ 1176 | { 1177 | "name": "Sebastian Bergmann", 1178 | "email": "sebastian@phpunit.de", 1179 | "role": "lead" 1180 | } 1181 | ], 1182 | "description": "The PHP Unit Testing framework.", 1183 | "homepage": "https://phpunit.de/", 1184 | "keywords": [ 1185 | "phpunit", 1186 | "testing", 1187 | "xunit" 1188 | ], 1189 | "support": { 1190 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1191 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 1192 | "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.5" 1193 | }, 1194 | "funding": [ 1195 | { 1196 | "url": "https://phpunit.de/sponsors.html", 1197 | "type": "custom" 1198 | }, 1199 | { 1200 | "url": "https://github.com/sebastianbergmann", 1201 | "type": "github" 1202 | }, 1203 | { 1204 | "url": "https://liberapay.com/sebastianbergmann", 1205 | "type": "liberapay" 1206 | }, 1207 | { 1208 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1209 | "type": "thanks_dev" 1210 | }, 1211 | { 1212 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 1213 | "type": "tidelift" 1214 | } 1215 | ], 1216 | "time": "2025-12-01T07:40:15+00:00" 1217 | }, 1218 | { 1219 | "name": "sebastian/cli-parser", 1220 | "version": "4.2.0", 1221 | "source": { 1222 | "type": "git", 1223 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 1224 | "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" 1225 | }, 1226 | "dist": { 1227 | "type": "zip", 1228 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", 1229 | "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", 1230 | "shasum": "" 1231 | }, 1232 | "require": { 1233 | "php": ">=8.3" 1234 | }, 1235 | "require-dev": { 1236 | "phpunit/phpunit": "^12.0" 1237 | }, 1238 | "type": "library", 1239 | "extra": { 1240 | "branch-alias": { 1241 | "dev-main": "4.2-dev" 1242 | } 1243 | }, 1244 | "autoload": { 1245 | "classmap": [ 1246 | "src/" 1247 | ] 1248 | }, 1249 | "notification-url": "https://packagist.org/downloads/", 1250 | "license": [ 1251 | "BSD-3-Clause" 1252 | ], 1253 | "authors": [ 1254 | { 1255 | "name": "Sebastian Bergmann", 1256 | "email": "sebastian@phpunit.de", 1257 | "role": "lead" 1258 | } 1259 | ], 1260 | "description": "Library for parsing CLI options", 1261 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 1262 | "support": { 1263 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 1264 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", 1265 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" 1266 | }, 1267 | "funding": [ 1268 | { 1269 | "url": "https://github.com/sebastianbergmann", 1270 | "type": "github" 1271 | }, 1272 | { 1273 | "url": "https://liberapay.com/sebastianbergmann", 1274 | "type": "liberapay" 1275 | }, 1276 | { 1277 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1278 | "type": "thanks_dev" 1279 | }, 1280 | { 1281 | "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", 1282 | "type": "tidelift" 1283 | } 1284 | ], 1285 | "time": "2025-09-14T09:36:45+00:00" 1286 | }, 1287 | { 1288 | "name": "sebastian/comparator", 1289 | "version": "7.1.3", 1290 | "source": { 1291 | "type": "git", 1292 | "url": "https://github.com/sebastianbergmann/comparator.git", 1293 | "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" 1294 | }, 1295 | "dist": { 1296 | "type": "zip", 1297 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", 1298 | "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", 1299 | "shasum": "" 1300 | }, 1301 | "require": { 1302 | "ext-dom": "*", 1303 | "ext-mbstring": "*", 1304 | "php": ">=8.3", 1305 | "sebastian/diff": "^7.0", 1306 | "sebastian/exporter": "^7.0" 1307 | }, 1308 | "require-dev": { 1309 | "phpunit/phpunit": "^12.2" 1310 | }, 1311 | "suggest": { 1312 | "ext-bcmath": "For comparing BcMath\\Number objects" 1313 | }, 1314 | "type": "library", 1315 | "extra": { 1316 | "branch-alias": { 1317 | "dev-main": "7.1-dev" 1318 | } 1319 | }, 1320 | "autoload": { 1321 | "classmap": [ 1322 | "src/" 1323 | ] 1324 | }, 1325 | "notification-url": "https://packagist.org/downloads/", 1326 | "license": [ 1327 | "BSD-3-Clause" 1328 | ], 1329 | "authors": [ 1330 | { 1331 | "name": "Sebastian Bergmann", 1332 | "email": "sebastian@phpunit.de" 1333 | }, 1334 | { 1335 | "name": "Jeff Welch", 1336 | "email": "whatthejeff@gmail.com" 1337 | }, 1338 | { 1339 | "name": "Volker Dusch", 1340 | "email": "github@wallbash.com" 1341 | }, 1342 | { 1343 | "name": "Bernhard Schussek", 1344 | "email": "bschussek@2bepublished.at" 1345 | } 1346 | ], 1347 | "description": "Provides the functionality to compare PHP values for equality", 1348 | "homepage": "https://github.com/sebastianbergmann/comparator", 1349 | "keywords": [ 1350 | "comparator", 1351 | "compare", 1352 | "equality" 1353 | ], 1354 | "support": { 1355 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1356 | "security": "https://github.com/sebastianbergmann/comparator/security/policy", 1357 | "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" 1358 | }, 1359 | "funding": [ 1360 | { 1361 | "url": "https://github.com/sebastianbergmann", 1362 | "type": "github" 1363 | }, 1364 | { 1365 | "url": "https://liberapay.com/sebastianbergmann", 1366 | "type": "liberapay" 1367 | }, 1368 | { 1369 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1370 | "type": "thanks_dev" 1371 | }, 1372 | { 1373 | "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", 1374 | "type": "tidelift" 1375 | } 1376 | ], 1377 | "time": "2025-08-20T11:27:00+00:00" 1378 | }, 1379 | { 1380 | "name": "sebastian/complexity", 1381 | "version": "5.0.0", 1382 | "source": { 1383 | "type": "git", 1384 | "url": "https://github.com/sebastianbergmann/complexity.git", 1385 | "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" 1386 | }, 1387 | "dist": { 1388 | "type": "zip", 1389 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", 1390 | "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", 1391 | "shasum": "" 1392 | }, 1393 | "require": { 1394 | "nikic/php-parser": "^5.0", 1395 | "php": ">=8.3" 1396 | }, 1397 | "require-dev": { 1398 | "phpunit/phpunit": "^12.0" 1399 | }, 1400 | "type": "library", 1401 | "extra": { 1402 | "branch-alias": { 1403 | "dev-main": "5.0-dev" 1404 | } 1405 | }, 1406 | "autoload": { 1407 | "classmap": [ 1408 | "src/" 1409 | ] 1410 | }, 1411 | "notification-url": "https://packagist.org/downloads/", 1412 | "license": [ 1413 | "BSD-3-Clause" 1414 | ], 1415 | "authors": [ 1416 | { 1417 | "name": "Sebastian Bergmann", 1418 | "email": "sebastian@phpunit.de", 1419 | "role": "lead" 1420 | } 1421 | ], 1422 | "description": "Library for calculating the complexity of PHP code units", 1423 | "homepage": "https://github.com/sebastianbergmann/complexity", 1424 | "support": { 1425 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1426 | "security": "https://github.com/sebastianbergmann/complexity/security/policy", 1427 | "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" 1428 | }, 1429 | "funding": [ 1430 | { 1431 | "url": "https://github.com/sebastianbergmann", 1432 | "type": "github" 1433 | } 1434 | ], 1435 | "time": "2025-02-07T04:55:25+00:00" 1436 | }, 1437 | { 1438 | "name": "sebastian/diff", 1439 | "version": "7.0.0", 1440 | "source": { 1441 | "type": "git", 1442 | "url": "https://github.com/sebastianbergmann/diff.git", 1443 | "reference": "7ab1ea946c012266ca32390913653d844ecd085f" 1444 | }, 1445 | "dist": { 1446 | "type": "zip", 1447 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", 1448 | "reference": "7ab1ea946c012266ca32390913653d844ecd085f", 1449 | "shasum": "" 1450 | }, 1451 | "require": { 1452 | "php": ">=8.3" 1453 | }, 1454 | "require-dev": { 1455 | "phpunit/phpunit": "^12.0", 1456 | "symfony/process": "^7.2" 1457 | }, 1458 | "type": "library", 1459 | "extra": { 1460 | "branch-alias": { 1461 | "dev-main": "7.0-dev" 1462 | } 1463 | }, 1464 | "autoload": { 1465 | "classmap": [ 1466 | "src/" 1467 | ] 1468 | }, 1469 | "notification-url": "https://packagist.org/downloads/", 1470 | "license": [ 1471 | "BSD-3-Clause" 1472 | ], 1473 | "authors": [ 1474 | { 1475 | "name": "Sebastian Bergmann", 1476 | "email": "sebastian@phpunit.de" 1477 | }, 1478 | { 1479 | "name": "Kore Nordmann", 1480 | "email": "mail@kore-nordmann.de" 1481 | } 1482 | ], 1483 | "description": "Diff implementation", 1484 | "homepage": "https://github.com/sebastianbergmann/diff", 1485 | "keywords": [ 1486 | "diff", 1487 | "udiff", 1488 | "unidiff", 1489 | "unified diff" 1490 | ], 1491 | "support": { 1492 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1493 | "security": "https://github.com/sebastianbergmann/diff/security/policy", 1494 | "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" 1495 | }, 1496 | "funding": [ 1497 | { 1498 | "url": "https://github.com/sebastianbergmann", 1499 | "type": "github" 1500 | } 1501 | ], 1502 | "time": "2025-02-07T04:55:46+00:00" 1503 | }, 1504 | { 1505 | "name": "sebastian/environment", 1506 | "version": "8.0.3", 1507 | "source": { 1508 | "type": "git", 1509 | "url": "https://github.com/sebastianbergmann/environment.git", 1510 | "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" 1511 | }, 1512 | "dist": { 1513 | "type": "zip", 1514 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", 1515 | "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", 1516 | "shasum": "" 1517 | }, 1518 | "require": { 1519 | "php": ">=8.3" 1520 | }, 1521 | "require-dev": { 1522 | "phpunit/phpunit": "^12.0" 1523 | }, 1524 | "suggest": { 1525 | "ext-posix": "*" 1526 | }, 1527 | "type": "library", 1528 | "extra": { 1529 | "branch-alias": { 1530 | "dev-main": "8.0-dev" 1531 | } 1532 | }, 1533 | "autoload": { 1534 | "classmap": [ 1535 | "src/" 1536 | ] 1537 | }, 1538 | "notification-url": "https://packagist.org/downloads/", 1539 | "license": [ 1540 | "BSD-3-Clause" 1541 | ], 1542 | "authors": [ 1543 | { 1544 | "name": "Sebastian Bergmann", 1545 | "email": "sebastian@phpunit.de" 1546 | } 1547 | ], 1548 | "description": "Provides functionality to handle HHVM/PHP environments", 1549 | "homepage": "https://github.com/sebastianbergmann/environment", 1550 | "keywords": [ 1551 | "Xdebug", 1552 | "environment", 1553 | "hhvm" 1554 | ], 1555 | "support": { 1556 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1557 | "security": "https://github.com/sebastianbergmann/environment/security/policy", 1558 | "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" 1559 | }, 1560 | "funding": [ 1561 | { 1562 | "url": "https://github.com/sebastianbergmann", 1563 | "type": "github" 1564 | }, 1565 | { 1566 | "url": "https://liberapay.com/sebastianbergmann", 1567 | "type": "liberapay" 1568 | }, 1569 | { 1570 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1571 | "type": "thanks_dev" 1572 | }, 1573 | { 1574 | "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", 1575 | "type": "tidelift" 1576 | } 1577 | ], 1578 | "time": "2025-08-12T14:11:56+00:00" 1579 | }, 1580 | { 1581 | "name": "sebastian/exporter", 1582 | "version": "7.0.2", 1583 | "source": { 1584 | "type": "git", 1585 | "url": "https://github.com/sebastianbergmann/exporter.git", 1586 | "reference": "016951ae10980765e4e7aee491eb288c64e505b7" 1587 | }, 1588 | "dist": { 1589 | "type": "zip", 1590 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", 1591 | "reference": "016951ae10980765e4e7aee491eb288c64e505b7", 1592 | "shasum": "" 1593 | }, 1594 | "require": { 1595 | "ext-mbstring": "*", 1596 | "php": ">=8.3", 1597 | "sebastian/recursion-context": "^7.0" 1598 | }, 1599 | "require-dev": { 1600 | "phpunit/phpunit": "^12.0" 1601 | }, 1602 | "type": "library", 1603 | "extra": { 1604 | "branch-alias": { 1605 | "dev-main": "7.0-dev" 1606 | } 1607 | }, 1608 | "autoload": { 1609 | "classmap": [ 1610 | "src/" 1611 | ] 1612 | }, 1613 | "notification-url": "https://packagist.org/downloads/", 1614 | "license": [ 1615 | "BSD-3-Clause" 1616 | ], 1617 | "authors": [ 1618 | { 1619 | "name": "Sebastian Bergmann", 1620 | "email": "sebastian@phpunit.de" 1621 | }, 1622 | { 1623 | "name": "Jeff Welch", 1624 | "email": "whatthejeff@gmail.com" 1625 | }, 1626 | { 1627 | "name": "Volker Dusch", 1628 | "email": "github@wallbash.com" 1629 | }, 1630 | { 1631 | "name": "Adam Harvey", 1632 | "email": "aharvey@php.net" 1633 | }, 1634 | { 1635 | "name": "Bernhard Schussek", 1636 | "email": "bschussek@gmail.com" 1637 | } 1638 | ], 1639 | "description": "Provides the functionality to export PHP variables for visualization", 1640 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1641 | "keywords": [ 1642 | "export", 1643 | "exporter" 1644 | ], 1645 | "support": { 1646 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1647 | "security": "https://github.com/sebastianbergmann/exporter/security/policy", 1648 | "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" 1649 | }, 1650 | "funding": [ 1651 | { 1652 | "url": "https://github.com/sebastianbergmann", 1653 | "type": "github" 1654 | }, 1655 | { 1656 | "url": "https://liberapay.com/sebastianbergmann", 1657 | "type": "liberapay" 1658 | }, 1659 | { 1660 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1661 | "type": "thanks_dev" 1662 | }, 1663 | { 1664 | "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", 1665 | "type": "tidelift" 1666 | } 1667 | ], 1668 | "time": "2025-09-24T06:16:11+00:00" 1669 | }, 1670 | { 1671 | "name": "sebastian/global-state", 1672 | "version": "8.0.2", 1673 | "source": { 1674 | "type": "git", 1675 | "url": "https://github.com/sebastianbergmann/global-state.git", 1676 | "reference": "ef1377171613d09edd25b7816f05be8313f9115d" 1677 | }, 1678 | "dist": { 1679 | "type": "zip", 1680 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", 1681 | "reference": "ef1377171613d09edd25b7816f05be8313f9115d", 1682 | "shasum": "" 1683 | }, 1684 | "require": { 1685 | "php": ">=8.3", 1686 | "sebastian/object-reflector": "^5.0", 1687 | "sebastian/recursion-context": "^7.0" 1688 | }, 1689 | "require-dev": { 1690 | "ext-dom": "*", 1691 | "phpunit/phpunit": "^12.0" 1692 | }, 1693 | "type": "library", 1694 | "extra": { 1695 | "branch-alias": { 1696 | "dev-main": "8.0-dev" 1697 | } 1698 | }, 1699 | "autoload": { 1700 | "classmap": [ 1701 | "src/" 1702 | ] 1703 | }, 1704 | "notification-url": "https://packagist.org/downloads/", 1705 | "license": [ 1706 | "BSD-3-Clause" 1707 | ], 1708 | "authors": [ 1709 | { 1710 | "name": "Sebastian Bergmann", 1711 | "email": "sebastian@phpunit.de" 1712 | } 1713 | ], 1714 | "description": "Snapshotting of global state", 1715 | "homepage": "https://www.github.com/sebastianbergmann/global-state", 1716 | "keywords": [ 1717 | "global state" 1718 | ], 1719 | "support": { 1720 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1721 | "security": "https://github.com/sebastianbergmann/global-state/security/policy", 1722 | "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" 1723 | }, 1724 | "funding": [ 1725 | { 1726 | "url": "https://github.com/sebastianbergmann", 1727 | "type": "github" 1728 | }, 1729 | { 1730 | "url": "https://liberapay.com/sebastianbergmann", 1731 | "type": "liberapay" 1732 | }, 1733 | { 1734 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1735 | "type": "thanks_dev" 1736 | }, 1737 | { 1738 | "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", 1739 | "type": "tidelift" 1740 | } 1741 | ], 1742 | "time": "2025-08-29T11:29:25+00:00" 1743 | }, 1744 | { 1745 | "name": "sebastian/lines-of-code", 1746 | "version": "4.0.0", 1747 | "source": { 1748 | "type": "git", 1749 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1750 | "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" 1751 | }, 1752 | "dist": { 1753 | "type": "zip", 1754 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", 1755 | "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", 1756 | "shasum": "" 1757 | }, 1758 | "require": { 1759 | "nikic/php-parser": "^5.0", 1760 | "php": ">=8.3" 1761 | }, 1762 | "require-dev": { 1763 | "phpunit/phpunit": "^12.0" 1764 | }, 1765 | "type": "library", 1766 | "extra": { 1767 | "branch-alias": { 1768 | "dev-main": "4.0-dev" 1769 | } 1770 | }, 1771 | "autoload": { 1772 | "classmap": [ 1773 | "src/" 1774 | ] 1775 | }, 1776 | "notification-url": "https://packagist.org/downloads/", 1777 | "license": [ 1778 | "BSD-3-Clause" 1779 | ], 1780 | "authors": [ 1781 | { 1782 | "name": "Sebastian Bergmann", 1783 | "email": "sebastian@phpunit.de", 1784 | "role": "lead" 1785 | } 1786 | ], 1787 | "description": "Library for counting the lines of code in PHP source code", 1788 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1789 | "support": { 1790 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1791 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", 1792 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" 1793 | }, 1794 | "funding": [ 1795 | { 1796 | "url": "https://github.com/sebastianbergmann", 1797 | "type": "github" 1798 | } 1799 | ], 1800 | "time": "2025-02-07T04:57:28+00:00" 1801 | }, 1802 | { 1803 | "name": "sebastian/object-enumerator", 1804 | "version": "7.0.0", 1805 | "source": { 1806 | "type": "git", 1807 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1808 | "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" 1809 | }, 1810 | "dist": { 1811 | "type": "zip", 1812 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", 1813 | "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", 1814 | "shasum": "" 1815 | }, 1816 | "require": { 1817 | "php": ">=8.3", 1818 | "sebastian/object-reflector": "^5.0", 1819 | "sebastian/recursion-context": "^7.0" 1820 | }, 1821 | "require-dev": { 1822 | "phpunit/phpunit": "^12.0" 1823 | }, 1824 | "type": "library", 1825 | "extra": { 1826 | "branch-alias": { 1827 | "dev-main": "7.0-dev" 1828 | } 1829 | }, 1830 | "autoload": { 1831 | "classmap": [ 1832 | "src/" 1833 | ] 1834 | }, 1835 | "notification-url": "https://packagist.org/downloads/", 1836 | "license": [ 1837 | "BSD-3-Clause" 1838 | ], 1839 | "authors": [ 1840 | { 1841 | "name": "Sebastian Bergmann", 1842 | "email": "sebastian@phpunit.de" 1843 | } 1844 | ], 1845 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1846 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1847 | "support": { 1848 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1849 | "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", 1850 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" 1851 | }, 1852 | "funding": [ 1853 | { 1854 | "url": "https://github.com/sebastianbergmann", 1855 | "type": "github" 1856 | } 1857 | ], 1858 | "time": "2025-02-07T04:57:48+00:00" 1859 | }, 1860 | { 1861 | "name": "sebastian/object-reflector", 1862 | "version": "5.0.0", 1863 | "source": { 1864 | "type": "git", 1865 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1866 | "reference": "4bfa827c969c98be1e527abd576533293c634f6a" 1867 | }, 1868 | "dist": { 1869 | "type": "zip", 1870 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", 1871 | "reference": "4bfa827c969c98be1e527abd576533293c634f6a", 1872 | "shasum": "" 1873 | }, 1874 | "require": { 1875 | "php": ">=8.3" 1876 | }, 1877 | "require-dev": { 1878 | "phpunit/phpunit": "^12.0" 1879 | }, 1880 | "type": "library", 1881 | "extra": { 1882 | "branch-alias": { 1883 | "dev-main": "5.0-dev" 1884 | } 1885 | }, 1886 | "autoload": { 1887 | "classmap": [ 1888 | "src/" 1889 | ] 1890 | }, 1891 | "notification-url": "https://packagist.org/downloads/", 1892 | "license": [ 1893 | "BSD-3-Clause" 1894 | ], 1895 | "authors": [ 1896 | { 1897 | "name": "Sebastian Bergmann", 1898 | "email": "sebastian@phpunit.de" 1899 | } 1900 | ], 1901 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1902 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1903 | "support": { 1904 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1905 | "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", 1906 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" 1907 | }, 1908 | "funding": [ 1909 | { 1910 | "url": "https://github.com/sebastianbergmann", 1911 | "type": "github" 1912 | } 1913 | ], 1914 | "time": "2025-02-07T04:58:17+00:00" 1915 | }, 1916 | { 1917 | "name": "sebastian/recursion-context", 1918 | "version": "7.0.1", 1919 | "source": { 1920 | "type": "git", 1921 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1922 | "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" 1923 | }, 1924 | "dist": { 1925 | "type": "zip", 1926 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", 1927 | "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", 1928 | "shasum": "" 1929 | }, 1930 | "require": { 1931 | "php": ">=8.3" 1932 | }, 1933 | "require-dev": { 1934 | "phpunit/phpunit": "^12.0" 1935 | }, 1936 | "type": "library", 1937 | "extra": { 1938 | "branch-alias": { 1939 | "dev-main": "7.0-dev" 1940 | } 1941 | }, 1942 | "autoload": { 1943 | "classmap": [ 1944 | "src/" 1945 | ] 1946 | }, 1947 | "notification-url": "https://packagist.org/downloads/", 1948 | "license": [ 1949 | "BSD-3-Clause" 1950 | ], 1951 | "authors": [ 1952 | { 1953 | "name": "Sebastian Bergmann", 1954 | "email": "sebastian@phpunit.de" 1955 | }, 1956 | { 1957 | "name": "Jeff Welch", 1958 | "email": "whatthejeff@gmail.com" 1959 | }, 1960 | { 1961 | "name": "Adam Harvey", 1962 | "email": "aharvey@php.net" 1963 | } 1964 | ], 1965 | "description": "Provides functionality to recursively process PHP variables", 1966 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1967 | "support": { 1968 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1969 | "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", 1970 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" 1971 | }, 1972 | "funding": [ 1973 | { 1974 | "url": "https://github.com/sebastianbergmann", 1975 | "type": "github" 1976 | }, 1977 | { 1978 | "url": "https://liberapay.com/sebastianbergmann", 1979 | "type": "liberapay" 1980 | }, 1981 | { 1982 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 1983 | "type": "thanks_dev" 1984 | }, 1985 | { 1986 | "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", 1987 | "type": "tidelift" 1988 | } 1989 | ], 1990 | "time": "2025-08-13T04:44:59+00:00" 1991 | }, 1992 | { 1993 | "name": "sebastian/type", 1994 | "version": "6.0.3", 1995 | "source": { 1996 | "type": "git", 1997 | "url": "https://github.com/sebastianbergmann/type.git", 1998 | "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" 1999 | }, 2000 | "dist": { 2001 | "type": "zip", 2002 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", 2003 | "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", 2004 | "shasum": "" 2005 | }, 2006 | "require": { 2007 | "php": ">=8.3" 2008 | }, 2009 | "require-dev": { 2010 | "phpunit/phpunit": "^12.0" 2011 | }, 2012 | "type": "library", 2013 | "extra": { 2014 | "branch-alias": { 2015 | "dev-main": "6.0-dev" 2016 | } 2017 | }, 2018 | "autoload": { 2019 | "classmap": [ 2020 | "src/" 2021 | ] 2022 | }, 2023 | "notification-url": "https://packagist.org/downloads/", 2024 | "license": [ 2025 | "BSD-3-Clause" 2026 | ], 2027 | "authors": [ 2028 | { 2029 | "name": "Sebastian Bergmann", 2030 | "email": "sebastian@phpunit.de", 2031 | "role": "lead" 2032 | } 2033 | ], 2034 | "description": "Collection of value objects that represent the types of the PHP type system", 2035 | "homepage": "https://github.com/sebastianbergmann/type", 2036 | "support": { 2037 | "issues": "https://github.com/sebastianbergmann/type/issues", 2038 | "security": "https://github.com/sebastianbergmann/type/security/policy", 2039 | "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" 2040 | }, 2041 | "funding": [ 2042 | { 2043 | "url": "https://github.com/sebastianbergmann", 2044 | "type": "github" 2045 | }, 2046 | { 2047 | "url": "https://liberapay.com/sebastianbergmann", 2048 | "type": "liberapay" 2049 | }, 2050 | { 2051 | "url": "https://thanks.dev/u/gh/sebastianbergmann", 2052 | "type": "thanks_dev" 2053 | }, 2054 | { 2055 | "url": "https://tidelift.com/funding/github/packagist/sebastian/type", 2056 | "type": "tidelift" 2057 | } 2058 | ], 2059 | "time": "2025-08-09T06:57:12+00:00" 2060 | }, 2061 | { 2062 | "name": "sebastian/version", 2063 | "version": "6.0.0", 2064 | "source": { 2065 | "type": "git", 2066 | "url": "https://github.com/sebastianbergmann/version.git", 2067 | "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" 2068 | }, 2069 | "dist": { 2070 | "type": "zip", 2071 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", 2072 | "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", 2073 | "shasum": "" 2074 | }, 2075 | "require": { 2076 | "php": ">=8.3" 2077 | }, 2078 | "type": "library", 2079 | "extra": { 2080 | "branch-alias": { 2081 | "dev-main": "6.0-dev" 2082 | } 2083 | }, 2084 | "autoload": { 2085 | "classmap": [ 2086 | "src/" 2087 | ] 2088 | }, 2089 | "notification-url": "https://packagist.org/downloads/", 2090 | "license": [ 2091 | "BSD-3-Clause" 2092 | ], 2093 | "authors": [ 2094 | { 2095 | "name": "Sebastian Bergmann", 2096 | "email": "sebastian@phpunit.de", 2097 | "role": "lead" 2098 | } 2099 | ], 2100 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2101 | "homepage": "https://github.com/sebastianbergmann/version", 2102 | "support": { 2103 | "issues": "https://github.com/sebastianbergmann/version/issues", 2104 | "security": "https://github.com/sebastianbergmann/version/security/policy", 2105 | "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" 2106 | }, 2107 | "funding": [ 2108 | { 2109 | "url": "https://github.com/sebastianbergmann", 2110 | "type": "github" 2111 | } 2112 | ], 2113 | "time": "2025-02-07T05:00:38+00:00" 2114 | }, 2115 | { 2116 | "name": "squizlabs/php_codesniffer", 2117 | "version": "3.13.2", 2118 | "source": { 2119 | "type": "git", 2120 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 2121 | "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" 2122 | }, 2123 | "dist": { 2124 | "type": "zip", 2125 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", 2126 | "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", 2127 | "shasum": "" 2128 | }, 2129 | "require": { 2130 | "ext-simplexml": "*", 2131 | "ext-tokenizer": "*", 2132 | "ext-xmlwriter": "*", 2133 | "php": ">=5.4.0" 2134 | }, 2135 | "require-dev": { 2136 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 2137 | }, 2138 | "bin": [ 2139 | "bin/phpcbf", 2140 | "bin/phpcs" 2141 | ], 2142 | "type": "library", 2143 | "extra": { 2144 | "branch-alias": { 2145 | "dev-master": "3.x-dev" 2146 | } 2147 | }, 2148 | "notification-url": "https://packagist.org/downloads/", 2149 | "license": [ 2150 | "BSD-3-Clause" 2151 | ], 2152 | "authors": [ 2153 | { 2154 | "name": "Greg Sherwood", 2155 | "role": "Former lead" 2156 | }, 2157 | { 2158 | "name": "Juliette Reinders Folmer", 2159 | "role": "Current lead" 2160 | }, 2161 | { 2162 | "name": "Contributors", 2163 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 2164 | } 2165 | ], 2166 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 2167 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 2168 | "keywords": [ 2169 | "phpcs", 2170 | "standards", 2171 | "static analysis" 2172 | ], 2173 | "support": { 2174 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 2175 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 2176 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 2177 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 2178 | }, 2179 | "funding": [ 2180 | { 2181 | "url": "https://github.com/PHPCSStandards", 2182 | "type": "github" 2183 | }, 2184 | { 2185 | "url": "https://github.com/jrfnl", 2186 | "type": "github" 2187 | }, 2188 | { 2189 | "url": "https://opencollective.com/php_codesniffer", 2190 | "type": "open_collective" 2191 | }, 2192 | { 2193 | "url": "https://thanks.dev/u/gh/phpcsstandards", 2194 | "type": "thanks_dev" 2195 | } 2196 | ], 2197 | "time": "2025-06-17T22:17:01+00:00" 2198 | }, 2199 | { 2200 | "name": "staabm/side-effects-detector", 2201 | "version": "1.0.5", 2202 | "source": { 2203 | "type": "git", 2204 | "url": "https://github.com/staabm/side-effects-detector.git", 2205 | "reference": "d8334211a140ce329c13726d4a715adbddd0a163" 2206 | }, 2207 | "dist": { 2208 | "type": "zip", 2209 | "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", 2210 | "reference": "d8334211a140ce329c13726d4a715adbddd0a163", 2211 | "shasum": "" 2212 | }, 2213 | "require": { 2214 | "ext-tokenizer": "*", 2215 | "php": "^7.4 || ^8.0" 2216 | }, 2217 | "require-dev": { 2218 | "phpstan/extension-installer": "^1.4.3", 2219 | "phpstan/phpstan": "^1.12.6", 2220 | "phpunit/phpunit": "^9.6.21", 2221 | "symfony/var-dumper": "^5.4.43", 2222 | "tomasvotruba/type-coverage": "1.0.0", 2223 | "tomasvotruba/unused-public": "1.0.0" 2224 | }, 2225 | "type": "library", 2226 | "autoload": { 2227 | "classmap": [ 2228 | "lib/" 2229 | ] 2230 | }, 2231 | "notification-url": "https://packagist.org/downloads/", 2232 | "license": [ 2233 | "MIT" 2234 | ], 2235 | "description": "A static analysis tool to detect side effects in PHP code", 2236 | "keywords": [ 2237 | "static analysis" 2238 | ], 2239 | "support": { 2240 | "issues": "https://github.com/staabm/side-effects-detector/issues", 2241 | "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" 2242 | }, 2243 | "funding": [ 2244 | { 2245 | "url": "https://github.com/staabm", 2246 | "type": "github" 2247 | } 2248 | ], 2249 | "time": "2024-10-20T05:08:20+00:00" 2250 | }, 2251 | { 2252 | "name": "theseer/tokenizer", 2253 | "version": "1.3.1", 2254 | "source": { 2255 | "type": "git", 2256 | "url": "https://github.com/theseer/tokenizer.git", 2257 | "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" 2258 | }, 2259 | "dist": { 2260 | "type": "zip", 2261 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", 2262 | "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", 2263 | "shasum": "" 2264 | }, 2265 | "require": { 2266 | "ext-dom": "*", 2267 | "ext-tokenizer": "*", 2268 | "ext-xmlwriter": "*", 2269 | "php": "^7.2 || ^8.0" 2270 | }, 2271 | "type": "library", 2272 | "autoload": { 2273 | "classmap": [ 2274 | "src/" 2275 | ] 2276 | }, 2277 | "notification-url": "https://packagist.org/downloads/", 2278 | "license": [ 2279 | "BSD-3-Clause" 2280 | ], 2281 | "authors": [ 2282 | { 2283 | "name": "Arne Blankerts", 2284 | "email": "arne@blankerts.de", 2285 | "role": "Developer" 2286 | } 2287 | ], 2288 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2289 | "support": { 2290 | "issues": "https://github.com/theseer/tokenizer/issues", 2291 | "source": "https://github.com/theseer/tokenizer/tree/1.3.1" 2292 | }, 2293 | "funding": [ 2294 | { 2295 | "url": "https://github.com/theseer", 2296 | "type": "github" 2297 | } 2298 | ], 2299 | "time": "2025-11-17T20:03:58+00:00" 2300 | }, 2301 | { 2302 | "name": "wmde/fundraising-phpcs", 2303 | "version": "v13.0.0", 2304 | "source": { 2305 | "type": "git", 2306 | "url": "https://github.com/wmde/fundraising-phpcs", 2307 | "reference": "b63239c82ca7af1074594490ee7e8280cf32aa67" 2308 | }, 2309 | "dist": { 2310 | "type": "zip", 2311 | "url": "https://api.github.com/repos/wmde/fundraising-phpcs/zipball/b63239c82ca7af1074594490ee7e8280cf32aa67", 2312 | "reference": "b63239c82ca7af1074594490ee7e8280cf32aa67", 2313 | "shasum": "" 2314 | }, 2315 | "require": { 2316 | "mediawiki/mediawiki-codesniffer": "~48.0" 2317 | }, 2318 | "type": "library", 2319 | "license": [ 2320 | "GPL-2.0-or-later" 2321 | ], 2322 | "description": "PHP coding style for the WMDE FUN team", 2323 | "time": "2025-09-08T10:21:07+00:00" 2324 | } 2325 | ], 2326 | "aliases": [], 2327 | "minimum-stability": "stable", 2328 | "stability-flags": {}, 2329 | "prefer-stable": false, 2330 | "prefer-lowest": false, 2331 | "platform": { 2332 | "php": ">=8.4" 2333 | }, 2334 | "platform-dev": {}, 2335 | "plugin-api-version": "2.6.0" 2336 | } 2337 | --------------------------------------------------------------------------------