├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .travis.yml ├── .travis.yml.dist ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── phpunit.xml.dist ├── src ├── Facades │ └── Urlbox.php ├── Urlbox.php └── UrlboxProvider.php └── tests ├── UrlboxTest.php └── bootstrap.php /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Run Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | laravel-tests: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: 8.1 20 | coverage: xdebug 21 | 22 | - name: Install Dependencies 23 | run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist 24 | 25 | - name: Execute tests 26 | env: 27 | XDEBUG_MODE: coverage 28 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | .LSOverride 39 | Icon 40 | .AppleDouble 41 | 42 | # Project files # 43 | ###################### 44 | config.json 45 | var/* 46 | logs/* 47 | composer.phar 48 | /vendor/ 49 | .idea 50 | 51 | # Workflow files # 52 | ###################### 53 | _schrott 54 | vendor 55 | node_modules 56 | var/* 57 | logs/* 58 | config.json 59 | .htaccess 60 | tests/build 61 | build 62 | #phpunit.xml 63 | .phpunit.result.cache 64 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - "5.5" 4 | - "5.4" 5 | - "7.0" 6 | 7 | env: 8 | # - DB=mysql 9 | 10 | before_script: 11 | # - composer require --dev aura/sql:1.3 12 | - composer install --dev --no-interaction 13 | # - if [[ "$DB" == "mysql" ]]; then mysql -e "create database IF NOT EXISTS tomkyle_test;" -utravis; fi 14 | 15 | script: 16 | - mkdir -p build/logs 17 | - phpunit 18 | 19 | 20 | after_script: 21 | - php vendor/bin/coveralls -v 22 | -------------------------------------------------------------------------------- /.travis.yml.dist: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - "5.5" 4 | - "5.4" 5 | 6 | env: 7 | # - DB=mysql 8 | 9 | before_script: 10 | # - composer require --dev aura/sql:1.3 11 | - composer install --dev --no-interaction 12 | # - if [[ "$DB" == "mysql" ]]; then mysql -e "create database IF NOT EXISTS tomkyle_test;" -utravis; fi 13 | 14 | script: 15 | - mkdir -p build/logs 16 | - phpunit 17 | 18 | 19 | after_script: 20 | - php vendor/bin/coveralls -v 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Carsten Witt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Urlbox/Screenshots 2 | 3 | ## Capture highly accurate webpage screenshots of any site using Urlbox.io in PHP 4 | 5 | This package uses the [Urlbox.io](https://urlbox.io) screenshot as a service to generate website screenshots. 6 | 7 | ## Installation 8 | 9 | This package can be installed through Composer. 10 | 11 | ```bash 12 | composer require urlbox/screenshots 13 | ``` 14 | 15 | 16 | ### Laravel Setup 17 | 18 | When using Laravel, if you are using a version pre v5.5 you will need to include the Service Provider manually: 19 | 20 | ```php 21 | // app/config/app.php 22 | 23 | 'providers' => [ 24 | // ... 25 | 'Urlbox\Screenshots\UrlboxProvider' 26 | ]; 27 | ``` 28 | 29 | Setup your API keys: 30 | 31 | ```php 32 | // config/services.php 33 | 34 | 'urlbox' => [ 35 | 'key' => env('URLBOX_KEY'), 36 | 'secret' => env('URLBOX_SECRET'), 37 | 'webhook_secret' => env('URLBOX_WEBHOOK_SECRET'), 38 | ]; 39 | ``` 40 | 41 | and in your .env file: 42 | 43 | ```bash 44 | # URLBOX 45 | URLBOX_KEY=YOUR_URLBOX_KEY 46 | URLBOX_SECRET=YOUR_URLBOX_SECRET 47 | URLBOX_WEBHOOK_SECRET=YOUR_URLBOX_WEBHOOK_SECRET 48 | ``` 49 | 50 | You can find your API Credentials and Webhook Secret [here](https://www.urlbox.io/dashboard/projects) 51 | 52 | ## Usage 53 | 54 | You will need a [Urlbox](https://urlbox.io) account to use this package. Please signup [here](https://urlbox.io/pricing) and get your API Key and Secret from the Urlbox dashboard once you have logged in. 55 | 56 | Here is a sample call to generate a Urlbox screenshot URL: 57 | 58 | ```php 59 | use Urlbox\Screenshots\Urlbox; 60 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET' ); 61 | 62 | $options = [ 63 | // only required option is a url: 64 | 'url' => 'example.com', 65 | 66 | // specify any other options to augment the screenshot... 67 | 'width' => 1280, 68 | 'height' => 1024, 69 | ]; 70 | 71 | // Create the Urlbox URL 72 | $urlboxUrl = $urlbox->generateSignedUrl( $options ); 73 | // $urlboxUrl is now 'https://api.urlbox.io/v1/API_KEY/TOKEN/png?url=example.com' 74 | 75 | // Generate a screenshot by loading the Urlbox URL in an img tag: 76 | echo 'Test screenshot generated by Urlbox' 77 | ``` 78 | 79 | If you're using Laravel and have set up the service provider, you can use the Facade provided: 80 | 81 | ```php 82 | use Urlbox\Screenshots\Facades\Urlbox; 83 | 84 | $options = [ 'url' => 'example.com' ]; 85 | $urlboxUrl = Urlbox::generateUrl( $options ); 86 | // $urlboxUrl is now 'https://api.urlbox.io/v1/API_KEY/TOKEN/png?url=example.com' 87 | ``` 88 | 89 | You can now use the result (`$urlboxUrl`) by placing it inside an `` tag as the `src` parameter. 90 | 91 | When you load the image, a screenshot of example.com will be returned. 92 | 93 | ## Options 94 | 95 | You can find the full list of available options within the [docs](https://urlbox.io/docs/options). Below is a list of the most commonly used options: 96 | 97 | | Option | Default | Description | 98 | |-----------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 99 | | `url` | - | The URL of the website you want to screenshot **(Required)** | 100 | | `width` | 1280 | Viewport width of the browser in pixels | 101 | | `height` | 1024 | Viewport height of the browser in pixels | 102 | | `selector` | - | Take a screenshot of the element that matches this selector | 103 | | `thumb_width` | - | Width in pixels of the generated thumbnail, leave off for full-size screenshot | 104 | | `format` | png | Image format of the resulting screenshot image (`png` or `jpg`) | 105 | | `user_agent` | - | User-Agent string used to emulate a particular client. | 106 | | `cookie` | - | Set a cookie value before taking a screenshot. E.g. OptIn=true. Can be set multiple times to set more than one cookie. | 107 | | `delay` | - | Amount of time to wait in milliseconds before urlbox takes the screenshot | 108 | | `wait_for` | - | Waits for the element specified by this selector to be visible on the page before taking a screenshot | 109 | | `timeout` | 30000 | Amount of time to wait in milliseconds for the website at url to respond | 110 | | `full_page` | false | Specify whether to capture the full-length of the website | 111 | | `flash` | false | Enable the flash plugin for flash using websites | 112 | | `force` | false | Take a fresh screenshot instead of getting a cached version | 113 | | `ttl` | 2592000 (30 days) | Short for 'time to live'. Number of seconds to keep a screenshot in the cache. Note the default is also the maximum value for this option. | 114 | | `quality` | 80 | JPEG only - image quality of resulting screenshot (0-100) | 115 | | `disable_js` | false | Turn off javascript on target url to prevent popups | 116 | | `retina` | false | Take a 'retina' or high definition screenshot equivalent to setting a device pixel ratio of 2.0 or @2x. Please note that retina screenshots will be double the normal dimensions and will normally take slightly longer to process due to the much bigger image size. | 117 | | `click` | - | Element selector that is clicked before taking a screenshot e.g. #clickme would click the element with id="clickme" | 118 | | `hover` | - | Element selector that is hovered before taking a screenshot e.g. #hoverme would hover over the element with id="hoverme" | 119 | | `bg_color` | - | Hex code or css color string. Some websites don't set a body background colour, and will show up as transparent backgrounds with PNG or black when using JPG. Use this setting to set a background colour. If the website explicitly sets a transparent background on the html or body elements, this setting will be overridden. | 120 | | `crop_width` | - | Crop the width of the screenshot to this size in pixels | 121 | | `hide_selector` | - | Hides all elements that match the element selector by setting their style to `display:none !important;`. Useful for hiding popups. | 122 | | `highlight` | - | Word to highlight on the page before capturing a screenshot | 123 | | `highlightfg` | white | Text color of the highlighted word | 124 | | `highlightbg` | red | Background color of the highlighted word | 125 | | `use_s3` | false | Save the screenshot directly to the S3 bucket configured on your account | 126 | | `webhook_url` | - | Urlbox will send a POST request back to the supplied URL with data about the screenshot in JSON format once it has completed rendering. | 127 | 128 | *** 129 | 130 | ## Other implementations 131 | 132 | * [Node.js](https://github.com/urlbox-io/urlbox-screenshots-node) 133 | 134 | ## Contributing 135 | 136 | We are open to pull requests. 137 | 138 | ## Security 139 | 140 | If you discover any security related issues, please email services@urlbox.io instead of using the issue tracker. 141 | 142 | ## About Urlbox 143 | 144 | Urlbox is a premium Screenshot as a Service API. It lets you render highly accurate screenshots of webpages and display them anywhere you like. [Urlbox.io](https://urlbox.io). 145 | 146 | ## License 147 | 148 | The MIT License (MIT). 149 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "urlbox/screenshots", 3 | "description": "Use urlbox to easily generate website thumbnail screenshots from a URL", 4 | "homepage": "https://github.com/urlbox/urlbox-php", 5 | "keywords": [ 6 | "convert", 7 | "render", 8 | "capture", 9 | "webpage", 10 | "image", 11 | "screenshot", 12 | "provider", 13 | "facade", 14 | "screenshots", 15 | "urlbox", 16 | "browser", 17 | "headless", 18 | "responsive", 19 | "retina", 20 | "phantomjs", 21 | "laravel", 22 | "thumbnail", 23 | "website", 24 | "preview", 25 | "thumbnails" 26 | ], 27 | "license": "MIT", 28 | "authors": [ 29 | { 30 | "name": "Chris Roebuck", 31 | "email": "chris@urlbox.io", 32 | "role": "Developer" 33 | } 34 | ], 35 | "require": { 36 | "php": "^7.4|^8.0", 37 | "guzzlehttp/guzzle": "^7.5.0", 38 | "ext-json": "*" 39 | }, 40 | "require-dev": { 41 | "phpunit/phpunit": "^9.5.26", 42 | "phpunit/php-code-coverage": "^9.2.18", 43 | "mockery/mockery": "^1.5" 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "Urlbox\\Screenshots\\": "src/" 48 | } 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Urlbox\\Screenshots\\Tests\\": "tests/" 53 | } 54 | }, 55 | "extra": { 56 | "laravel": { 57 | "providers": [ 58 | "Urlbox\\Screenshots\\UrlboxProvider" 59 | ], 60 | "aliases": { 61 | "Urlbox": "Urlbox\\Screenshots\\Facade\\Urlbox" 62 | } 63 | } 64 | }, 65 | "config": { 66 | "allow-plugins": { 67 | "symfony/flex": true 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "baa09048ac3d6f09e083fdb16ce3f546", 8 | "packages": [ 9 | { 10 | "name": "guzzlehttp/guzzle", 11 | "version": "7.5.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/guzzle/guzzle.git", 15 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", 20 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-json": "*", 25 | "guzzlehttp/promises": "^1.5", 26 | "guzzlehttp/psr7": "^1.9 || ^2.4", 27 | "php": "^7.2.5 || ^8.0", 28 | "psr/http-client": "^1.0", 29 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 30 | }, 31 | "provide": { 32 | "psr/http-client-implementation": "1.0" 33 | }, 34 | "require-dev": { 35 | "bamarni/composer-bin-plugin": "^1.8.1", 36 | "ext-curl": "*", 37 | "php-http/client-integration-tests": "^3.0", 38 | "phpunit/phpunit": "^8.5.29 || ^9.5.23", 39 | "psr/log": "^1.1 || ^2.0 || ^3.0" 40 | }, 41 | "suggest": { 42 | "ext-curl": "Required for CURL handler support", 43 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 44 | "psr/log": "Required for using the Log middleware" 45 | }, 46 | "type": "library", 47 | "extra": { 48 | "bamarni-bin": { 49 | "bin-links": true, 50 | "forward-command": false 51 | }, 52 | "branch-alias": { 53 | "dev-master": "7.5-dev" 54 | } 55 | }, 56 | "autoload": { 57 | "files": [ 58 | "src/functions_include.php" 59 | ], 60 | "psr-4": { 61 | "GuzzleHttp\\": "src/" 62 | } 63 | }, 64 | "notification-url": "https://packagist.org/downloads/", 65 | "license": [ 66 | "MIT" 67 | ], 68 | "authors": [ 69 | { 70 | "name": "Graham Campbell", 71 | "email": "hello@gjcampbell.co.uk", 72 | "homepage": "https://github.com/GrahamCampbell" 73 | }, 74 | { 75 | "name": "Michael Dowling", 76 | "email": "mtdowling@gmail.com", 77 | "homepage": "https://github.com/mtdowling" 78 | }, 79 | { 80 | "name": "Jeremy Lindblom", 81 | "email": "jeremeamia@gmail.com", 82 | "homepage": "https://github.com/jeremeamia" 83 | }, 84 | { 85 | "name": "George Mponos", 86 | "email": "gmponos@gmail.com", 87 | "homepage": "https://github.com/gmponos" 88 | }, 89 | { 90 | "name": "Tobias Nyholm", 91 | "email": "tobias.nyholm@gmail.com", 92 | "homepage": "https://github.com/Nyholm" 93 | }, 94 | { 95 | "name": "Márk Sági-Kazár", 96 | "email": "mark.sagikazar@gmail.com", 97 | "homepage": "https://github.com/sagikazarmark" 98 | }, 99 | { 100 | "name": "Tobias Schultze", 101 | "email": "webmaster@tubo-world.de", 102 | "homepage": "https://github.com/Tobion" 103 | } 104 | ], 105 | "description": "Guzzle is a PHP HTTP client library", 106 | "keywords": [ 107 | "client", 108 | "curl", 109 | "framework", 110 | "http", 111 | "http client", 112 | "psr-18", 113 | "psr-7", 114 | "rest", 115 | "web service" 116 | ], 117 | "support": { 118 | "issues": "https://github.com/guzzle/guzzle/issues", 119 | "source": "https://github.com/guzzle/guzzle/tree/7.5.0" 120 | }, 121 | "funding": [ 122 | { 123 | "url": "https://github.com/GrahamCampbell", 124 | "type": "github" 125 | }, 126 | { 127 | "url": "https://github.com/Nyholm", 128 | "type": "github" 129 | }, 130 | { 131 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 132 | "type": "tidelift" 133 | } 134 | ], 135 | "time": "2022-08-28T15:39:27+00:00" 136 | }, 137 | { 138 | "name": "guzzlehttp/promises", 139 | "version": "1.5.2", 140 | "source": { 141 | "type": "git", 142 | "url": "https://github.com/guzzle/promises.git", 143 | "reference": "b94b2807d85443f9719887892882d0329d1e2598" 144 | }, 145 | "dist": { 146 | "type": "zip", 147 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", 148 | "reference": "b94b2807d85443f9719887892882d0329d1e2598", 149 | "shasum": "" 150 | }, 151 | "require": { 152 | "php": ">=5.5" 153 | }, 154 | "require-dev": { 155 | "symfony/phpunit-bridge": "^4.4 || ^5.1" 156 | }, 157 | "type": "library", 158 | "extra": { 159 | "branch-alias": { 160 | "dev-master": "1.5-dev" 161 | } 162 | }, 163 | "autoload": { 164 | "files": [ 165 | "src/functions_include.php" 166 | ], 167 | "psr-4": { 168 | "GuzzleHttp\\Promise\\": "src/" 169 | } 170 | }, 171 | "notification-url": "https://packagist.org/downloads/", 172 | "license": [ 173 | "MIT" 174 | ], 175 | "authors": [ 176 | { 177 | "name": "Graham Campbell", 178 | "email": "hello@gjcampbell.co.uk", 179 | "homepage": "https://github.com/GrahamCampbell" 180 | }, 181 | { 182 | "name": "Michael Dowling", 183 | "email": "mtdowling@gmail.com", 184 | "homepage": "https://github.com/mtdowling" 185 | }, 186 | { 187 | "name": "Tobias Nyholm", 188 | "email": "tobias.nyholm@gmail.com", 189 | "homepage": "https://github.com/Nyholm" 190 | }, 191 | { 192 | "name": "Tobias Schultze", 193 | "email": "webmaster@tubo-world.de", 194 | "homepage": "https://github.com/Tobion" 195 | } 196 | ], 197 | "description": "Guzzle promises library", 198 | "keywords": [ 199 | "promise" 200 | ], 201 | "support": { 202 | "issues": "https://github.com/guzzle/promises/issues", 203 | "source": "https://github.com/guzzle/promises/tree/1.5.2" 204 | }, 205 | "funding": [ 206 | { 207 | "url": "https://github.com/GrahamCampbell", 208 | "type": "github" 209 | }, 210 | { 211 | "url": "https://github.com/Nyholm", 212 | "type": "github" 213 | }, 214 | { 215 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 216 | "type": "tidelift" 217 | } 218 | ], 219 | "time": "2022-08-28T14:55:35+00:00" 220 | }, 221 | { 222 | "name": "guzzlehttp/psr7", 223 | "version": "2.6.1", 224 | "source": { 225 | "type": "git", 226 | "url": "https://github.com/guzzle/psr7.git", 227 | "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" 228 | }, 229 | "dist": { 230 | "type": "zip", 231 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", 232 | "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", 233 | "shasum": "" 234 | }, 235 | "require": { 236 | "php": "^7.2.5 || ^8.0", 237 | "psr/http-factory": "^1.0", 238 | "psr/http-message": "^1.1 || ^2.0", 239 | "ralouphie/getallheaders": "^3.0" 240 | }, 241 | "provide": { 242 | "psr/http-factory-implementation": "1.0", 243 | "psr/http-message-implementation": "1.0" 244 | }, 245 | "require-dev": { 246 | "bamarni/composer-bin-plugin": "^1.8.1", 247 | "http-interop/http-factory-tests": "^0.9", 248 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 249 | }, 250 | "suggest": { 251 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 252 | }, 253 | "type": "library", 254 | "extra": { 255 | "bamarni-bin": { 256 | "bin-links": true, 257 | "forward-command": false 258 | } 259 | }, 260 | "autoload": { 261 | "psr-4": { 262 | "GuzzleHttp\\Psr7\\": "src/" 263 | } 264 | }, 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "MIT" 268 | ], 269 | "authors": [ 270 | { 271 | "name": "Graham Campbell", 272 | "email": "hello@gjcampbell.co.uk", 273 | "homepage": "https://github.com/GrahamCampbell" 274 | }, 275 | { 276 | "name": "Michael Dowling", 277 | "email": "mtdowling@gmail.com", 278 | "homepage": "https://github.com/mtdowling" 279 | }, 280 | { 281 | "name": "George Mponos", 282 | "email": "gmponos@gmail.com", 283 | "homepage": "https://github.com/gmponos" 284 | }, 285 | { 286 | "name": "Tobias Nyholm", 287 | "email": "tobias.nyholm@gmail.com", 288 | "homepage": "https://github.com/Nyholm" 289 | }, 290 | { 291 | "name": "Márk Sági-Kazár", 292 | "email": "mark.sagikazar@gmail.com", 293 | "homepage": "https://github.com/sagikazarmark" 294 | }, 295 | { 296 | "name": "Tobias Schultze", 297 | "email": "webmaster@tubo-world.de", 298 | "homepage": "https://github.com/Tobion" 299 | }, 300 | { 301 | "name": "Márk Sági-Kazár", 302 | "email": "mark.sagikazar@gmail.com", 303 | "homepage": "https://sagikazarmark.hu" 304 | } 305 | ], 306 | "description": "PSR-7 message implementation that also provides common utility methods", 307 | "keywords": [ 308 | "http", 309 | "message", 310 | "psr-7", 311 | "request", 312 | "response", 313 | "stream", 314 | "uri", 315 | "url" 316 | ], 317 | "support": { 318 | "issues": "https://github.com/guzzle/psr7/issues", 319 | "source": "https://github.com/guzzle/psr7/tree/2.6.1" 320 | }, 321 | "funding": [ 322 | { 323 | "url": "https://github.com/GrahamCampbell", 324 | "type": "github" 325 | }, 326 | { 327 | "url": "https://github.com/Nyholm", 328 | "type": "github" 329 | }, 330 | { 331 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 332 | "type": "tidelift" 333 | } 334 | ], 335 | "time": "2023-08-27T10:13:57+00:00" 336 | }, 337 | { 338 | "name": "psr/http-client", 339 | "version": "1.0.1", 340 | "source": { 341 | "type": "git", 342 | "url": "https://github.com/php-fig/http-client.git", 343 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" 344 | }, 345 | "dist": { 346 | "type": "zip", 347 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 348 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 349 | "shasum": "" 350 | }, 351 | "require": { 352 | "php": "^7.0 || ^8.0", 353 | "psr/http-message": "^1.0" 354 | }, 355 | "type": "library", 356 | "extra": { 357 | "branch-alias": { 358 | "dev-master": "1.0.x-dev" 359 | } 360 | }, 361 | "autoload": { 362 | "psr-4": { 363 | "Psr\\Http\\Client\\": "src/" 364 | } 365 | }, 366 | "notification-url": "https://packagist.org/downloads/", 367 | "license": [ 368 | "MIT" 369 | ], 370 | "authors": [ 371 | { 372 | "name": "PHP-FIG", 373 | "homepage": "http://www.php-fig.org/" 374 | } 375 | ], 376 | "description": "Common interface for HTTP clients", 377 | "homepage": "https://github.com/php-fig/http-client", 378 | "keywords": [ 379 | "http", 380 | "http-client", 381 | "psr", 382 | "psr-18" 383 | ], 384 | "support": { 385 | "source": "https://github.com/php-fig/http-client/tree/master" 386 | }, 387 | "time": "2020-06-29T06:28:15+00:00" 388 | }, 389 | { 390 | "name": "psr/http-factory", 391 | "version": "1.0.2", 392 | "source": { 393 | "type": "git", 394 | "url": "https://github.com/php-fig/http-factory.git", 395 | "reference": "e616d01114759c4c489f93b099585439f795fe35" 396 | }, 397 | "dist": { 398 | "type": "zip", 399 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", 400 | "reference": "e616d01114759c4c489f93b099585439f795fe35", 401 | "shasum": "" 402 | }, 403 | "require": { 404 | "php": ">=7.0.0", 405 | "psr/http-message": "^1.0 || ^2.0" 406 | }, 407 | "type": "library", 408 | "extra": { 409 | "branch-alias": { 410 | "dev-master": "1.0.x-dev" 411 | } 412 | }, 413 | "autoload": { 414 | "psr-4": { 415 | "Psr\\Http\\Message\\": "src/" 416 | } 417 | }, 418 | "notification-url": "https://packagist.org/downloads/", 419 | "license": [ 420 | "MIT" 421 | ], 422 | "authors": [ 423 | { 424 | "name": "PHP-FIG", 425 | "homepage": "https://www.php-fig.org/" 426 | } 427 | ], 428 | "description": "Common interfaces for PSR-7 HTTP message factories", 429 | "keywords": [ 430 | "factory", 431 | "http", 432 | "message", 433 | "psr", 434 | "psr-17", 435 | "psr-7", 436 | "request", 437 | "response" 438 | ], 439 | "support": { 440 | "source": "https://github.com/php-fig/http-factory/tree/1.0.2" 441 | }, 442 | "time": "2023-04-10T20:10:41+00:00" 443 | }, 444 | { 445 | "name": "psr/http-message", 446 | "version": "1.1", 447 | "source": { 448 | "type": "git", 449 | "url": "https://github.com/php-fig/http-message.git", 450 | "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" 451 | }, 452 | "dist": { 453 | "type": "zip", 454 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", 455 | "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", 456 | "shasum": "" 457 | }, 458 | "require": { 459 | "php": "^7.2 || ^8.0" 460 | }, 461 | "type": "library", 462 | "extra": { 463 | "branch-alias": { 464 | "dev-master": "1.1.x-dev" 465 | } 466 | }, 467 | "autoload": { 468 | "psr-4": { 469 | "Psr\\Http\\Message\\": "src/" 470 | } 471 | }, 472 | "notification-url": "https://packagist.org/downloads/", 473 | "license": [ 474 | "MIT" 475 | ], 476 | "authors": [ 477 | { 478 | "name": "PHP-FIG", 479 | "homepage": "http://www.php-fig.org/" 480 | } 481 | ], 482 | "description": "Common interface for HTTP messages", 483 | "homepage": "https://github.com/php-fig/http-message", 484 | "keywords": [ 485 | "http", 486 | "http-message", 487 | "psr", 488 | "psr-7", 489 | "request", 490 | "response" 491 | ], 492 | "support": { 493 | "source": "https://github.com/php-fig/http-message/tree/1.1" 494 | }, 495 | "time": "2023-04-04T09:50:52+00:00" 496 | }, 497 | { 498 | "name": "ralouphie/getallheaders", 499 | "version": "3.0.3", 500 | "source": { 501 | "type": "git", 502 | "url": "https://github.com/ralouphie/getallheaders.git", 503 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 504 | }, 505 | "dist": { 506 | "type": "zip", 507 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 508 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 509 | "shasum": "" 510 | }, 511 | "require": { 512 | "php": ">=5.6" 513 | }, 514 | "require-dev": { 515 | "php-coveralls/php-coveralls": "^2.1", 516 | "phpunit/phpunit": "^5 || ^6.5" 517 | }, 518 | "type": "library", 519 | "autoload": { 520 | "files": [ 521 | "src/getallheaders.php" 522 | ] 523 | }, 524 | "notification-url": "https://packagist.org/downloads/", 525 | "license": [ 526 | "MIT" 527 | ], 528 | "authors": [ 529 | { 530 | "name": "Ralph Khattar", 531 | "email": "ralph.khattar@gmail.com" 532 | } 533 | ], 534 | "description": "A polyfill for getallheaders.", 535 | "support": { 536 | "issues": "https://github.com/ralouphie/getallheaders/issues", 537 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 538 | }, 539 | "time": "2019-03-08T08:55:37+00:00" 540 | }, 541 | { 542 | "name": "symfony/deprecation-contracts", 543 | "version": "v3.2.1", 544 | "source": { 545 | "type": "git", 546 | "url": "https://github.com/symfony/deprecation-contracts.git", 547 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" 548 | }, 549 | "dist": { 550 | "type": "zip", 551 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", 552 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", 553 | "shasum": "" 554 | }, 555 | "require": { 556 | "php": ">=8.1" 557 | }, 558 | "type": "library", 559 | "extra": { 560 | "branch-alias": { 561 | "dev-main": "3.3-dev" 562 | }, 563 | "thanks": { 564 | "name": "symfony/contracts", 565 | "url": "https://github.com/symfony/contracts" 566 | } 567 | }, 568 | "autoload": { 569 | "files": [ 570 | "function.php" 571 | ] 572 | }, 573 | "notification-url": "https://packagist.org/downloads/", 574 | "license": [ 575 | "MIT" 576 | ], 577 | "authors": [ 578 | { 579 | "name": "Nicolas Grekas", 580 | "email": "p@tchwork.com" 581 | }, 582 | { 583 | "name": "Symfony Community", 584 | "homepage": "https://symfony.com/contributors" 585 | } 586 | ], 587 | "description": "A generic function and convention to trigger deprecation notices", 588 | "homepage": "https://symfony.com", 589 | "support": { 590 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" 591 | }, 592 | "funding": [ 593 | { 594 | "url": "https://symfony.com/sponsor", 595 | "type": "custom" 596 | }, 597 | { 598 | "url": "https://github.com/fabpot", 599 | "type": "github" 600 | }, 601 | { 602 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 603 | "type": "tidelift" 604 | } 605 | ], 606 | "time": "2023-03-01T10:25:55+00:00" 607 | } 608 | ], 609 | "packages-dev": [ 610 | { 611 | "name": "doctrine/instantiator", 612 | "version": "2.0.0", 613 | "source": { 614 | "type": "git", 615 | "url": "https://github.com/doctrine/instantiator.git", 616 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" 617 | }, 618 | "dist": { 619 | "type": "zip", 620 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 621 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 622 | "shasum": "" 623 | }, 624 | "require": { 625 | "php": "^8.1" 626 | }, 627 | "require-dev": { 628 | "doctrine/coding-standard": "^11", 629 | "ext-pdo": "*", 630 | "ext-phar": "*", 631 | "phpbench/phpbench": "^1.2", 632 | "phpstan/phpstan": "^1.9.4", 633 | "phpstan/phpstan-phpunit": "^1.3", 634 | "phpunit/phpunit": "^9.5.27", 635 | "vimeo/psalm": "^5.4" 636 | }, 637 | "type": "library", 638 | "autoload": { 639 | "psr-4": { 640 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 641 | } 642 | }, 643 | "notification-url": "https://packagist.org/downloads/", 644 | "license": [ 645 | "MIT" 646 | ], 647 | "authors": [ 648 | { 649 | "name": "Marco Pivetta", 650 | "email": "ocramius@gmail.com", 651 | "homepage": "https://ocramius.github.io/" 652 | } 653 | ], 654 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 655 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 656 | "keywords": [ 657 | "constructor", 658 | "instantiate" 659 | ], 660 | "support": { 661 | "issues": "https://github.com/doctrine/instantiator/issues", 662 | "source": "https://github.com/doctrine/instantiator/tree/2.0.0" 663 | }, 664 | "funding": [ 665 | { 666 | "url": "https://www.doctrine-project.org/sponsorship.html", 667 | "type": "custom" 668 | }, 669 | { 670 | "url": "https://www.patreon.com/phpdoctrine", 671 | "type": "patreon" 672 | }, 673 | { 674 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 675 | "type": "tidelift" 676 | } 677 | ], 678 | "time": "2022-12-30T00:23:10+00:00" 679 | }, 680 | { 681 | "name": "hamcrest/hamcrest-php", 682 | "version": "v2.0.1", 683 | "source": { 684 | "type": "git", 685 | "url": "https://github.com/hamcrest/hamcrest-php.git", 686 | "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" 687 | }, 688 | "dist": { 689 | "type": "zip", 690 | "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", 691 | "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", 692 | "shasum": "" 693 | }, 694 | "require": { 695 | "php": "^5.3|^7.0|^8.0" 696 | }, 697 | "replace": { 698 | "cordoval/hamcrest-php": "*", 699 | "davedevelopment/hamcrest-php": "*", 700 | "kodova/hamcrest-php": "*" 701 | }, 702 | "require-dev": { 703 | "phpunit/php-file-iterator": "^1.4 || ^2.0", 704 | "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" 705 | }, 706 | "type": "library", 707 | "extra": { 708 | "branch-alias": { 709 | "dev-master": "2.1-dev" 710 | } 711 | }, 712 | "autoload": { 713 | "classmap": [ 714 | "hamcrest" 715 | ] 716 | }, 717 | "notification-url": "https://packagist.org/downloads/", 718 | "license": [ 719 | "BSD-3-Clause" 720 | ], 721 | "description": "This is the PHP port of Hamcrest Matchers", 722 | "keywords": [ 723 | "test" 724 | ], 725 | "support": { 726 | "issues": "https://github.com/hamcrest/hamcrest-php/issues", 727 | "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" 728 | }, 729 | "time": "2020-07-09T08:09:16+00:00" 730 | }, 731 | { 732 | "name": "mockery/mockery", 733 | "version": "1.5.1", 734 | "source": { 735 | "type": "git", 736 | "url": "https://github.com/mockery/mockery.git", 737 | "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" 738 | }, 739 | "dist": { 740 | "type": "zip", 741 | "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", 742 | "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", 743 | "shasum": "" 744 | }, 745 | "require": { 746 | "hamcrest/hamcrest-php": "^2.0.1", 747 | "lib-pcre": ">=7.0", 748 | "php": "^7.3 || ^8.0" 749 | }, 750 | "conflict": { 751 | "phpunit/phpunit": "<8.0" 752 | }, 753 | "require-dev": { 754 | "phpunit/phpunit": "^8.5 || ^9.3" 755 | }, 756 | "type": "library", 757 | "extra": { 758 | "branch-alias": { 759 | "dev-master": "1.4.x-dev" 760 | } 761 | }, 762 | "autoload": { 763 | "psr-0": { 764 | "Mockery": "library/" 765 | } 766 | }, 767 | "notification-url": "https://packagist.org/downloads/", 768 | "license": [ 769 | "BSD-3-Clause" 770 | ], 771 | "authors": [ 772 | { 773 | "name": "Pádraic Brady", 774 | "email": "padraic.brady@gmail.com", 775 | "homepage": "http://blog.astrumfutura.com" 776 | }, 777 | { 778 | "name": "Dave Marshall", 779 | "email": "dave.marshall@atstsolutions.co.uk", 780 | "homepage": "http://davedevelopment.co.uk" 781 | } 782 | ], 783 | "description": "Mockery is a simple yet flexible PHP mock object framework", 784 | "homepage": "https://github.com/mockery/mockery", 785 | "keywords": [ 786 | "BDD", 787 | "TDD", 788 | "library", 789 | "mock", 790 | "mock objects", 791 | "mockery", 792 | "stub", 793 | "test", 794 | "test double", 795 | "testing" 796 | ], 797 | "support": { 798 | "issues": "https://github.com/mockery/mockery/issues", 799 | "source": "https://github.com/mockery/mockery/tree/1.5.1" 800 | }, 801 | "time": "2022-09-07T15:32:08+00:00" 802 | }, 803 | { 804 | "name": "myclabs/deep-copy", 805 | "version": "1.11.1", 806 | "source": { 807 | "type": "git", 808 | "url": "https://github.com/myclabs/DeepCopy.git", 809 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 810 | }, 811 | "dist": { 812 | "type": "zip", 813 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 814 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 815 | "shasum": "" 816 | }, 817 | "require": { 818 | "php": "^7.1 || ^8.0" 819 | }, 820 | "conflict": { 821 | "doctrine/collections": "<1.6.8", 822 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 823 | }, 824 | "require-dev": { 825 | "doctrine/collections": "^1.6.8", 826 | "doctrine/common": "^2.13.3 || ^3.2.2", 827 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 828 | }, 829 | "type": "library", 830 | "autoload": { 831 | "files": [ 832 | "src/DeepCopy/deep_copy.php" 833 | ], 834 | "psr-4": { 835 | "DeepCopy\\": "src/DeepCopy/" 836 | } 837 | }, 838 | "notification-url": "https://packagist.org/downloads/", 839 | "license": [ 840 | "MIT" 841 | ], 842 | "description": "Create deep copies (clones) of your objects", 843 | "keywords": [ 844 | "clone", 845 | "copy", 846 | "duplicate", 847 | "object", 848 | "object graph" 849 | ], 850 | "support": { 851 | "issues": "https://github.com/myclabs/DeepCopy/issues", 852 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 853 | }, 854 | "funding": [ 855 | { 856 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 857 | "type": "tidelift" 858 | } 859 | ], 860 | "time": "2023-03-08T13:26:56+00:00" 861 | }, 862 | { 863 | "name": "nikic/php-parser", 864 | "version": "v4.15.4", 865 | "source": { 866 | "type": "git", 867 | "url": "https://github.com/nikic/PHP-Parser.git", 868 | "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" 869 | }, 870 | "dist": { 871 | "type": "zip", 872 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", 873 | "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", 874 | "shasum": "" 875 | }, 876 | "require": { 877 | "ext-tokenizer": "*", 878 | "php": ">=7.0" 879 | }, 880 | "require-dev": { 881 | "ircmaxell/php-yacc": "^0.0.7", 882 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 883 | }, 884 | "bin": [ 885 | "bin/php-parse" 886 | ], 887 | "type": "library", 888 | "extra": { 889 | "branch-alias": { 890 | "dev-master": "4.9-dev" 891 | } 892 | }, 893 | "autoload": { 894 | "psr-4": { 895 | "PhpParser\\": "lib/PhpParser" 896 | } 897 | }, 898 | "notification-url": "https://packagist.org/downloads/", 899 | "license": [ 900 | "BSD-3-Clause" 901 | ], 902 | "authors": [ 903 | { 904 | "name": "Nikita Popov" 905 | } 906 | ], 907 | "description": "A PHP parser written in PHP", 908 | "keywords": [ 909 | "parser", 910 | "php" 911 | ], 912 | "support": { 913 | "issues": "https://github.com/nikic/PHP-Parser/issues", 914 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" 915 | }, 916 | "time": "2023-03-05T19:49:14+00:00" 917 | }, 918 | { 919 | "name": "phar-io/manifest", 920 | "version": "2.0.3", 921 | "source": { 922 | "type": "git", 923 | "url": "https://github.com/phar-io/manifest.git", 924 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 925 | }, 926 | "dist": { 927 | "type": "zip", 928 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 929 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 930 | "shasum": "" 931 | }, 932 | "require": { 933 | "ext-dom": "*", 934 | "ext-phar": "*", 935 | "ext-xmlwriter": "*", 936 | "phar-io/version": "^3.0.1", 937 | "php": "^7.2 || ^8.0" 938 | }, 939 | "type": "library", 940 | "extra": { 941 | "branch-alias": { 942 | "dev-master": "2.0.x-dev" 943 | } 944 | }, 945 | "autoload": { 946 | "classmap": [ 947 | "src/" 948 | ] 949 | }, 950 | "notification-url": "https://packagist.org/downloads/", 951 | "license": [ 952 | "BSD-3-Clause" 953 | ], 954 | "authors": [ 955 | { 956 | "name": "Arne Blankerts", 957 | "email": "arne@blankerts.de", 958 | "role": "Developer" 959 | }, 960 | { 961 | "name": "Sebastian Heuer", 962 | "email": "sebastian@phpeople.de", 963 | "role": "Developer" 964 | }, 965 | { 966 | "name": "Sebastian Bergmann", 967 | "email": "sebastian@phpunit.de", 968 | "role": "Developer" 969 | } 970 | ], 971 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 972 | "support": { 973 | "issues": "https://github.com/phar-io/manifest/issues", 974 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 975 | }, 976 | "time": "2021-07-20T11:28:43+00:00" 977 | }, 978 | { 979 | "name": "phar-io/version", 980 | "version": "3.2.1", 981 | "source": { 982 | "type": "git", 983 | "url": "https://github.com/phar-io/version.git", 984 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 985 | }, 986 | "dist": { 987 | "type": "zip", 988 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 989 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 990 | "shasum": "" 991 | }, 992 | "require": { 993 | "php": "^7.2 || ^8.0" 994 | }, 995 | "type": "library", 996 | "autoload": { 997 | "classmap": [ 998 | "src/" 999 | ] 1000 | }, 1001 | "notification-url": "https://packagist.org/downloads/", 1002 | "license": [ 1003 | "BSD-3-Clause" 1004 | ], 1005 | "authors": [ 1006 | { 1007 | "name": "Arne Blankerts", 1008 | "email": "arne@blankerts.de", 1009 | "role": "Developer" 1010 | }, 1011 | { 1012 | "name": "Sebastian Heuer", 1013 | "email": "sebastian@phpeople.de", 1014 | "role": "Developer" 1015 | }, 1016 | { 1017 | "name": "Sebastian Bergmann", 1018 | "email": "sebastian@phpunit.de", 1019 | "role": "Developer" 1020 | } 1021 | ], 1022 | "description": "Library for handling version information and constraints", 1023 | "support": { 1024 | "issues": "https://github.com/phar-io/version/issues", 1025 | "source": "https://github.com/phar-io/version/tree/3.2.1" 1026 | }, 1027 | "time": "2022-02-21T01:04:05+00:00" 1028 | }, 1029 | { 1030 | "name": "phpunit/php-code-coverage", 1031 | "version": "9.2.26", 1032 | "source": { 1033 | "type": "git", 1034 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1035 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" 1036 | }, 1037 | "dist": { 1038 | "type": "zip", 1039 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 1040 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 1041 | "shasum": "" 1042 | }, 1043 | "require": { 1044 | "ext-dom": "*", 1045 | "ext-libxml": "*", 1046 | "ext-xmlwriter": "*", 1047 | "nikic/php-parser": "^4.15", 1048 | "php": ">=7.3", 1049 | "phpunit/php-file-iterator": "^3.0.3", 1050 | "phpunit/php-text-template": "^2.0.2", 1051 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 1052 | "sebastian/complexity": "^2.0", 1053 | "sebastian/environment": "^5.1.2", 1054 | "sebastian/lines-of-code": "^1.0.3", 1055 | "sebastian/version": "^3.0.1", 1056 | "theseer/tokenizer": "^1.2.0" 1057 | }, 1058 | "require-dev": { 1059 | "phpunit/phpunit": "^9.3" 1060 | }, 1061 | "suggest": { 1062 | "ext-pcov": "PHP extension that provides line coverage", 1063 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 1064 | }, 1065 | "type": "library", 1066 | "extra": { 1067 | "branch-alias": { 1068 | "dev-master": "9.2-dev" 1069 | } 1070 | }, 1071 | "autoload": { 1072 | "classmap": [ 1073 | "src/" 1074 | ] 1075 | }, 1076 | "notification-url": "https://packagist.org/downloads/", 1077 | "license": [ 1078 | "BSD-3-Clause" 1079 | ], 1080 | "authors": [ 1081 | { 1082 | "name": "Sebastian Bergmann", 1083 | "email": "sebastian@phpunit.de", 1084 | "role": "lead" 1085 | } 1086 | ], 1087 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1088 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1089 | "keywords": [ 1090 | "coverage", 1091 | "testing", 1092 | "xunit" 1093 | ], 1094 | "support": { 1095 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 1096 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" 1097 | }, 1098 | "funding": [ 1099 | { 1100 | "url": "https://github.com/sebastianbergmann", 1101 | "type": "github" 1102 | } 1103 | ], 1104 | "time": "2023-03-06T12:58:08+00:00" 1105 | }, 1106 | { 1107 | "name": "phpunit/php-file-iterator", 1108 | "version": "3.0.6", 1109 | "source": { 1110 | "type": "git", 1111 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1112 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 1113 | }, 1114 | "dist": { 1115 | "type": "zip", 1116 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 1117 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 1118 | "shasum": "" 1119 | }, 1120 | "require": { 1121 | "php": ">=7.3" 1122 | }, 1123 | "require-dev": { 1124 | "phpunit/phpunit": "^9.3" 1125 | }, 1126 | "type": "library", 1127 | "extra": { 1128 | "branch-alias": { 1129 | "dev-master": "3.0-dev" 1130 | } 1131 | }, 1132 | "autoload": { 1133 | "classmap": [ 1134 | "src/" 1135 | ] 1136 | }, 1137 | "notification-url": "https://packagist.org/downloads/", 1138 | "license": [ 1139 | "BSD-3-Clause" 1140 | ], 1141 | "authors": [ 1142 | { 1143 | "name": "Sebastian Bergmann", 1144 | "email": "sebastian@phpunit.de", 1145 | "role": "lead" 1146 | } 1147 | ], 1148 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1149 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1150 | "keywords": [ 1151 | "filesystem", 1152 | "iterator" 1153 | ], 1154 | "support": { 1155 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 1156 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 1157 | }, 1158 | "funding": [ 1159 | { 1160 | "url": "https://github.com/sebastianbergmann", 1161 | "type": "github" 1162 | } 1163 | ], 1164 | "time": "2021-12-02T12:48:52+00:00" 1165 | }, 1166 | { 1167 | "name": "phpunit/php-invoker", 1168 | "version": "3.1.1", 1169 | "source": { 1170 | "type": "git", 1171 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 1172 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 1173 | }, 1174 | "dist": { 1175 | "type": "zip", 1176 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 1177 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 1178 | "shasum": "" 1179 | }, 1180 | "require": { 1181 | "php": ">=7.3" 1182 | }, 1183 | "require-dev": { 1184 | "ext-pcntl": "*", 1185 | "phpunit/phpunit": "^9.3" 1186 | }, 1187 | "suggest": { 1188 | "ext-pcntl": "*" 1189 | }, 1190 | "type": "library", 1191 | "extra": { 1192 | "branch-alias": { 1193 | "dev-master": "3.1-dev" 1194 | } 1195 | }, 1196 | "autoload": { 1197 | "classmap": [ 1198 | "src/" 1199 | ] 1200 | }, 1201 | "notification-url": "https://packagist.org/downloads/", 1202 | "license": [ 1203 | "BSD-3-Clause" 1204 | ], 1205 | "authors": [ 1206 | { 1207 | "name": "Sebastian Bergmann", 1208 | "email": "sebastian@phpunit.de", 1209 | "role": "lead" 1210 | } 1211 | ], 1212 | "description": "Invoke callables with a timeout", 1213 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 1214 | "keywords": [ 1215 | "process" 1216 | ], 1217 | "support": { 1218 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 1219 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 1220 | }, 1221 | "funding": [ 1222 | { 1223 | "url": "https://github.com/sebastianbergmann", 1224 | "type": "github" 1225 | } 1226 | ], 1227 | "time": "2020-09-28T05:58:55+00:00" 1228 | }, 1229 | { 1230 | "name": "phpunit/php-text-template", 1231 | "version": "2.0.4", 1232 | "source": { 1233 | "type": "git", 1234 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1235 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 1236 | }, 1237 | "dist": { 1238 | "type": "zip", 1239 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1240 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1241 | "shasum": "" 1242 | }, 1243 | "require": { 1244 | "php": ">=7.3" 1245 | }, 1246 | "require-dev": { 1247 | "phpunit/phpunit": "^9.3" 1248 | }, 1249 | "type": "library", 1250 | "extra": { 1251 | "branch-alias": { 1252 | "dev-master": "2.0-dev" 1253 | } 1254 | }, 1255 | "autoload": { 1256 | "classmap": [ 1257 | "src/" 1258 | ] 1259 | }, 1260 | "notification-url": "https://packagist.org/downloads/", 1261 | "license": [ 1262 | "BSD-3-Clause" 1263 | ], 1264 | "authors": [ 1265 | { 1266 | "name": "Sebastian Bergmann", 1267 | "email": "sebastian@phpunit.de", 1268 | "role": "lead" 1269 | } 1270 | ], 1271 | "description": "Simple template engine.", 1272 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1273 | "keywords": [ 1274 | "template" 1275 | ], 1276 | "support": { 1277 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 1278 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 1279 | }, 1280 | "funding": [ 1281 | { 1282 | "url": "https://github.com/sebastianbergmann", 1283 | "type": "github" 1284 | } 1285 | ], 1286 | "time": "2020-10-26T05:33:50+00:00" 1287 | }, 1288 | { 1289 | "name": "phpunit/php-timer", 1290 | "version": "5.0.3", 1291 | "source": { 1292 | "type": "git", 1293 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1294 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 1295 | }, 1296 | "dist": { 1297 | "type": "zip", 1298 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1299 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1300 | "shasum": "" 1301 | }, 1302 | "require": { 1303 | "php": ">=7.3" 1304 | }, 1305 | "require-dev": { 1306 | "phpunit/phpunit": "^9.3" 1307 | }, 1308 | "type": "library", 1309 | "extra": { 1310 | "branch-alias": { 1311 | "dev-master": "5.0-dev" 1312 | } 1313 | }, 1314 | "autoload": { 1315 | "classmap": [ 1316 | "src/" 1317 | ] 1318 | }, 1319 | "notification-url": "https://packagist.org/downloads/", 1320 | "license": [ 1321 | "BSD-3-Clause" 1322 | ], 1323 | "authors": [ 1324 | { 1325 | "name": "Sebastian Bergmann", 1326 | "email": "sebastian@phpunit.de", 1327 | "role": "lead" 1328 | } 1329 | ], 1330 | "description": "Utility class for timing", 1331 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1332 | "keywords": [ 1333 | "timer" 1334 | ], 1335 | "support": { 1336 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 1337 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 1338 | }, 1339 | "funding": [ 1340 | { 1341 | "url": "https://github.com/sebastianbergmann", 1342 | "type": "github" 1343 | } 1344 | ], 1345 | "time": "2020-10-26T13:16:10+00:00" 1346 | }, 1347 | { 1348 | "name": "phpunit/phpunit", 1349 | "version": "9.6.4", 1350 | "source": { 1351 | "type": "git", 1352 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1353 | "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" 1354 | }, 1355 | "dist": { 1356 | "type": "zip", 1357 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", 1358 | "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", 1359 | "shasum": "" 1360 | }, 1361 | "require": { 1362 | "doctrine/instantiator": "^1.3.1 || ^2", 1363 | "ext-dom": "*", 1364 | "ext-json": "*", 1365 | "ext-libxml": "*", 1366 | "ext-mbstring": "*", 1367 | "ext-xml": "*", 1368 | "ext-xmlwriter": "*", 1369 | "myclabs/deep-copy": "^1.10.1", 1370 | "phar-io/manifest": "^2.0.3", 1371 | "phar-io/version": "^3.0.2", 1372 | "php": ">=7.3", 1373 | "phpunit/php-code-coverage": "^9.2.13", 1374 | "phpunit/php-file-iterator": "^3.0.5", 1375 | "phpunit/php-invoker": "^3.1.1", 1376 | "phpunit/php-text-template": "^2.0.3", 1377 | "phpunit/php-timer": "^5.0.2", 1378 | "sebastian/cli-parser": "^1.0.1", 1379 | "sebastian/code-unit": "^1.0.6", 1380 | "sebastian/comparator": "^4.0.8", 1381 | "sebastian/diff": "^4.0.3", 1382 | "sebastian/environment": "^5.1.3", 1383 | "sebastian/exporter": "^4.0.5", 1384 | "sebastian/global-state": "^5.0.1", 1385 | "sebastian/object-enumerator": "^4.0.3", 1386 | "sebastian/resource-operations": "^3.0.3", 1387 | "sebastian/type": "^3.2", 1388 | "sebastian/version": "^3.0.2" 1389 | }, 1390 | "suggest": { 1391 | "ext-soap": "*", 1392 | "ext-xdebug": "*" 1393 | }, 1394 | "bin": [ 1395 | "phpunit" 1396 | ], 1397 | "type": "library", 1398 | "extra": { 1399 | "branch-alias": { 1400 | "dev-master": "9.6-dev" 1401 | } 1402 | }, 1403 | "autoload": { 1404 | "files": [ 1405 | "src/Framework/Assert/Functions.php" 1406 | ], 1407 | "classmap": [ 1408 | "src/" 1409 | ] 1410 | }, 1411 | "notification-url": "https://packagist.org/downloads/", 1412 | "license": [ 1413 | "BSD-3-Clause" 1414 | ], 1415 | "authors": [ 1416 | { 1417 | "name": "Sebastian Bergmann", 1418 | "email": "sebastian@phpunit.de", 1419 | "role": "lead" 1420 | } 1421 | ], 1422 | "description": "The PHP Unit Testing framework.", 1423 | "homepage": "https://phpunit.de/", 1424 | "keywords": [ 1425 | "phpunit", 1426 | "testing", 1427 | "xunit" 1428 | ], 1429 | "support": { 1430 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1431 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" 1432 | }, 1433 | "funding": [ 1434 | { 1435 | "url": "https://phpunit.de/sponsors.html", 1436 | "type": "custom" 1437 | }, 1438 | { 1439 | "url": "https://github.com/sebastianbergmann", 1440 | "type": "github" 1441 | }, 1442 | { 1443 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 1444 | "type": "tidelift" 1445 | } 1446 | ], 1447 | "time": "2023-02-27T13:06:37+00:00" 1448 | }, 1449 | { 1450 | "name": "sebastian/cli-parser", 1451 | "version": "1.0.1", 1452 | "source": { 1453 | "type": "git", 1454 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 1455 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 1456 | }, 1457 | "dist": { 1458 | "type": "zip", 1459 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1460 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1461 | "shasum": "" 1462 | }, 1463 | "require": { 1464 | "php": ">=7.3" 1465 | }, 1466 | "require-dev": { 1467 | "phpunit/phpunit": "^9.3" 1468 | }, 1469 | "type": "library", 1470 | "extra": { 1471 | "branch-alias": { 1472 | "dev-master": "1.0-dev" 1473 | } 1474 | }, 1475 | "autoload": { 1476 | "classmap": [ 1477 | "src/" 1478 | ] 1479 | }, 1480 | "notification-url": "https://packagist.org/downloads/", 1481 | "license": [ 1482 | "BSD-3-Clause" 1483 | ], 1484 | "authors": [ 1485 | { 1486 | "name": "Sebastian Bergmann", 1487 | "email": "sebastian@phpunit.de", 1488 | "role": "lead" 1489 | } 1490 | ], 1491 | "description": "Library for parsing CLI options", 1492 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 1493 | "support": { 1494 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 1495 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 1496 | }, 1497 | "funding": [ 1498 | { 1499 | "url": "https://github.com/sebastianbergmann", 1500 | "type": "github" 1501 | } 1502 | ], 1503 | "time": "2020-09-28T06:08:49+00:00" 1504 | }, 1505 | { 1506 | "name": "sebastian/code-unit", 1507 | "version": "1.0.8", 1508 | "source": { 1509 | "type": "git", 1510 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1511 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 1512 | }, 1513 | "dist": { 1514 | "type": "zip", 1515 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 1516 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 1517 | "shasum": "" 1518 | }, 1519 | "require": { 1520 | "php": ">=7.3" 1521 | }, 1522 | "require-dev": { 1523 | "phpunit/phpunit": "^9.3" 1524 | }, 1525 | "type": "library", 1526 | "extra": { 1527 | "branch-alias": { 1528 | "dev-master": "1.0-dev" 1529 | } 1530 | }, 1531 | "autoload": { 1532 | "classmap": [ 1533 | "src/" 1534 | ] 1535 | }, 1536 | "notification-url": "https://packagist.org/downloads/", 1537 | "license": [ 1538 | "BSD-3-Clause" 1539 | ], 1540 | "authors": [ 1541 | { 1542 | "name": "Sebastian Bergmann", 1543 | "email": "sebastian@phpunit.de", 1544 | "role": "lead" 1545 | } 1546 | ], 1547 | "description": "Collection of value objects that represent the PHP code units", 1548 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1549 | "support": { 1550 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 1551 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 1552 | }, 1553 | "funding": [ 1554 | { 1555 | "url": "https://github.com/sebastianbergmann", 1556 | "type": "github" 1557 | } 1558 | ], 1559 | "time": "2020-10-26T13:08:54+00:00" 1560 | }, 1561 | { 1562 | "name": "sebastian/code-unit-reverse-lookup", 1563 | "version": "2.0.3", 1564 | "source": { 1565 | "type": "git", 1566 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1567 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1568 | }, 1569 | "dist": { 1570 | "type": "zip", 1571 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1572 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1573 | "shasum": "" 1574 | }, 1575 | "require": { 1576 | "php": ">=7.3" 1577 | }, 1578 | "require-dev": { 1579 | "phpunit/phpunit": "^9.3" 1580 | }, 1581 | "type": "library", 1582 | "extra": { 1583 | "branch-alias": { 1584 | "dev-master": "2.0-dev" 1585 | } 1586 | }, 1587 | "autoload": { 1588 | "classmap": [ 1589 | "src/" 1590 | ] 1591 | }, 1592 | "notification-url": "https://packagist.org/downloads/", 1593 | "license": [ 1594 | "BSD-3-Clause" 1595 | ], 1596 | "authors": [ 1597 | { 1598 | "name": "Sebastian Bergmann", 1599 | "email": "sebastian@phpunit.de" 1600 | } 1601 | ], 1602 | "description": "Looks up which function or method a line of code belongs to", 1603 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1604 | "support": { 1605 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1606 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1607 | }, 1608 | "funding": [ 1609 | { 1610 | "url": "https://github.com/sebastianbergmann", 1611 | "type": "github" 1612 | } 1613 | ], 1614 | "time": "2020-09-28T05:30:19+00:00" 1615 | }, 1616 | { 1617 | "name": "sebastian/comparator", 1618 | "version": "4.0.8", 1619 | "source": { 1620 | "type": "git", 1621 | "url": "https://github.com/sebastianbergmann/comparator.git", 1622 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1623 | }, 1624 | "dist": { 1625 | "type": "zip", 1626 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1627 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1628 | "shasum": "" 1629 | }, 1630 | "require": { 1631 | "php": ">=7.3", 1632 | "sebastian/diff": "^4.0", 1633 | "sebastian/exporter": "^4.0" 1634 | }, 1635 | "require-dev": { 1636 | "phpunit/phpunit": "^9.3" 1637 | }, 1638 | "type": "library", 1639 | "extra": { 1640 | "branch-alias": { 1641 | "dev-master": "4.0-dev" 1642 | } 1643 | }, 1644 | "autoload": { 1645 | "classmap": [ 1646 | "src/" 1647 | ] 1648 | }, 1649 | "notification-url": "https://packagist.org/downloads/", 1650 | "license": [ 1651 | "BSD-3-Clause" 1652 | ], 1653 | "authors": [ 1654 | { 1655 | "name": "Sebastian Bergmann", 1656 | "email": "sebastian@phpunit.de" 1657 | }, 1658 | { 1659 | "name": "Jeff Welch", 1660 | "email": "whatthejeff@gmail.com" 1661 | }, 1662 | { 1663 | "name": "Volker Dusch", 1664 | "email": "github@wallbash.com" 1665 | }, 1666 | { 1667 | "name": "Bernhard Schussek", 1668 | "email": "bschussek@2bepublished.at" 1669 | } 1670 | ], 1671 | "description": "Provides the functionality to compare PHP values for equality", 1672 | "homepage": "https://github.com/sebastianbergmann/comparator", 1673 | "keywords": [ 1674 | "comparator", 1675 | "compare", 1676 | "equality" 1677 | ], 1678 | "support": { 1679 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1680 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1681 | }, 1682 | "funding": [ 1683 | { 1684 | "url": "https://github.com/sebastianbergmann", 1685 | "type": "github" 1686 | } 1687 | ], 1688 | "time": "2022-09-14T12:41:17+00:00" 1689 | }, 1690 | { 1691 | "name": "sebastian/complexity", 1692 | "version": "2.0.2", 1693 | "source": { 1694 | "type": "git", 1695 | "url": "https://github.com/sebastianbergmann/complexity.git", 1696 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1697 | }, 1698 | "dist": { 1699 | "type": "zip", 1700 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1701 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1702 | "shasum": "" 1703 | }, 1704 | "require": { 1705 | "nikic/php-parser": "^4.7", 1706 | "php": ">=7.3" 1707 | }, 1708 | "require-dev": { 1709 | "phpunit/phpunit": "^9.3" 1710 | }, 1711 | "type": "library", 1712 | "extra": { 1713 | "branch-alias": { 1714 | "dev-master": "2.0-dev" 1715 | } 1716 | }, 1717 | "autoload": { 1718 | "classmap": [ 1719 | "src/" 1720 | ] 1721 | }, 1722 | "notification-url": "https://packagist.org/downloads/", 1723 | "license": [ 1724 | "BSD-3-Clause" 1725 | ], 1726 | "authors": [ 1727 | { 1728 | "name": "Sebastian Bergmann", 1729 | "email": "sebastian@phpunit.de", 1730 | "role": "lead" 1731 | } 1732 | ], 1733 | "description": "Library for calculating the complexity of PHP code units", 1734 | "homepage": "https://github.com/sebastianbergmann/complexity", 1735 | "support": { 1736 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1737 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1738 | }, 1739 | "funding": [ 1740 | { 1741 | "url": "https://github.com/sebastianbergmann", 1742 | "type": "github" 1743 | } 1744 | ], 1745 | "time": "2020-10-26T15:52:27+00:00" 1746 | }, 1747 | { 1748 | "name": "sebastian/diff", 1749 | "version": "4.0.4", 1750 | "source": { 1751 | "type": "git", 1752 | "url": "https://github.com/sebastianbergmann/diff.git", 1753 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" 1754 | }, 1755 | "dist": { 1756 | "type": "zip", 1757 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1758 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1759 | "shasum": "" 1760 | }, 1761 | "require": { 1762 | "php": ">=7.3" 1763 | }, 1764 | "require-dev": { 1765 | "phpunit/phpunit": "^9.3", 1766 | "symfony/process": "^4.2 || ^5" 1767 | }, 1768 | "type": "library", 1769 | "extra": { 1770 | "branch-alias": { 1771 | "dev-master": "4.0-dev" 1772 | } 1773 | }, 1774 | "autoload": { 1775 | "classmap": [ 1776 | "src/" 1777 | ] 1778 | }, 1779 | "notification-url": "https://packagist.org/downloads/", 1780 | "license": [ 1781 | "BSD-3-Clause" 1782 | ], 1783 | "authors": [ 1784 | { 1785 | "name": "Sebastian Bergmann", 1786 | "email": "sebastian@phpunit.de" 1787 | }, 1788 | { 1789 | "name": "Kore Nordmann", 1790 | "email": "mail@kore-nordmann.de" 1791 | } 1792 | ], 1793 | "description": "Diff implementation", 1794 | "homepage": "https://github.com/sebastianbergmann/diff", 1795 | "keywords": [ 1796 | "diff", 1797 | "udiff", 1798 | "unidiff", 1799 | "unified diff" 1800 | ], 1801 | "support": { 1802 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1803 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" 1804 | }, 1805 | "funding": [ 1806 | { 1807 | "url": "https://github.com/sebastianbergmann", 1808 | "type": "github" 1809 | } 1810 | ], 1811 | "time": "2020-10-26T13:10:38+00:00" 1812 | }, 1813 | { 1814 | "name": "sebastian/environment", 1815 | "version": "5.1.5", 1816 | "source": { 1817 | "type": "git", 1818 | "url": "https://github.com/sebastianbergmann/environment.git", 1819 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 1820 | }, 1821 | "dist": { 1822 | "type": "zip", 1823 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1824 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1825 | "shasum": "" 1826 | }, 1827 | "require": { 1828 | "php": ">=7.3" 1829 | }, 1830 | "require-dev": { 1831 | "phpunit/phpunit": "^9.3" 1832 | }, 1833 | "suggest": { 1834 | "ext-posix": "*" 1835 | }, 1836 | "type": "library", 1837 | "extra": { 1838 | "branch-alias": { 1839 | "dev-master": "5.1-dev" 1840 | } 1841 | }, 1842 | "autoload": { 1843 | "classmap": [ 1844 | "src/" 1845 | ] 1846 | }, 1847 | "notification-url": "https://packagist.org/downloads/", 1848 | "license": [ 1849 | "BSD-3-Clause" 1850 | ], 1851 | "authors": [ 1852 | { 1853 | "name": "Sebastian Bergmann", 1854 | "email": "sebastian@phpunit.de" 1855 | } 1856 | ], 1857 | "description": "Provides functionality to handle HHVM/PHP environments", 1858 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1859 | "keywords": [ 1860 | "Xdebug", 1861 | "environment", 1862 | "hhvm" 1863 | ], 1864 | "support": { 1865 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1866 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 1867 | }, 1868 | "funding": [ 1869 | { 1870 | "url": "https://github.com/sebastianbergmann", 1871 | "type": "github" 1872 | } 1873 | ], 1874 | "time": "2023-02-03T06:03:51+00:00" 1875 | }, 1876 | { 1877 | "name": "sebastian/exporter", 1878 | "version": "4.0.5", 1879 | "source": { 1880 | "type": "git", 1881 | "url": "https://github.com/sebastianbergmann/exporter.git", 1882 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1883 | }, 1884 | "dist": { 1885 | "type": "zip", 1886 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1887 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1888 | "shasum": "" 1889 | }, 1890 | "require": { 1891 | "php": ">=7.3", 1892 | "sebastian/recursion-context": "^4.0" 1893 | }, 1894 | "require-dev": { 1895 | "ext-mbstring": "*", 1896 | "phpunit/phpunit": "^9.3" 1897 | }, 1898 | "type": "library", 1899 | "extra": { 1900 | "branch-alias": { 1901 | "dev-master": "4.0-dev" 1902 | } 1903 | }, 1904 | "autoload": { 1905 | "classmap": [ 1906 | "src/" 1907 | ] 1908 | }, 1909 | "notification-url": "https://packagist.org/downloads/", 1910 | "license": [ 1911 | "BSD-3-Clause" 1912 | ], 1913 | "authors": [ 1914 | { 1915 | "name": "Sebastian Bergmann", 1916 | "email": "sebastian@phpunit.de" 1917 | }, 1918 | { 1919 | "name": "Jeff Welch", 1920 | "email": "whatthejeff@gmail.com" 1921 | }, 1922 | { 1923 | "name": "Volker Dusch", 1924 | "email": "github@wallbash.com" 1925 | }, 1926 | { 1927 | "name": "Adam Harvey", 1928 | "email": "aharvey@php.net" 1929 | }, 1930 | { 1931 | "name": "Bernhard Schussek", 1932 | "email": "bschussek@gmail.com" 1933 | } 1934 | ], 1935 | "description": "Provides the functionality to export PHP variables for visualization", 1936 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1937 | "keywords": [ 1938 | "export", 1939 | "exporter" 1940 | ], 1941 | "support": { 1942 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1943 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1944 | }, 1945 | "funding": [ 1946 | { 1947 | "url": "https://github.com/sebastianbergmann", 1948 | "type": "github" 1949 | } 1950 | ], 1951 | "time": "2022-09-14T06:03:37+00:00" 1952 | }, 1953 | { 1954 | "name": "sebastian/global-state", 1955 | "version": "5.0.5", 1956 | "source": { 1957 | "type": "git", 1958 | "url": "https://github.com/sebastianbergmann/global-state.git", 1959 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" 1960 | }, 1961 | "dist": { 1962 | "type": "zip", 1963 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1964 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1965 | "shasum": "" 1966 | }, 1967 | "require": { 1968 | "php": ">=7.3", 1969 | "sebastian/object-reflector": "^2.0", 1970 | "sebastian/recursion-context": "^4.0" 1971 | }, 1972 | "require-dev": { 1973 | "ext-dom": "*", 1974 | "phpunit/phpunit": "^9.3" 1975 | }, 1976 | "suggest": { 1977 | "ext-uopz": "*" 1978 | }, 1979 | "type": "library", 1980 | "extra": { 1981 | "branch-alias": { 1982 | "dev-master": "5.0-dev" 1983 | } 1984 | }, 1985 | "autoload": { 1986 | "classmap": [ 1987 | "src/" 1988 | ] 1989 | }, 1990 | "notification-url": "https://packagist.org/downloads/", 1991 | "license": [ 1992 | "BSD-3-Clause" 1993 | ], 1994 | "authors": [ 1995 | { 1996 | "name": "Sebastian Bergmann", 1997 | "email": "sebastian@phpunit.de" 1998 | } 1999 | ], 2000 | "description": "Snapshotting of global state", 2001 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 2002 | "keywords": [ 2003 | "global state" 2004 | ], 2005 | "support": { 2006 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 2007 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" 2008 | }, 2009 | "funding": [ 2010 | { 2011 | "url": "https://github.com/sebastianbergmann", 2012 | "type": "github" 2013 | } 2014 | ], 2015 | "time": "2022-02-14T08:28:10+00:00" 2016 | }, 2017 | { 2018 | "name": "sebastian/lines-of-code", 2019 | "version": "1.0.3", 2020 | "source": { 2021 | "type": "git", 2022 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 2023 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 2024 | }, 2025 | "dist": { 2026 | "type": "zip", 2027 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2028 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2029 | "shasum": "" 2030 | }, 2031 | "require": { 2032 | "nikic/php-parser": "^4.6", 2033 | "php": ">=7.3" 2034 | }, 2035 | "require-dev": { 2036 | "phpunit/phpunit": "^9.3" 2037 | }, 2038 | "type": "library", 2039 | "extra": { 2040 | "branch-alias": { 2041 | "dev-master": "1.0-dev" 2042 | } 2043 | }, 2044 | "autoload": { 2045 | "classmap": [ 2046 | "src/" 2047 | ] 2048 | }, 2049 | "notification-url": "https://packagist.org/downloads/", 2050 | "license": [ 2051 | "BSD-3-Clause" 2052 | ], 2053 | "authors": [ 2054 | { 2055 | "name": "Sebastian Bergmann", 2056 | "email": "sebastian@phpunit.de", 2057 | "role": "lead" 2058 | } 2059 | ], 2060 | "description": "Library for counting the lines of code in PHP source code", 2061 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 2062 | "support": { 2063 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 2064 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 2065 | }, 2066 | "funding": [ 2067 | { 2068 | "url": "https://github.com/sebastianbergmann", 2069 | "type": "github" 2070 | } 2071 | ], 2072 | "time": "2020-11-28T06:42:11+00:00" 2073 | }, 2074 | { 2075 | "name": "sebastian/object-enumerator", 2076 | "version": "4.0.4", 2077 | "source": { 2078 | "type": "git", 2079 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 2080 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 2081 | }, 2082 | "dist": { 2083 | "type": "zip", 2084 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 2085 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 2086 | "shasum": "" 2087 | }, 2088 | "require": { 2089 | "php": ">=7.3", 2090 | "sebastian/object-reflector": "^2.0", 2091 | "sebastian/recursion-context": "^4.0" 2092 | }, 2093 | "require-dev": { 2094 | "phpunit/phpunit": "^9.3" 2095 | }, 2096 | "type": "library", 2097 | "extra": { 2098 | "branch-alias": { 2099 | "dev-master": "4.0-dev" 2100 | } 2101 | }, 2102 | "autoload": { 2103 | "classmap": [ 2104 | "src/" 2105 | ] 2106 | }, 2107 | "notification-url": "https://packagist.org/downloads/", 2108 | "license": [ 2109 | "BSD-3-Clause" 2110 | ], 2111 | "authors": [ 2112 | { 2113 | "name": "Sebastian Bergmann", 2114 | "email": "sebastian@phpunit.de" 2115 | } 2116 | ], 2117 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 2118 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 2119 | "support": { 2120 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 2121 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 2122 | }, 2123 | "funding": [ 2124 | { 2125 | "url": "https://github.com/sebastianbergmann", 2126 | "type": "github" 2127 | } 2128 | ], 2129 | "time": "2020-10-26T13:12:34+00:00" 2130 | }, 2131 | { 2132 | "name": "sebastian/object-reflector", 2133 | "version": "2.0.4", 2134 | "source": { 2135 | "type": "git", 2136 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 2137 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 2138 | }, 2139 | "dist": { 2140 | "type": "zip", 2141 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2142 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2143 | "shasum": "" 2144 | }, 2145 | "require": { 2146 | "php": ">=7.3" 2147 | }, 2148 | "require-dev": { 2149 | "phpunit/phpunit": "^9.3" 2150 | }, 2151 | "type": "library", 2152 | "extra": { 2153 | "branch-alias": { 2154 | "dev-master": "2.0-dev" 2155 | } 2156 | }, 2157 | "autoload": { 2158 | "classmap": [ 2159 | "src/" 2160 | ] 2161 | }, 2162 | "notification-url": "https://packagist.org/downloads/", 2163 | "license": [ 2164 | "BSD-3-Clause" 2165 | ], 2166 | "authors": [ 2167 | { 2168 | "name": "Sebastian Bergmann", 2169 | "email": "sebastian@phpunit.de" 2170 | } 2171 | ], 2172 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 2173 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 2174 | "support": { 2175 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 2176 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 2177 | }, 2178 | "funding": [ 2179 | { 2180 | "url": "https://github.com/sebastianbergmann", 2181 | "type": "github" 2182 | } 2183 | ], 2184 | "time": "2020-10-26T13:14:26+00:00" 2185 | }, 2186 | { 2187 | "name": "sebastian/recursion-context", 2188 | "version": "4.0.5", 2189 | "source": { 2190 | "type": "git", 2191 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 2192 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 2193 | }, 2194 | "dist": { 2195 | "type": "zip", 2196 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 2197 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 2198 | "shasum": "" 2199 | }, 2200 | "require": { 2201 | "php": ">=7.3" 2202 | }, 2203 | "require-dev": { 2204 | "phpunit/phpunit": "^9.3" 2205 | }, 2206 | "type": "library", 2207 | "extra": { 2208 | "branch-alias": { 2209 | "dev-master": "4.0-dev" 2210 | } 2211 | }, 2212 | "autoload": { 2213 | "classmap": [ 2214 | "src/" 2215 | ] 2216 | }, 2217 | "notification-url": "https://packagist.org/downloads/", 2218 | "license": [ 2219 | "BSD-3-Clause" 2220 | ], 2221 | "authors": [ 2222 | { 2223 | "name": "Sebastian Bergmann", 2224 | "email": "sebastian@phpunit.de" 2225 | }, 2226 | { 2227 | "name": "Jeff Welch", 2228 | "email": "whatthejeff@gmail.com" 2229 | }, 2230 | { 2231 | "name": "Adam Harvey", 2232 | "email": "aharvey@php.net" 2233 | } 2234 | ], 2235 | "description": "Provides functionality to recursively process PHP variables", 2236 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 2237 | "support": { 2238 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 2239 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 2240 | }, 2241 | "funding": [ 2242 | { 2243 | "url": "https://github.com/sebastianbergmann", 2244 | "type": "github" 2245 | } 2246 | ], 2247 | "time": "2023-02-03T06:07:39+00:00" 2248 | }, 2249 | { 2250 | "name": "sebastian/resource-operations", 2251 | "version": "3.0.3", 2252 | "source": { 2253 | "type": "git", 2254 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 2255 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 2256 | }, 2257 | "dist": { 2258 | "type": "zip", 2259 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2260 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2261 | "shasum": "" 2262 | }, 2263 | "require": { 2264 | "php": ">=7.3" 2265 | }, 2266 | "require-dev": { 2267 | "phpunit/phpunit": "^9.0" 2268 | }, 2269 | "type": "library", 2270 | "extra": { 2271 | "branch-alias": { 2272 | "dev-master": "3.0-dev" 2273 | } 2274 | }, 2275 | "autoload": { 2276 | "classmap": [ 2277 | "src/" 2278 | ] 2279 | }, 2280 | "notification-url": "https://packagist.org/downloads/", 2281 | "license": [ 2282 | "BSD-3-Clause" 2283 | ], 2284 | "authors": [ 2285 | { 2286 | "name": "Sebastian Bergmann", 2287 | "email": "sebastian@phpunit.de" 2288 | } 2289 | ], 2290 | "description": "Provides a list of PHP built-in functions that operate on resources", 2291 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 2292 | "support": { 2293 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 2294 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 2295 | }, 2296 | "funding": [ 2297 | { 2298 | "url": "https://github.com/sebastianbergmann", 2299 | "type": "github" 2300 | } 2301 | ], 2302 | "time": "2020-09-28T06:45:17+00:00" 2303 | }, 2304 | { 2305 | "name": "sebastian/type", 2306 | "version": "3.2.1", 2307 | "source": { 2308 | "type": "git", 2309 | "url": "https://github.com/sebastianbergmann/type.git", 2310 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 2311 | }, 2312 | "dist": { 2313 | "type": "zip", 2314 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2315 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2316 | "shasum": "" 2317 | }, 2318 | "require": { 2319 | "php": ">=7.3" 2320 | }, 2321 | "require-dev": { 2322 | "phpunit/phpunit": "^9.5" 2323 | }, 2324 | "type": "library", 2325 | "extra": { 2326 | "branch-alias": { 2327 | "dev-master": "3.2-dev" 2328 | } 2329 | }, 2330 | "autoload": { 2331 | "classmap": [ 2332 | "src/" 2333 | ] 2334 | }, 2335 | "notification-url": "https://packagist.org/downloads/", 2336 | "license": [ 2337 | "BSD-3-Clause" 2338 | ], 2339 | "authors": [ 2340 | { 2341 | "name": "Sebastian Bergmann", 2342 | "email": "sebastian@phpunit.de", 2343 | "role": "lead" 2344 | } 2345 | ], 2346 | "description": "Collection of value objects that represent the types of the PHP type system", 2347 | "homepage": "https://github.com/sebastianbergmann/type", 2348 | "support": { 2349 | "issues": "https://github.com/sebastianbergmann/type/issues", 2350 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 2351 | }, 2352 | "funding": [ 2353 | { 2354 | "url": "https://github.com/sebastianbergmann", 2355 | "type": "github" 2356 | } 2357 | ], 2358 | "time": "2023-02-03T06:13:03+00:00" 2359 | }, 2360 | { 2361 | "name": "sebastian/version", 2362 | "version": "3.0.2", 2363 | "source": { 2364 | "type": "git", 2365 | "url": "https://github.com/sebastianbergmann/version.git", 2366 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 2367 | }, 2368 | "dist": { 2369 | "type": "zip", 2370 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 2371 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 2372 | "shasum": "" 2373 | }, 2374 | "require": { 2375 | "php": ">=7.3" 2376 | }, 2377 | "type": "library", 2378 | "extra": { 2379 | "branch-alias": { 2380 | "dev-master": "3.0-dev" 2381 | } 2382 | }, 2383 | "autoload": { 2384 | "classmap": [ 2385 | "src/" 2386 | ] 2387 | }, 2388 | "notification-url": "https://packagist.org/downloads/", 2389 | "license": [ 2390 | "BSD-3-Clause" 2391 | ], 2392 | "authors": [ 2393 | { 2394 | "name": "Sebastian Bergmann", 2395 | "email": "sebastian@phpunit.de", 2396 | "role": "lead" 2397 | } 2398 | ], 2399 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2400 | "homepage": "https://github.com/sebastianbergmann/version", 2401 | "support": { 2402 | "issues": "https://github.com/sebastianbergmann/version/issues", 2403 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 2404 | }, 2405 | "funding": [ 2406 | { 2407 | "url": "https://github.com/sebastianbergmann", 2408 | "type": "github" 2409 | } 2410 | ], 2411 | "time": "2020-09-28T06:39:44+00:00" 2412 | }, 2413 | { 2414 | "name": "theseer/tokenizer", 2415 | "version": "1.2.1", 2416 | "source": { 2417 | "type": "git", 2418 | "url": "https://github.com/theseer/tokenizer.git", 2419 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2420 | }, 2421 | "dist": { 2422 | "type": "zip", 2423 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2424 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2425 | "shasum": "" 2426 | }, 2427 | "require": { 2428 | "ext-dom": "*", 2429 | "ext-tokenizer": "*", 2430 | "ext-xmlwriter": "*", 2431 | "php": "^7.2 || ^8.0" 2432 | }, 2433 | "type": "library", 2434 | "autoload": { 2435 | "classmap": [ 2436 | "src/" 2437 | ] 2438 | }, 2439 | "notification-url": "https://packagist.org/downloads/", 2440 | "license": [ 2441 | "BSD-3-Clause" 2442 | ], 2443 | "authors": [ 2444 | { 2445 | "name": "Arne Blankerts", 2446 | "email": "arne@blankerts.de", 2447 | "role": "Developer" 2448 | } 2449 | ], 2450 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2451 | "support": { 2452 | "issues": "https://github.com/theseer/tokenizer/issues", 2453 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2454 | }, 2455 | "funding": [ 2456 | { 2457 | "url": "https://github.com/theseer", 2458 | "type": "github" 2459 | } 2460 | ], 2461 | "time": "2021-07-28T10:34:58+00:00" 2462 | } 2463 | ], 2464 | "aliases": [], 2465 | "minimum-stability": "stable", 2466 | "stability-flags": [], 2467 | "prefer-stable": false, 2468 | "prefer-lowest": false, 2469 | "platform": { 2470 | "php": "^7.4|^8.0", 2471 | "ext-json": "*" 2472 | }, 2473 | "platform-dev": [], 2474 | "plugin-api-version": "2.3.0" 2475 | } 2476 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | src/Facades/Urlbox.php 9 | src/UrlboxProvider.php 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | tests 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | tests/src 11 | 12 | 13 | 14 | 15 | 16 | src 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Facades/Urlbox.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Urlbox extends Facade 14 | { 15 | /** 16 | * Get the registered name of the component. 17 | * 18 | * @return string 19 | */ 20 | protected static function getFacadeAccessor() 21 | { 22 | return 'urlbox'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Urlbox.php: -------------------------------------------------------------------------------- 1 | ensureIsValidCredentials( $apiKey, $apiSecret ); 30 | 31 | $this->apiKey = $apiKey; 32 | $this->apiSecret = $apiSecret; 33 | $this->webhookSecret = $webhookSecret; 34 | 35 | $this->client = $client ?? new Client(); 36 | } 37 | 38 | /** 39 | * @param string $apiKey 40 | * @param string $apiSecret 41 | * 42 | * @return void 43 | * 44 | * @throws InvalidArgumentException 45 | */ 46 | private function ensureIsValidCredentials( string $apiKey, string $apiSecret ) 47 | { 48 | if ( empty( $apiKey ) ) { 49 | throw new InvalidArgumentException( 'Requires an api key - https://www.urlbox.io/dashboard/projects' ); 50 | } 51 | 52 | if ( empty( $apiSecret ) ) { 53 | throw new InvalidArgumentException( 'Requires an api secret - https://www.urlbox.io/dashboard/projects' ); 54 | } 55 | } 56 | 57 | /** 58 | * @param string $apiKey 59 | * @param string $apiSecret 60 | * @param string|null $webhookSecret 61 | * @param Client|null $client 62 | * 63 | * @return Urlbox 64 | * @throws InvalidArgumentException 65 | */ 66 | public static function fromCredentials( string $apiKey, string $apiSecret, ?string $webhookSecret = null, ?Client $client = null ): Urlbox 67 | { 68 | return new self( $apiKey, $apiSecret, $webhookSecret, $client ); 69 | } 70 | 71 | /** 72 | * @param array $options 73 | * @param string|null $saveToDiskPath 74 | * 75 | * @return array{renderUrl: string, size: int, localPath: string} 76 | * @throws GuzzleException 77 | */ 78 | public function render( array $options, ?string $saveToDiskPath = null ): array 79 | { 80 | $response = $this->makeUrlboxPostRequest( '/render/sync', $options ); 81 | 82 | if ( $saveToDiskPath !== null ) { 83 | $imageResponse = $this->client->get( $response['renderUrl'] ); 84 | file_put_contents( $saveToDiskPath, $imageResponse->getBody()->getContents() ); 85 | $response['localPath'] = $saveToDiskPath; 86 | } 87 | 88 | return $response; 89 | } 90 | 91 | /** 92 | * @param string $endpoint 93 | * @param array $options 94 | * 95 | * @return array{renderUrl: string, size: int} 96 | * @throws GuzzleException 97 | */ 98 | private function makeUrlboxPostRequest( string $endpoint, array $options ): array 99 | { 100 | $response = $this->client->post( 101 | $this->baseUrl . $endpoint, 102 | [ 103 | RequestOptions::HEADERS => [ 104 | 'Authorization' => 'Bearer ' . $this->apiSecret, 105 | ], 106 | RequestOptions::JSON => $options 107 | ] 108 | ); 109 | 110 | return json_decode( $response->getBody()->getContents(), true ); 111 | } 112 | 113 | /** 114 | * @param array $options 115 | * 116 | * @return array{status: string, renderId: string, statusUrl: string} 117 | * @throws GuzzleException 118 | */ 119 | public function renderAsync( array $options ): array 120 | { 121 | return $this->makeUrlboxPostRequest( '/render', $options ); 122 | } 123 | 124 | /** 125 | * @param array $options 126 | * 127 | * @return string 128 | */ 129 | public function generateSignedUrl( array $options ): string 130 | { 131 | return $this->generateUrl( $options, true ); 132 | } 133 | 134 | /** 135 | * @param array $options 136 | * @param bool $signed 137 | * 138 | * @return string 139 | */ 140 | private function generateUrl( array $options, bool $signed = true ): string 141 | { 142 | $format = $options['format'] ?? 'png'; 143 | unset( $options['format'] ); 144 | 145 | $queryStringParts = []; 146 | foreach ( $options as $key => $values ) { 147 | $values = is_array( $values ) ? $values : [ $values ]; 148 | foreach ( $values as $value ) { 149 | if ( isset( $value ) ) { 150 | $encodedValue = $this->sanitizeValue( $value ); 151 | $queryStringParts[] = "$key=$encodedValue"; 152 | } 153 | } 154 | } 155 | 156 | $queryString = implode( "&", $queryStringParts ); 157 | 158 | $generatedUrl = $this->baseUrl . '/' . $this->apiKey . '/'; 159 | 160 | if ( $signed ) { 161 | $generatedUrl .= hash_hmac( "sha1", $queryString, $this->apiSecret ) . '/'; 162 | } 163 | 164 | return $generatedUrl . $format . '?' . $queryString; 165 | } 166 | 167 | /** 168 | * @param array $options 169 | * 170 | * @return string 171 | */ 172 | public function generateUnsignedUrl( array $options ): string 173 | { 174 | return $this->generateUrl( $options, false ); 175 | } 176 | 177 | /** 178 | * @param mixed $value 179 | * 180 | * @return string|null 181 | */ 182 | private function sanitizeValue( $value ): ?string 183 | { 184 | if ( is_string( $value ) ) { 185 | return $this->encodeURIComponent( $value ); 186 | } 187 | 188 | return var_export( $value, true ); 189 | } 190 | 191 | /** 192 | * @param string $value 193 | * 194 | * @return string 195 | */ 196 | private function encodeURIComponent( string $value ): string 197 | { 198 | $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ); 199 | 200 | return strtr( rawurlencode( $value ), $revert ); 201 | } 202 | 203 | /** 204 | * @param string $header 205 | * @param string $content 206 | * 207 | * @return bool 208 | * @throws Exception 209 | */ 210 | public function verifyWebhookSignature( string $header, string $content ): bool 211 | { 212 | if ( empty( $this->webhookSecret ) ) { 213 | throw new Exception( 'Unable to verify signature as Webhook Secret is not set. You can find your webhook secret inside your project\'s settings - https://www.urlbox.io/dashboard/projects' ); 214 | } 215 | 216 | if ( empty( $header ) ) { 217 | throw new InvalidArgumentException( 'Unable to verify signature as header is empty. Please ensure you pass the `x-urlbox-signature` from the header of the webhook response' ); 218 | } 219 | 220 | $generatedHash = hash_hmac( 'sha256', $this->getTimestamp( $header ) . '.' . $content, $this->webhookSecret ); 221 | $requestHash = $this->getSignature( $header ); 222 | 223 | return $generatedHash === $requestHash; 224 | } 225 | 226 | /** 227 | * @param string $header 228 | * 229 | * @return string 230 | */ 231 | private function getTimestamp( string $header ): string 232 | { 233 | return array_reverse( explode( 't=', explode( ',', $header )[0], 2 ) )[0]; 234 | } 235 | 236 | /** 237 | * @param string $header 238 | * 239 | * @return string 240 | */ 241 | private function getSignature( string $header ): string 242 | { 243 | return array_reverse( explode( 'sha256=', $header, 2 ) )[0]; 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /src/UrlboxProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton( Urlbox::class, function ( $app ) { 14 | $key = config( 'services.urlbox.key' ); 15 | $secret = config( 'services.urlbox.secret' ); 16 | $webhook_secret = config( 'services.urlbox.webhook_secret' ); 17 | 18 | if ( ! $key || ! $secret ) { 19 | throw new InvalidArgumentException( 'Please ensure you have set values for `services.urlbox.key` and `services.urlbox.secret`' ); 20 | } 21 | 22 | return new Urlbox( $key, $secret, $webhook_secret, $app->make( Client::class ) ); 23 | } ); 24 | 25 | $this->app->alias( Urlbox::class, 'urlbox' ); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/UrlboxTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 16 | Urlbox::class, 17 | Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ) 18 | ); 19 | } 20 | 21 | public function testConstructorThrowsInvalidArgumentExceptionWhenApiKeyIsEmpty() 22 | { 23 | $this->expectException( InvalidArgumentException::class ); 24 | new Urlbox( '', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 25 | } 26 | 27 | public function testConstructorThrowsInvalidArgumentExceptionWhenApiSecretIsEmpty() 28 | { 29 | $this->expectException( InvalidArgumentException::class ); 30 | new Urlbox( 'API_KEY', '', Mockery::mock( Client::class ) ); 31 | } 32 | 33 | public function testFromCredentialsReturnsUrlboxInstanceWhenValidCredentialsSupplied() 34 | { 35 | $this->assertInstanceOf( 36 | Urlbox::class, 37 | Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ) 38 | ); 39 | } 40 | 41 | public function testFromCredentialsThrowsInvalidArgumentExceptionWhenApiKeyIsEmpty() 42 | { 43 | $this->expectException( InvalidArgumentException::class ); 44 | Urlbox::fromCredentials( '', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 45 | } 46 | 47 | public function testFromCredentialsThrowsInvalidArgumentExceptionWhenApiSecretIsEmpty() 48 | { 49 | $this->expectException( InvalidArgumentException::class ); 50 | Urlbox::fromCredentials( 'API_KEY', '', Mockery::mock( Client::class ) ); 51 | } 52 | 53 | public function testRenderReturnsJsonArrayOfApiResponse() 54 | { 55 | $guzzleMock = Mockery::mock( Client::class ) 56 | ->shouldReceive( 'post' ) 57 | ->with( Mockery::capture( $url ), Mockery::capture( $requestOptions ) ) 58 | ->andReturn( $this->getMockedGuzzleResponse( '{"renderUrl":"http://storage.foobar.com/urlbox/renders/123456.png","size":525949}' ) ) 59 | ->getMock(); 60 | 61 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', $guzzleMock ); 62 | 63 | $response = $urlbox->render( [ 64 | 'url' => 'https://example.com', 65 | 'format' => 'png' 66 | ] ); 67 | 68 | $this->assertEquals( 69 | [ 'renderUrl' => 'http://storage.foobar.com/urlbox/renders/123456.png', 'size' => 525949 ], 70 | $response 71 | ); 72 | $this->assertEquals( 'https://api.urlbox.io/v1/render/sync', $url ); 73 | $this->assertEquals( [ 74 | 'headers' => [ 75 | 'Authorization' => 'Bearer API_SECRET', 76 | ], 77 | 'json' => [ 78 | 'url' => 'https://example.com', 79 | 'format' => 'png' 80 | ] 81 | ], $requestOptions ); 82 | } 83 | 84 | private function getMockedGuzzleResponse( string $content ): MockInterface 85 | { 86 | return Mockery::mock( GuzzleHttp\Psr7\Response::class ) 87 | ->shouldReceive( 'getBody' ) 88 | ->andReturn( 89 | Mockery::mock( GuzzleHttp\Psr7\Stream::class ) 90 | ->shouldReceive( 'getContents' ) 91 | ->andReturn( $content ) 92 | ->getMock() 93 | ) 94 | ->getMock(); 95 | } 96 | 97 | public function testRenderSavesToDiskAndReturnsJsonArrayOfApiResponseWithLocalPath() 98 | { 99 | $guzzleMock = Mockery::mock( Client::class ) 100 | ->shouldReceive( 'post' ) 101 | ->with( Mockery::capture( $postUrl ), Mockery::capture( $postRequestOptions ) ) 102 | ->andReturn( $this->getMockedGuzzleResponse( '{"renderUrl":"http://storage.foobar.com/urlbox/renders/123456.png","size":525949}' ) ) 103 | ->shouldReceive( 'get' ) 104 | ->with( Mockery::capture( $getUrl ) ) 105 | ->andReturn( $this->getMockedGuzzleResponse( 'foo bar' ) ) 106 | ->getMock(); 107 | 108 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', $guzzleMock ); 109 | 110 | $filename = tempnam( '/tmp', 'URLBOX' ); 111 | 112 | $result = $urlbox->render( [ 113 | 'url' => 'https://example.com', 114 | 'format' => 'png' 115 | ], $filename ); 116 | 117 | $this->assertEquals( 118 | [ 119 | 'renderUrl' => 'http://storage.foobar.com/urlbox/renders/123456.png', 120 | 'size' => 525949, 121 | 'localPath' => $filename 122 | ], 123 | $result 124 | ); 125 | 126 | $this->assertEquals( 'https://api.urlbox.io/v1/render/sync', $postUrl ); 127 | $this->assertEquals( [ 128 | 'headers' => [ 129 | 'Authorization' => 'Bearer API_SECRET', 130 | ], 131 | 'json' => [ 132 | 'url' => 'https://example.com', 133 | 'format' => 'png' 134 | ] 135 | ], $postRequestOptions ); 136 | $this->assertEquals( 'http://storage.foobar.com/urlbox/renders/123456.png', $getUrl ); 137 | 138 | $this->assertEquals( 139 | 'foo bar', 140 | file_get_contents( $filename ) 141 | ); 142 | } 143 | 144 | public function testRenderSyncReturnsJsonArrayOfApiResponse() 145 | { 146 | $guzzleMock = Mockery::mock( Client::class ) 147 | ->shouldReceive( 'post' ) 148 | ->with( Mockery::capture( $url ), Mockery::capture( $requestOptions ) ) 149 | ->andReturn( $this->getMockedGuzzleResponse( '{"status":"created","renderId":"00000000-0000-0000-0000-000000000000","statusUrl":"https://api.urlbox.io/render/00000000-0000-0000-0000-000000000000"}' ) ) 150 | ->getMock(); 151 | 152 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', $guzzleMock ); 153 | 154 | $response = $urlbox->renderAsync( [ 155 | 'url' => 'https://example.com', 156 | 'format' => 'png' 157 | ] ); 158 | 159 | $this->assertEquals( 160 | [ 161 | 'status' => 'created', 162 | 'renderId' => '00000000-0000-0000-0000-000000000000', 163 | 'statusUrl' => 'https://api.urlbox.io/render/00000000-0000-0000-0000-000000000000' 164 | ], 165 | $response 166 | ); 167 | $this->assertEquals( 'https://api.urlbox.io/v1/render', $url ); 168 | $this->assertEquals( [ 169 | 'headers' => [ 170 | 'Authorization' => 'Bearer API_SECRET', 171 | ], 172 | 'json' => [ 173 | 'url' => 'https://example.com', 174 | 'format' => 'png' 175 | ] 176 | ], $requestOptions ); 177 | } 178 | 179 | public function testGenerateSignedUrlDefaultFormatToPng() 180 | { 181 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 182 | $this->assertEquals( 183 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/png?url=https%3A%2F%2Fexample.com', 184 | $urlbox->generateSignedUrl( [ 'url' => 'https://example.com' ] ) 185 | ); 186 | } 187 | 188 | public function testGenerateSignedUrlCanSetFormatToPng() 189 | { 190 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 191 | 192 | $url = $urlbox->generateSignedUrl( [ 193 | 'url' => 'https://example.com', 194 | 'format' => 'png' 195 | ] ); 196 | 197 | $this->assertEquals( 198 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/png?url=https%3A%2F%2Fexample.com', 199 | $url 200 | ); 201 | } 202 | 203 | public function testGenerateSignedUrlCanSetFormatToJpg() 204 | { 205 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 206 | 207 | $url = $urlbox->generateSignedUrl( [ 208 | 'url' => 'https://example.com', 209 | 'format' => 'jpg' 210 | ] ); 211 | 212 | $this->assertEquals( 213 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/jpg?url=https%3A%2F%2Fexample.com', 214 | $url 215 | ); 216 | } 217 | 218 | public function testGenerateSignedUrlCanSetFormatToJpeg() 219 | { 220 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 221 | 222 | $url = $urlbox->generateSignedUrl( [ 223 | 'url' => 'https://example.com', 224 | 'format' => 'jpeg' 225 | ] ); 226 | 227 | $this->assertEquals( 228 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/jpeg?url=https%3A%2F%2Fexample.com', 229 | $url 230 | ); 231 | } 232 | 233 | public function testGenerateSignedUrlCanSetFormatToAvif() 234 | { 235 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 236 | 237 | $url = $urlbox->generateSignedUrl( [ 238 | 'url' => 'https://example.com', 239 | 'format' => 'avif' 240 | ] ); 241 | 242 | $this->assertEquals( 243 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/avif?url=https%3A%2F%2Fexample.com', 244 | $url 245 | ); 246 | } 247 | 248 | public function testGenerateSignedUrlCanSetFormatToWebp() 249 | { 250 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 251 | 252 | $url = $urlbox->generateSignedUrl( [ 253 | 'url' => 'https://example.com', 254 | 'format' => 'webp' 255 | ] ); 256 | 257 | $this->assertEquals( 258 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/webp?url=https%3A%2F%2Fexample.com', 259 | $url 260 | ); 261 | } 262 | 263 | public function testGenerateSignedUrlCanSetFormatToPdf() 264 | { 265 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 266 | 267 | $url = $urlbox->generateSignedUrl( [ 268 | 'url' => 'https://example.com', 269 | 'format' => 'pdf' 270 | ] ); 271 | 272 | $this->assertEquals( 273 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/pdf?url=https%3A%2F%2Fexample.com', 274 | $url 275 | ); 276 | } 277 | 278 | public function testGenerateSignedUrlCanSetFormatToSvg() 279 | { 280 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 281 | 282 | $url = $urlbox->generateSignedUrl( [ 283 | 'url' => 'https://example.com', 284 | 'format' => 'svg' 285 | ] ); 286 | 287 | $this->assertEquals( 288 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/svg?url=https%3A%2F%2Fexample.com', 289 | $url 290 | ); 291 | } 292 | 293 | public function testGenerateSignedUrlCanSetFormatToHtml() 294 | { 295 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 296 | 297 | $url = $urlbox->generateSignedUrl( [ 298 | 'url' => 'https://example.com', 299 | 'format' => 'html' 300 | ] ); 301 | 302 | $this->assertEquals( 303 | 'https://api.urlbox.io/v1/API_KEY/5eaae418596fb183174660503df908a3966f4ba5/html?url=https%3A%2F%2Fexample.com', 304 | $url 305 | ); 306 | } 307 | 308 | public function testGenerateSignedUrlEncodesOptionsCorrectly() 309 | { 310 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 311 | 312 | $url = $urlbox->generateSignedUrl( [ 313 | 'url' => 'https://example.com/~!@#$%^&*(){}[]=:/,;?+\'"\\', 314 | 'format' => 'png', 315 | 'block_ads' => true 316 | ] ); 317 | 318 | $this->assertEquals( 319 | "https://api.urlbox.io/v1/API_KEY/897c2361c52a5eb41b9128a2b7e70ffd5fefd662/png?url=https%3A%2F%2Fexample.com%2F~!%40%23%24%25%5E%26*()%7B%7D%5B%5D%3D%3A%2F%2C%3B%3F%2B'%22%5C&block_ads=true", 320 | $url 321 | ); 322 | } 323 | 324 | public function testGenerateSignedUrlProducesCorrectUrlForTheKitchenSink() 325 | { 326 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 327 | $options = [ 328 | 'format' => 'png', 329 | 'url' => 'https://app_staging.example.com/misc/template_preview.php?dsfdsfsdf&acc=79&cb=ba86b4c1®ions=%5B%7B%22id%22%3A%22dsfds%22%2C%22data%22%3A%7B%22html%22%3A%22It%20works!%22%7D%2C%22type%22%3A%22html%22%7D%5D&state=published&tid=7&sig=a642316f7e0ac9d783c30ef30a89bed3204252000319a2789851bc3de65ea216', 330 | 'delay' => 5000, 331 | 'selector' => '#trynow', 332 | 'full_page' => true, 333 | 'width' => 1280, 334 | 'height' => '1024', 335 | 'cookie' => [ 'ckplns=1', 'foo=bar' ], 336 | 'user_agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.32 (KHTML, like Gecko) Version/10.0 Mobile/14A5261v Safari/602.1', 337 | 'retina' => 'true', 338 | 'thumb_width' => '400', 339 | 'crop_width' => 500, 340 | 'ttl' => '604800', 341 | 'force' => true, 342 | 'headless' => false, 343 | 'wait_for' => '.someel', 344 | 'click' => '#tab-specs-trigger', 345 | 'hover' => 'a[href="https://google.com"]', 346 | 'bg_color' => '#bbbddd', 347 | 'highlight' => 'trump|inauguration', 348 | 'highlightbg' => '#11cc77', 349 | 'highlightfg' => 'green', 350 | 'hide_selector' => '.modal-backdrop, #email-roadblock-topographic-modal', 351 | 'flash' => 'true', 352 | 'timeout' => 40000, 353 | 's3_path' => '/path/to/image with space', 354 | 'use_s3' => 'true', 355 | ]; 356 | 357 | $url = $urlbox->generateSignedUrl( $options ); 358 | 359 | $this->assertEquals( 360 | "https://api.urlbox.io/v1/API_KEY/5280bc0f0fa198eb6fcde9fd3f32280dec496ee3/png?url=https%3A%2F%2Fapp_staging.example.com%2Fmisc%2Ftemplate_preview.php%3Fdsfdsfsdf%26acc%3D79%26cb%3Dba86b4c1%26regions%3D%255B%257B%2522id%2522%253A%2522dsfds%2522%252C%2522data%2522%253A%257B%2522html%2522%253A%2522It%2520works!%2522%257D%252C%2522type%2522%253A%2522html%2522%257D%255D%26state%3Dpublished%26tid%3D7%26sig%3Da642316f7e0ac9d783c30ef30a89bed3204252000319a2789851bc3de65ea216&delay=5000&selector=%23trynow&full_page=true&width=1280&height=1024&cookie=ckplns%3D1&cookie=foo%3Dbar&user_agent=Mozilla%2F5.0%20(iPhone%3B%20CPU%20iPhone%20OS%2010_0%20like%20Mac%20OS%20X)%20AppleWebKit%2F602.1.32%20(KHTML%2C%20like%20Gecko)%20Version%2F10.0%20Mobile%2F14A5261v%20Safari%2F602.1&retina=true&thumb_width=400&crop_width=500&ttl=604800&force=true&headless=false&wait_for=.someel&click=%23tab-specs-trigger&hover=a%5Bhref%3D%22https%3A%2F%2Fgoogle.com%22%5D&bg_color=%23bbbddd&highlight=trump%7Cinauguration&highlightbg=%2311cc77&highlightfg=green&hide_selector=.modal-backdrop%2C%20%23email-roadblock-topographic-modal&flash=true&timeout=40000&s3_path=%2Fpath%2Fto%2Fimage%20with%20space&use_s3=true", 361 | $url 362 | ); 363 | } 364 | 365 | public function testGenerateUnsignedUrlDefaultFormatToPng() 366 | { 367 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 368 | $this->assertEquals( 369 | 'https://api.urlbox.io/v1/API_KEY/png?url=https%3A%2F%2Fexample.com', 370 | $urlbox->generateUnsignedUrl( [ 'url' => 'https://example.com' ] ) 371 | ); 372 | } 373 | 374 | public function testGenerateUnsignedUrlCanSetFormatToPng() 375 | { 376 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 377 | 378 | $url = $urlbox->generateUnsignedUrl( [ 379 | 'url' => 'https://example.com', 380 | 'format' => 'png' 381 | ] ); 382 | 383 | $this->assertEquals( 384 | 'https://api.urlbox.io/v1/API_KEY/png?url=https%3A%2F%2Fexample.com', 385 | $url 386 | ); 387 | } 388 | 389 | public function testGenerateUnsignedUrlCanSetFormatToJpg() 390 | { 391 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 392 | 393 | $url = $urlbox->generateUnsignedUrl( [ 394 | 'url' => 'https://example.com', 395 | 'format' => 'jpg' 396 | ] ); 397 | 398 | $this->assertEquals( 399 | 'https://api.urlbox.io/v1/API_KEY/jpg?url=https%3A%2F%2Fexample.com', 400 | $url 401 | ); 402 | } 403 | 404 | public function testGenerateUnsignedUrlCanSetFormatToJpeg() 405 | { 406 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 407 | 408 | $url = $urlbox->generateUnsignedUrl( [ 409 | 'url' => 'https://example.com', 410 | 'format' => 'jpeg' 411 | ] ); 412 | 413 | $this->assertEquals( 414 | 'https://api.urlbox.io/v1/API_KEY/jpeg?url=https%3A%2F%2Fexample.com', 415 | $url 416 | ); 417 | } 418 | 419 | public function testGenerateUnsignedUrlCanSetFormatToAvif() 420 | { 421 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 422 | 423 | $url = $urlbox->generateUnsignedUrl( [ 424 | 'url' => 'https://example.com', 425 | 'format' => 'avif' 426 | ] ); 427 | 428 | $this->assertEquals( 429 | 'https://api.urlbox.io/v1/API_KEY/avif?url=https%3A%2F%2Fexample.com', 430 | $url 431 | ); 432 | } 433 | 434 | public function testGenerateUnsignedUrlCanSetFormatToWebp() 435 | { 436 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 437 | 438 | $url = $urlbox->generateUnsignedUrl( [ 439 | 'url' => 'https://example.com', 440 | 'format' => 'webp' 441 | ] ); 442 | 443 | $this->assertEquals( 444 | 'https://api.urlbox.io/v1/API_KEY/webp?url=https%3A%2F%2Fexample.com', 445 | $url 446 | ); 447 | } 448 | 449 | public function testGenerateUnsignedUrlCanSetFormatToPdf() 450 | { 451 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 452 | 453 | $url = $urlbox->generateUnsignedUrl( [ 454 | 'url' => 'https://example.com', 455 | 'format' => 'pdf' 456 | ] ); 457 | 458 | $this->assertEquals( 459 | 'https://api.urlbox.io/v1/API_KEY/pdf?url=https%3A%2F%2Fexample.com', 460 | $url 461 | ); 462 | } 463 | 464 | public function testGenerateUnsignedUrlCanSetFormatToSvg() 465 | { 466 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 467 | 468 | $url = $urlbox->generateUnsignedUrl( [ 469 | 'url' => 'https://example.com', 470 | 'format' => 'svg' 471 | ] ); 472 | 473 | $this->assertEquals( 474 | 'https://api.urlbox.io/v1/API_KEY/svg?url=https%3A%2F%2Fexample.com', 475 | $url 476 | ); 477 | } 478 | 479 | public function testGenerateUnsignedUrlCanSetFormatToHtml() 480 | { 481 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 482 | 483 | $url = $urlbox->generateUnsignedUrl( [ 484 | 'url' => 'https://example.com', 485 | 'format' => 'html' 486 | ] ); 487 | 488 | $this->assertEquals( 489 | 'https://api.urlbox.io/v1/API_KEY/html?url=https%3A%2F%2Fexample.com', 490 | $url 491 | ); 492 | } 493 | 494 | public function testGenerateUnsignedUrlEncodesOptionsCorrectly() 495 | { 496 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 497 | 498 | $url = $urlbox->generateUnsignedUrl( [ 499 | 'url' => 'https://example.com/~!@#$%^&*(){}[]=:/,;?+\'"\\', 500 | 'format' => 'png', 501 | 'block_ads' => true 502 | ] ); 503 | 504 | $this->assertEquals( 505 | "https://api.urlbox.io/v1/API_KEY/png?url=https%3A%2F%2Fexample.com%2F~!%40%23%24%25%5E%26*()%7B%7D%5B%5D%3D%3A%2F%2C%3B%3F%2B'%22%5C&block_ads=true", 506 | $url 507 | ); 508 | } 509 | 510 | public function testGenerateUnsignedUrlProducesCorrectUrlForTheKitchenSink() 511 | { 512 | $urlbox = Urlbox::fromCredentials( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET', Mockery::mock( Client::class ) ); 513 | $options = [ 514 | 'format' => 'png', 515 | 'url' => 'https://app_staging.example.com/misc/template_preview.php?dsfdsfsdf&acc=79&cb=ba86b4c1®ions=%5B%7B%22id%22%3A%22dsfds%22%2C%22data%22%3A%7B%22html%22%3A%22It%20works!%22%7D%2C%22type%22%3A%22html%22%7D%5D&state=published&tid=7&sig=a642316f7e0ac9d783c30ef30a89bed3204252000319a2789851bc3de65ea216', 516 | 'delay' => 5000, 517 | 'selector' => '#trynow', 518 | 'full_page' => true, 519 | 'width' => 1280, 520 | 'height' => '1024', 521 | 'cookie' => [ 'ckplns=1', 'foo=bar' ], 522 | 'user_agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.32 (KHTML, like Gecko) Version/10.0 Mobile/14A5261v Safari/602.1', 523 | 'retina' => 'true', 524 | 'thumb_width' => '400', 525 | 'crop_width' => 500, 526 | 'ttl' => '604800', 527 | 'force' => true, 528 | 'headless' => false, 529 | 'wait_for' => '.someel', 530 | 'click' => '#tab-specs-trigger', 531 | 'hover' => 'a[href="https://google.com"]', 532 | 'bg_color' => '#bbbddd', 533 | 'highlight' => 'trump|inauguration', 534 | 'highlightbg' => '#11cc77', 535 | 'highlightfg' => 'green', 536 | 'hide_selector' => '.modal-backdrop, #email-roadblock-topographic-modal', 537 | 'flash' => 'true', 538 | 'timeout' => 40000, 539 | 's3_path' => '/path/to/image with space', 540 | 'use_s3' => 'true', 541 | ]; 542 | 543 | $url = $urlbox->generateUnsignedUrl( $options ); 544 | 545 | $this->assertEquals( 546 | "https://api.urlbox.io/v1/API_KEY/png?url=https%3A%2F%2Fapp_staging.example.com%2Fmisc%2Ftemplate_preview.php%3Fdsfdsfsdf%26acc%3D79%26cb%3Dba86b4c1%26regions%3D%255B%257B%2522id%2522%253A%2522dsfds%2522%252C%2522data%2522%253A%257B%2522html%2522%253A%2522It%2520works!%2522%257D%252C%2522type%2522%253A%2522html%2522%257D%255D%26state%3Dpublished%26tid%3D7%26sig%3Da642316f7e0ac9d783c30ef30a89bed3204252000319a2789851bc3de65ea216&delay=5000&selector=%23trynow&full_page=true&width=1280&height=1024&cookie=ckplns%3D1&cookie=foo%3Dbar&user_agent=Mozilla%2F5.0%20(iPhone%3B%20CPU%20iPhone%20OS%2010_0%20like%20Mac%20OS%20X)%20AppleWebKit%2F602.1.32%20(KHTML%2C%20like%20Gecko)%20Version%2F10.0%20Mobile%2F14A5261v%20Safari%2F602.1&retina=true&thumb_width=400&crop_width=500&ttl=604800&force=true&headless=false&wait_for=.someel&click=%23tab-specs-trigger&hover=a%5Bhref%3D%22https%3A%2F%2Fgoogle.com%22%5D&bg_color=%23bbbddd&highlight=trump%7Cinauguration&highlightbg=%2311cc77&highlightfg=green&hide_selector=.modal-backdrop%2C%20%23email-roadblock-topographic-modal&flash=true&timeout=40000&s3_path=%2Fpath%2Fto%2Fimage%20with%20space&use_s3=true", 547 | $url 548 | ); 549 | } 550 | 551 | public function testVerifyWebhookSignatureThrowsExceptionWhenWebhookSecretNotSet() 552 | { 553 | try { 554 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET' ); 555 | $urlbox->verifyWebhookSignature( 't=1,sha256=foobar', '' ); 556 | 557 | $this->fail( 'Expected Exception not thrown' ); 558 | } catch ( Exception $exception ) { 559 | $this->assertEquals( 560 | 'Unable to verify signature as Webhook Secret is not set. You can find your webhook secret inside your project\'s settings - https://www.urlbox.io/dashboard/projects', 561 | $exception->getMessage() 562 | ); 563 | } 564 | } 565 | 566 | public function testVerifyWebhookSignatureThrowsExceptionWhenHeaderIsEmpty() 567 | { 568 | try { 569 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET' ); 570 | $urlbox->verifyWebhookSignature( '', '' ); 571 | 572 | $this->fail( 'Expected Exception not thrown' ); 573 | } catch ( InvalidArgumentException $exception ) { 574 | $this->assertEquals( 575 | 'Unable to verify signature as header is empty. Please ensure you pass the `x-urlbox-signature` from the header of the webhook response', 576 | $exception->getMessage() 577 | ); 578 | } 579 | } 580 | 581 | public function testVerifyWebhookSignatureReturnsTrueWhenSignatureMatches() 582 | { 583 | $content = '{"event": "render.succeeded","renderId": "19a59ab6-a5aa-4cde-86cb-d2b23302fd84","result": {"renderUrl": "https://renders.urlbox.io/urlbox1/renders/6215a3df94d7588f7d910513/2022/7/6/19a59ab6-a5aa-4cde-86cb-d2b23302fd84.png","size": 34097},"meta": {"startTime": "2022-07-06T17:49:18.593Z","endTime": "2022-07-06T17:49:21.103Z"}}'; 584 | $header = 't=1657129761,sha256=ddbceae3998704c0b264d8e8c1d486df9f1c0b6cdb77e6e13ce7de4a72fbd81d'; 585 | 586 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET' ); 587 | 588 | $this->assertTrue( $urlbox->verifyWebhookSignature( $header, $content ) ); 589 | } 590 | 591 | public function testVerifyWebhookSignatureReturnsFalseWhenSignatureDoNotMatch() 592 | { 593 | $content = '{"event": "render.succeeded","renderId": "19a59ab6-a5aa-4cde-86cb-d2b23302fd84","result": {"renderUrl": "https://renders.urlbox.io/urlbox1/renders/6215a3df94d7588f7d910513/2022/7/6/19a59ab6-a5aa-4cde-86cb-d2b23302fd84.png","size": 34097},"meta": {"startTime": "2022-07-06T17:49:18.593Z","endTime": "2022-07-06T17:49:21.103Z"}}'; 594 | $header = 't=1657129761,sha256=foobare3998704c0b264d8e8c1d4foobar1c0b6cdb77e6e13ce7de4a72foobar'; 595 | 596 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'WEBHOOK_SECRET' ); 597 | 598 | $this->assertFalse( $urlbox->verifyWebhookSignature( $header, $content ) ); 599 | } 600 | 601 | public function testVerifyWebhookSignatureReturnsFalseWhenWebhookSecretIncorrect() 602 | { 603 | $content = '{"event": "render.succeeded","renderId": "19a59ab6-a5aa-4cde-86cb-d2b23302fd84","result": {"renderUrl": "https://renders.urlbox.io/urlbox1/renders/6215a3df94d7588f7d910513/2022/7/6/19a59ab6-a5aa-4cde-86cb-d2b23302fd84.png","size": 34097},"meta": {"startTime": "2022-07-06T17:49:18.593Z","endTime": "2022-07-06T17:49:21.103Z"}}'; 604 | $header = 't=1657129761,sha256=ddbceae3998704c0b264d8e8c1d486df9f1c0b6cdb77e6e13ce7de4a72fbd81d'; 605 | 606 | $urlbox = new Urlbox( 'API_KEY', 'API_SECRET', 'INCORRECT_WEBHOOK_SECRET' ); 607 | 608 | $this->assertFalse( $urlbox->verifyWebhookSignature( $header, $content ) ); 609 | } 610 | 611 | 612 | } 613 | 614 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | $autoloader = __DIR__ . '/../vendor/autoload.php'; 12 | if (!is_readable( $autoloader )) { 13 | die("\nMissing Composer's vendor/autoload.php; run 'composer update' first.\n\n"); 14 | } 15 | require $autoloader; 16 | --------------------------------------------------------------------------------