diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..96824f8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +# PHP PSR-2 Coding Standards +# http://www.php-fig.org/psr/psr-2/ +[*.php] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.github/workflows/php-lint.yml b/.github/workflows/php-lint.yml new file mode 100644 index 0000000..53b8176 --- /dev/null +++ b/.github/workflows/php-lint.yml @@ -0,0 +1,64 @@ +name: PHP Code Linting + +on: + push: + branches: + - main + # Only run if PHP-related files changed. + paths: + - '.github/workflows/php-lint.yml' + - '**.php' + - 'phpcs.xml.dist' + - 'phpmd.xml' + - 'phpstan.neon.dist' + - 'composer.json' + - 'composer.lock' + pull_request: + branches: + - main + # Only run if PHP-related files changed. + paths: + - '.github/workflows/php-lint.yml' + - '**.php' + - 'phpcs.xml.dist' + - 'phpmd.xml' + - 'phpstan.neon.dist' + - 'composer.json' + - 'composer.lock' + types: + - opened + - reopened + - synchronize + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + php-lint: + name: PHP + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + + - name: Validate Composer configuration + run: composer validate + + - name: Install PHP dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 + with: + composer-options: '--prefer-dist --no-progress --no-interaction' + + - name: PHP Lint + run: composer lint + + - name: PHPStan + run: composer phpstan + + - name: PHPMD + run: composer phpmd diff --git a/.github/workflows/php-test.yml b/.github/workflows/php-test.yml new file mode 100644 index 0000000..a17907e --- /dev/null +++ b/.github/workflows/php-test.yml @@ -0,0 +1,54 @@ +name: PHP Unit Testing + +on: + push: + branches: + - main + # Only run if PHP-related files changed. + paths: + - '.github/workflows/php-test.yml' + - '**.php' + - 'phpunit.xml.dist' + - 'composer.json' + - 'composer.lock' + pull_request: + branches: + - main + # Only run if PHP-related files changed. + paths: + - '.github/workflows/php-test.yml' + - '**.php' + - 'phpunit.xml.dist' + - 'composer.json' + - 'composer.lock' + types: + - opened + - reopened + - synchronize + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + php-test: + name: PHP + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + + - name: Validate Composer configuration + run: composer validate + + - name: Install PHP dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 + with: + composer-options: '--prefer-dist --no-progress --no-interaction' + + - name: PHPUnit Test + run: composer test diff --git a/.gitignore b/.gitignore index 668ec4f..b4a9189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ .DS_Store -lib/ \ No newline at end of file +lib/ +vendor/ +*.cache \ No newline at end of file diff --git a/README.md b/README.md index 792d4d4..b47cf99 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Third Party Capital is a resource that consolidates best practices for loading popular third-parties in a single place. +The project provides implementations that can be used in the following languages: +* JavaScript (see [`src` directory](./src)) +* PHP (see [`inc` directory](./inc)) + ## Rationale There is a large, cross-functional Chrome initiative that aims to improve third-party resource loading on the web. One part of this effort is to provide a default set of recommendations, or "components," to developers. These components will help developers sequence and fetch popular third-party resources at the right time to minimize their overall impact to page performance. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3514ccd --- /dev/null +++ b/composer.json @@ -0,0 +1,45 @@ +{ + "name": "googlechromelabs/third-party-capital", + "description": "This package is a collection of classes and utilities that can be used to efficiently load third-party libraries into your PHP application.", + "type": "library", + "license": "Apache-2.0", + "require": { + "php": ">=7" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpmd/phpmd": "^2.9", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9", + "slevomat/coding-standard": "^8.9" + }, + "scripts": { + "lint": "phpcs --standard=phpcs.xml.dist", + "format": "phpcbf --standard=phpcs.xml.dist", + "test": "phpunit -c phpunit.xml.dist --verbose", + "phpmd": "phpmd . text phpmd.xml", + "phpstan": "phpstan analyse --memory-limit=2048M" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "archive": { + "exclude": ["/.husky", "/config", "/src", "!/*.json"] + }, + "autoload": { + "psr-4": { + "GoogleChromeLabs\\ThirdPartyCapital\\": "inc/" + } + }, + "autoload-dev": { + "psr-4": { + "GoogleChromeLabs\\ThirdPartyCapital\\TestData\\": "tests/phpunit/testdata", + "GoogleChromeLabs\\ThirdPartyCapital\\TestUtils\\": "tests/phpunit/utils" + } + } +} + \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9078756 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3237 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "16bd045d073c0751ad48aa5d331e13f0", + "packages": [], + "packages-dev": [ + { + "name": "composer/pcre", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-10-11T07:11:09+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + }, + "time": "2023-12-10T21:03:43+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.25.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" + }, + "time": "2024-01-04T17:06:16+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.10.54", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3e25f279dada0adc14ffd7bad09af2e2fc3523bb", + "reference": "3e25f279dada0adc14ffd7bad09af2e2fc3523bb", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2024-01-05T15:50:47+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.30", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:47:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-12-01T16:55:19+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "8.14.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.23.1", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "phing/phing": "2.17.4", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.10.37", + "phpstan/phpstan-deprecation-rules": "1.1.4", + "phpstan/phpstan-phpunit": "1.3.14", + "phpstan/phpstan-strict-rules": "1.5.1", + "phpunit/phpunit": "8.5.21|9.6.8|10.3.5" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/8.14.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2023-10-08T07:28:08+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" + }, + { + "name": "symfony/config", + "version": "v5.4.31", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "dd5ea39de228813aba0c23c3a4153da2a4cf3cd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/dd5ea39de228813aba0c23c3a4153da2a4cf3cd9", + "reference": "dd5ea39de228813aba0c23c3a4153da2a4cf3cd9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" + }, + "conflict": { + "symfony/finder": "<4.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v5.4.31" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-09T08:22:43+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v5.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "75d568165a65fa7d8124869ec7c3a90424352e6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/75d568165a65fa7d8124869ec7c3a90424352e6c", + "reference": "75d568165a65fa7d8124869ec7c3a90424352e6c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4.26" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/config": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4.26|^5.0|^6.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-28T09:31:38+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.25", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.25" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-31T13:04:02+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2023-11-20T00:12:19+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/inc/Contracts/Arrayable.php b/inc/Contracts/Arrayable.php new file mode 100644 index 0000000..ea6818c --- /dev/null +++ b/inc/Contracts/Arrayable.php @@ -0,0 +1,24 @@ +$field = isset($data[ $field ]) ? (string) $data[ $field ] : ''; + } + + $to3pScript = static function ($scriptData) { + return new ThirdPartyScriptData($scriptData); + }; + + $this->html = isset($data['html']) ? new ThirdPartyHtmlData($data['html']) : null; + $this->stylesheets = isset($data['stylesheets']) ? array_map('strval', $data['stylesheets']) : []; + $this->scripts = isset($data['scripts']) ? array_map($to3pScript, $data['scripts']) : []; + } + + /** + * Gets the third party identifier. + * + * @return string Third party identifier. + */ + public function getId(): string + { + return $this->id; + } + + /** + * Gets the third party description. + * + * @return string Third party description. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Gets the third party website. + * + * @return string Third party website, if provided. + */ + public function getWebsite(): string + { + return $this->website; + } + + /** + * Gets the HTML element needed for the third party. + * + * @return ThirdPartyHtmlData|null HTML element needed for the third party, or null. + */ + public function getHtml() + { + return $this->html; + } + + /** + * Gets the stylesheets needed for the third party. + * + * @return string[] Stylesheets needed for the third party. + */ + public function getStylesheets(): array + { + return $this->stylesheets; + } + + /** + * Gets the scripts needed for the third party. + * + * @return ThirdPartyScriptData[] Scripts needed for the third party. + */ + public function getScripts(): array + { + return $this->scripts; + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + $data = [ + 'id' => $this->id, + 'description' => $this->description, + ]; + if ($this->website) { + $data['website'] = $this->website; + } + if ($this->html) { + $data['html'] = $this->html->toArray(); + } + if ($this->stylesheets) { + $data['stylesheets'] = $this->stylesheets; + } + if ($this->scripts) { + $data['scripts'] = array_map( + static function (ThirdPartyScriptData $scriptData) { + return $scriptData->toArray(); + }, + $this->scripts + ); + } + + return $data; + } + + /** + * Creates a new instance from a JSON file with third party configuration data. + * + * @param string $file_path Absolute path to the JSON file. + * @return ThirdPartyData Third party data instance based on the JSON data. + */ + public static function fromJsonFile(string $file_path): ThirdPartyData + { + $data = json_decode(file_get_contents($file_path), true); + return new self($data); + } +} diff --git a/inc/Data/ThirdPartyDataFormatter.php b/inc/Data/ThirdPartyDataFormatter.php new file mode 100644 index 0000000..a2123fc --- /dev/null +++ b/inc/Data/ThirdPartyDataFormatter.php @@ -0,0 +1,295 @@ +getHtml(); + $scriptsData = $data->getScripts(); + + $allScriptParams = array_reduce( + $scriptsData, + static function ($acc, ThirdPartyScriptData $scriptData) { + foreach ($scriptData->getParams() as $param) { + $acc[] = $param; + } + return $acc; + }, + [] + ); + + $scriptUrlParamInputs = self::intersectArgs($args, $allScriptParams); + + $htmlUrlParamInputs = []; + $htmlSlugParamInput = []; + if ($htmlData) { + if (isset($htmlData->getAttributes()['src']) + && $htmlData->getAttributes()['src'] instanceof ThirdPartySrcValue + ) { + $htmlUrlParamInputs = self::intersectArgs( + $args, + $htmlData->getAttributes()['src']->getParams() + ); + $htmlSlugParamInput = self::intersectArgs( + $args, + [$htmlData->getAttributes()['src']->getSlugParam()] + ); + } + } + + $htmlAttrInputs = self::diffArgs( + $args, + array_keys( + array_merge( + $scriptUrlParamInputs, + $htmlUrlParamInputs, + $htmlSlugParamInput + ) + ) + ); + + $newData = $data->toArray(); + if (isset($newData['html']) && $newData['html']) { + $newData['html'] = self::formatHtml( + $newData['html']['element'], + $newData['html']['attributes'], + $htmlAttrInputs, + $htmlUrlParamInputs, + $htmlSlugParamInput + ); + } + if (isset($newData['scripts']) && $newData['scripts']) { + $newData['scripts'] = array_map( + static function ($scriptData) use ($scriptUrlParamInputs) { + if (isset($scriptData['url'])) { + $scriptData['url'] = self::formatUrl( + $scriptData['url'], + $scriptData['params'], + $scriptUrlParamInputs + ); + } else { + $scriptData['code'] = self::formatCode( + $scriptData['code'], + $scriptUrlParamInputs + ); + } + unset($scriptData['params']); // Params are irrelevant for formatted output. + return $scriptData; + }, + $newData['scripts'] + ); + } + + return new ThirdPartyOutput($newData); + } + + /** + * Formats the given HTML arguments into an HTML string. + * + * @see https://github.com/GoogleChromeLabs/third-party-capital/blob/0831b937a8468e0f74bd79edd5a59fa8b2e6e763/src/utils/index.ts#L55 + * + * @param string $element Element tag name for the HTML element. + * @param array $attributes Attributes for the HTML element. + * @param array $htmlAttrArgs Input arguments for the HTML element attributes. + * @param array $urlQueryParamArgs Input arguments for the src attribute query parameters. + * @param array $slugParamArg Optional. Input argument for the src attribute slug query parameter. + * Default empty array. + * @return string HTML string. + */ + public static function formatHtml( + string $element, + array $attributes, + array $htmlAttrArgs, + array $urlQueryParamArgs, + array $slugParamArg = [] + ): string { + if (! $attributes) { + return "<{$element}>"; + } + + if (isset($attributes['src']['url'])) { + $attributes['src'] = self::formatUrl( + $attributes['src']['url'], + $attributes['src']['params'] ?? [], + $urlQueryParamArgs, + $slugParamArg + ); + } + + // Overwrite default attributes with arguments as needed. + foreach ($htmlAttrArgs as $name => $value) { + $attributes[ $name ] = $value; + } + + $htmlAttributes = new HtmlAttributes($attributes); + return "<{$element}{$htmlAttributes}>"; + } + + /** + * Formats the given URL arguments into a URL string. + * + * @see https://github.com/GoogleChromeLabs/third-party-capital/blob/0831b937a8468e0f74bd79edd5a59fa8b2e6e763/src/utils/index.ts#L28 + * + * @param string $url Base URL. + * @param string[] $params Parameter names. + * @param array $args Input arguments for the src attribute query parameters. + * @param array $slugParamArg Optional. Input argument for the src attribute slug query parameter. + * Default empty array. + * @return string HTML string. + */ + public static function formatUrl(string $url, array $params, array $args, array $slugParamArg = []): string + { + if ($slugParamArg) { + $slug = array_values($slugParamArg)[0]; + + $path = parse_url($url, PHP_URL_PATH); + if ($path) { + $trailingSlash = str_ends_with($path, '/') ? '/' : ''; + $url = str_replace( + $path, + substr($path, 0, - strlen(basename($path) . $trailingSlash)) . $slug . $trailingSlash, + $url + ); + } else { + $url = rtrim($url, '/') . '/' . $slug; + } + } + + if ($params && $args) { + $queryArgs = self::intersectArgs($args, $params); + if ($queryArgs) { + $url = self::setUrlQueryArgs($url, $queryArgs); + } + } + + return $url; + } + + /** + * Formats the given code arguments into a code string. + * + * @see https://github.com/GoogleChromeLabs/third-party-capital/blob/0831b937a8468e0f74bd79edd5a59fa8b2e6e763/src/utils/index.ts#L48 + * + * @param string $code Code string with placeholders for URL query parameters. + * @param array $args Input arguments for the src attribute query parameters. + * @return string HTML string. + */ + public static function formatCode(string $code, array $args): string + { + return preg_replace_callback( + '/{{([^}]+)}}/', + static function ($matches) use ($args) { + if (isset($args[ $matches[1] ])) { + return $args[ $matches[1] ]; + } + return ''; + }, + $code + ); + } + + /** + * Returns the subset of the given $args that refers to parameter within the given $params. + * + * @param array $args Input arguments. + * @param string[] $params Parameter names. + * @return array Intersection of $args based on $params. + */ + private static function intersectArgs(array $args, array $params): array + { + return array_intersect_key($args, array_flip($params)); + } + + /** + * Returns the subset of the given $args that refers to parameter not within the given $params. + * + * @param array $args Input arguments. + * @param string[] $params Parameter names. + * @return array Diff of $args based on $params. + */ + private static function diffArgs(array $args, array $params): array + { + return array_diff_key($args, array_flip($params)); + } + + /** + * Sets the given query $args on the given URL. + * + * @param string $url URL. + * @param array $args Input arguments for the URL query string. + * @return string URL including query arguments. + */ + private static function setUrlQueryArgs(string $url, array $args): string + { + if (! $args) { + return $url; + } + + $frag = strstr($url, '#'); + if ($frag) { + $url = substr($url, 0, -strlen($frag)); + } else { + $frag = ''; + } + + if (str_contains($url, '?')) { + list( $url, $query ) = explode('?', $url, 2); + $url .= '?'; + } else { + $url .= '?'; + $query = ''; + } + + parse_str($query, $qs); + $qs = self::urlencodeRecursive($qs); + foreach ($args as $key => $value) { + $qs[ $key ] = $value; + } + + $query = http_build_query($qs); + + return ( $query ? $url . $query : rtrim($url, '?') ) . $frag; + } + + /** + * URL-encodes a value or a potentially nested array structure. + * + * @param mixed $value Scalar value or array to URL-encode. + * @return mixed URL-encoded result. + */ + private static function urlencodeRecursive($value) + { + if (is_array($value)) { + foreach ($value as $index => $item) { + $value[ $index ] = self::urlencodeRecursive($item); + } + return $value; + } + + return urlencode($value); + } +} diff --git a/inc/Data/ThirdPartyHtmlAttributes.php b/inc/Data/ThirdPartyHtmlAttributes.php new file mode 100644 index 0000000..4722934 --- /dev/null +++ b/inc/Data/ThirdPartyHtmlAttributes.php @@ -0,0 +1,51 @@ +getUrl() . '"'; + } + + return parent::toAttrString($name, $value); + } +} diff --git a/inc/Data/ThirdPartyHtmlData.php b/inc/Data/ThirdPartyHtmlData.php new file mode 100644 index 0000000..eccc116 --- /dev/null +++ b/inc/Data/ThirdPartyHtmlData.php @@ -0,0 +1,111 @@ +validateData($htmlData); + $this->setData($htmlData); + } + + /** + * Gets the element tag name for the HTML element. + * + * @return string Element tag name for the HTML element. + */ + public function getElement(): string + { + return $this->element; + } + + /** + * Gets the attributes for the HTML element. + * + * @return ThirdPartyHtmlAttributes Attributes for the HTML element. + */ + public function getAttributes(): ThirdPartyHtmlAttributes + { + return $this->attributes; + } + + /** + * Validates the given HTML data. + * + * @param array $htmlData HTML data, e.g. from a third party JSON file. + * + * @throws InvalidThirdPartyDataException Thrown when provided HTML data is invalid. + */ + private function validateData(array $htmlData) + { + if (!isset($htmlData['element'])) { + throw new InvalidThirdPartyDataException('Missing HTML element.'); + } + if (!isset($htmlData['attributes'])) { + throw new InvalidThirdPartyDataException('Missing HTML attributes.'); + } + if (!isset($htmlData['attributes']['src'])) { + throw new InvalidThirdPartyDataException('Missing HTML src attribute.'); + } + } + + /** + * Sets the given HTML data. + * + * @param array $htmlData HTML data, e.g. from a third party JSON file. + */ + private function setData(array $htmlData) + { + $this->element = (string) $htmlData['element']; + $this->attributes = new ThirdPartyHtmlAttributes($htmlData['attributes']); + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + return [ + 'element' => $this->element, + 'attributes' => $this->attributes->toArray(), + ]; + } +} diff --git a/inc/Data/ThirdPartyOutput.php b/inc/Data/ThirdPartyOutput.php new file mode 100644 index 0000000..554f8fe --- /dev/null +++ b/inc/Data/ThirdPartyOutput.php @@ -0,0 +1,173 @@ +$field = isset($data[ $field ]) ? (string) $data[ $field ] : ''; + } + + $to3pScript = static function ($scriptData) { + return new ThirdPartyScriptOutput($scriptData); + }; + + $this->stylesheets = isset($data['stylesheets']) ? array_map('strval', $data['stylesheets']) : []; + $this->scripts = isset($data['scripts']) ? array_map($to3pScript, $data['scripts']) : []; + } + + /** + * Gets the third party identifier. + * + * @return string Third party identifier. + */ + public function getId(): string + { + return $this->id; + } + + /** + * Gets the third party description. + * + * @return string Third party description. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Gets the third party website. + * + * @return string Third party website, if provided. + */ + public function getWebsite(): string + { + return $this->website; + } + + /** + * Gets the HTML needed for the third party. + * + * @return string HTML needed for the third party. + */ + public function getHtml(): string + { + return $this->html; + } + + /** + * Gets the stylesheets needed for the third party. + * + * @return string[] Stylesheets needed for the third party. + */ + public function getStylesheets(): array + { + return $this->stylesheets; + } + + /** + * Gets the scripts needed for the third party. + * + * @return ThirdPartyScriptOutput[] Scripts needed for the third party. + */ + public function getScripts(): array + { + return $this->scripts; + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + $data = [ + 'id' => $this->id, + 'description' => $this->description, + ]; + if ($this->website) { + $data['website'] = $this->website; + } + if ($this->html) { + $data['html'] = $this->html; + } + if ($this->stylesheets) { + $data['stylesheets'] = $this->stylesheets; + } + if ($this->scripts) { + $data['scripts'] = array_map( + static function (ThirdPartyScriptOutput $scriptData) { + return $scriptData->toArray(); + }, + $this->scripts + ); + } + + return $data; + } +} diff --git a/inc/Data/ThirdPartyScriptData.php b/inc/Data/ThirdPartyScriptData.php new file mode 100644 index 0000000..2e1f994 --- /dev/null +++ b/inc/Data/ThirdPartyScriptData.php @@ -0,0 +1,283 @@ +validateData($scriptData); + $this->setData($scriptData); + } + + /** + * Gets the strategy for including the script. + * + * @return string Strategy for including the script. + */ + public function getStrategy(): string + { + return $this->strategy; + } + + /** + * Gets the location where to include the script. + * + * @return string Location where to include the script. + */ + public function getLocation(): string + { + return $this->location; + } + + /** + * Gets the action how to include the script. + * + * @return string Action how to include the script. + */ + public function getAction(): string + { + return $this->action; + } + + /** + * Gets the script URL, if an external script. + * + * @return string Script URL, if an external script. + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * Gets the script code, if an inline script. + * + * @return string Script code, if an inline script. + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Gets the script key, if provided. + * + * @return string Script key, if provided. + */ + public function getKey(): string + { + return $this->key; + } + + /** + * Gets the list of parameters for the script, if needed. + * + * @return string[] List of parameters for the script, if needed. + */ + public function getParams(): array + { + return $this->params; + } + + /** + * Determines whether the script is an external script. + * + * @return bool True if an external script, false if an inline script. + */ + public function isExternal(): bool + { + return '' !== $this->url; + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + $data = [ + 'strategy' => $this->strategy, + 'location' => $this->location, + 'action' => $this->action, + ]; + if ($this->url) { + $data['url'] = $this->url; + } else { + $data['code'] = $this->code; + } + if ($this->key) { + $data['key'] = $this->key; + } + if ($this->params) { + $data['params'] = $this->params; + } + + return $data; + } + + /** + * Validates the given script data. + * + * @param array $scriptData Script data, e.g. from a third party JSON file. + * + * @throws InvalidThirdPartyDataException Thrown when provided script data is invalid. + */ + private function validateData(array $scriptData) + { + $enumFields = ['strategy', 'location', 'action']; + foreach ($enumFields as $enumField) { + if (!isset($scriptData[ $enumField ])) { + throw new InvalidThirdPartyDataException( + sprintf('Missing script %s.', $enumField) + ); + } + $methodName = 'isValid' . ucfirst($enumField); + if (! call_user_func([$this, $methodName], $scriptData[ $enumField ])) { + throw new InvalidThirdPartyDataException( + sprintf('Invalid script %s.', $enumField) + ); + } + } + + if (!isset($scriptData['url']) && !isset($scriptData['code'])) { + throw new InvalidThirdPartyDataException( + 'Missing both script URL and script code, one of which must be provided.' + ); + } + if (isset($scriptData['url']) && isset($scriptData['code'])) { + throw new InvalidThirdPartyDataException('Only one of script URL or script code must be provided.'); + } + } + + /** + * Checks whether the given strategy is valid. + * + * @param string $strategy Strategy to validate. + * @return bool True if strategy is valid, false otherwise. + */ + private function isValidStrategy(string $strategy): bool + { + return self::STRATEGY_SERVER === $strategy + || self::STRATEGY_CLIENT === $strategy + || self::STRATEGY_IDLE === $strategy + || self::STRATEGY_WORKER === $strategy; + } + + /** + * Checks whether the given location is valid. + * + * @param string $location Location to validate. + * @return bool True if location is valid, false otherwise. + */ + private function isValidLocation(string $location): bool + { + return self::LOCATION_HEAD === $location || self::LOCATION_BODY === $location; + } + + /** + * Checks whether the given action is valid. + * + * @param string $action Action to validate. + * @return bool True if action is valid, false otherwise. + */ + private function isValidAction(string $action): bool + { + return self::ACTION_APPEND === $action || self::ACTION_PREPEND === $action; + } + + /** + * Sets the given script data. + * + * @param array $scriptData Script data, e.g. from a third party JSON file. + */ + private function setData(array $scriptData) + { + $strFields = ['strategy', 'location', 'action', 'url', 'code', 'key']; + foreach ($strFields as $field) { + $this->$field = isset($scriptData[ $field ]) ? (string) $scriptData[ $field ] : ''; + } + + $this->params = isset($scriptData['params']) ? array_map('strval', $scriptData['params']) : []; + } +} diff --git a/inc/Data/ThirdPartyScriptOutput.php b/inc/Data/ThirdPartyScriptOutput.php new file mode 100644 index 0000000..223d0f7 --- /dev/null +++ b/inc/Data/ThirdPartyScriptOutput.php @@ -0,0 +1,168 @@ +$field = isset($scriptData[ $field ]) ? (string) $scriptData[ $field ] : ''; + } + } + + /** + * Gets the strategy for including the script. + * + * @return string Strategy for including the script. + */ + public function getStrategy(): string + { + return $this->strategy; + } + + /** + * Gets the location where to include the script. + * + * @return string Location where to include the script. + */ + public function getLocation(): string + { + return $this->location; + } + + /** + * Gets the action how to include the script. + * + * @return string Action how to include the script. + */ + public function getAction(): string + { + return $this->action; + } + + /** + * Gets the script URL, if an external script. + * + * @return string Script URL, if an external script. + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * Gets the script code, if an inline script. + * + * @return string Script code, if an inline script. + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Gets the script key, if provided. + * + * @return string Script key, if provided. + */ + public function getKey(): string + { + return $this->key; + } + + /** + * Determines whether the script is an external script. + * + * @return bool True if an external script, false if an inline script. + */ + public function isExternal(): bool + { + return '' !== $this->url; + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + $data = [ + 'strategy' => $this->strategy, + 'location' => $this->location, + 'action' => $this->action, + ]; + if ($this->url) { + $data['url'] = $this->url; + } else { + $data['code'] = $this->code; + } + if ($this->key) { + $data['key'] = $this->key; + } + + return $data; + } +} diff --git a/inc/Data/ThirdPartySrcValue.php b/inc/Data/ThirdPartySrcValue.php new file mode 100644 index 0000000..297d118 --- /dev/null +++ b/inc/Data/ThirdPartySrcValue.php @@ -0,0 +1,106 @@ +url = $srcData['url']; + $this->slugParam = isset($srcData['slugParam']) ? (string) $srcData['slugParam'] : ''; + $this->params = isset($srcData['params']) ? array_map('strval', $srcData['params']) : []; + } + + /** + * Gets the URL for the src value. + * + * @return string URL for the src value. + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * Gets the slug param for the src value, if needed. + * + * @return string Slug param for the src value, if needed. + */ + public function getSlugParam(): string + { + return $this->slugParam; + } + + /** + * Gets the list of parameters for the src value, if needed. + * + * @return string[] List of parameters for the src value, if needed. + */ + public function getParams(): array + { + return $this->params; + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + $data = ['url' => $this->url]; + if ($this->slugParam) { + $data['slugParam'] = $this->slugParam; + } + if ($this->params) { + $data['params'] = $this->params; + } + return $data; + } +} diff --git a/inc/Exception/InvalidThirdPartyDataException.php b/inc/Exception/InvalidThirdPartyDataException.php new file mode 100644 index 0000000..e03d51f --- /dev/null +++ b/inc/Exception/InvalidThirdPartyDataException.php @@ -0,0 +1,20 @@ +jsonFilePath = $this->getJsonFilePath(); + + $this->setArgs($args); + } + + /** + * Gets the third party identifier. + * + * @return string Third party identifier. + */ + public function getId(): string + { + $this->lazilyInitialize(); + + return $this->output->getId(); + } + + /** + * Sets input arguments for the integration. + * + * @param array $args Input arguments to set. + */ + public function setArgs(array $args) + { + $this->args = $args; + + // Reset third party output. + $this->output = null; + } + + /** + * Gets the HTML output for the integration. + * + * Only relevant if the integration provides user-facing output. + * + * @return string HTML output, or empty string if not applicable. + */ + public function getHtml(): string + { + $this->lazilyInitialize(); + + return $this->output->getHtml(); + } + + /** + * Gets the stylesheet URLs for the integration. + * + * Only relevant if the integration provides stylesheets to use. + * + * @return string[] List of stylesheet URLs, or empty array if not applicable. + */ + public function getStylesheets(): array + { + $this->lazilyInitialize(); + + return $this->output->getStylesheets(); + } + + /** + * Gets the script definitions for the integration. + * + * Only relevant if the integration provides scripts to use. + * + * @return ThirdPartyScriptOutput[] List of script definition objects, or empty array if not applicable. + */ + public function getScripts(): array + { + $this->lazilyInitialize(); + + return $this->output->getScripts(); + } + + /** + * Gets the path to the third party data JSON file. + * + * @return string Absolute path to the JSON file. + */ + abstract protected function getJsonFilePath(): string; + + /** + * Lazily initializes the data and output instances, only if they aren't initialized yet. + * + * The data instance is only initialized once as it is agnostic to the input arguments. + * The output instance needs to be reinitialized whenever the input arguments change. + */ + private function lazilyInitialize() + { + if (! $this->data) { + $this->data = ThirdPartyData::fromJsonFile($this->jsonFilePath); + } + + if (! $this->output) { + $this->output = ThirdPartyDataFormatter::formatData($this->data, $this->args); + } + } +} diff --git a/inc/ThirdParties/YouTubeEmbed.php b/inc/ThirdParties/YouTubeEmbed.php new file mode 100644 index 0000000..8acefc7 --- /dev/null +++ b/inc/ThirdParties/YouTubeEmbed.php @@ -0,0 +1,29 @@ + $value) { + $this->attr[ $name ] = $this->sanitizeAttr($name, $value); + } + } + + /** + * Checks if the given attribute is set. + * + * @since n.e.x.t + * + * @param string $name Attribute name. + * @return bool True if the attribute is set, false otherwise. + */ + #[\ReturnTypeWillChange] + public function offsetExists($name) + { + return isset($this->attr[ $name ]); + } + + /** + * Gets the value for the given attribute. + * + * @since n.e.x.t + * + * @param string $name Attribute name. + * @return mixed Value for the given attribute. + * + * @throws NotFoundException Thrown if the attribute is not set. + */ + #[\ReturnTypeWillChange] + public function offsetGet($name) + { + if (!isset($this->attr[ $name ])) { + throw new NotFoundException( + sprintf( + 'Attribute with name %s not set.', + $name + ) + ); + } + + return $this->attr[ $name ]; + } + + /** + * Sets the given value for the given attribute. + * + * @since n.e.x.t + * + * @param string $name Attribute name. + * @param mixed $value Attribute value. + */ + #[\ReturnTypeWillChange] + public function offsetSet($name, $value) + { + // Not implemented, as the attributes are read-only. + } + + /** + * Unsets the value for the given attribute. + * + * @since n.e.x.t + * + * @param string $name Attribute name. + */ + #[\ReturnTypeWillChange] + public function offsetUnset($name) + { + // Not implemented, as the attributes are read-only. + } + + /** + * Returns an iterator for the attributes. + * + * @since n.e.x.t + * + * @return Traversable Attributes iterator. + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->attr); + } + + /** + * Returns an array representation of the data. + * + * @return array Associative array of data. + */ + public function toArray(): array + { + return array_map( + static function ($value) { + if ($value instanceof Arrayable) { + return $value->toArray(); + } + return $value; + }, + $this->attr + ); + } + + /** + * Returns the HTML string of attributes to append to the HTML element tag name. + * + * @return string HTML attributes string. + */ + public function __toString(): string + { + $output = ''; + foreach ($this->attr as $name => $value) { + $output .= $this->toAttrString($name, $value); + } + return $output; + } + + /** + * Returns the sanitized attribute value for the given attribute name and value. + * + * @param string $name Attribute name. + * @param mixed $value Attribute value. + * @return mixed Sanitized attribute value. + */ + protected function sanitizeAttr(string $name, $value) + { + if (is_bool($value)) { + return $value; + } + return (string) $value; + } + + /** + * Returns the attribute string for the given attribute name and value. + * + * @param string $name Attribute name. + * @param mixed $value Attribute value. + * @return string HTML attribute string (starts with a space), or empty string to skip. + */ + protected function toAttrString(string $name, $value): string + { + if (is_bool($value)) { + return $value ? ' ' . $name : ''; + } + + return ' ' . $name . '="' . $value . '"'; + } +} diff --git a/inc/Util/ThirdPartiesDir.php b/inc/Util/ThirdPartiesDir.php new file mode 100644 index 0000000..20e306e --- /dev/null +++ b/inc/Util/ThirdPartiesDir.php @@ -0,0 +1,30 @@ + + + PHPCS rules for Third Party Capital. + + + + + + + + + + ./inc + ./tests/phpunit + \ No newline at end of file diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..bf23549 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */node_modules/* + */tests/* + */vendor/* + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..2090fef --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,5 @@ +parameters: + level: 5 + paths: + - inc/ + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..0682a00 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + inc + + + + + tests/phpunit/tests + + + \ No newline at end of file diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php new file mode 100644 index 0000000..6467fce --- /dev/null +++ b/tests/phpunit/bootstrap.php @@ -0,0 +1,14 @@ +assertSame($expected, $output->toArray()); + } + + public function dataFormatData() + { + return [ + 'minimum fields' => [ + [ + 'id' => 'a-useless-service', + 'description' => 'This service cannot do anything. Nobody would do that in production.', + ], + [], + [ + 'id' => 'a-useless-service', + 'description' => 'This service cannot do anything. Nobody would do that in production.', + ], + ], + 'basic example' => [ + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something.', + 'website' => 'https://my-service.com/', + 'html' => [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => 'https://example.com/my-video/', + 'width' => '1920', + 'height' => '1080', + ], + ], + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ], + [], + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something.', + 'website' => 'https://my-service.com/', + 'html' => '', + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ], + ], + 'with HTML params' => [ + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something dynamic.', + 'website' => 'https://my-service.com/', + 'html' => [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => [ + 'url' => 'https://example.com/my-video/', + 'params' => ['v'], + ], + 'width' => '1920', + 'height' => '1080', + ], + ], + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ], + [ + 'v' => '12345', + 'loading' => 'lazy', + ], + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something dynamic.', + 'website' => 'https://my-service.com/', + 'html' => '', + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ], + ], + 'with HTML slug param' => [ + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something with a slug param.', + 'website' => 'https://my-service.com/', + 'html' => [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => [ + 'url' => 'https://example.com/design-pattern/blue/', + 'slugParam' => 'color', + 'params' => ['id'], + ], + 'width' => '1920', + 'height' => '1080', + ], + ], + 'stylesheets' => ['https://example.com/style.css'], + ], + [ + 'id' => '481', + 'color' => 'green', + 'loading' => 'lazy', + 'allowfullscreen' => false, + ], + [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something with a slug param.', + 'website' => 'https://my-service.com/', + 'html' => '', + 'stylesheets' => ['https://example.com/style.css'], + ], + ], + 'with script params' => [ + [ + 'id' => 'my-service', + 'description' => 'A service that allows loading an Analytics script.', + 'website' => 'https://my-service.com/', + 'scripts' => [ + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_WORKER, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'url' => 'https://example.com/analytics/', + 'key' => 'my-analytics', + 'params' => ['id', 'anonymizeIP', 'enhancedAttribution'], + ], + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_WORKER, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'code' => 'exampleAnalytics.init()', + ], + ], + ], + [ + 'id' => '987123', + 'anonymizeIP' => 1, + ], + [ + 'id' => 'my-service', + 'description' => 'A service that allows loading an Analytics script.', + 'website' => 'https://my-service.com/', + 'scripts' => [ + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_WORKER, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'url' => 'https://example.com/analytics/?id=987123&anonymizeIP=1', + 'key' => 'my-analytics', + ], + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_WORKER, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'code' => 'exampleAnalytics.init()', + ], + ], + ], + ], + ]; + } + + public function testFormatHtmlWithoutAttributes() + { + $html = ThirdPartyDataFormatter::formatHtml('video', [], [], []); + $this->assertSame('', $html); + } + + public function testFormatHtmlWithAttributes() + { + $html = ThirdPartyDataFormatter::formatHtml( + 'video', + [ + 'src' => 'https://example.com/custom-video.mpg', + 'width' => '1024', + 'height' => '768', + 'controls' => true, + 'muted' => false, + ], + [], + [] + ); + $this->assertSame( + '', + $html + ); + } + + public function testFormatHtmlWithAttributesIncludingSrcValue() + { + $html = ThirdPartyDataFormatter::formatHtml( + 'iframe', + [ + 'src' => [ + 'url' => 'https://logo-service.com/embed/unicolor', + 'slugParam' => 'kind', + 'params' => ['id', 'style', 'lang'] + ], + 'width' => '1920', + 'height' => '1080', + ], + [], + [ + 'id' => '987654', + 'lang' => 'de', + ], + ['kind' => 'duotone'] + ); + $this->assertSame( + '', + $html + ); + } + + public function testFormatHtmlWithAttributesIncludingSrcValueAndAttributeArgs() + { + $html = ThirdPartyDataFormatter::formatHtml( + 'iframe', + [ + 'src' => [ + 'url' => 'https://example.com/embed/', + 'params' => ['id'] + ], + 'width' => '1920', + 'height' => '1080', + ], + [ + 'loading' => 'lazy', + ], + [ + 'id' => '23', + ] + ); + $this->assertSame( + '', + $html + ); + } + + public function testFormatUrlWithNoParamsOrArgs() + { + $url = ThirdPartyDataFormatter::formatUrl('https://example.com/embed/', [], []); + $this->assertSame('https://example.com/embed/', $url); + } + + public function testFormatUrlWithParamsButNoArgs() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com/embed/', + ['id', 'lang'], + [] + ); + $this->assertSame('https://example.com/embed/', $url); + } + + public function testFormatUrlWithParamsAndArgs() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com/embed/', + ['id', 'direction', 'lang', 'style'], + [ + 'id' => '8642', + 'lang' => 'es', + ] + ); + $this->assertSame('https://example.com/embed/?id=8642&lang=es', $url); + } + + public function testFormatUrlWithSlugParamAndPathWithoutTrailingSlash() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com/embed/static', + ['id'], + [], + ['mode' => 'interactive'] + ); + $this->assertSame('https://example.com/embed/interactive', $url); + } + + public function testFormatUrlWithSlugParamAndPathWithTrailingSlash() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com/embed/static/', + ['id'], + [], + ['mode' => 'interactive'] + ); + $this->assertSame('https://example.com/embed/interactive/', $url); + } + + public function testFormatUrlWithSlugParamAndNoPath() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com', + ['id'], + [], + ['mode' => 'interactive'] + ); + $this->assertSame('https://example.com/interactive', $url); + } + + public function testFormatUrlWithQueryAndSlugParamAndParamsAndArgs() + { + $url = ThirdPartyDataFormatter::formatUrl( + 'https://example.com/embed/static?forcedParam=value', + ['id'], + ['id' => '12345'], + ['mode' => 'interactive'] + ); + $this->assertSame('https://example.com/embed/interactive?forcedParam=value&id=12345', $url); + } + + public function testFormatCodeWithoutArgs() + { + $code = ThirdPartyDataFormatter::formatCode( + 'document.querySelector("{{selector}}").addEventListener(api.{{callback}});', + [] + ); + $this->assertSame( + 'document.querySelector("").addEventListener(api.);', + $code + ); + } + + public function testFormatCodeWithArgs() + { + $code = ThirdPartyDataFormatter::formatCode( + 'document.querySelector("{{selector}}").addEventListener(api.{{callback}});', + [ + 'selector' => '.my-cta-button', + 'callback' => 'addToCart', + ] + ); + $this->assertSame( + 'document.querySelector(".my-cta-button").addEventListener(api.addToCart);', + $code + ); + } + + public function testFormatCodeWithArgsIncorrectOrderAndTooMany() + { + $code = ThirdPartyDataFormatter::formatCode( + 'document.querySelector("{{selector}}").addEventListener(api.{{callback}});', + [ + 'callback' => 'addToCart', + 'device' => 'phone', + 'selector' => '.my-cta-button', + ] + ); + $this->assertSame( + 'document.querySelector(".my-cta-button").addEventListener(api.addToCart);', + $code + ); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyDataTest.php b/tests/phpunit/tests/Data/ThirdPartyDataTest.php new file mode 100644 index 0000000..ef35dbe --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyDataTest.php @@ -0,0 +1,120 @@ +runGetterTestCase(ThirdPartyData::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'id', + 'getter' => 'getId', + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'description', + 'getter' => 'getDescription', + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'website', + 'getter' => 'getWebsite', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'html', + 'getter' => 'getHtml', + 'value' => [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => [ + 'url' => 'https://example.com/my-video/', + 'params' => ['v'], + ], + 'width' => '1920', + 'height' => '1080', + ], + ], + 'default' => null, + 'required' => false, + ], + [ + 'field' => 'stylesheets', + 'getter' => 'getStylesheets', + 'value' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + 'default' => [], + 'required' => false, + ], + [ + 'field' => 'scripts', + 'getter' => 'getScripts', + 'value' => [ + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'url' => 'https://example.com/', + 'key' => 'my-analytics', + 'params' => ['id'], + ], + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'code' => 'window.dataLayer=window.dataLayer', + ], + ], + 'default' => [], + 'required' => false, + ], + ], InvalidThirdPartyDataException::class); + } + + public function testToArray() + { + $input = [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something.', + 'website' => 'https://my-service.com/', + 'html' => [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => [ + 'url' => 'https://example.com/my-video/', + 'params' => ['v'], + ], + 'width' => '1920', + 'height' => '1080', + ], + ], + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ]; + $data = new ThirdPartyData($input); + $this->assertSame($input, $data->toArray()); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyHtmlAttributesTest.php b/tests/phpunit/tests/Data/ThirdPartyHtmlAttributesTest.php new file mode 100644 index 0000000..fdb5ee3 --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyHtmlAttributesTest.php @@ -0,0 +1,47 @@ + [ + 'url' => 'https://embed-test.com', + 'params' => ['id'], + ], + ]); + $this->assertInstanceOf(ThirdPartySrcValue::class, $attrs['src']); + $this->assertSame('https://embed-test.com', $attrs['src']->getUrl()); + $this->assertSame(['id'], $attrs['src']->getParams()); + } + + public function testToStringWithSrcValue() + { + $attrs = new ThirdPartyHtmlAttributes([ + 'id' => 'test-unique-id', + 'src' => [ + 'url' => 'https://embed-test.com', + 'params' => ['id'], + ], + 'defer' => true, + ]); + $this->assertSame( + ' id="test-unique-id" src="https://embed-test.com" defer', + (string) $attrs + ); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyHtmlDataTest.php b/tests/phpunit/tests/Data/ThirdPartyHtmlDataTest.php new file mode 100644 index 0000000..71b401d --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyHtmlDataTest.php @@ -0,0 +1,62 @@ +runGetterTestCase(ThirdPartyHtmlData::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'element', + 'getter' => 'getElement', + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'attributes', + 'getter' => 'getAttributes', + 'value' => ['src' => 'https://example.com'], + 'default' => [], + 'required' => true, + ], + ], InvalidThirdPartyDataException::class); + } + + public function testToArray() + { + $input = [ + 'element' => 'iframe', + 'attributes' => [ + 'src' => [ + 'url' => 'https://example.com/my-video/', + 'params' => ['v'], + ], + 'width' => '1920', + 'height' => '1080', + ], + ]; + $htmlData = new ThirdPartyHtmlData($input); + $this->assertSame($input, $htmlData->toArray()); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyOutputTest.php b/tests/phpunit/tests/Data/ThirdPartyOutputTest.php new file mode 100644 index 0000000..2d73cb9 --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyOutputTest.php @@ -0,0 +1,97 @@ +runGetterTestCase(ThirdPartyOutput::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'id', + 'getter' => 'getId', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'description', + 'getter' => 'getDescription', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'website', + 'getter' => 'getWebsite', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'html', + 'getter' => 'getHtml', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'stylesheets', + 'getter' => 'getStylesheets', + 'value' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + 'default' => [], + 'required' => false, + ], + [ + 'field' => 'scripts', + 'getter' => 'getScripts', + 'value' => [ + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'url' => 'https://example.com/?id=12345789', + 'key' => 'my-analytics', + ], + [ + 'strategy' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'code' => 'window.dataLayer=window.dataLayer', + ], + ], + 'default' => [], + 'required' => false, + ], + ]); + } + + public function testToArray() + { + $input = [ + 'id' => 'my-service', + 'description' => 'A service that allows embedding something.', + 'website' => 'https://my-service.com/', + 'html' => '', + 'stylesheets' => ['https://example.com/style.css', 'https://example.com/style-2.css'], + ]; + $output = new ThirdPartyOutput($input); + $this->assertSame($input, $output->toArray()); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyScriptDataTest.php b/tests/phpunit/tests/Data/ThirdPartyScriptDataTest.php new file mode 100644 index 0000000..bd6ee02 --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyScriptDataTest.php @@ -0,0 +1,151 @@ + ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + ]; + + /** + * @dataProvider dataGetMethods + */ + public function testGetMethods(string $getMethod, array $args, $expected) + { + $this->runGetterTestCase(ThirdPartyScriptData::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'strategy', + 'getter' => 'getStrategy', + 'value' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'location', + 'getter' => 'getLocation', + 'value' => ThirdPartyScriptData::LOCATION_HEAD, + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'action', + 'getter' => 'getAction', + 'value' => ThirdPartyScriptData::ACTION_APPEND, + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'url', + 'getter' => 'getUrl', + 'default' => '', + 'required' => true, + ], // Don't cover 'code' here as it can only be provided if 'url' is not provided. See separate test below. + [ + 'field' => 'key', + 'getter' => 'getKey', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'params', + 'getter' => 'getParams', + 'default' => [], + 'required' => false, + ], + ], InvalidThirdPartyDataException::class); + } + + public function testConstructorWithUrlAndNoCode() + { + $data = ['url' => 'https://example.com/']; + $scriptData = new ThirdPartyScriptData(array_merge($this->baseData, $data)); + + $this->assertSame($data['url'], $scriptData->getUrl()); + } + + public function testConstructorWithCodeAndNoUrl() + { + $data = ['code' => 'window.dataLayer=window.dataLayer']; + $scriptData = new ThirdPartyScriptData(array_merge($this->baseData, $data)); + + $this->assertSame($data['code'], $scriptData->getCode()); + } + + public function testConstructorWithNoUrlAndNoCode() + { + $this->expectException(InvalidThirdPartyDataException::class); + + $scriptData = new ThirdPartyScriptData($this->baseData); + } + + public function testConstructorWithUrlAndCode() + { + $this->expectException(InvalidThirdPartyDataException::class); + + $data = [ + 'url' => 'https://example.com/', + 'code' => 'window.dataLayer=window.dataLayer', + ]; + $scriptData = new ThirdPartyScriptData(array_merge($this->baseData, $data)); + } + + /** + * @dataProvider dataIsExternal + */ + public function testIsExternal(array $data, bool $expected) + { + $scriptData = new ThirdPartyScriptData(array_merge($this->baseData, $data)); + if ($expected) { + $this->assertTrue($scriptData->isExternal()); + } else { + $this->assertFalse($scriptData->isExternal()); + } + } + + public function dataIsExternal() + { + return [ + 'with URL' => [ + ['url' => 'https://example.com/'], + true, + ], + 'with code' => [ + ['code' => 'window.dataLayer=window.dataLayer'], + false, + ], + ]; + } + + public function testToArray() + { + $input = array_merge( + $this->baseData, + [ + 'url' => 'https://example.com/', + 'key' => 'my-analytics', + 'params' => ['id'], + ] + ); + $scriptData = new ThirdPartyScriptData($input); + $this->assertSame($input, $scriptData->toArray()); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartyScriptOutputTest.php b/tests/phpunit/tests/Data/ThirdPartyScriptOutputTest.php new file mode 100644 index 0000000..330f2b7 --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartyScriptOutputTest.php @@ -0,0 +1,81 @@ +runGetterTestCase(ThirdPartyScriptOutput::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'strategy', + 'getter' => 'getStrategy', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'location', + 'getter' => 'getLocation', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'action', + 'getter' => 'getAction', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'url', + 'getter' => 'getUrl', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'code', + 'getter' => 'getCode', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'key', + 'getter' => 'getKey', + 'default' => '', + 'required' => false, + ], + ]); + } + + public function testToArray() + { + $input = [ + 'strategy' => ThirdPartyScriptData::STRATEGY_CLIENT, + 'location' => ThirdPartyScriptData::LOCATION_HEAD, + 'action' => ThirdPartyScriptData::ACTION_APPEND, + 'url' => 'https://example.com/', + 'key' => 'my-analytics', + ]; + $scriptOutput = new ThirdPartyScriptOutput($input); + $this->assertSame($input, $scriptOutput->toArray()); + } +} diff --git a/tests/phpunit/tests/Data/ThirdPartySrcValueTest.php b/tests/phpunit/tests/Data/ThirdPartySrcValueTest.php new file mode 100644 index 0000000..7f8b5d3 --- /dev/null +++ b/tests/phpunit/tests/Data/ThirdPartySrcValueTest.php @@ -0,0 +1,62 @@ +runGetterTestCase(ThirdPartySrcValue::class, $getMethod, $args, $expected); + } + + public function dataGetMethods() + { + return $this->gettersToTestCases([ + [ + 'field' => 'url', + 'getter' => 'getUrl', + 'default' => '', + 'required' => true, + ], + [ + 'field' => 'slugParam', + 'getter' => 'getSlugParam', + 'default' => '', + 'required' => false, + ], + [ + 'field' => 'params', + 'getter' => 'getParams', + 'default' => [], + 'required' => false, + ], + ], InvalidThirdPartyDataException::class); + } + + public function testToArray() + { + $input = [ + 'url' => 'https://my-embed.com', + 'slugParam' => 'type', + 'params' => ['id', 'mode'], + ]; + $srcValue = new ThirdPartySrcValue($input); + $this->assertSame($input, $srcValue->toArray()); + } +} diff --git a/tests/phpunit/tests/Util/HtmlAttributesTest.php b/tests/phpunit/tests/Util/HtmlAttributesTest.php new file mode 100644 index 0000000..54cc5cf --- /dev/null +++ b/tests/phpunit/tests/Util/HtmlAttributesTest.php @@ -0,0 +1,145 @@ + 'test-class']); + $this->assertTrue(isset($attrs['class'])); + $this->assertFalse(isset($attrs['id'])); + } + + public function testOffsetExistsWithMissingAttr() + { + $attrs = new HtmlAttributes(['class' => 'test-class']); + $this->assertFalse(isset($attrs['id'])); + } + + public function testOffsetGetWithPresentStringAttr() + { + $attrs = new HtmlAttributes(['class' => 'demo-class']); + $this->assertSame('demo-class', $attrs['class']); + } + + public function testOffsetGetWithPresentIntAttr() + { + $attrs = new HtmlAttributes(['min' => 3]); + $this->assertSame('3', $attrs['min']); // Sanitized into string. + } + + public function testOffsetGetWithPresentBoolAttr() + { + $attrs = new HtmlAttributes(['defer' => true]); + $this->assertSame(true, $attrs['defer']); + } + + public function testOffsetGetWithMissingAttr() + { + $this->expectException(NotFoundException::class); + + $attrs = new HtmlAttributes(['class' => 'demo-class']); + $attrs['id']; + } + + public function testOffsetSet() + { + $attrs = new HtmlAttributes(['class' => 'test-class']); + $this->assertSame('test-class', $attrs['class']); + + // Class is read-only so setting shouldn't do anything. + $attrs['class'] = 'another-class'; + $this->assertSame('test-class', $attrs['class']); + } + + public function testOffsetUnset() + { + $attrs = new HtmlAttributes(['class' => 'demo-class']); + $this->assertTrue(isset($attrs['class'])); + + // Class is read-only so unsetting shouldn't do anything. + unset($attrs['class']); + $this->assertTrue(isset($attrs['class'])); + } + + public function testGetIterator() + { + $attrs = new HtmlAttributes([ + 'id' => 'unique-id', + 'class' => 'test-class', + ]); + $output = ''; + foreach ($attrs as $attr => $value) { + $output .= "{$attr}:{$value};"; + } + $this->assertSame('id:unique-id;class:test-class;', $output); + } + + public function testToArray() + { + $input = [ + 'id' => 'unique-id', + 'class' => 'test-class', + ]; + $attrs = new HtmlAttributes($input); + $this->assertSame($input, $attrs->toArray()); + } + + /** + * @dataProvider dataToString + */ + public function testToString(array $input, string $expected) + { + $attrs = new HtmlAttributes($input); + $this->assertSame($expected, (string) $attrs); + } + + public function dataToString() + { + return [ + 'regular' => [ + [ + 'id' => 'unique-id', + 'class' => 'test-class', + ], + ' id="unique-id" class="test-class"', + ], + 'with bool enabled' => [ + [ + 'id' => 'random-id', + 'editable' => true, + ], + ' id="random-id" editable', + ], + 'with bool disabled' => [ + [ + 'id' => 'unique-id', + 'defer' => false, + ], + ' id="unique-id"', + ], + 'with bool mixed' => [ + [ + 'id' => 'unique-id', + 'defer' => false, + 'async' => true, + 'class' => 'demo-class', + ], + ' id="unique-id" async class="demo-class"', + ], + ]; + } +} diff --git a/tests/phpunit/tests/Util/ThirdPartiesDirTest.php b/tests/phpunit/tests/Util/ThirdPartiesDirTest.php new file mode 100644 index 0000000..ac2c9d0 --- /dev/null +++ b/tests/phpunit/tests/Util/ThirdPartiesDirTest.php @@ -0,0 +1,29 @@ +assertStringEndsWith('/src/third-parties/google-analytics/data.json', $absPath); + } + + public function testGetFilePathWithLeadingSlash() + { + $absPath = ThirdPartiesDir::getFilePath('/google-analytics/data.json'); + $this->assertStringEndsWith('/src/third-parties/google-analytics/data.json', $absPath); + } +} diff --git a/tests/phpunit/utils/TestCase.php b/tests/phpunit/utils/TestCase.php new file mode 100644 index 0000000..32cb0cf --- /dev/null +++ b/tests/phpunit/utils/TestCase.php @@ -0,0 +1,119 @@ +expectException($expected); + + $instance = new $className($args); + call_user_func([$instance, $getMethod]); + return; + } + + $instance = new $className($args); + $result = call_user_func([$instance, $getMethod]); + if ($result instanceof Arrayable) { + $result = $result->toArray(); + } elseif (is_array($result)) { + $result = array_map( + static function ($entry) { + if ($entry instanceof Arrayable) { + return $entry->toArray(); + } + return $entry; + }, + $result + ); + } + $this->assertSame($expected, $result); + } + + protected function gettersToTestCases(array $getters, string $exceptionClass = null): array + { + if (!$exceptionClass) { + $exceptionClass = Exception::class; + } + + $requiredFields = []; + foreach ($getters as $getter) { + if (!isset($getter['required']) || ! $getter['required']) { + continue; + } + + if (!isset($getter['value'])) { + $type = isset($getter['default']) ? gettype($getter['default']) : 'string'; + $value = $this->createValueOfType($type); + } else { + $value = $getter['value']; + } + $requiredFields[$getter['field']] = $value; + } + + $testCases = []; + foreach ($getters as $getter) { + if (!isset($getter['value'])) { + $type = isset($getter['default']) ? gettype($getter['default']) : 'string'; + $value = $this->createValueOfType($type); + } else { + $value = $getter['value']; + } + $args = $requiredFields; + $args[$getter['field']] = $value; + + $testCases["{$getter['getter']} with value"] = [ + $getter['getter'], + $args, + $value, + ]; + + if ((isset($getter['required']) && $getter['required']) || isset($getter['default'])) { + unset($args[$getter['field']]); + + $testCases["{$getter['getter']} without value"] = [ + $getter['getter'], + $args, + isset($getter['required']) && $getter['required'] ? + $exceptionClass : + $getter['default'] + ]; + } + } + return $testCases; + } + + private function createValueOfType(string $type) + { + switch ($type) { + case 'bool': + case 'boolean': + return true; + case 'double': + case 'float': + return 5.9; + case 'int': + case 'integer': + return 23; + case 'array': + return ['id']; + } + + // Default 'string'. + return 'something'; + } +}