diff --git a/.distignore b/.distignore index 5a49d18e..ca510fd0 100644 --- a/.distignore +++ b/.distignore @@ -18,4 +18,6 @@ CREDITS.md LICENSE.md package-lock.json package.json +phpcs.xml +phpunit.xml.dist README.md diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml new file mode 100644 index 00000000..aacc513a --- /dev/null +++ b/.github/workflows/php-compatibility.yml @@ -0,0 +1,32 @@ +name: PHP Compatibility + +on: + push: + branches: + - develop + - trunk + pull_request: + branches: + - develop + +jobs: + php_compatibility: + name: PHP minimum 7.0 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: composer install + + - name: Run PHP Compatibility + run: vendor/bin/phpcs *.php includes -p --standard=PHPCompatibilityWP --extensions=php --runtime-set testVersion 7.0- diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml new file mode 100644 index 00000000..157bd576 --- /dev/null +++ b/.github/workflows/phpcs.yml @@ -0,0 +1,31 @@ +name: PHPCS + +on: + push: + branches: + - develop + - trunk + pull_request: + branches: + - develop + +jobs: + phpcs: + name: phpcs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: composer:v2 + + - name: composer install + run: composer install + + - name: Run PHPCS + run: composer run phpcs diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 00000000..2db88e61 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,54 @@ +name: PHPUnit + +env: + COMPOSER_VERSION: "2" + COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache" + +on: + push: + branches: + - develop + - trunk + pull_request: + branches: + - develop + +jobs: + phpunit: + name: PHP ${{ matrix.php }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # We claim to support from 5.6+ but WP Mock only supports 7.1+ + php: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1' ] + os: [ ubuntu-latest ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set standard 10up cache directories + run: | + composer config -g cache-dir "${{ env.COMPOSER_CACHE }}" + + - name: Prepare composer cache + uses: actions/cache@v2 + with: + path: ${{ env.COMPOSER_CACHE }} + key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + composer-${{ env.COMPOSER_VERSION }}- + + - name: Set PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer update -W + + - name: PHPUnit + run: './vendor/bin/phpunit' diff --git a/.github/workflows/wordpress-plugin-asset-update.yml b/.github/workflows/wordpress-plugin-asset-update.yml index 3b4507b8..5ba86f65 100644 --- a/.github/workflows/wordpress-plugin-asset-update.yml +++ b/.github/workflows/wordpress-plugin-asset-update.yml @@ -6,16 +6,16 @@ on: - trunk jobs: - master: + trunk: name: Push to trunk runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Build run: | - composer install + composer install --no-dev - name: WordPress.org plugin asset/readme update uses: 10up/action-wordpress-plugin-asset-update@stable env: SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} - SVN_USERNAME: ${{ secrets.SVN_USERNAME }} \ No newline at end of file + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} diff --git a/.github/workflows/wordpress-plugin-deploy.yml b/.github/workflows/wordpress-plugin-deploy.yml index 06ff9caf..52d55429 100644 --- a/.github/workflows/wordpress-plugin-deploy.yml +++ b/.github/workflows/wordpress-plugin-deploy.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v2 - name: Build run: | - composer install + composer install --no-dev - name: WordPress Plugin Deploy id: deploy uses: 10up/action-wordpress-plugin-deploy@stable diff --git a/.gitignore b/.gitignore index d7462e90..08000cdc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules tests/cypress/downloads tests/cypress/screenshots tests/cypress/videos +.phpunit.result.cache diff --git a/CHANGELOG.md b/CHANGELOG.md index d7110c1d..8b99d742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file, per [the Ke ## [Unreleased] - TBD +## [2.0.3] - 2022-09-01 + +### Added +- More robust PHP testing (props [@iamdharmesh](https://github.com/iamdharmesh), [@faisal-alvi](https://github.com/faisal-alvi) via [#71](https://github.com/10up/safe-svg/pull/71), [#73](https://github.com/10up/safe-svg/pull/73)). + +### Fixed +- Addressed PHPCS errors (props [@iamdharmesh](https://github.com/iamdharmesh), [@faisal-alvi](https://github.com/faisal-alvi) via [#73](https://github.com/10up/safe-svg/pull/73)). + ## [2.0.2] - 2022-06-27 ### Added - Dependency security scanning (props [@jeffpaul](https://github.com/jeffpaul) via [#60](https://github.com/10up/safe-svg/pull/60)). @@ -218,6 +226,7 @@ All notable changes to this project will be documented in this file, per [the Ke - Initial Release. [Unreleased]: https://github.com/10up/safe-svg/compare/trunk...develop +[2.0.3]: https://github.com/10up/safe-svg/compare/2.0.2...2.0.3 [2.0.2]: https://github.com/10up/safe-svg/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/10up/safe-svg/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/10up/safe-svg/compare/1.9.10...2.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a92eb1c..225d0e2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ The `develop` branch is the development branch which means it contains the next ## Release instructions 1. Branch: Starting from `develop`, cut a release branch named `release/X.Y.Z` for your changes. -1. Version bump: Bump the version number in `readme.txt`, `safe-svg.php`, `package.json` and `package-lock.json` if it does not already reflect the version being released. +1. Version bump: Bump the version number in `readme.txt`, `safe-svg.php`, `package.json` and `package-lock.json` if it does not already reflect the version being released. In `safe-svg.php` update both the plugin "Version:" property and the plugin `SAFE_SVG_VERSION` constant. 1. Changelog: Add/update the changelog in `CHANGELOG.md` and `readme.txt`. 1. Props: update `CREDITS.md` with any new contributors, confirm maintainers are accurate. 1. New files: Check to be sure any new files/paths that are unnecessary in the production version are included in `.distignore`. diff --git a/CREDITS.md b/CREDITS.md index 77b57347..13cc4ad2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -12,7 +12,7 @@ The following individuals are responsible for curating the list of issues, respo Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, translation, testing, etc. -[Daryll Doyle (@darylldoyle)](https://github.com/darylldoyle), [Lewis Cowles (@LewisCowles1986)](https://github.com/LewisCowles1986), [Daniel M. Hendricks (@dmhendricks)](https://github.com/dmhendricks), [Dan Pock (@mallardduck)](https://github.com/mallardduck), [K. Adam White (@kadamwhite)](https://github.com/kadamwhite), [Joe Hoyle (@joehoyle)](https://github.com/joehoyle), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Brandon Berg (@BBerg10up)](https://github.com/BBerg10up), [Max Lyuchin (@cadic)](https://github.com/cadic), [Mehidi Hassan (@mehidi258)](https://github.com/mehidi258), [Dharmesh Patel (@iamdharmesh)](https://github.com/iamdharmesh), [Timothy Decker (@amdd-tim)](https://github.com/amdd-tim), [Brooke Campbell](https://www.linkedin.com/in/brookecampbelldesign/), [Mehul Kaklotar (@mehulkaklotar)](https://github.com/mehulkaklotar), [@smerriman](https://github.com/smerriman), [Darin Kotter (@dkotter)](https://github.com/dkotter), [Manuel Friedli (@fritteli)](https://github.com/fritteli), [David Hamann (@davidhamann)](https://github.com/davidhamann), [@j-hoffmann](https://github.com/j-hoffmann), [Peter Wilson (@peterwilsoncc)](https://github.com/peterwilsoncc), [Torsten Landsiedel (@Zodiac1978)](https://github.com/Zodiac1978), [Axel DUCORON (@aksld)](https://github.com/aksld), [Mario Rader (@r8r)](https://github.com/r8r), [Jeremy Turowetz (@jerturowetz)](https://github.com/jerturowetz), [Robert O'Rourke (@roborourke)](https://github.com/roborourke), [Dominik Schilling (@ocean90)](https://github.com/ocean90), [Adam Wills (@AdamWills)](https://github.com/AdamWills). +[Daryll Doyle (@darylldoyle)](https://github.com/darylldoyle), [Lewis Cowles (@LewisCowles1986)](https://github.com/LewisCowles1986), [Daniel M. Hendricks (@dmhendricks)](https://github.com/dmhendricks), [Dan Pock (@mallardduck)](https://github.com/mallardduck), [K. Adam White (@kadamwhite)](https://github.com/kadamwhite), [Joe Hoyle (@joehoyle)](https://github.com/joehoyle), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Brandon Berg (@BBerg10up)](https://github.com/BBerg10up), [Max Lyuchin (@cadic)](https://github.com/cadic), [Mehidi Hassan (@mehidi258)](https://github.com/mehidi258), [Dharmesh Patel (@iamdharmesh)](https://github.com/iamdharmesh), [Timothy Decker (@amdd-tim)](https://github.com/amdd-tim), [Brooke Campbell](https://www.linkedin.com/in/brookecampbelldesign/), [Mehul Kaklotar (@mehulkaklotar)](https://github.com/mehulkaklotar), [@smerriman](https://github.com/smerriman), [Darin Kotter (@dkotter)](https://github.com/dkotter), [Manuel Friedli (@fritteli)](https://github.com/fritteli), [David Hamann (@davidhamann)](https://github.com/davidhamann), [@j-hoffmann](https://github.com/j-hoffmann), [Peter Wilson (@peterwilsoncc)](https://github.com/peterwilsoncc), [Torsten Landsiedel (@Zodiac1978)](https://github.com/Zodiac1978), [Axel DUCORON (@aksld)](https://github.com/aksld), [Mario Rader (@r8r)](https://github.com/r8r), [Jeremy Turowetz (@jerturowetz)](https://github.com/jerturowetz), [Robert O'Rourke (@roborourke)](https://github.com/roborourke), [Dominik Schilling (@ocean90)](https://github.com/ocean90), [Adam Wills (@AdamWills)](https://github.com/AdamWills), [Faisal Alvi (@faisal-alvi)](https://github.com/faisal-alvi). ## Libraries diff --git a/composer.json b/composer.json index 6cc87014..3dc99961 100644 --- a/composer.json +++ b/composer.json @@ -22,5 +22,21 @@ "require": { "php": ">=7.0", "enshrined/svg-sanitize": "^0.15.2" + }, + "require-dev": { + "10up/phpcs-composer": "dev-master", + "10up/wp_mock": "^0.4.2", + "yoast/phpunit-polyfills": "^1.0" + }, + "scripts": { + "phpcs": "./vendor/bin/phpcs . -p -s", + "phpcbf": "./vendor/bin/phpcbf .", + "phpcs:compat": "./vendor/bin/phpcs *.php includes -p --standard=PHPCompatibilityWP --extensions=php --runtime-set testVersion 7.0-", + "test:unit": "./vendor/bin/phpunit" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/composer.lock b/composer.lock index 2732a40f..e297d906 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9d1ff5348c8ca13a8c11ebc9cd72fe3d", + "content-hash": "6923999b7d950fb978fb0193173461c2", "packages": [ { "name": "enshrined/svg-sanitize", @@ -52,15 +52,2702 @@ "time": "2022-02-21T09:13:59+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "10up/phpcs-composer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/10up/phpcs-composer.git", + "reference": "2f5c3608bc03fe1ca65acf462dd7b5008f6829a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/10up/phpcs-composer/zipball/2f5c3608bc03fe1ca65acf462dd7b5008f6829a0", + "reference": "2f5c3608bc03fe1ca65acf462dd7b5008f6829a0", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "*", + "phpcompatibility/phpcompatibility-wp": "^2", + "squizlabs/php_codesniffer": "^3.4.0", + "wp-coding-standards/wpcs": "*" + }, + "default-branch": true, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ephraim Gregor", + "email": "ephraim.gregor@10up.com" + } + ], + "support": { + "issues": "https://github.com/10up/phpcs-composer/issues", + "source": "https://github.com/10up/phpcs-composer/tree/master" + }, + "time": "2021-01-08T03:03:06+00:00" + }, + { + "name": "10up/wp_mock", + "version": "0.4.2", + "source": { + "type": "git", + "url": "https://github.com/10up/wp_mock.git", + "reference": "9019226eb50df275aa86bb15bfc98a619601ee49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/10up/wp_mock/zipball/9019226eb50df275aa86bb15bfc98a619601ee49", + "reference": "9019226eb50df275aa86bb15bfc98a619601ee49", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.1", + "mockery/mockery": "^1.0", + "php": ">=7.1", + "phpunit/phpunit": ">=7.0" + }, + "require-dev": { + "behat/behat": "^3.0", + "php-coveralls/php-coveralls": "^2.1", + "sebastian/comparator": ">=1.2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "WP_Mock\\": "./php/WP_Mock" + }, + "classmap": [ + "php/WP_Mock.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "A mocking library to take the pain out of unit testing for WordPress", + "support": { + "issues": "https://github.com/10up/wp_mock/issues", + "source": "https://github.com/10up/wp_mock/tree/master" + }, + "time": "2019-03-16T03:44:39+00:00" + }, + { + "name": "antecedent/patchwork", + "version": "2.1.21", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "http://patchwork2.org/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "support": { + "issues": "https://github.com/antecedent/patchwork/issues", + "source": "https://github.com/antecedent/patchwork/tree/2.1.21" + }, + "time": "2022-02-07T07:28:34+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\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/Dealerdirect/phpcodesniffer-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/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "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.22" + }, + "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.4.1" + }, + "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-03-03T08:28:38+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "reference": "c10a5f6e06fc2470ab1822fa13fa2a7380f8fbac", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.5.0" + }, + "time": "2022-01-20T13:18:17+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "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.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "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.14.0" + }, + "time": "2022-05-31T20:59:12+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": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "ddabec839cc003651f2ce695c938686d1086cf43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43", + "reference": "ddabec839cc003651f2ce695c938686d1086cf43", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer '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", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "time": "2021-02-15T10:24:51+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "d55de55f88697b9cdb94bccf04f14eb3b11cf308" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/d55de55f88697b9cdb94bccf04f14eb3b11cf308", + "reference": "d55de55f88697b9cdb94bccf04f14eb3b11cf308", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer '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", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "time": "2021-12-30T16:37:40+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.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": "*", + "ext-xdebug": "*" + }, + "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", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-07T09:28:20+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.5.21", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "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", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.13", + "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.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.0", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-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", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-06-19T12:14:25+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.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "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.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "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.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "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.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+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.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "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": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+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.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "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": "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.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-15T09:54:48+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": "squizlabs/php_codesniffer", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.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": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "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.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "7da1894633f168fe244afc6de00d141f27517b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62", + "reference": "7da1894633f168fe244afc6de00d141f27517b62", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.3.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "time": "2020-05-13T23:57:56+00:00" + }, + { + "name": "yoast/phpunit-polyfills", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", + "reference": "5ea3536428944955f969bc764bbe09738e151ada" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/5ea3536428944955f969bc764bbe09738e151ada", + "reference": "5ea3536428944955f969bc764bbe09738e151ada", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "require-dev": { + "yoast/yoastcs": "^2.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "files": [ + "phpunitpolyfills-autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Team Yoast", + "email": "support@yoast.com", + "homepage": "https://yoast.com" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors" + } + ], + "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests", + "homepage": "https://github.com/Yoast/PHPUnit-Polyfills", + "keywords": [ + "phpunit", + "polyfill", + "testing" + ], + "support": { + "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "source": "https://github.com/Yoast/PHPUnit-Polyfills" + }, + "time": "2021-11-23T01:37:03+00:00" + } + ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "10up/phpcs-composer": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=7.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/includes/safe-svg-attributes.php b/includes/safe-svg-attributes.php index 773f980b..28dd5b05 100644 --- a/includes/safe-svg-attributes.php +++ b/includes/safe-svg-attributes.php @@ -1,6 +1,13 @@ + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..7a05e266 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + ./tests/unit + + + + + + + diff --git a/readme.txt b/readme.txt index ae25f3b6..ad5d6e5c 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: 10up, enshrined Tags: svg, sanitize, upload, sanitise, security, svg upload, image, vector, file, graphic, media, mime Requires at least: 4.7 Tested up to: 6.0 -Stable tag: 2.0.2 +Stable tag: 2.0.3 Requires PHP: 7.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -66,6 +66,10 @@ They take one argument that must be returned. See below for examples: == Changelog == += 2.0.3 - 2022-09-01 = +* **Added:** More robust PHP testing (props [@iamdharmesh](https://github.com/iamdharmesh), [@faisal-alvi](https://github.com/faisal-alvi) via [#71](https://github.com/10up/safe-svg/pull/71), [#73](https://github.com/10up/safe-svg/pull/73)). +* **Fixed:** Addressed PHPCS errors (props [@iamdharmesh](https://github.com/iamdharmesh), [@faisal-alvi](https://github.com/faisal-alvi) via [#73](https://github.com/10up/safe-svg/pull/73)). + = 2.0.2 - 2022-06-27 = * **Added:** Dependency security scanning (props [@jeffpaul](https://github.com/jeffpaul) via [#60](https://github.com/10up/safe-svg/pull/60)). * **Added:** End-to-end testing with Cypress (props [@iamdharmesh](https://github.com/iamdharmesh) via [#64](https://github.com/10up/safe-svg/pull/64)). diff --git a/safe-svg.php b/safe-svg.php index 195cdf4b..5418edbc 100644 --- a/safe-svg.php +++ b/safe-svg.php @@ -1,9 +1,9 @@ sanitizer = new enshrined\svgSanitize\Sanitizer(); - $this->sanitizer->minify( true ); - - add_filter( 'upload_mimes', array( $this, 'allow_svg' ) ); - add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_for_svg' ) ); - add_filter( 'wp_check_filetype_and_ext', array( $this, 'fix_mime_type_svg' ), 75, 4 ); - add_filter( 'wp_prepare_attachment_for_js', array( $this, 'fix_admin_preview' ), 10, 3 ); - add_filter( 'wp_get_attachment_image_src', array( $this, 'one_pixel_fix' ), 10, 4 ); - add_filter( 'admin_post_thumbnail_html', array( $this, 'featured_image_fix' ), 10, 3 ); - add_action( 'admin_enqueue_scripts', array( $this, 'load_custom_admin_style' ) ); - add_action( 'get_image_tag', array( $this, 'get_image_tag_override' ), 10, 6 ); - add_filter( 'wp_generate_attachment_metadata', array( $this, 'skip_svg_regeneration' ), 10, 2 ); - add_filter( 'wp_get_attachment_metadata', array( $this, 'metadata_error_fix' ), 10, 2 ); - add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'disable_srcset' ), 10, 4 ); - } - - /** - * Allow SVG Uploads - * - * @param $mimes - * - * @return mixed - */ - public function allow_svg( $mimes ) { - $mimes['svg'] = 'image/svg+xml'; - $mimes['svgz'] = 'image/svg+xml'; - - return $mimes; - } - - /** - * Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs - * - * @thanks @lewiscowles - * - * @param null $data - * @param null $file - * @param null $filename - * @param null $mimes - * - * @return null - */ - public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) { - $ext = isset( $data['ext'] ) ? $data['ext'] : ''; - if ( strlen( $ext ) < 1 ) { - $exploded = explode( '.', $filename ); - $ext = strtolower( end( $exploded ) ); - } - if ( $ext === 'svg' ) { - $data['type'] = 'image/svg+xml'; - $data['ext'] = 'svg'; - } elseif ( $ext === 'svgz' ) { - $data['type'] = 'image/svg+xml'; - $data['ext'] = 'svgz'; - } - - return $data; - } - - /** - * Check if the file is an SVG, if so handle appropriately - * - * @param $file - * - * @return mixed - */ - public function check_for_svg( $file ) { - - // Ensure we have a proper file path before processing - if ( ! isset( $file['tmp_name'] ) ) { - return $file; - } - - $file_name = isset( $file['name'] ) ? $file['name'] : ''; - $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file_name ); - $type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : ''; - - if ( $type === 'image/svg+xml' ) { - if ( ! $this->sanitize( $file['tmp_name'] ) ) { - $file['error'] = __( "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded", - 'safe-svg' ); - } - } - - return $file; - } - - /** - * Sanitize the SVG - * - * @param $file - * - * @return bool|int - */ - protected function sanitize( $file ) { - $dirty = file_get_contents( $file ); - - // Is the SVG gzipped? If so we try and decode the string - if ( $is_zipped = $this->is_gzipped( $dirty ) ) { - $dirty = gzdecode( $dirty ); - - // If decoding fails, bail as we're not secure - if ( $dirty === false ) { - return false; - } - } - - /** - * Load extra filters to allow devs to access the safe tags and attrs by themselves. - */ - $this->sanitizer->setAllowedTags( new safe_svg_tags() ); - $this->sanitizer->setAllowedAttrs( new safe_svg_attributes() ); - - $clean = $this->sanitizer->sanitize( $dirty ); - - if ( $clean === false ) { - return false; - } - - // If we were gzipped, we need to re-zip - if ( $is_zipped ) { - $clean = gzencode( $clean ); - } - - file_put_contents( $file, $clean ); - - return true; - } - - /** - * Check if the contents are gzipped - * - * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format - * - * @param $contents - * - * @return bool - */ - protected function is_gzipped( $contents ) { - if ( function_exists( 'mb_strpos' ) ) { - return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" ); - } else { - return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" ); - } - } - - /** - * Filters the attachment data prepared for JavaScript to add the sizes array to the response - * - * @param array $response Array of prepared attachment data. - * @param int|object $attachment Attachment ID or object. - * @param array $meta Array of attachment meta data. - * - * @return array - */ - public function fix_admin_preview( $response, $attachment, $meta ) { - - if ( $response['mime'] == 'image/svg+xml' ) { - $dimensions = $this->svg_dimensions( get_attached_file( $attachment->ID ) ); - - if ( $dimensions ) { - $response = array_merge( $response, $dimensions ); - } - - $possible_sizes = apply_filters( 'image_size_names_choose', array( - 'full' => __( 'Full Size' ), - 'thumbnail' => __( 'Thumbnail' ), - 'medium' => __( 'Medium' ), - 'large' => __( 'Large' ), - ) ); - - $sizes = array(); - - foreach ( $possible_sizes as $size => $label ) { - $default_height = 2000; - $default_width = 2000; - - if ( 'full' === $size && $dimensions ) { - $default_height = $dimensions['height']; - $default_width = $dimensions['width']; - } - - $sizes[ $size ] = array( - 'height' => get_option( "{$size}_size_w", $default_height ), - 'width' => get_option( "{$size}_size_h", $default_width ), - 'url' => $response['url'], - 'orientation' => 'portrait', - ); - } - - $response['sizes'] = $sizes; - $response['icon'] = $response['url']; - } - - return $response; - } - - /** - * Filters the image src result. - * If the image size doesn't exist, set a default size of 100 for width and height - * - * @param array|false $image Either array with src, width & height, icon src, or false. - * @param int $attachment_id Image attachment ID. - * @param string|array $size Size of image. Image size or array of width and height values - * (in that order). Default 'thumbnail'. - * @param bool $icon Whether the image should be treated as an icon. Default false. - * - * @return array - */ - public function one_pixel_fix( $image, $attachment_id, $size, $icon ) { - if ( get_post_mime_type( $attachment_id ) === 'image/svg+xml' ) { - $dimensions = $this->svg_dimensions( get_attached_file( $attachment_id ) ); - - if ( $dimensions ) { - $image[1] = $dimensions['width']; - $image[2] = $dimensions['height']; - } else { - $image[1] = 100; - $image[2] = 100; - } - } - - return $image; - } - - /** - * If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix. - * - * @param string $content Admin post thumbnail HTML markup. - * @param int $post_id Post ID. - * @param int $thumbnail_id Thumbnail ID. - * - * @return string - */ - public function featured_image_fix( $content, $post_id, $thumbnail_id ) { - $mime = get_post_mime_type( $thumbnail_id ); - - if ( 'image/svg+xml' === $mime ) { - $content = sprintf( '%s', $content ); - } - - return $content; - } - - /** - * Load our custom CSS sheet. - */ - function load_custom_admin_style() { - wp_enqueue_style( 'safe-svg-css', plugins_url( 'assets/safe-svg.css', __FILE__ ), array() ); - } - - /** - * Override the default height and width string on an SVG - * - * @param string $html HTML content for the image. - * @param int $id Attachment ID. - * @param string $alt Alternate text. - * @param string $title Attachment title. - * @param string $align Part of the class name for aligning the image. - * @param string|array $size Size of image. Image size or array of width and height values (in that order). - * Default 'medium'. - * - * @return mixed - */ - function get_image_tag_override( $html, $id, $alt, $title, $align, $size ) { - $mime = get_post_mime_type( $id ); - - if ( 'image/svg+xml' === $mime ) { - if ( is_array( $size ) ) { - $width = $size[0]; - $height = $size[1]; - } elseif ( 'full' == $size && $dimensions = $this->svg_dimensions( get_attached_file( $id ) ) ) { - $width = $dimensions['width']; - $height = $dimensions['height']; - } else { - $width = get_option( "{$size}_size_w", false ); - $height = get_option( "{$size}_size_h", false ); - } - - if ( $height && $width ) { - $html = str_replace( 'width="1" ', sprintf( 'width="%s" ', $width ), $html ); - $html = str_replace( 'height="1" ', sprintf( 'height="%s" ', $height ), $html ); - } else { - $html = str_replace( 'width="1" ', '', $html ); - $html = str_replace( 'height="1" ', '', $html ); - } - - $html = str_replace( '/>', ' role="img" />', $html ); - } - - return $html; - } - - /** - * Skip regenerating SVGs - * - * @param int $attachment_id Attachment Id to process. - * @param string $file Filepath of the Attached image. - * - * @return mixed Metadata for attachment. - */ - function skip_svg_regeneration( $metadata, $attachment_id ) { - $mime = get_post_mime_type( $attachment_id ); - if ( 'image/svg+xml' === $mime ) { - $additional_image_sizes = wp_get_additional_image_sizes(); - $svg_path = get_attached_file( $attachment_id ); - $upload_dir = wp_upload_dir(); - // get the path relative to /uploads/ - found no better way: - $relative_path = str_replace( trailingslashit( $upload_dir['basedir'] ), '', $svg_path ); - $filename = basename( $svg_path ); - - $dimensions = $this->svg_dimensions( $svg_path ); - - if ( ! $dimensions ) { - return $metadata; - } - - $metadata = array( - 'width' => intval( $dimensions['width'] ), - 'height' => intval( $dimensions['height'] ), - 'file' => $relative_path - ); - - // Might come handy to create the sizes array too - But it's not needed for this workaround! Always links to original svg-file => Hey, it's a vector graphic! ;) - $sizes = array(); - foreach ( get_intermediate_image_sizes() as $s ) { - $sizes[ $s ] = array( 'width' => '', 'height' => '', 'crop' => false ); - - if ( isset( $additional_image_sizes[ $s ]['width'] ) ) { - // For theme-added sizes - $sizes[ $s ]['width'] = intval( $additional_image_sizes[ $s ]['width'] ); - } else { - // For default sizes set in options - $sizes[ $s ]['width'] = get_option( "{$s}_size_w" ); - } - - if ( isset( $additional_image_sizes[ $s ]['height'] ) ) { - // For theme-added sizes - $sizes[ $s ]['height'] = intval( $additional_image_sizes[ $s ]['height'] ); - } else { - // For default sizes set in options - $sizes[ $s ]['height'] = get_option( "{$s}_size_h" ); - } - - if ( isset( $additional_image_sizes[ $s ]['crop'] ) ) { - // For theme-added sizes - $sizes[ $s ]['crop'] = intval( $additional_image_sizes[ $s ]['crop'] ); - } else { - // For default sizes set in options - $sizes[ $s ]['crop'] = get_option( "{$s}_crop" ); - } - - $sizes[ $s ]['file'] = $filename; - $sizes[ $s ]['mime-type'] = $mime; - } - $metadata['sizes'] = $sizes; - } - - return $metadata; - } - - /** - * Filters the attachment meta data. - * - * @param array|bool $data Array of meta data for the given attachment, or false - * if the object does not exist. - * @param int $post_id Attachment ID. - */ - function metadata_error_fix( $data, $post_id ) { - - // If it's a WP_Error regenerate metadata and save it - if ( is_wp_error( $data ) ) { - $data = wp_generate_attachment_metadata( $post_id, get_attached_file( $post_id ) ); - wp_update_attachment_metadata( $post_id, $data ); - } - - return $data; - } - - /** - * Get SVG size from the width/height or viewport. - * - * @param string|false $svg The file path to where the SVG file should be, false otherwise. - * - * @return array|bool - */ - protected function svg_dimensions( $svg ) { - $svg = @simplexml_load_file( $svg ); - $width = 0; - $height = 0; - if ( $svg ) { - $attributes = $svg->attributes(); - - if ( isset( $attributes->viewBox ) ) { - $sizes = explode( ' ', $attributes->viewBox ); - if ( isset( $sizes[2], $sizes[3] ) ) { - $viewbox_width = floatval( $sizes[2] ); - $viewbox_height = floatval( $sizes[3] ); - } - } - - if ( isset( $attributes->width, $attributes->height ) && is_numeric( (float) $attributes->width ) && is_numeric( (float) $attributes->height ) && ! $this->str_ends_with( (string) $attributes->width, '%' ) && ! $this->str_ends_with( (string) $attributes->height, '%' ) ) { - $attr_width = floatval( $attributes->width ); - $attr_height = floatval( $attributes->height ); - } - - /** - * Decide which attributes of the SVG we use first for image tag dimensions. - * - * We default to using the parameters in the viewbox attribute but - * that can be overridden using this filter if you'd prefer to use - * the width and height attributes. - * - * @hook safe_svg_use_width_height_attributes - * - * @param {bool} $false If the width & height attributes should be used first. Default false. - * @param {string} $svg The file path to the SVG. - * - * @return {bool} If we should use the width & height attributes first or not. - */ - $use_width_height = (bool) apply_filters( 'safe_svg_use_width_height_attributes', false, $svg ); - - if ( $use_width_height ) { - if ( isset( $attr_width, $attr_height ) ) { - $width = $attr_width; - $height = $attr_height; - } elseif ( isset( $viewbox_width, $viewbox_height ) ) { - $width = $viewbox_width; - $height = $viewbox_height; - } - } else { - if ( isset( $viewbox_width, $viewbox_height ) ) { - $width = $viewbox_width; - $height = $viewbox_height; - } elseif ( isset( $attr_width, $attr_height ) ) { - $width = $attr_width; - $height = $attr_height; - } - } - - if ( ! $width && ! $height ) { - return false; - } - } - - return array( - 'width' => $width, - 'height' => $height, - 'orientation' => ( $width > $height ) ? 'landscape' : 'portrait' - ); - } - - /** - * Disable the creation of srcset on SVG images. - * - * @param array $image_meta The image meta data. - * @param int[] $size_array { - * An array of requested width and height values. - * - * @type int $0 The width in pixels. - * @type int $1 The height in pixels. - * } - * @param string $image_src The 'src' of the image. - * @param int $attachment_id The image attachment ID. - */ - public function disable_srcset( $image_meta, $size_array, $image_src, $attachment_id ) { - if ( $attachment_id && 'image/svg+xml' === get_post_mime_type( $attachment_id ) ) { - $image_meta['sizes'] = array(); - } - - return $image_meta; - } - - /** - * Polyfill for `str_ends_with()` function added in PHP 8.0. - * - * Performs a case-sensitive check indicating if - * the haystack ends with needle. - * - * @param string $haystack The string to search in. - * @param string $needle The substring to search for in the `$haystack`. - * @return bool True if `$haystack` ends with `$needle`, otherwise false. - */ - protected function str_ends_with( $haystack, $needle ) { - if ( function_exists( 'str_ends_with' ) ) { - return str_ends_with( $haystack, $needle ); - } - - if ( '' === $haystack && '' !== $needle ) { - return false; - } - - $len = strlen( $needle ); - return 0 === substr_compare( $haystack, $needle, -$len, $len ); - } - - } + /** + * Class safe_svg + */ + class safe_svg { + + /** + * The sanitizer + * + * @var \enshrined\svgSanitize\Sanitizer + */ + protected $sanitizer; + + /** + * Set up the class + */ + public function __construct() { + $this->sanitizer = new enshrined\svgSanitize\Sanitizer(); + $this->sanitizer->minify( true ); + + add_filter( 'upload_mimes', array( $this, 'allow_svg' ) ); + add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_for_svg' ) ); + add_filter( 'wp_check_filetype_and_ext', array( $this, 'fix_mime_type_svg' ), 75, 4 ); + add_filter( 'wp_prepare_attachment_for_js', array( $this, 'fix_admin_preview' ), 10, 3 ); + add_filter( 'wp_get_attachment_image_src', array( $this, 'one_pixel_fix' ), 10, 4 ); + add_filter( 'admin_post_thumbnail_html', array( $this, 'featured_image_fix' ), 10, 3 ); + add_action( 'admin_enqueue_scripts', array( $this, 'load_custom_admin_style' ) ); + add_action( 'get_image_tag', array( $this, 'get_image_tag_override' ), 10, 6 ); + add_filter( 'wp_generate_attachment_metadata', array( $this, 'skip_svg_regeneration' ), 10, 2 ); + add_filter( 'wp_get_attachment_metadata', array( $this, 'metadata_error_fix' ), 10, 2 ); + add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'disable_srcset' ), 10, 4 ); + } + + /** + * Allow SVG Uploads + * + * @param array $mimes Mime types keyed by the file extension regex corresponding to those types. + * + * @return mixed + */ + public function allow_svg( $mimes ) { + $mimes['svg'] = 'image/svg+xml'; + $mimes['svgz'] = 'image/svg+xml'; + + return $mimes; + } + + /** + * Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs + * + * @thanks @lewiscowles + * + * @param array $data Values for the extension, mime type, and corrected filename. + * @param string $file Full path to the file. + * @param string $filename The name of the file. + * @param string[] $mimes Array of mime types keyed by their file extension regex. + * + * @return null + */ + public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) { + $ext = isset( $data['ext'] ) ? $data['ext'] : ''; + if ( strlen( $ext ) < 1 ) { + $exploded = explode( '.', $filename ); + $ext = strtolower( end( $exploded ) ); + } + if ( 'svg' === $ext ) { + $data['type'] = 'image/svg+xml'; + $data['ext'] = 'svg'; + } elseif ( 'svgz' === $ext ) { + $data['type'] = 'image/svg+xml'; + $data['ext'] = 'svgz'; + } + + return $data; + } + + /** + * Check if the file is an SVG, if so handle appropriately + * + * @param array $file An array of data for a single file. + * + * @return mixed + */ + public function check_for_svg( $file ) { + + // Ensure we have a proper file path before processing + if ( ! isset( $file['tmp_name'] ) ) { + return $file; + } + + $file_name = isset( $file['name'] ) ? $file['name'] : ''; + $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file_name ); + $type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : ''; + + if ( 'image/svg+xml' === $type ) { + if ( ! $this->sanitize( $file['tmp_name'] ) ) { + $file['error'] = __( + "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded", + 'safe-svg' + ); + } + } + + return $file; + } + + /** + * Sanitize the SVG + * + * @param string $file Temp file path. + * + * @return bool|int + */ + protected function sanitize( $file ) { + $dirty = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + + // Is the SVG gzipped? If so we try and decode the string + $is_zipped = $this->is_gzipped( $dirty ); + if ( $is_zipped ) { + $dirty = gzdecode( $dirty ); + + // If decoding fails, bail as we're not secure + if ( false === $dirty ) { + return false; + } + } + + /** + * Load extra filters to allow devs to access the safe tags and attrs by themselves. + */ + $this->sanitizer->setAllowedTags( new safe_svg_tags() ); + $this->sanitizer->setAllowedAttrs( new safe_svg_attributes() ); + + $clean = $this->sanitizer->sanitize( $dirty ); + + if ( false === $clean ) { + return false; + } + + // If we were gzipped, we need to re-zip + if ( $is_zipped ) { + $clean = gzencode( $clean ); + } + + file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + + return true; + } + + /** + * Check if the contents are gzipped + * + * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format + * + * @param string $contents Content to check. + * + * @return bool + */ + protected function is_gzipped( $contents ) { + // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found + if ( function_exists( 'mb_strpos' ) ) { + return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" ); + } else { + return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" ); + } + // phpcs:enable + } + + /** + * Filters the attachment data prepared for JavaScript to add the sizes array to the response + * + * @param array $response Array of prepared attachment data. + * @param int|object $attachment Attachment ID or object. + * @param array $meta Array of attachment meta data. + * + * @return array + */ + public function fix_admin_preview( $response, $attachment, $meta ) { + + if ( 'image/svg+xml' === $response['mime'] ) { + $dimensions = $this->svg_dimensions( get_attached_file( $attachment->ID ) ); + + if ( $dimensions ) { + $response = array_merge( $response, $dimensions ); + } + + $possible_sizes = apply_filters( + 'image_size_names_choose', + array( + 'full' => __( 'Full Size' ), + 'thumbnail' => __( 'Thumbnail' ), + 'medium' => __( 'Medium' ), + 'large' => __( 'Large' ), + ) + ); + + $sizes = array(); + + foreach ( $possible_sizes as $size => $label ) { + $default_height = 2000; + $default_width = 2000; + + if ( 'full' === $size && $dimensions ) { + $default_height = $dimensions['height']; + $default_width = $dimensions['width']; + } + + $sizes[ $size ] = array( + 'height' => get_option( "{$size}_size_w", $default_height ), + 'width' => get_option( "{$size}_size_h", $default_width ), + 'url' => $response['url'], + 'orientation' => 'portrait', + ); + } + + $response['sizes'] = $sizes; + $response['icon'] = $response['url']; + } + + return $response; + } + + /** + * Filters the image src result. + * If the image size doesn't exist, set a default size of 100 for width and height + * + * @param array|false $image Either array with src, width & height, icon src, or false. + * @param int $attachment_id Image attachment ID. + * @param string|array $size Size of image. Image size or array of width and height values + * (in that order). Default 'thumbnail'. + * @param bool $icon Whether the image should be treated as an icon. Default false. + * + * @return array + */ + public function one_pixel_fix( $image, $attachment_id, $size, $icon ) { + if ( get_post_mime_type( $attachment_id ) === 'image/svg+xml' ) { + $dimensions = $this->svg_dimensions( get_attached_file( $attachment_id ) ); + + if ( $dimensions ) { + $image[1] = $dimensions['width']; + $image[2] = $dimensions['height']; + } else { + $image[1] = 100; + $image[2] = 100; + } + } + + return $image; + } + + /** + * If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix. + * + * @param string $content Admin post thumbnail HTML markup. + * @param int $post_id Post ID. + * @param int $thumbnail_id Thumbnail ID. + * + * @return string + */ + public function featured_image_fix( $content, $post_id, $thumbnail_id ) { + $mime = get_post_mime_type( $thumbnail_id ); + + if ( 'image/svg+xml' === $mime ) { + $content = sprintf( '%s', $content ); + } + + return $content; + } + + /** + * Load our custom CSS sheet. + */ + public function load_custom_admin_style() { + wp_enqueue_style( 'safe-svg-css', plugins_url( 'assets/safe-svg.css', __FILE__ ), array(), SAFE_SVG_VERSION ); + } + + /** + * Override the default height and width string on an SVG + * + * @param string $html HTML content for the image. + * @param int $id Attachment ID. + * @param string $alt Alternate text. + * @param string $title Attachment title. + * @param string $align Part of the class name for aligning the image. + * @param string|array $size Size of image. Image size or array of width and height values (in that order). + * Default 'medium'. + * + * @return mixed + */ + public function get_image_tag_override( $html, $id, $alt, $title, $align, $size ) { + $mime = get_post_mime_type( $id ); + + if ( 'image/svg+xml' === $mime ) { + if ( is_array( $size ) ) { + $width = $size[0]; + $height = $size[1]; + // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found, Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure + } elseif ( 'full' === $size && $dimensions = $this->svg_dimensions( get_attached_file( $id ) ) ) { + $width = $dimensions['width']; + $height = $dimensions['height']; + } else { + $width = get_option( "{$size}_size_w", false ); + $height = get_option( "{$size}_size_h", false ); + } + + if ( $height && $width ) { + $html = str_replace( 'width="1" ', sprintf( 'width="%s" ', $width ), $html ); + $html = str_replace( 'height="1" ', sprintf( 'height="%s" ', $height ), $html ); + } else { + $html = str_replace( 'width="1" ', '', $html ); + $html = str_replace( 'height="1" ', '', $html ); + } + + $html = str_replace( '/>', ' role="img" />', $html ); + } + + return $html; + } + + /** + * Skip regenerating SVGs + * + * @param array $metadata An array of attachment meta data. + * @param int $attachment_id Attachment Id to process. + * + * @return mixed Metadata for attachment. + */ + public function skip_svg_regeneration( $metadata, $attachment_id ) { + $mime = get_post_mime_type( $attachment_id ); + if ( 'image/svg+xml' === $mime ) { + $additional_image_sizes = wp_get_additional_image_sizes(); + $svg_path = get_attached_file( $attachment_id ); + $upload_dir = wp_upload_dir(); + // get the path relative to /uploads/ - found no better way: + $relative_path = str_replace( trailingslashit( $upload_dir['basedir'] ), '', $svg_path ); + $filename = basename( $svg_path ); + + $dimensions = $this->svg_dimensions( $svg_path ); + + if ( ! $dimensions ) { + return $metadata; + } + + $metadata = array( + 'width' => intval( $dimensions['width'] ), + 'height' => intval( $dimensions['height'] ), + 'file' => $relative_path, + ); + + // Might come handy to create the sizes array too - But it's not needed for this workaround! Always links to original svg-file => Hey, it's a vector graphic! ;) + $sizes = array(); + foreach ( get_intermediate_image_sizes() as $s ) { + $sizes[ $s ] = array( + 'width' => '', + 'height' => '', + 'crop' => false, + ); + + if ( isset( $additional_image_sizes[ $s ]['width'] ) ) { + // For theme-added sizes + $sizes[ $s ]['width'] = intval( $additional_image_sizes[ $s ]['width'] ); + } else { + // For default sizes set in options + $sizes[ $s ]['width'] = get_option( "{$s}_size_w" ); + } + + if ( isset( $additional_image_sizes[ $s ]['height'] ) ) { + // For theme-added sizes + $sizes[ $s ]['height'] = intval( $additional_image_sizes[ $s ]['height'] ); + } else { + // For default sizes set in options + $sizes[ $s ]['height'] = get_option( "{$s}_size_h" ); + } + + if ( isset( $additional_image_sizes[ $s ]['crop'] ) ) { + // For theme-added sizes + $sizes[ $s ]['crop'] = intval( $additional_image_sizes[ $s ]['crop'] ); + } else { + // For default sizes set in options + $sizes[ $s ]['crop'] = get_option( "{$s}_crop" ); + } + + $sizes[ $s ]['file'] = $filename; + $sizes[ $s ]['mime-type'] = $mime; + } + $metadata['sizes'] = $sizes; + } + + return $metadata; + } + + /** + * Filters the attachment meta data. + * + * @param array|bool $data Array of meta data for the given attachment, or false + * if the object does not exist. + * @param int $post_id Attachment ID. + */ + public function metadata_error_fix( $data, $post_id ) { + + // If it's a WP_Error regenerate metadata and save it + if ( is_wp_error( $data ) ) { + $data = wp_generate_attachment_metadata( $post_id, get_attached_file( $post_id ) ); + wp_update_attachment_metadata( $post_id, $data ); + } + + return $data; + } + + /** + * Get SVG size from the width/height or viewport. + * + * @param string|false $svg The file path to where the SVG file should be, false otherwise. + * + * @return array|bool + */ + protected function svg_dimensions( $svg ) { + $svg = @simplexml_load_file( $svg ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $width = 0; + $height = 0; + if ( $svg ) { + $attributes = $svg->attributes(); + + if ( isset( $attributes->viewBox ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $sizes = explode( ' ', $attributes->viewBox ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + if ( isset( $sizes[2], $sizes[3] ) ) { + $viewbox_width = floatval( $sizes[2] ); + $viewbox_height = floatval( $sizes[3] ); + } + } + + if ( isset( $attributes->width, $attributes->height ) && is_numeric( (float) $attributes->width ) && is_numeric( (float) $attributes->height ) && ! $this->str_ends_with( (string) $attributes->width, '%' ) && ! $this->str_ends_with( (string) $attributes->height, '%' ) ) { + $attr_width = floatval( $attributes->width ); + $attr_height = floatval( $attributes->height ); + } + + /** + * Decide which attributes of the SVG we use first for image tag dimensions. + * + * We default to using the parameters in the viewbox attribute but + * that can be overridden using this filter if you'd prefer to use + * the width and height attributes. + * + * @hook safe_svg_use_width_height_attributes + * + * @param {bool} $false If the width & height attributes should be used first. Default false. + * @param {string} $svg The file path to the SVG. + * + * @return {bool} If we should use the width & height attributes first or not. + */ + $use_width_height = (bool) apply_filters( 'safe_svg_use_width_height_attributes', false, $svg ); + + if ( $use_width_height ) { + if ( isset( $attr_width, $attr_height ) ) { + $width = $attr_width; + $height = $attr_height; + } elseif ( isset( $viewbox_width, $viewbox_height ) ) { + $width = $viewbox_width; + $height = $viewbox_height; + } + } else { + if ( isset( $viewbox_width, $viewbox_height ) ) { + $width = $viewbox_width; + $height = $viewbox_height; + } elseif ( isset( $attr_width, $attr_height ) ) { + $width = $attr_width; + $height = $attr_height; + } + } + + if ( ! $width && ! $height ) { + return false; + } + } + + return array( + 'width' => $width, + 'height' => $height, + 'orientation' => ( $width > $height ) ? 'landscape' : 'portrait', + ); + } + + /** + * Disable the creation of srcset on SVG images. + * + * @param array $image_meta The image meta data. + * @param int[] $size_array { + * An array of requested width and height values. + * + * @type int $0 The width in pixels. + * @type int $1 The height in pixels. + * } + * @param string $image_src The 'src' of the image. + * @param int $attachment_id The image attachment ID. + */ + public function disable_srcset( $image_meta, $size_array, $image_src, $attachment_id ) { + if ( $attachment_id && 'image/svg+xml' === get_post_mime_type( $attachment_id ) ) { + $image_meta['sizes'] = array(); + } + + return $image_meta; + } + + /** + * Polyfill for `str_ends_with()` function added in PHP 8.0. + * + * Performs a case-sensitive check indicating if + * the haystack ends with needle. + * + * @param string $haystack The string to search in. + * @param string $needle The substring to search for in the `$haystack`. + * @return bool True if `$haystack` ends with `$needle`, otherwise false. + */ + protected function str_ends_with( $haystack, $needle ) { + if ( function_exists( 'str_ends_with' ) ) { + return str_ends_with( $haystack, $needle ); + } + + if ( '' === $haystack && '' !== $needle ) { + return false; + } + + $len = strlen( $needle ); + return 0 === substr_compare( $haystack, $needle, -$len, $len ); + } + + } } $safe_svg = new safe_svg(); diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php new file mode 100644 index 00000000..d6908bb1 --- /dev/null +++ b/tests/unit/bootstrap.php @@ -0,0 +1,17 @@ + + + + + + + + + + +shouldn't be here + + + diff --git a/tests/unit/files/svgCleanOne.svg b/tests/unit/files/svgCleanOne.svg new file mode 100644 index 00000000..07f376c2 --- /dev/null +++ b/tests/unit/files/svgCleanOne.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/unit/files/svgNoDimensions.svg b/tests/unit/files/svgNoDimensions.svg new file mode 100644 index 00000000..8c846dbd --- /dev/null +++ b/tests/unit/files/svgNoDimensions.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/unit/files/svgTestOne.svg b/tests/unit/files/svgTestOne.svg new file mode 100644 index 00000000..a4a9d592 --- /dev/null +++ b/tests/unit/files/svgTestOne.svg @@ -0,0 +1,15 @@ + + + + + + + + + + +shouldn't be here + + + diff --git a/tests/unit/test-safe-svg-attributes.php b/tests/unit/test-safe-svg-attributes.php new file mode 100644 index 00000000..a8864fc0 --- /dev/null +++ b/tests/unit/test-safe-svg-attributes.php @@ -0,0 +1,50 @@ +assertIsArray( $svg_attributes ); + + $filtered_svg_attributes = array_merge( $svg_attributes, array( 'customAttribute' ) ); + \WP_Mock::onFilter( 'svg_allowed_attributes' ) + ->with( $svg_attributes ) + ->reply( $filtered_svg_attributes ); + + $svg_attributes = safe_svg_attributes::getAttributes(); + $this->assertContains( 'customAttribute', $svg_attributes ); + $this->assertSame( $svg_attributes, $filtered_svg_attributes ); + } +} diff --git a/tests/unit/test-safe-svg-tags.php b/tests/unit/test-safe-svg-tags.php new file mode 100644 index 00000000..1f1d51fd --- /dev/null +++ b/tests/unit/test-safe-svg-tags.php @@ -0,0 +1,50 @@ +assertIsArray( $svg_tags ); + + $filtered_svg_tags = array_merge( $svg_tags, array( 'customTag' ) ); + \WP_Mock::onFilter( 'svg_allowed_tags' ) + ->with( $svg_tags ) + ->reply( $filtered_svg_tags ); + + $svg_tags = safe_svg_tags::getTags(); + $this->assertContains( 'customTag', $svg_tags ); + $this->assertSame( $svg_tags, $filtered_svg_tags ); + } +} diff --git a/tests/unit/test-safe-svg.php b/tests/unit/test-safe-svg.php new file mode 100644 index 00000000..b57af6c2 --- /dev/null +++ b/tests/unit/test-safe-svg.php @@ -0,0 +1,248 @@ +instance = new safe_svg(); + } + + /** + * Tear down WP Mock. + * + * @return void + */ + public function tearDown(): void { + \WP_Mock::tearDown(); + } + + /** + * Test constructor. + */ + public function test_constructor() { + \WP_Mock::expectFilterAdded( 'upload_mimes', array( $this->instance, 'allow_svg' ) ); + \WP_Mock::expectFilterAdded( 'wp_handle_upload_prefilter', array( $this->instance, 'check_for_svg' ) ); + \WP_Mock::expectFilterAdded( 'wp_check_filetype_and_ext', array( $this->instance, 'fix_mime_type_svg' ), 75, 4 ); + \WP_Mock::expectFilterAdded( 'wp_prepare_attachment_for_js', array( $this->instance, 'fix_admin_preview' ), 10, 3 ); + \WP_Mock::expectFilterAdded( 'wp_get_attachment_image_src', array( $this->instance, 'one_pixel_fix' ), 10, 4 ); + \WP_Mock::expectFilterAdded( 'admin_post_thumbnail_html', array( $this->instance, 'featured_image_fix' ), 10, 3 ); + \WP_Mock::expectActionAdded( 'admin_enqueue_scripts', array( $this->instance, 'load_custom_admin_style' ) ); + \WP_Mock::expectActionAdded( 'get_image_tag', array( $this->instance, 'get_image_tag_override' ), 10, 6 ); + \WP_Mock::expectFilterAdded( 'wp_generate_attachment_metadata', array( $this->instance, 'skip_svg_regeneration' ), 10, 2 ); + \WP_Mock::expectFilterAdded( 'wp_get_attachment_metadata', array( $this->instance, 'metadata_error_fix' ), 10, 2 ); + \WP_Mock::expectFilterAdded( 'wp_calculate_image_srcset_meta', array( $this->instance, 'disable_srcset' ), 10, 4 ); + + $this->instance->__construct(); + $this->assertConditionsMet(); + } + + /** + * Test allow_svg function. + * + * @return void + */ + public function test_allow_svg() { + $allowed_svg = $this->instance->allow_svg( array() ); + $this->assertNotEmpty( $allowed_svg ); + $this->assertContains( 'image/svg+xml', $allowed_svg ); + } + + /** + * Test fix_mime_type_svg function. + * + * @return void + */ + public function test_fix_mime_type_svg() { + $data = $this->instance->fix_mime_type_svg( + array( + 'ext' => 'svg', + 'type' => '', + ) + ); + $this->assertSame( $data['ext'], 'svg' ); + $this->assertSame( $data['type'], 'image/svg+xml' ); + + $data = $this->instance->fix_mime_type_svg( + array( + 'ext' => 'svgz', + 'type' => '', + ) + ); + $this->assertSame( $data['ext'], 'svgz' ); + $this->assertSame( $data['type'], 'image/svg+xml' ); + + $data = $this->instance->fix_mime_type_svg( null, null, 'test.svg', null ); + $this->assertSame( $data['ext'], 'svg' ); + $this->assertSame( $data['type'], 'image/svg+xml' ); + } + + /** + * Test `check_for_svg` function. + * - Test sanitize for valid svg + * - Test error for bad svg + * + * @return void + */ + public function test_check_for_svg() { + \WP_Mock::userFunction( + 'wp_check_filetype_and_ext', + array( + 'return' => array( + 'ext' => 'svg', + 'type' => 'image/svg+xml', + ), + ) + ); + + // Test sanitize on valid SVG. + $temp = tempnam( sys_get_temp_dir(), 'TMP_' ); + $files_dir = __DIR__ . '/files'; + copy( "{$files_dir}/svgTestOne.svg", $temp ); + + $file = array( + 'tmp_name' => $temp, + 'name' => 'svgTestOne.svg', + ); + + $this->instance->check_for_svg( $file ); + + $expected = str_replace( array( "\r", "\n" ), ' ', file_get_contents( $files_dir . '/svgCleanOne.svg' ) ); + $sanitized = file_get_contents( $temp ); + $this->assertXmlStringEqualsXmlString( $expected, $sanitized ); + + // Test bad SVG. + $filename = 'badXmlTestOne.svg'; + $temp = tempnam( sys_get_temp_dir(), 'TMP_' ); + $files_dir = __DIR__ . '/files'; + copy( "{$files_dir}/{$filename}", $temp ); + + $file = array( + 'tmp_name' => $temp, + 'name' => $filename, + ); + + $result = $this->instance->check_for_svg( $file ); + $this->assertArrayHasKey( 'error', $result ); + } + + /** + * Test `one_pixel_fix` function. + * This function tests svg_dimensions() as well. + * + * @return void + */ + public function test_one_pixel_fix() { + \WP_Mock::userFunction( + 'get_post_mime_type', + array( + 'args' => 1, + 'return' => 'image/svg+xml', + ) + ); + + \WP_Mock::userFunction( + 'get_attached_file', + array( + 'args' => 1, + 'return_in_order' => array( + __DIR__ . '/files/svgCleanOne.svg', + __DIR__ . '/files/svgNoDimensions.svg', + ), + ) + ); + + // Test SVG Dimensions + $image_sizes = $this->instance->one_pixel_fix( array(), 1, 'thumbnail', false ); + if ( ! empty( $image_sizes ) ) { + $image_sizes = array_map( 'intval', $image_sizes ); + } + $this->assertSame( + array( + 1 => 600, + 2 => 600, + ), + $image_sizes + ); + + // Test Default Dimensions + $image_sizes = $this->instance->one_pixel_fix( array(), 1, 'thumbnail', false ); + if ( ! empty( $image_sizes ) ) { + $image_sizes = array_map( 'intval', $image_sizes ); + } + $this->assertSame( + array( + 1 => 100, + 2 => 100, + ), + $image_sizes + ); + } + + /** + * Test `fix_admin_preview` function. + * + * @return void + */ + public function test_fix_admin_preview() { + \WP_Mock::userFunction( + 'get_attached_file', + array( + 'args' => 1, + 'return' => __DIR__ . '/files/svgCleanOne.svg', + ) + ); + \WP_Mock::passthruFunction( 'get_option' ); + + $response = $this->instance->fix_admin_preview( + array( + 'mime' => 'image/svg+xml', + 'url' => '', + ), + (object) array( 'ID' => 1 ), + array() + ); + + $this->assertIsArray( $response ); + $this->assertSame( 600, intval( $response['width'] ) ); + $this->assertSame( 600, intval( $response['height'] ) ); + } + + /** + * Test `featured_image_fix` function. + * + * @return void + */ + public function test_featured_image_fix() { + \WP_Mock::userFunction( + 'get_post_mime_type', + array( + 'args' => 1, + 'return' => 'image/svg+xml', + ) + ); + + $response = $this->instance->featured_image_fix( 'test', 1, 1 ); + $this->assertSame( 'test', $response ); + } +}