├── .github └── workflows │ └── phpunit.yml ├── .gitignore ├── README.md ├── assets └── admin.css ├── changelog.txt ├── composer.json ├── composer.lock ├── hm-redirects.php ├── includes ├── admin.php ├── class-cli-commands.php ├── handle-redirects.php ├── post-type.php └── utilities.php ├── phpcs.ruleset.xml ├── phpunit.xml.dist └── tests ├── bootstrap.php ├── class-admin-test.php ├── class-handle-redirects-test.php └── class-utilities-test.php /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | jobs: 14 | run-tests: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out repository code 18 | uses: actions/checkout@v2 19 | - name: Setup PHP with tools 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: '7.4' 23 | tools: composer:v2 24 | extensions: zip 25 | env: 26 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | fail-fast: true 28 | - name: Composer install 29 | run: composer install 30 | - name: Login to Docker Hub 31 | env: 32 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} 33 | DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} 34 | run: echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin 35 | - name: Plugin Tester 36 | run: docker run --rm -e WP_VERSION=5.8 -v ${{ github.workspace }}:/code humanmade/plugin-tester 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HM Redirects 2 | 3 | Allows to redirect one path to another path on the same domain. 4 | 5 | ## Architecture 6 | Redirects are stored as a custom post type and use the following fields: 7 | 8 | - `post_name` to store the md5 hash of the _From_ path. This column is used because it is indexed, and allows fast queries. `md5` is used to simplify the storage. 9 | - `post_title` to store the _From_ path. 10 | - `post_excerpt`to store the the _To_ path. 11 | 12 | ## Tips 13 | This plugin uses `wp_safe_redirect()` to redirect. You will have to whitelist your redirect target domains using WordPress' `allowed_redirect_hosts` filter, otherwise the redirect will not work. 14 | One way to get a list of redirect target domains is to run the WP-CLI command: `wp hm-redirects find-domains`. Another is to add them dynamically just-in-time using the filter `hm_redirects_matched_redirect`. 15 | 16 | ## Attributions 17 | Props for the data storage approach to VIP's [WPCOM Legacy Redirector](https://github.com/Automattic/WPCOM-Legacy-Redirector). 18 | 19 | ## Contributing 20 | 21 | ### Before tagging a release 22 | 23 | * Update the [version string on line 8](hm-redirects.php). 24 | 25 | ### Running tests 26 | Currently the plugin's automated tests [run against PHP 7.4 and WP 5.8](.github/workflows/phpunit.yml). PHPUnit doesn't need to be installed, however: 27 | ``` 28 | composer install 29 | docker run --rm -e WP_VERSION=5.8 -v $PWD:/code humanmade/plugin-tester 30 | ``` 31 | -------------------------------------------------------------------------------- /assets/admin.css: -------------------------------------------------------------------------------- 1 | /** 2 | * HM Redirects Admin Styles. 3 | */ 4 | 5 | /* Hide unnecessary elements of the default post edit screen. */ 6 | #post-body-content, 7 | #submitdiv .misc-pub-curtime, 8 | #submitdiv .misc-pub-visibility { 9 | display: none; 10 | } 11 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | == Changelog == 2 | 3 | = 0.5.2 = 4 | * Save URLs as provided rather than lowercasing them #38 5 | 6 | = 0.5.1 = 7 | * Fix no trailing slash bug #34 8 | 9 | = 0.5.0 = 10 | * Fix redirect CPT to be private #26 11 | * Add filters to modify the contents of the HTTP Status Code drop down #21 12 | * Sanitise full from URL to relative path #16 13 | * Fix redirects with query arguments bug #15 14 | * Remove title field from Post Edit screen #14 15 | * Improve Redirect listing screen #13 16 | * Increase the size of the From and To URL fields #12 17 | * Add a filter to modify URLs before they are being saved #11 18 | * Set the order of the menu item to sit next to Tools #6 19 | * Hide Visibility and Date Settings in the Post Box #1 20 | * Fix Redirect Matching with non ASCII URLs #24 21 | 22 | = 0.4.2 = 23 | * Fix fatal error during WP CLI script execution caused by incorrect class name when calling WP_CLI::add_command 24 | * Fix php linting errors 25 | 26 | = 0.4.1 = 27 | * Use `get_page_by_path()` instead of a `WP_Query` in `get_redirect_post()`. The `get_page_by_path()` function uses 28 | an object cache bucket for post paths to id matches, therefore potentially speeding up the lookup. 29 | 30 | = 0.4 = 31 | * Remove usage of `filter_var()` to support domains with non-ASCII characters. 32 | 33 | = 0.3 = 34 | * Add WP-CLI commands. 35 | 36 | = 0.2 = 37 | * Internationalisation improvements. 38 | 39 | = 0.1 = 40 | * Initial release. Handles redirects path and query argument redirects on the same domain. 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "humanmade/hm-redirects", 3 | "description": "Simple plugin for handling WordPress redirects in a scalable manner.", 4 | "type": "wordpress-plugin", 5 | "homepage" : "https://github.com/humanmade/hm-redirects", 6 | "license" : "GPL-2.0+", 7 | "keywords": [ 8 | "WordPress", 9 | "redirects" 10 | ], 11 | "require": { 12 | "php": ">=5.4", 13 | "composer/installers": "^1.0|^2.0" 14 | }, 15 | "require-dev": { 16 | "humanmade/coding-standards": "^0.5.0", 17 | "phpunit/phpunit": "^7.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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": "2008893ea1a95f43382da2e441ff90ac", 8 | "packages": [ 9 | { 10 | "name": "composer/installers", 11 | "version": "v1.12.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/composer/installers.git", 15 | "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19", 20 | "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "composer-plugin-api": "^1.0 || ^2.0" 25 | }, 26 | "replace": { 27 | "roundcube/plugin-installer": "*", 28 | "shama/baton": "*" 29 | }, 30 | "require-dev": { 31 | "composer/composer": "1.6.* || ^2.0", 32 | "composer/semver": "^1 || ^3", 33 | "phpstan/phpstan": "^0.12.55", 34 | "phpstan/phpstan-phpunit": "^0.12.16", 35 | "symfony/phpunit-bridge": "^4.2 || ^5", 36 | "symfony/process": "^2.3" 37 | }, 38 | "type": "composer-plugin", 39 | "extra": { 40 | "class": "Composer\\Installers\\Plugin", 41 | "branch-alias": { 42 | "dev-main": "1.x-dev" 43 | } 44 | }, 45 | "autoload": { 46 | "psr-4": { 47 | "Composer\\Installers\\": "src/Composer/Installers" 48 | } 49 | }, 50 | "notification-url": "https://packagist.org/downloads/", 51 | "license": [ 52 | "MIT" 53 | ], 54 | "authors": [ 55 | { 56 | "name": "Kyle Robinson Young", 57 | "email": "kyle@dontkry.com", 58 | "homepage": "https://github.com/shama" 59 | } 60 | ], 61 | "description": "A multi-framework Composer library installer", 62 | "homepage": "https://composer.github.io/installers/", 63 | "keywords": [ 64 | "Craft", 65 | "Dolibarr", 66 | "Eliasis", 67 | "Hurad", 68 | "ImageCMS", 69 | "Kanboard", 70 | "Lan Management System", 71 | "MODX Evo", 72 | "MantisBT", 73 | "Mautic", 74 | "Maya", 75 | "OXID", 76 | "Plentymarkets", 77 | "Porto", 78 | "RadPHP", 79 | "SMF", 80 | "Starbug", 81 | "Thelia", 82 | "Whmcs", 83 | "WolfCMS", 84 | "agl", 85 | "aimeos", 86 | "annotatecms", 87 | "attogram", 88 | "bitrix", 89 | "cakephp", 90 | "chef", 91 | "cockpit", 92 | "codeigniter", 93 | "concrete5", 94 | "croogo", 95 | "dokuwiki", 96 | "drupal", 97 | "eZ Platform", 98 | "elgg", 99 | "expressionengine", 100 | "fuelphp", 101 | "grav", 102 | "installer", 103 | "itop", 104 | "joomla", 105 | "known", 106 | "kohana", 107 | "laravel", 108 | "lavalite", 109 | "lithium", 110 | "magento", 111 | "majima", 112 | "mako", 113 | "mediawiki", 114 | "miaoxing", 115 | "modulework", 116 | "modx", 117 | "moodle", 118 | "osclass", 119 | "pantheon", 120 | "phpbb", 121 | "piwik", 122 | "ppi", 123 | "processwire", 124 | "puppet", 125 | "pxcms", 126 | "reindex", 127 | "roundcube", 128 | "shopware", 129 | "silverstripe", 130 | "sydes", 131 | "sylius", 132 | "symfony", 133 | "tastyigniter", 134 | "typo3", 135 | "wordpress", 136 | "yawik", 137 | "zend", 138 | "zikula" 139 | ], 140 | "support": { 141 | "issues": "https://github.com/composer/installers/issues", 142 | "source": "https://github.com/composer/installers/tree/v1.12.0" 143 | }, 144 | "funding": [ 145 | { 146 | "url": "https://packagist.com", 147 | "type": "custom" 148 | }, 149 | { 150 | "url": "https://github.com/composer", 151 | "type": "github" 152 | }, 153 | { 154 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 155 | "type": "tidelift" 156 | } 157 | ], 158 | "time": "2021-09-13T08:19:44+00:00" 159 | } 160 | ], 161 | "packages-dev": [ 162 | { 163 | "name": "doctrine/instantiator", 164 | "version": "1.4.0", 165 | "source": { 166 | "type": "git", 167 | "url": "https://github.com/doctrine/instantiator.git", 168 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" 169 | }, 170 | "dist": { 171 | "type": "zip", 172 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", 173 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", 174 | "shasum": "" 175 | }, 176 | "require": { 177 | "php": "^7.1 || ^8.0" 178 | }, 179 | "require-dev": { 180 | "doctrine/coding-standard": "^8.0", 181 | "ext-pdo": "*", 182 | "ext-phar": "*", 183 | "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", 184 | "phpstan/phpstan": "^0.12", 185 | "phpstan/phpstan-phpunit": "^0.12", 186 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 187 | }, 188 | "type": "library", 189 | "autoload": { 190 | "psr-4": { 191 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 192 | } 193 | }, 194 | "notification-url": "https://packagist.org/downloads/", 195 | "license": [ 196 | "MIT" 197 | ], 198 | "authors": [ 199 | { 200 | "name": "Marco Pivetta", 201 | "email": "ocramius@gmail.com", 202 | "homepage": "https://ocramius.github.io/" 203 | } 204 | ], 205 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 206 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 207 | "keywords": [ 208 | "constructor", 209 | "instantiate" 210 | ], 211 | "support": { 212 | "issues": "https://github.com/doctrine/instantiator/issues", 213 | "source": "https://github.com/doctrine/instantiator/tree/1.4.0" 214 | }, 215 | "funding": [ 216 | { 217 | "url": "https://www.doctrine-project.org/sponsorship.html", 218 | "type": "custom" 219 | }, 220 | { 221 | "url": "https://www.patreon.com/phpdoctrine", 222 | "type": "patreon" 223 | }, 224 | { 225 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 226 | "type": "tidelift" 227 | } 228 | ], 229 | "time": "2020-11-10T18:47:58+00:00" 230 | }, 231 | { 232 | "name": "fig-r/psr2r-sniffer", 233 | "version": "0.5.2", 234 | "source": { 235 | "type": "git", 236 | "url": "https://github.com/php-fig-rectified/psr2r-sniffer.git", 237 | "reference": "7eb462bcf19abcae122855a6d79cc8f768c77880" 238 | }, 239 | "dist": { 240 | "type": "zip", 241 | "url": "https://api.github.com/repos/php-fig-rectified/psr2r-sniffer/zipball/7eb462bcf19abcae122855a6d79cc8f768c77880", 242 | "reference": "7eb462bcf19abcae122855a6d79cc8f768c77880", 243 | "shasum": "" 244 | }, 245 | "require": { 246 | "php": ">=5.4.16", 247 | "squizlabs/php_codesniffer": "^3.0" 248 | }, 249 | "bin": [ 250 | "bin/tokenize", 251 | "bin/sniff" 252 | ], 253 | "type": "phpcodesniffer-standard", 254 | "autoload": { 255 | "psr-4": { 256 | "PSR2R\\": "PSR2R" 257 | } 258 | }, 259 | "notification-url": "https://packagist.org/downloads/", 260 | "license": [ 261 | "MIT" 262 | ], 263 | "authors": [ 264 | { 265 | "name": "Mark Scherer", 266 | "homepage": "http://www.dereuromark.de", 267 | "role": "Contributor" 268 | } 269 | ], 270 | "description": "Code-Sniffer, Auto-Fixer and Tokenizer for PSR2-R", 271 | "keywords": [ 272 | "codesniffer", 273 | "cs" 274 | ], 275 | "support": { 276 | "issues": "https://github.com/php-fig-rectified/psr2r-sniffer/issues", 277 | "source": "https://github.com/php-fig-rectified/psr2r-sniffer/tree/0.5.2" 278 | }, 279 | "time": "2019-07-30T11:13:07+00:00" 280 | }, 281 | { 282 | "name": "humanmade/coding-standards", 283 | "version": "v0.5.0", 284 | "source": { 285 | "type": "git", 286 | "url": "https://github.com/humanmade/coding-standards.git", 287 | "reference": "b35747249bcc727a9eff22f746aaf9758d8a90ce" 288 | }, 289 | "dist": { 290 | "type": "zip", 291 | "url": "https://api.github.com/repos/humanmade/coding-standards/zipball/b35747249bcc727a9eff22f746aaf9758d8a90ce", 292 | "reference": "b35747249bcc727a9eff22f746aaf9758d8a90ce", 293 | "shasum": "" 294 | }, 295 | "require": { 296 | "fig-r/psr2r-sniffer": "^0.5.0", 297 | "squizlabs/php_codesniffer": "^3.1", 298 | "wp-coding-standards/wpcs": "^0.14.0" 299 | }, 300 | "require-dev": { 301 | "phpunit/phpunit": "^5.7" 302 | }, 303 | "type": "project", 304 | "notification-url": "https://packagist.org/downloads/", 305 | "license": [ 306 | "GPL-2.0-or-later" 307 | ], 308 | "description": "Human Made coding standards", 309 | "support": { 310 | "issues": "https://github.com/humanmade/coding-standards/issues", 311 | "source": "https://github.com/humanmade/coding-standards/tree/v0.5.0" 312 | }, 313 | "time": "2018-05-22T13:24:47+00:00" 314 | }, 315 | { 316 | "name": "myclabs/deep-copy", 317 | "version": "1.10.2", 318 | "source": { 319 | "type": "git", 320 | "url": "https://github.com/myclabs/DeepCopy.git", 321 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" 322 | }, 323 | "dist": { 324 | "type": "zip", 325 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", 326 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", 327 | "shasum": "" 328 | }, 329 | "require": { 330 | "php": "^7.1 || ^8.0" 331 | }, 332 | "replace": { 333 | "myclabs/deep-copy": "self.version" 334 | }, 335 | "require-dev": { 336 | "doctrine/collections": "^1.0", 337 | "doctrine/common": "^2.6", 338 | "phpunit/phpunit": "^7.1" 339 | }, 340 | "type": "library", 341 | "autoload": { 342 | "psr-4": { 343 | "DeepCopy\\": "src/DeepCopy/" 344 | }, 345 | "files": [ 346 | "src/DeepCopy/deep_copy.php" 347 | ] 348 | }, 349 | "notification-url": "https://packagist.org/downloads/", 350 | "license": [ 351 | "MIT" 352 | ], 353 | "description": "Create deep copies (clones) of your objects", 354 | "keywords": [ 355 | "clone", 356 | "copy", 357 | "duplicate", 358 | "object", 359 | "object graph" 360 | ], 361 | "support": { 362 | "issues": "https://github.com/myclabs/DeepCopy/issues", 363 | "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" 364 | }, 365 | "funding": [ 366 | { 367 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 368 | "type": "tidelift" 369 | } 370 | ], 371 | "time": "2020-11-13T09:40:50+00:00" 372 | }, 373 | { 374 | "name": "phar-io/manifest", 375 | "version": "1.0.3", 376 | "source": { 377 | "type": "git", 378 | "url": "https://github.com/phar-io/manifest.git", 379 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 380 | }, 381 | "dist": { 382 | "type": "zip", 383 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 384 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 385 | "shasum": "" 386 | }, 387 | "require": { 388 | "ext-dom": "*", 389 | "ext-phar": "*", 390 | "phar-io/version": "^2.0", 391 | "php": "^5.6 || ^7.0" 392 | }, 393 | "type": "library", 394 | "extra": { 395 | "branch-alias": { 396 | "dev-master": "1.0.x-dev" 397 | } 398 | }, 399 | "autoload": { 400 | "classmap": [ 401 | "src/" 402 | ] 403 | }, 404 | "notification-url": "https://packagist.org/downloads/", 405 | "license": [ 406 | "BSD-3-Clause" 407 | ], 408 | "authors": [ 409 | { 410 | "name": "Arne Blankerts", 411 | "email": "arne@blankerts.de", 412 | "role": "Developer" 413 | }, 414 | { 415 | "name": "Sebastian Heuer", 416 | "email": "sebastian@phpeople.de", 417 | "role": "Developer" 418 | }, 419 | { 420 | "name": "Sebastian Bergmann", 421 | "email": "sebastian@phpunit.de", 422 | "role": "Developer" 423 | } 424 | ], 425 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 426 | "support": { 427 | "issues": "https://github.com/phar-io/manifest/issues", 428 | "source": "https://github.com/phar-io/manifest/tree/master" 429 | }, 430 | "time": "2018-07-08T19:23:20+00:00" 431 | }, 432 | { 433 | "name": "phar-io/version", 434 | "version": "2.0.1", 435 | "source": { 436 | "type": "git", 437 | "url": "https://github.com/phar-io/version.git", 438 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 439 | }, 440 | "dist": { 441 | "type": "zip", 442 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 443 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 444 | "shasum": "" 445 | }, 446 | "require": { 447 | "php": "^5.6 || ^7.0" 448 | }, 449 | "type": "library", 450 | "autoload": { 451 | "classmap": [ 452 | "src/" 453 | ] 454 | }, 455 | "notification-url": "https://packagist.org/downloads/", 456 | "license": [ 457 | "BSD-3-Clause" 458 | ], 459 | "authors": [ 460 | { 461 | "name": "Arne Blankerts", 462 | "email": "arne@blankerts.de", 463 | "role": "Developer" 464 | }, 465 | { 466 | "name": "Sebastian Heuer", 467 | "email": "sebastian@phpeople.de", 468 | "role": "Developer" 469 | }, 470 | { 471 | "name": "Sebastian Bergmann", 472 | "email": "sebastian@phpunit.de", 473 | "role": "Developer" 474 | } 475 | ], 476 | "description": "Library for handling version information and constraints", 477 | "support": { 478 | "issues": "https://github.com/phar-io/version/issues", 479 | "source": "https://github.com/phar-io/version/tree/master" 480 | }, 481 | "time": "2018-07-08T19:19:57+00:00" 482 | }, 483 | { 484 | "name": "phpdocumentor/reflection-common", 485 | "version": "2.2.0", 486 | "source": { 487 | "type": "git", 488 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 489 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 490 | }, 491 | "dist": { 492 | "type": "zip", 493 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 494 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 495 | "shasum": "" 496 | }, 497 | "require": { 498 | "php": "^7.2 || ^8.0" 499 | }, 500 | "type": "library", 501 | "extra": { 502 | "branch-alias": { 503 | "dev-2.x": "2.x-dev" 504 | } 505 | }, 506 | "autoload": { 507 | "psr-4": { 508 | "phpDocumentor\\Reflection\\": "src/" 509 | } 510 | }, 511 | "notification-url": "https://packagist.org/downloads/", 512 | "license": [ 513 | "MIT" 514 | ], 515 | "authors": [ 516 | { 517 | "name": "Jaap van Otterdijk", 518 | "email": "opensource@ijaap.nl" 519 | } 520 | ], 521 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 522 | "homepage": "http://www.phpdoc.org", 523 | "keywords": [ 524 | "FQSEN", 525 | "phpDocumentor", 526 | "phpdoc", 527 | "reflection", 528 | "static analysis" 529 | ], 530 | "support": { 531 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 532 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 533 | }, 534 | "time": "2020-06-27T09:03:43+00:00" 535 | }, 536 | { 537 | "name": "phpdocumentor/reflection-docblock", 538 | "version": "5.3.0", 539 | "source": { 540 | "type": "git", 541 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 542 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 543 | }, 544 | "dist": { 545 | "type": "zip", 546 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 547 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 548 | "shasum": "" 549 | }, 550 | "require": { 551 | "ext-filter": "*", 552 | "php": "^7.2 || ^8.0", 553 | "phpdocumentor/reflection-common": "^2.2", 554 | "phpdocumentor/type-resolver": "^1.3", 555 | "webmozart/assert": "^1.9.1" 556 | }, 557 | "require-dev": { 558 | "mockery/mockery": "~1.3.2", 559 | "psalm/phar": "^4.8" 560 | }, 561 | "type": "library", 562 | "extra": { 563 | "branch-alias": { 564 | "dev-master": "5.x-dev" 565 | } 566 | }, 567 | "autoload": { 568 | "psr-4": { 569 | "phpDocumentor\\Reflection\\": "src" 570 | } 571 | }, 572 | "notification-url": "https://packagist.org/downloads/", 573 | "license": [ 574 | "MIT" 575 | ], 576 | "authors": [ 577 | { 578 | "name": "Mike van Riel", 579 | "email": "me@mikevanriel.com" 580 | }, 581 | { 582 | "name": "Jaap van Otterdijk", 583 | "email": "account@ijaap.nl" 584 | } 585 | ], 586 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 587 | "support": { 588 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 589 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" 590 | }, 591 | "time": "2021-10-19T17:43:47+00:00" 592 | }, 593 | { 594 | "name": "phpdocumentor/type-resolver", 595 | "version": "1.6.0", 596 | "source": { 597 | "type": "git", 598 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 599 | "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" 600 | }, 601 | "dist": { 602 | "type": "zip", 603 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", 604 | "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", 605 | "shasum": "" 606 | }, 607 | "require": { 608 | "php": "^7.2 || ^8.0", 609 | "phpdocumentor/reflection-common": "^2.0" 610 | }, 611 | "require-dev": { 612 | "ext-tokenizer": "*", 613 | "psalm/phar": "^4.8" 614 | }, 615 | "type": "library", 616 | "extra": { 617 | "branch-alias": { 618 | "dev-1.x": "1.x-dev" 619 | } 620 | }, 621 | "autoload": { 622 | "psr-4": { 623 | "phpDocumentor\\Reflection\\": "src" 624 | } 625 | }, 626 | "notification-url": "https://packagist.org/downloads/", 627 | "license": [ 628 | "MIT" 629 | ], 630 | "authors": [ 631 | { 632 | "name": "Mike van Riel", 633 | "email": "me@mikevanriel.com" 634 | } 635 | ], 636 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 637 | "support": { 638 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 639 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" 640 | }, 641 | "time": "2022-01-04T19:58:01+00:00" 642 | }, 643 | { 644 | "name": "phpspec/prophecy", 645 | "version": "v1.15.0", 646 | "source": { 647 | "type": "git", 648 | "url": "https://github.com/phpspec/prophecy.git", 649 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" 650 | }, 651 | "dist": { 652 | "type": "zip", 653 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 654 | "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", 655 | "shasum": "" 656 | }, 657 | "require": { 658 | "doctrine/instantiator": "^1.2", 659 | "php": "^7.2 || ~8.0, <8.2", 660 | "phpdocumentor/reflection-docblock": "^5.2", 661 | "sebastian/comparator": "^3.0 || ^4.0", 662 | "sebastian/recursion-context": "^3.0 || ^4.0" 663 | }, 664 | "require-dev": { 665 | "phpspec/phpspec": "^6.0 || ^7.0", 666 | "phpunit/phpunit": "^8.0 || ^9.0" 667 | }, 668 | "type": "library", 669 | "extra": { 670 | "branch-alias": { 671 | "dev-master": "1.x-dev" 672 | } 673 | }, 674 | "autoload": { 675 | "psr-4": { 676 | "Prophecy\\": "src/Prophecy" 677 | } 678 | }, 679 | "notification-url": "https://packagist.org/downloads/", 680 | "license": [ 681 | "MIT" 682 | ], 683 | "authors": [ 684 | { 685 | "name": "Konstantin Kudryashov", 686 | "email": "ever.zet@gmail.com", 687 | "homepage": "http://everzet.com" 688 | }, 689 | { 690 | "name": "Marcello Duarte", 691 | "email": "marcello.duarte@gmail.com" 692 | } 693 | ], 694 | "description": "Highly opinionated mocking framework for PHP 5.3+", 695 | "homepage": "https://github.com/phpspec/prophecy", 696 | "keywords": [ 697 | "Double", 698 | "Dummy", 699 | "fake", 700 | "mock", 701 | "spy", 702 | "stub" 703 | ], 704 | "support": { 705 | "issues": "https://github.com/phpspec/prophecy/issues", 706 | "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" 707 | }, 708 | "time": "2021-12-08T12:19:24+00:00" 709 | }, 710 | { 711 | "name": "phpunit/php-code-coverage", 712 | "version": "6.1.4", 713 | "source": { 714 | "type": "git", 715 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 716 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" 717 | }, 718 | "dist": { 719 | "type": "zip", 720 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 721 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 722 | "shasum": "" 723 | }, 724 | "require": { 725 | "ext-dom": "*", 726 | "ext-xmlwriter": "*", 727 | "php": "^7.1", 728 | "phpunit/php-file-iterator": "^2.0", 729 | "phpunit/php-text-template": "^1.2.1", 730 | "phpunit/php-token-stream": "^3.0", 731 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 732 | "sebastian/environment": "^3.1 || ^4.0", 733 | "sebastian/version": "^2.0.1", 734 | "theseer/tokenizer": "^1.1" 735 | }, 736 | "require-dev": { 737 | "phpunit/phpunit": "^7.0" 738 | }, 739 | "suggest": { 740 | "ext-xdebug": "^2.6.0" 741 | }, 742 | "type": "library", 743 | "extra": { 744 | "branch-alias": { 745 | "dev-master": "6.1-dev" 746 | } 747 | }, 748 | "autoload": { 749 | "classmap": [ 750 | "src/" 751 | ] 752 | }, 753 | "notification-url": "https://packagist.org/downloads/", 754 | "license": [ 755 | "BSD-3-Clause" 756 | ], 757 | "authors": [ 758 | { 759 | "name": "Sebastian Bergmann", 760 | "email": "sebastian@phpunit.de", 761 | "role": "lead" 762 | } 763 | ], 764 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 765 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 766 | "keywords": [ 767 | "coverage", 768 | "testing", 769 | "xunit" 770 | ], 771 | "support": { 772 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 773 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master" 774 | }, 775 | "time": "2018-10-31T16:06:48+00:00" 776 | }, 777 | { 778 | "name": "phpunit/php-file-iterator", 779 | "version": "2.0.5", 780 | "source": { 781 | "type": "git", 782 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 783 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" 784 | }, 785 | "dist": { 786 | "type": "zip", 787 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 788 | "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", 789 | "shasum": "" 790 | }, 791 | "require": { 792 | "php": ">=7.1" 793 | }, 794 | "require-dev": { 795 | "phpunit/phpunit": "^8.5" 796 | }, 797 | "type": "library", 798 | "extra": { 799 | "branch-alias": { 800 | "dev-master": "2.0.x-dev" 801 | } 802 | }, 803 | "autoload": { 804 | "classmap": [ 805 | "src/" 806 | ] 807 | }, 808 | "notification-url": "https://packagist.org/downloads/", 809 | "license": [ 810 | "BSD-3-Clause" 811 | ], 812 | "authors": [ 813 | { 814 | "name": "Sebastian Bergmann", 815 | "email": "sebastian@phpunit.de", 816 | "role": "lead" 817 | } 818 | ], 819 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 820 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 821 | "keywords": [ 822 | "filesystem", 823 | "iterator" 824 | ], 825 | "support": { 826 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 827 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" 828 | }, 829 | "funding": [ 830 | { 831 | "url": "https://github.com/sebastianbergmann", 832 | "type": "github" 833 | } 834 | ], 835 | "time": "2021-12-02T12:42:26+00:00" 836 | }, 837 | { 838 | "name": "phpunit/php-text-template", 839 | "version": "1.2.1", 840 | "source": { 841 | "type": "git", 842 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 843 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 844 | }, 845 | "dist": { 846 | "type": "zip", 847 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 848 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 849 | "shasum": "" 850 | }, 851 | "require": { 852 | "php": ">=5.3.3" 853 | }, 854 | "type": "library", 855 | "autoload": { 856 | "classmap": [ 857 | "src/" 858 | ] 859 | }, 860 | "notification-url": "https://packagist.org/downloads/", 861 | "license": [ 862 | "BSD-3-Clause" 863 | ], 864 | "authors": [ 865 | { 866 | "name": "Sebastian Bergmann", 867 | "email": "sebastian@phpunit.de", 868 | "role": "lead" 869 | } 870 | ], 871 | "description": "Simple template engine.", 872 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 873 | "keywords": [ 874 | "template" 875 | ], 876 | "support": { 877 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 878 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" 879 | }, 880 | "time": "2015-06-21T13:50:34+00:00" 881 | }, 882 | { 883 | "name": "phpunit/php-timer", 884 | "version": "2.1.3", 885 | "source": { 886 | "type": "git", 887 | "url": "https://github.com/sebastianbergmann/php-timer.git", 888 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" 889 | }, 890 | "dist": { 891 | "type": "zip", 892 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", 893 | "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", 894 | "shasum": "" 895 | }, 896 | "require": { 897 | "php": ">=7.1" 898 | }, 899 | "require-dev": { 900 | "phpunit/phpunit": "^8.5" 901 | }, 902 | "type": "library", 903 | "extra": { 904 | "branch-alias": { 905 | "dev-master": "2.1-dev" 906 | } 907 | }, 908 | "autoload": { 909 | "classmap": [ 910 | "src/" 911 | ] 912 | }, 913 | "notification-url": "https://packagist.org/downloads/", 914 | "license": [ 915 | "BSD-3-Clause" 916 | ], 917 | "authors": [ 918 | { 919 | "name": "Sebastian Bergmann", 920 | "email": "sebastian@phpunit.de", 921 | "role": "lead" 922 | } 923 | ], 924 | "description": "Utility class for timing", 925 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 926 | "keywords": [ 927 | "timer" 928 | ], 929 | "support": { 930 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 931 | "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" 932 | }, 933 | "funding": [ 934 | { 935 | "url": "https://github.com/sebastianbergmann", 936 | "type": "github" 937 | } 938 | ], 939 | "time": "2020-11-30T08:20:02+00:00" 940 | }, 941 | { 942 | "name": "phpunit/php-token-stream", 943 | "version": "3.1.3", 944 | "source": { 945 | "type": "git", 946 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 947 | "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" 948 | }, 949 | "dist": { 950 | "type": "zip", 951 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", 952 | "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", 953 | "shasum": "" 954 | }, 955 | "require": { 956 | "ext-tokenizer": "*", 957 | "php": ">=7.1" 958 | }, 959 | "require-dev": { 960 | "phpunit/phpunit": "^7.0" 961 | }, 962 | "type": "library", 963 | "extra": { 964 | "branch-alias": { 965 | "dev-master": "3.1-dev" 966 | } 967 | }, 968 | "autoload": { 969 | "classmap": [ 970 | "src/" 971 | ] 972 | }, 973 | "notification-url": "https://packagist.org/downloads/", 974 | "license": [ 975 | "BSD-3-Clause" 976 | ], 977 | "authors": [ 978 | { 979 | "name": "Sebastian Bergmann", 980 | "email": "sebastian@phpunit.de" 981 | } 982 | ], 983 | "description": "Wrapper around PHP's tokenizer extension.", 984 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 985 | "keywords": [ 986 | "tokenizer" 987 | ], 988 | "support": { 989 | "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", 990 | "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3" 991 | }, 992 | "funding": [ 993 | { 994 | "url": "https://github.com/sebastianbergmann", 995 | "type": "github" 996 | } 997 | ], 998 | "abandoned": true, 999 | "time": "2021-07-26T12:15:06+00:00" 1000 | }, 1001 | { 1002 | "name": "phpunit/phpunit", 1003 | "version": "7.5.20", 1004 | "source": { 1005 | "type": "git", 1006 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1007 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" 1008 | }, 1009 | "dist": { 1010 | "type": "zip", 1011 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", 1012 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", 1013 | "shasum": "" 1014 | }, 1015 | "require": { 1016 | "doctrine/instantiator": "^1.1", 1017 | "ext-dom": "*", 1018 | "ext-json": "*", 1019 | "ext-libxml": "*", 1020 | "ext-mbstring": "*", 1021 | "ext-xml": "*", 1022 | "myclabs/deep-copy": "^1.7", 1023 | "phar-io/manifest": "^1.0.2", 1024 | "phar-io/version": "^2.0", 1025 | "php": "^7.1", 1026 | "phpspec/prophecy": "^1.7", 1027 | "phpunit/php-code-coverage": "^6.0.7", 1028 | "phpunit/php-file-iterator": "^2.0.1", 1029 | "phpunit/php-text-template": "^1.2.1", 1030 | "phpunit/php-timer": "^2.1", 1031 | "sebastian/comparator": "^3.0", 1032 | "sebastian/diff": "^3.0", 1033 | "sebastian/environment": "^4.0", 1034 | "sebastian/exporter": "^3.1", 1035 | "sebastian/global-state": "^2.0", 1036 | "sebastian/object-enumerator": "^3.0.3", 1037 | "sebastian/resource-operations": "^2.0", 1038 | "sebastian/version": "^2.0.1" 1039 | }, 1040 | "conflict": { 1041 | "phpunit/phpunit-mock-objects": "*" 1042 | }, 1043 | "require-dev": { 1044 | "ext-pdo": "*" 1045 | }, 1046 | "suggest": { 1047 | "ext-soap": "*", 1048 | "ext-xdebug": "*", 1049 | "phpunit/php-invoker": "^2.0" 1050 | }, 1051 | "bin": [ 1052 | "phpunit" 1053 | ], 1054 | "type": "library", 1055 | "extra": { 1056 | "branch-alias": { 1057 | "dev-master": "7.5-dev" 1058 | } 1059 | }, 1060 | "autoload": { 1061 | "classmap": [ 1062 | "src/" 1063 | ] 1064 | }, 1065 | "notification-url": "https://packagist.org/downloads/", 1066 | "license": [ 1067 | "BSD-3-Clause" 1068 | ], 1069 | "authors": [ 1070 | { 1071 | "name": "Sebastian Bergmann", 1072 | "email": "sebastian@phpunit.de", 1073 | "role": "lead" 1074 | } 1075 | ], 1076 | "description": "The PHP Unit Testing framework.", 1077 | "homepage": "https://phpunit.de/", 1078 | "keywords": [ 1079 | "phpunit", 1080 | "testing", 1081 | "xunit" 1082 | ], 1083 | "support": { 1084 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1085 | "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20" 1086 | }, 1087 | "time": "2020-01-08T08:45:45+00:00" 1088 | }, 1089 | { 1090 | "name": "sebastian/code-unit-reverse-lookup", 1091 | "version": "1.0.2", 1092 | "source": { 1093 | "type": "git", 1094 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1095 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" 1096 | }, 1097 | "dist": { 1098 | "type": "zip", 1099 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1100 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1101 | "shasum": "" 1102 | }, 1103 | "require": { 1104 | "php": ">=5.6" 1105 | }, 1106 | "require-dev": { 1107 | "phpunit/phpunit": "^8.5" 1108 | }, 1109 | "type": "library", 1110 | "extra": { 1111 | "branch-alias": { 1112 | "dev-master": "1.0.x-dev" 1113 | } 1114 | }, 1115 | "autoload": { 1116 | "classmap": [ 1117 | "src/" 1118 | ] 1119 | }, 1120 | "notification-url": "https://packagist.org/downloads/", 1121 | "license": [ 1122 | "BSD-3-Clause" 1123 | ], 1124 | "authors": [ 1125 | { 1126 | "name": "Sebastian Bergmann", 1127 | "email": "sebastian@phpunit.de" 1128 | } 1129 | ], 1130 | "description": "Looks up which function or method a line of code belongs to", 1131 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1132 | "support": { 1133 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1134 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" 1135 | }, 1136 | "funding": [ 1137 | { 1138 | "url": "https://github.com/sebastianbergmann", 1139 | "type": "github" 1140 | } 1141 | ], 1142 | "time": "2020-11-30T08:15:22+00:00" 1143 | }, 1144 | { 1145 | "name": "sebastian/comparator", 1146 | "version": "3.0.3", 1147 | "source": { 1148 | "type": "git", 1149 | "url": "https://github.com/sebastianbergmann/comparator.git", 1150 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" 1151 | }, 1152 | "dist": { 1153 | "type": "zip", 1154 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", 1155 | "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", 1156 | "shasum": "" 1157 | }, 1158 | "require": { 1159 | "php": ">=7.1", 1160 | "sebastian/diff": "^3.0", 1161 | "sebastian/exporter": "^3.1" 1162 | }, 1163 | "require-dev": { 1164 | "phpunit/phpunit": "^8.5" 1165 | }, 1166 | "type": "library", 1167 | "extra": { 1168 | "branch-alias": { 1169 | "dev-master": "3.0-dev" 1170 | } 1171 | }, 1172 | "autoload": { 1173 | "classmap": [ 1174 | "src/" 1175 | ] 1176 | }, 1177 | "notification-url": "https://packagist.org/downloads/", 1178 | "license": [ 1179 | "BSD-3-Clause" 1180 | ], 1181 | "authors": [ 1182 | { 1183 | "name": "Sebastian Bergmann", 1184 | "email": "sebastian@phpunit.de" 1185 | }, 1186 | { 1187 | "name": "Jeff Welch", 1188 | "email": "whatthejeff@gmail.com" 1189 | }, 1190 | { 1191 | "name": "Volker Dusch", 1192 | "email": "github@wallbash.com" 1193 | }, 1194 | { 1195 | "name": "Bernhard Schussek", 1196 | "email": "bschussek@2bepublished.at" 1197 | } 1198 | ], 1199 | "description": "Provides the functionality to compare PHP values for equality", 1200 | "homepage": "https://github.com/sebastianbergmann/comparator", 1201 | "keywords": [ 1202 | "comparator", 1203 | "compare", 1204 | "equality" 1205 | ], 1206 | "support": { 1207 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1208 | "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" 1209 | }, 1210 | "funding": [ 1211 | { 1212 | "url": "https://github.com/sebastianbergmann", 1213 | "type": "github" 1214 | } 1215 | ], 1216 | "time": "2020-11-30T08:04:30+00:00" 1217 | }, 1218 | { 1219 | "name": "sebastian/diff", 1220 | "version": "3.0.3", 1221 | "source": { 1222 | "type": "git", 1223 | "url": "https://github.com/sebastianbergmann/diff.git", 1224 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" 1225 | }, 1226 | "dist": { 1227 | "type": "zip", 1228 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 1229 | "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", 1230 | "shasum": "" 1231 | }, 1232 | "require": { 1233 | "php": ">=7.1" 1234 | }, 1235 | "require-dev": { 1236 | "phpunit/phpunit": "^7.5 || ^8.0", 1237 | "symfony/process": "^2 || ^3.3 || ^4" 1238 | }, 1239 | "type": "library", 1240 | "extra": { 1241 | "branch-alias": { 1242 | "dev-master": "3.0-dev" 1243 | } 1244 | }, 1245 | "autoload": { 1246 | "classmap": [ 1247 | "src/" 1248 | ] 1249 | }, 1250 | "notification-url": "https://packagist.org/downloads/", 1251 | "license": [ 1252 | "BSD-3-Clause" 1253 | ], 1254 | "authors": [ 1255 | { 1256 | "name": "Sebastian Bergmann", 1257 | "email": "sebastian@phpunit.de" 1258 | }, 1259 | { 1260 | "name": "Kore Nordmann", 1261 | "email": "mail@kore-nordmann.de" 1262 | } 1263 | ], 1264 | "description": "Diff implementation", 1265 | "homepage": "https://github.com/sebastianbergmann/diff", 1266 | "keywords": [ 1267 | "diff", 1268 | "udiff", 1269 | "unidiff", 1270 | "unified diff" 1271 | ], 1272 | "support": { 1273 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1274 | "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" 1275 | }, 1276 | "funding": [ 1277 | { 1278 | "url": "https://github.com/sebastianbergmann", 1279 | "type": "github" 1280 | } 1281 | ], 1282 | "time": "2020-11-30T07:59:04+00:00" 1283 | }, 1284 | { 1285 | "name": "sebastian/environment", 1286 | "version": "4.2.4", 1287 | "source": { 1288 | "type": "git", 1289 | "url": "https://github.com/sebastianbergmann/environment.git", 1290 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" 1291 | }, 1292 | "dist": { 1293 | "type": "zip", 1294 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1295 | "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", 1296 | "shasum": "" 1297 | }, 1298 | "require": { 1299 | "php": ">=7.1" 1300 | }, 1301 | "require-dev": { 1302 | "phpunit/phpunit": "^7.5" 1303 | }, 1304 | "suggest": { 1305 | "ext-posix": "*" 1306 | }, 1307 | "type": "library", 1308 | "extra": { 1309 | "branch-alias": { 1310 | "dev-master": "4.2-dev" 1311 | } 1312 | }, 1313 | "autoload": { 1314 | "classmap": [ 1315 | "src/" 1316 | ] 1317 | }, 1318 | "notification-url": "https://packagist.org/downloads/", 1319 | "license": [ 1320 | "BSD-3-Clause" 1321 | ], 1322 | "authors": [ 1323 | { 1324 | "name": "Sebastian Bergmann", 1325 | "email": "sebastian@phpunit.de" 1326 | } 1327 | ], 1328 | "description": "Provides functionality to handle HHVM/PHP environments", 1329 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1330 | "keywords": [ 1331 | "Xdebug", 1332 | "environment", 1333 | "hhvm" 1334 | ], 1335 | "support": { 1336 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1337 | "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" 1338 | }, 1339 | "funding": [ 1340 | { 1341 | "url": "https://github.com/sebastianbergmann", 1342 | "type": "github" 1343 | } 1344 | ], 1345 | "time": "2020-11-30T07:53:42+00:00" 1346 | }, 1347 | { 1348 | "name": "sebastian/exporter", 1349 | "version": "3.1.4", 1350 | "source": { 1351 | "type": "git", 1352 | "url": "https://github.com/sebastianbergmann/exporter.git", 1353 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" 1354 | }, 1355 | "dist": { 1356 | "type": "zip", 1357 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1358 | "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", 1359 | "shasum": "" 1360 | }, 1361 | "require": { 1362 | "php": ">=7.0", 1363 | "sebastian/recursion-context": "^3.0" 1364 | }, 1365 | "require-dev": { 1366 | "ext-mbstring": "*", 1367 | "phpunit/phpunit": "^8.5" 1368 | }, 1369 | "type": "library", 1370 | "extra": { 1371 | "branch-alias": { 1372 | "dev-master": "3.1.x-dev" 1373 | } 1374 | }, 1375 | "autoload": { 1376 | "classmap": [ 1377 | "src/" 1378 | ] 1379 | }, 1380 | "notification-url": "https://packagist.org/downloads/", 1381 | "license": [ 1382 | "BSD-3-Clause" 1383 | ], 1384 | "authors": [ 1385 | { 1386 | "name": "Sebastian Bergmann", 1387 | "email": "sebastian@phpunit.de" 1388 | }, 1389 | { 1390 | "name": "Jeff Welch", 1391 | "email": "whatthejeff@gmail.com" 1392 | }, 1393 | { 1394 | "name": "Volker Dusch", 1395 | "email": "github@wallbash.com" 1396 | }, 1397 | { 1398 | "name": "Adam Harvey", 1399 | "email": "aharvey@php.net" 1400 | }, 1401 | { 1402 | "name": "Bernhard Schussek", 1403 | "email": "bschussek@gmail.com" 1404 | } 1405 | ], 1406 | "description": "Provides the functionality to export PHP variables for visualization", 1407 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1408 | "keywords": [ 1409 | "export", 1410 | "exporter" 1411 | ], 1412 | "support": { 1413 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1414 | "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" 1415 | }, 1416 | "funding": [ 1417 | { 1418 | "url": "https://github.com/sebastianbergmann", 1419 | "type": "github" 1420 | } 1421 | ], 1422 | "time": "2021-11-11T13:51:24+00:00" 1423 | }, 1424 | { 1425 | "name": "sebastian/global-state", 1426 | "version": "2.0.0", 1427 | "source": { 1428 | "type": "git", 1429 | "url": "https://github.com/sebastianbergmann/global-state.git", 1430 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1431 | }, 1432 | "dist": { 1433 | "type": "zip", 1434 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1435 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1436 | "shasum": "" 1437 | }, 1438 | "require": { 1439 | "php": "^7.0" 1440 | }, 1441 | "require-dev": { 1442 | "phpunit/phpunit": "^6.0" 1443 | }, 1444 | "suggest": { 1445 | "ext-uopz": "*" 1446 | }, 1447 | "type": "library", 1448 | "extra": { 1449 | "branch-alias": { 1450 | "dev-master": "2.0-dev" 1451 | } 1452 | }, 1453 | "autoload": { 1454 | "classmap": [ 1455 | "src/" 1456 | ] 1457 | }, 1458 | "notification-url": "https://packagist.org/downloads/", 1459 | "license": [ 1460 | "BSD-3-Clause" 1461 | ], 1462 | "authors": [ 1463 | { 1464 | "name": "Sebastian Bergmann", 1465 | "email": "sebastian@phpunit.de" 1466 | } 1467 | ], 1468 | "description": "Snapshotting of global state", 1469 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1470 | "keywords": [ 1471 | "global state" 1472 | ], 1473 | "support": { 1474 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1475 | "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" 1476 | }, 1477 | "time": "2017-04-27T15:39:26+00:00" 1478 | }, 1479 | { 1480 | "name": "sebastian/object-enumerator", 1481 | "version": "3.0.4", 1482 | "source": { 1483 | "type": "git", 1484 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1485 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" 1486 | }, 1487 | "dist": { 1488 | "type": "zip", 1489 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1490 | "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", 1491 | "shasum": "" 1492 | }, 1493 | "require": { 1494 | "php": ">=7.0", 1495 | "sebastian/object-reflector": "^1.1.1", 1496 | "sebastian/recursion-context": "^3.0" 1497 | }, 1498 | "require-dev": { 1499 | "phpunit/phpunit": "^6.0" 1500 | }, 1501 | "type": "library", 1502 | "extra": { 1503 | "branch-alias": { 1504 | "dev-master": "3.0.x-dev" 1505 | } 1506 | }, 1507 | "autoload": { 1508 | "classmap": [ 1509 | "src/" 1510 | ] 1511 | }, 1512 | "notification-url": "https://packagist.org/downloads/", 1513 | "license": [ 1514 | "BSD-3-Clause" 1515 | ], 1516 | "authors": [ 1517 | { 1518 | "name": "Sebastian Bergmann", 1519 | "email": "sebastian@phpunit.de" 1520 | } 1521 | ], 1522 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1523 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1524 | "support": { 1525 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1526 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" 1527 | }, 1528 | "funding": [ 1529 | { 1530 | "url": "https://github.com/sebastianbergmann", 1531 | "type": "github" 1532 | } 1533 | ], 1534 | "time": "2020-11-30T07:40:27+00:00" 1535 | }, 1536 | { 1537 | "name": "sebastian/object-reflector", 1538 | "version": "1.1.2", 1539 | "source": { 1540 | "type": "git", 1541 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1542 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" 1543 | }, 1544 | "dist": { 1545 | "type": "zip", 1546 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1547 | "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", 1548 | "shasum": "" 1549 | }, 1550 | "require": { 1551 | "php": ">=7.0" 1552 | }, 1553 | "require-dev": { 1554 | "phpunit/phpunit": "^6.0" 1555 | }, 1556 | "type": "library", 1557 | "extra": { 1558 | "branch-alias": { 1559 | "dev-master": "1.1-dev" 1560 | } 1561 | }, 1562 | "autoload": { 1563 | "classmap": [ 1564 | "src/" 1565 | ] 1566 | }, 1567 | "notification-url": "https://packagist.org/downloads/", 1568 | "license": [ 1569 | "BSD-3-Clause" 1570 | ], 1571 | "authors": [ 1572 | { 1573 | "name": "Sebastian Bergmann", 1574 | "email": "sebastian@phpunit.de" 1575 | } 1576 | ], 1577 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1578 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1579 | "support": { 1580 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1581 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" 1582 | }, 1583 | "funding": [ 1584 | { 1585 | "url": "https://github.com/sebastianbergmann", 1586 | "type": "github" 1587 | } 1588 | ], 1589 | "time": "2020-11-30T07:37:18+00:00" 1590 | }, 1591 | { 1592 | "name": "sebastian/recursion-context", 1593 | "version": "3.0.1", 1594 | "source": { 1595 | "type": "git", 1596 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1597 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" 1598 | }, 1599 | "dist": { 1600 | "type": "zip", 1601 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", 1602 | "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", 1603 | "shasum": "" 1604 | }, 1605 | "require": { 1606 | "php": ">=7.0" 1607 | }, 1608 | "require-dev": { 1609 | "phpunit/phpunit": "^6.0" 1610 | }, 1611 | "type": "library", 1612 | "extra": { 1613 | "branch-alias": { 1614 | "dev-master": "3.0.x-dev" 1615 | } 1616 | }, 1617 | "autoload": { 1618 | "classmap": [ 1619 | "src/" 1620 | ] 1621 | }, 1622 | "notification-url": "https://packagist.org/downloads/", 1623 | "license": [ 1624 | "BSD-3-Clause" 1625 | ], 1626 | "authors": [ 1627 | { 1628 | "name": "Sebastian Bergmann", 1629 | "email": "sebastian@phpunit.de" 1630 | }, 1631 | { 1632 | "name": "Jeff Welch", 1633 | "email": "whatthejeff@gmail.com" 1634 | }, 1635 | { 1636 | "name": "Adam Harvey", 1637 | "email": "aharvey@php.net" 1638 | } 1639 | ], 1640 | "description": "Provides functionality to recursively process PHP variables", 1641 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1642 | "support": { 1643 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1644 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" 1645 | }, 1646 | "funding": [ 1647 | { 1648 | "url": "https://github.com/sebastianbergmann", 1649 | "type": "github" 1650 | } 1651 | ], 1652 | "time": "2020-11-30T07:34:24+00:00" 1653 | }, 1654 | { 1655 | "name": "sebastian/resource-operations", 1656 | "version": "2.0.2", 1657 | "source": { 1658 | "type": "git", 1659 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1660 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" 1661 | }, 1662 | "dist": { 1663 | "type": "zip", 1664 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1665 | "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", 1666 | "shasum": "" 1667 | }, 1668 | "require": { 1669 | "php": ">=7.1" 1670 | }, 1671 | "type": "library", 1672 | "extra": { 1673 | "branch-alias": { 1674 | "dev-master": "2.0-dev" 1675 | } 1676 | }, 1677 | "autoload": { 1678 | "classmap": [ 1679 | "src/" 1680 | ] 1681 | }, 1682 | "notification-url": "https://packagist.org/downloads/", 1683 | "license": [ 1684 | "BSD-3-Clause" 1685 | ], 1686 | "authors": [ 1687 | { 1688 | "name": "Sebastian Bergmann", 1689 | "email": "sebastian@phpunit.de" 1690 | } 1691 | ], 1692 | "description": "Provides a list of PHP built-in functions that operate on resources", 1693 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1694 | "support": { 1695 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1696 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" 1697 | }, 1698 | "funding": [ 1699 | { 1700 | "url": "https://github.com/sebastianbergmann", 1701 | "type": "github" 1702 | } 1703 | ], 1704 | "time": "2020-11-30T07:30:19+00:00" 1705 | }, 1706 | { 1707 | "name": "sebastian/version", 1708 | "version": "2.0.1", 1709 | "source": { 1710 | "type": "git", 1711 | "url": "https://github.com/sebastianbergmann/version.git", 1712 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1713 | }, 1714 | "dist": { 1715 | "type": "zip", 1716 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1717 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1718 | "shasum": "" 1719 | }, 1720 | "require": { 1721 | "php": ">=5.6" 1722 | }, 1723 | "type": "library", 1724 | "extra": { 1725 | "branch-alias": { 1726 | "dev-master": "2.0.x-dev" 1727 | } 1728 | }, 1729 | "autoload": { 1730 | "classmap": [ 1731 | "src/" 1732 | ] 1733 | }, 1734 | "notification-url": "https://packagist.org/downloads/", 1735 | "license": [ 1736 | "BSD-3-Clause" 1737 | ], 1738 | "authors": [ 1739 | { 1740 | "name": "Sebastian Bergmann", 1741 | "email": "sebastian@phpunit.de", 1742 | "role": "lead" 1743 | } 1744 | ], 1745 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1746 | "homepage": "https://github.com/sebastianbergmann/version", 1747 | "support": { 1748 | "issues": "https://github.com/sebastianbergmann/version/issues", 1749 | "source": "https://github.com/sebastianbergmann/version/tree/master" 1750 | }, 1751 | "time": "2016-10-03T07:35:21+00:00" 1752 | }, 1753 | { 1754 | "name": "squizlabs/php_codesniffer", 1755 | "version": "3.6.2", 1756 | "source": { 1757 | "type": "git", 1758 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 1759 | "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" 1760 | }, 1761 | "dist": { 1762 | "type": "zip", 1763 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", 1764 | "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", 1765 | "shasum": "" 1766 | }, 1767 | "require": { 1768 | "ext-simplexml": "*", 1769 | "ext-tokenizer": "*", 1770 | "ext-xmlwriter": "*", 1771 | "php": ">=5.4.0" 1772 | }, 1773 | "require-dev": { 1774 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 1775 | }, 1776 | "bin": [ 1777 | "bin/phpcs", 1778 | "bin/phpcbf" 1779 | ], 1780 | "type": "library", 1781 | "extra": { 1782 | "branch-alias": { 1783 | "dev-master": "3.x-dev" 1784 | } 1785 | }, 1786 | "notification-url": "https://packagist.org/downloads/", 1787 | "license": [ 1788 | "BSD-3-Clause" 1789 | ], 1790 | "authors": [ 1791 | { 1792 | "name": "Greg Sherwood", 1793 | "role": "lead" 1794 | } 1795 | ], 1796 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 1797 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", 1798 | "keywords": [ 1799 | "phpcs", 1800 | "standards" 1801 | ], 1802 | "support": { 1803 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", 1804 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", 1805 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" 1806 | }, 1807 | "time": "2021-12-12T21:44:58+00:00" 1808 | }, 1809 | { 1810 | "name": "symfony/polyfill-ctype", 1811 | "version": "v1.24.0", 1812 | "source": { 1813 | "type": "git", 1814 | "url": "https://github.com/symfony/polyfill-ctype.git", 1815 | "reference": "30885182c981ab175d4d034db0f6f469898070ab" 1816 | }, 1817 | "dist": { 1818 | "type": "zip", 1819 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", 1820 | "reference": "30885182c981ab175d4d034db0f6f469898070ab", 1821 | "shasum": "" 1822 | }, 1823 | "require": { 1824 | "php": ">=7.1" 1825 | }, 1826 | "provide": { 1827 | "ext-ctype": "*" 1828 | }, 1829 | "suggest": { 1830 | "ext-ctype": "For best performance" 1831 | }, 1832 | "type": "library", 1833 | "extra": { 1834 | "branch-alias": { 1835 | "dev-main": "1.23-dev" 1836 | }, 1837 | "thanks": { 1838 | "name": "symfony/polyfill", 1839 | "url": "https://github.com/symfony/polyfill" 1840 | } 1841 | }, 1842 | "autoload": { 1843 | "psr-4": { 1844 | "Symfony\\Polyfill\\Ctype\\": "" 1845 | }, 1846 | "files": [ 1847 | "bootstrap.php" 1848 | ] 1849 | }, 1850 | "notification-url": "https://packagist.org/downloads/", 1851 | "license": [ 1852 | "MIT" 1853 | ], 1854 | "authors": [ 1855 | { 1856 | "name": "Gert de Pagter", 1857 | "email": "BackEndTea@gmail.com" 1858 | }, 1859 | { 1860 | "name": "Symfony Community", 1861 | "homepage": "https://symfony.com/contributors" 1862 | } 1863 | ], 1864 | "description": "Symfony polyfill for ctype functions", 1865 | "homepage": "https://symfony.com", 1866 | "keywords": [ 1867 | "compatibility", 1868 | "ctype", 1869 | "polyfill", 1870 | "portable" 1871 | ], 1872 | "support": { 1873 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" 1874 | }, 1875 | "funding": [ 1876 | { 1877 | "url": "https://symfony.com/sponsor", 1878 | "type": "custom" 1879 | }, 1880 | { 1881 | "url": "https://github.com/fabpot", 1882 | "type": "github" 1883 | }, 1884 | { 1885 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1886 | "type": "tidelift" 1887 | } 1888 | ], 1889 | "time": "2021-10-20T20:35:02+00:00" 1890 | }, 1891 | { 1892 | "name": "theseer/tokenizer", 1893 | "version": "1.2.1", 1894 | "source": { 1895 | "type": "git", 1896 | "url": "https://github.com/theseer/tokenizer.git", 1897 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 1898 | }, 1899 | "dist": { 1900 | "type": "zip", 1901 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 1902 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 1903 | "shasum": "" 1904 | }, 1905 | "require": { 1906 | "ext-dom": "*", 1907 | "ext-tokenizer": "*", 1908 | "ext-xmlwriter": "*", 1909 | "php": "^7.2 || ^8.0" 1910 | }, 1911 | "type": "library", 1912 | "autoload": { 1913 | "classmap": [ 1914 | "src/" 1915 | ] 1916 | }, 1917 | "notification-url": "https://packagist.org/downloads/", 1918 | "license": [ 1919 | "BSD-3-Clause" 1920 | ], 1921 | "authors": [ 1922 | { 1923 | "name": "Arne Blankerts", 1924 | "email": "arne@blankerts.de", 1925 | "role": "Developer" 1926 | } 1927 | ], 1928 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1929 | "support": { 1930 | "issues": "https://github.com/theseer/tokenizer/issues", 1931 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 1932 | }, 1933 | "funding": [ 1934 | { 1935 | "url": "https://github.com/theseer", 1936 | "type": "github" 1937 | } 1938 | ], 1939 | "time": "2021-07-28T10:34:58+00:00" 1940 | }, 1941 | { 1942 | "name": "webmozart/assert", 1943 | "version": "1.10.0", 1944 | "source": { 1945 | "type": "git", 1946 | "url": "https://github.com/webmozarts/assert.git", 1947 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" 1948 | }, 1949 | "dist": { 1950 | "type": "zip", 1951 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", 1952 | "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", 1953 | "shasum": "" 1954 | }, 1955 | "require": { 1956 | "php": "^7.2 || ^8.0", 1957 | "symfony/polyfill-ctype": "^1.8" 1958 | }, 1959 | "conflict": { 1960 | "phpstan/phpstan": "<0.12.20", 1961 | "vimeo/psalm": "<4.6.1 || 4.6.2" 1962 | }, 1963 | "require-dev": { 1964 | "phpunit/phpunit": "^8.5.13" 1965 | }, 1966 | "type": "library", 1967 | "extra": { 1968 | "branch-alias": { 1969 | "dev-master": "1.10-dev" 1970 | } 1971 | }, 1972 | "autoload": { 1973 | "psr-4": { 1974 | "Webmozart\\Assert\\": "src/" 1975 | } 1976 | }, 1977 | "notification-url": "https://packagist.org/downloads/", 1978 | "license": [ 1979 | "MIT" 1980 | ], 1981 | "authors": [ 1982 | { 1983 | "name": "Bernhard Schussek", 1984 | "email": "bschussek@gmail.com" 1985 | } 1986 | ], 1987 | "description": "Assertions to validate method input/output with nice error messages.", 1988 | "keywords": [ 1989 | "assert", 1990 | "check", 1991 | "validate" 1992 | ], 1993 | "support": { 1994 | "issues": "https://github.com/webmozarts/assert/issues", 1995 | "source": "https://github.com/webmozarts/assert/tree/1.10.0" 1996 | }, 1997 | "time": "2021-03-09T10:59:23+00:00" 1998 | }, 1999 | { 2000 | "name": "wp-coding-standards/wpcs", 2001 | "version": "0.14.1", 2002 | "source": { 2003 | "type": "git", 2004 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", 2005 | "reference": "cf6b310caad735816caef7573295f8a534374706" 2006 | }, 2007 | "dist": { 2008 | "type": "zip", 2009 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/cf6b310caad735816caef7573295f8a534374706", 2010 | "reference": "cf6b310caad735816caef7573295f8a534374706", 2011 | "shasum": "" 2012 | }, 2013 | "require": { 2014 | "php": ">=5.3", 2015 | "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" 2016 | }, 2017 | "suggest": { 2018 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" 2019 | }, 2020 | "type": "phpcodesniffer-standard", 2021 | "notification-url": "https://packagist.org/downloads/", 2022 | "license": [ 2023 | "MIT" 2024 | ], 2025 | "authors": [ 2026 | { 2027 | "name": "Contributors", 2028 | "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" 2029 | } 2030 | ], 2031 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", 2032 | "keywords": [ 2033 | "phpcs", 2034 | "standards", 2035 | "wordpress" 2036 | ], 2037 | "support": { 2038 | "issues": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues", 2039 | "source": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards", 2040 | "wiki": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki" 2041 | }, 2042 | "time": "2018-02-16T01:57:48+00:00" 2043 | } 2044 | ], 2045 | "aliases": [], 2046 | "minimum-stability": "stable", 2047 | "stability-flags": [], 2048 | "prefer-stable": false, 2049 | "prefer-lowest": false, 2050 | "platform": { 2051 | "php": ">=5.4" 2052 | }, 2053 | "platform-dev": [], 2054 | "plugin-api-version": "2.1.0" 2055 | } 2056 | -------------------------------------------------------------------------------- /hm-redirects.php: -------------------------------------------------------------------------------- 1 | post_type !== Redirects_Post_Type\SLUG ) { 46 | return; 47 | } 48 | 49 | wp_enqueue_style( 'hm-redirects-admin', plugins_url( '/assets/admin.css', dirname( __FILE__ ) ), [], null, 'screen' ); 50 | } 51 | 52 | /** 53 | * Filter the posts listing columns. 54 | * 55 | * @param array $columns List of column ids and labels. 56 | * @return array 57 | */ 58 | function filter_posts_columns( array $columns ) : array { 59 | $columns = [ 60 | 'title' => __( 'From', 'hm-redirects' ), 61 | 'to' => __( 'To', 'hm-redirects' ), 62 | 'status' => __( 'Status', 'hm-redirects' ), 63 | 'date' => __( 'Date' ), 64 | ]; 65 | 66 | return $columns; 67 | } 68 | 69 | /** 70 | * Output for the custom admin columns. 71 | * 72 | * @param string $column Current column ID. 73 | * @param int $post_id Current post ID. 74 | */ 75 | function posts_columns_content( string $column, int $post_id ) { 76 | $post = get_post( $post_id ); 77 | 78 | if ( $column === 'to' ) { 79 | echo esc_html( $post->post_excerpt ); 80 | } 81 | 82 | if ( $column === 'status' ) { 83 | echo intval( $post->post_content_filtered ); 84 | } 85 | } 86 | 87 | /** 88 | * Add a new action to visit the redirected link. 89 | * 90 | * @param array $actions Current row actions. 91 | * @param WP_Post $post Current row post. 92 | */ 93 | function row_actions( $actions, $post ) { 94 | if ( $post->post_status === 'trash' || $post->post_type !== Redirects_Post_Type\SLUG ) { 95 | return $actions; 96 | } 97 | 98 | $url = get_the_title( $post ); 99 | /* translators: %s: Redirect source URL */ 100 | $aria_label = esc_html( sprintf( __( 'Visit %s', 'hm-redirects' ), $url ) ); 101 | 102 | unset($actions['inline hide-if-no-js']); 103 | return array_merge( 104 | [ 105 | 'visit' => sprintf( 106 | '%s', 107 | esc_url( home_url( $url ) ), 108 | $aria_label, 109 | esc_html__( 'Visit', 'hm-redirects' ) 110 | ), 111 | ], 112 | $actions 113 | ); 114 | } 115 | 116 | /** 117 | * Output the redirects metabox, 118 | * 119 | * @param WP_Post $post The currently edited post. 120 | */ 121 | function output_meta_box( WP_Post $post ) { 122 | $valid_status_codes = [ 301, 302, 303, 307, ]; 123 | $status_code_labels = [ 124 | 301 => 'Moved Permanently', 125 | 302 => 'Found (Moved temporarily)', 126 | 303 => 'See Other', 127 | 307 => 'Temporary Redirect', 128 | ]; 129 | 130 | /** 131 | * Filter available status codes. 132 | * 133 | * @param array $status_code_labels Array of status codes and labels. The array keys are the status code and the values are the labels. 134 | */ 135 | $status_code_labels = apply_filters( 'hm_redirects_status_codes', $status_code_labels ); 136 | 137 | /** 138 | * Filter the default selected status code. 139 | * 140 | * @param int $default_status_code Defaults to 302. 141 | */ 142 | $default_status_code = apply_filters( 'hm_redirects_default_status_code', 302 ); 143 | 144 | if ( ! in_array( $default_status_code, $valid_status_codes, true ) ) { 145 | $default_status_code = 302; 146 | } 147 | 148 | $status_code = ! empty( $post->post_content_filtered ) ? $post->post_content_filtered : $default_status_code; 149 | ?> 150 |

151 |
152 | 153 |

154 |

155 | 156 |

157 |
158 | 159 |

160 | 161 |

162 | 163 | 168 | 169 |

170 | Utilities\normalise_url( Utilities\sanitise_and_normalise_url( $unsafe_from ) ), 220 | 'to_url' => Utilities\sanitise_and_normalise_url( $unsafe_to ), 221 | 'status_code' => absint( $unsafe_status_code ), 222 | ]; 223 | } 224 | 225 | /** 226 | * Load the plugin translations. 227 | */ 228 | function load_plugin_textdomain() { 229 | \load_plugin_textdomain( 'hm-redirects', false, basename( dirname( dirname( __FILE__ ) ) ) . '/languages/' ); 230 | } 231 | -------------------------------------------------------------------------------- /includes/class-cli-commands.php: -------------------------------------------------------------------------------- 1 | get_var( 46 | $wpdb->prepare( 47 | "SELECT COUNT( ID ) FROM $wpdb->posts WHERE post_type = %s AND post_excerpt LIKE %s", 48 | REDIRECTS_POST_TYPE, 49 | 'http%' 50 | ) 51 | ); 52 | 53 | $progress_bar = WP_CLI\Utils\make_progress_bar( 'Finding domains', $total_redirects ); 54 | 55 | do { 56 | $redirect_urls = $wpdb->get_col( 57 | $wpdb->prepare( 58 | "SELECT post_excerpt FROM $wpdb->posts WHERE post_type = %s AND post_excerpt LIKE %s ORDER BY ID ASC LIMIT %d, %d", 59 | REDIRECTS_POST_TYPE, 60 | 'http%', 61 | ( $paged * $posts_per_page ), 62 | $posts_per_page 63 | ) 64 | ); 65 | 66 | foreach ( $redirect_urls as $redirect_url ) { 67 | $progress_bar->tick(); 68 | 69 | if ( ! empty( $redirect_url ) ) { 70 | $redirect_host = wp_parse_url( $redirect_url, PHP_URL_HOST ); 71 | if ( $redirect_host ) { 72 | $domains[] = $redirect_host; 73 | } 74 | } 75 | } 76 | 77 | // Throttle. 78 | sleep( 1 ); 79 | $paged++; 80 | } while ( count( $redirect_urls ) ); 81 | 82 | $progress_bar->finish(); 83 | 84 | $domains = array_unique( $domains ); 85 | WP_CLI::line( sprintf( 'Found %s unique outbound domains', number_format( count( $domains ) ) ) ); 86 | 87 | foreach ( $domains as $domain ) { 88 | WP_CLI::line( $domain ); 89 | } 90 | } 91 | 92 | /** 93 | * Insert a single redirect. 94 | * 95 | * ## OPTIONS 96 | * 97 | * 98 | * : URL to redirect from. Must be relative to the root of the site. 99 | * 100 | * 101 | * : Destination URL to redirect to, or post ID. 102 | * 103 | * [--code=] 104 | * : Redirect code to use. 105 | * --- 106 | * default: 302 107 | * --- 108 | * 109 | * @subcommand insert-redirect 110 | * @synopsis [--code=] 111 | * 112 | * @param string[] $args Positional arguments. 113 | * @param string[] $assoc_args Not used. 114 | * 115 | * @throws WP_CLI\ExitException Exception if the post to redirect to does not exist. 116 | */ 117 | public function insert_redirect( $args, $assoc_args ) { 118 | $from = $args[0]; 119 | if ( ! ctype_digit( $assoc_args['code'] ) ) { 120 | WP_CLI::error( sprintf( 'Code must be numeric, "%s" is invalid', $assoc_args['code'] ) ); 121 | } 122 | 123 | $code = (int) $assoc_args['code']; 124 | if ( $code < 300 || $code >= 400 ) { 125 | WP_CLI::error( sprintf( 'Code must be a 3XX status code for redirects, %d is invalid', $code ) ); 126 | } 127 | 128 | if ( ctype_digit( $args[1] ) ) { 129 | $to = get_permalink( (int) $args[1] ); 130 | if ( ! $to ) { 131 | WP_CLI::error( sprintf( 'Destination post %s cannot be found', $args[1] ) ); 132 | } 133 | } else { 134 | $to = esc_url_raw( $args[1] ); 135 | } 136 | 137 | $redirect = Utilities\insert_redirect( $from, $to, $code ); 138 | 139 | if ( is_wp_error( $redirect ) ) { 140 | WP_CLI::error( 141 | sprintf( 142 | "Couldn't insert %s -> %s: %s", 143 | $from, 144 | $to, 145 | implode( PHP_EOL, $redirect->get_error_messages() ) 146 | ) 147 | ); 148 | } 149 | 150 | WP_CLI::success( sprintf( 'Inserted %s -> %s', $from, $to ) ); 151 | } 152 | 153 | /** 154 | * Bulk import redirects from a CSV file. 155 | * 156 | * CSV structure: redirect_from_path,(redirect_to_post_id|redirect_to_path|redirect_to_url),[status_code default:301] 157 | * 158 | * ## OPTIONS 159 | * 160 | * [--format=] 161 | * : Render output in a particular format. 162 | * --- 163 | * default: csv 164 | * options: 165 | * - table 166 | * - json 167 | * - yaml 168 | * - csv 169 | * --- 170 | * 171 | * [--verbose] 172 | * 173 | * @subcommand import-from-csv 174 | * @synopsis --csv= [--format=] [--verbose] 175 | * 176 | * @param string[] $args Positional arguments. 177 | * @param string[] $assoc_args Associative arguments. 178 | * 179 | * @throws WP_CLI\ExitException If the CSV file cannot be opened. 180 | */ 181 | public function import_from_csv( $args, $assoc_args ) { 182 | define( 'WP_IMPORTING', true ); 183 | 184 | $csv = trim( WP_CLI\Utils\get_flag_value( $assoc_args, 'csv' ) ); 185 | $format = WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ); 186 | $verbose = isset( $assoc_args['verbose'] ); 187 | 188 | if ( ! $csv || ! file_exists( $csv ) ) { 189 | WP_CLI::error( "Invalid 'csv' file" ); 190 | } 191 | 192 | if ( ! $verbose ) { 193 | WP_CLI::line( 'Processing...' ); 194 | } 195 | 196 | $handle = fopen( $csv, 'r' ); 197 | if ( $handle === false ) { 198 | WP_CLI::error( "Cannot open 'csv' file" ); 199 | } 200 | 201 | $notices = []; 202 | $row = 0; 203 | 204 | while ( ( $data = fgetcsv( $handle, 2000, ',' ) ) !== false ) { 205 | $row++; 206 | $redirect_from = $data[0]; 207 | $redirect_to = $data[1]; 208 | $status = $data[2] ?? 301; 209 | 210 | // Convert "redirect to" post IDs to permalinks. 211 | if ( ctype_digit( $redirect_to ) ) { 212 | $redirect_to = get_permalink( (int) $redirect_to ); 213 | 214 | if ( ! $redirect_to ) { 215 | $notices[] = [ 216 | 'redirect_from' => $redirect_from, 217 | 'redirect_to' => $data[1], 218 | 'message' => 'Skipped - could not find redirect_to post', 219 | ]; 220 | 221 | continue; 222 | } 223 | } 224 | 225 | if ( $verbose ) { 226 | WP_CLI::line( "Adding (CSV) redirect for {$redirect_from} to {$redirect_to}" ); 227 | WP_CLI::line( "-- at $row" ); 228 | } elseif ( 0 === $row % 100 ) { 229 | WP_CLI::line( "Processing row $row" ); 230 | } 231 | 232 | $redirect = Utilities\insert_redirect( 233 | Utilities\normalise_url( Utilities\sanitise_and_normalise_url( $redirect_from ) ), 234 | Utilities\sanitise_and_normalise_url( $redirect_to ), 235 | absint( $status ) 236 | ); 237 | 238 | // Record any error notices. 239 | if ( is_wp_error( $redirect ) ) { 240 | $notices[] = [ 241 | 'redirect_from' => $redirect_from, 242 | 'redirect_to' => $redirect_to, 243 | 'message' => sprintf( 244 | 'Could not insert redirect: %s', 245 | implode( PHP_EOL, $redirect->get_error_messages() ) 246 | ), 247 | ]; 248 | 249 | // Record success notices. 250 | } elseif ( $verbose ) { 251 | $notices[] = [ 252 | 'redirect_from' => $redirect_from, 253 | 'redirect_to' => $redirect_to, 254 | 'message' => 'Successfully imported', 255 | ]; 256 | } 257 | 258 | if ( 0 === $row % 100 ) { 259 | Utilities\clear_object_cache(); 260 | 261 | // Throttle writes. 262 | sleep( 1 ); 263 | } 264 | } 265 | 266 | fclose( $handle ); 267 | 268 | if ( count( $notices ) > 0 ) { 269 | WP_CLI\Utils\format_items( $format, $notices, [ 'redirect_from', 'redirect_to', 'message' ] ); 270 | } else { 271 | // phpcs:disable WordPress.XSS.EscapeOutput 272 | echo WP_CLI::colorize( '%GAll of your redirects have been imported. Nice work!%n ' ); 273 | // phpcs:enable 274 | } 275 | } 276 | 277 | /** 278 | * Bulk import redirects from URLs stored as meta values for posts. 279 | * 280 | * ## OPTIONS 281 | * 282 | * [--start=] 283 | * 284 | * [--end=] 285 | * 286 | * [--skip_dupes=] 287 | * 288 | * [--format=] 289 | * : Render output in a particular format. 290 | * --- 291 | * default: csv 292 | * options: 293 | * - table 294 | * - json 295 | * - yaml 296 | * - csv 297 | * --- 298 | * 299 | * [--dry_run] 300 | * 301 | * [--verbose] 302 | * : Display notices for sucessful imports and duplicates (if skip_dupes is used) 303 | * 304 | * @subcommand import-from-meta 305 | * @synopsis --meta_key= [--start=] [--end=] [--skip_dupes=] [--format=] [--dry_run] [--verbose] 306 | * 307 | * @param string[] $args Positional arguments. 308 | * @param string[] $assoc_args Associative arguments. 309 | */ 310 | function import_from_meta( $args, $assoc_args ) { 311 | global $wpdb; 312 | 313 | define( 'WP_IMPORTING', true ); 314 | 315 | $meta_key = isset( $assoc_args['meta_key'] ) ? sanitize_key( $assoc_args['meta_key'] ) : 'change-me'; 316 | $offset = isset( $assoc_args['start'] ) ? intval( $assoc_args['start'] ) : 0; 317 | $end_offset = isset( $assoc_args['end'] ) ? intval( $assoc_args['end'] ) : 99999999; 318 | ; 319 | 320 | $skip_dupes = isset( $assoc_args['skip_dupes'] ) ? (bool) intval( $assoc_args['skip_dupes'] ) : false; 321 | $dry_run = isset( $assoc_args['dry_run'] ); 322 | $format = WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ); 323 | $verbose = isset( $assoc_args['verbose'] ); 324 | 325 | if ( $dry_run ) { 326 | WP_CLI::line( '---Dry Run---' ); 327 | } else { 328 | WP_CLI::line( '---Live Run--' ); 329 | } 330 | 331 | // Check we have any work to do. 332 | $total_redirects = $wpdb->get_var( 333 | $wpdb->prepare( 334 | "SELECT COUNT( post_id ) FROM $wpdb->postmeta WHERE meta_key = %s", 335 | $meta_key 336 | ) 337 | ); 338 | 339 | if ( $total_redirects === 0 ) { 340 | WP_CLI::error( sprintf( 'No redirects found for meta_key: %s', $meta_key ) ); 341 | } 342 | 343 | $progress_bar = WP_CLI\Utils\make_progress_bar( sprintf( 'Importing %s redirects', number_format( $total_redirects ) ), $total_redirects ); 344 | $notices = []; 345 | 346 | // Start the import; loop through batches of posts. 347 | do { 348 | $redirects = $wpdb->get_results( 349 | $wpdb->prepare( 350 | "SELECT post_id as redirect_to_post_id, meta_value as abs_redirect_from FROM $wpdb->postmeta WHERE meta_key = %s ORDER BY post_id ASC LIMIT %d, 1000", 351 | $meta_key, 352 | $offset 353 | ) 354 | ); 355 | 356 | $total = count( $redirects ); 357 | $row = 0; 358 | 359 | foreach ( $redirects as $redirect ) { 360 | $row++; 361 | $progress_bar->tick(); 362 | 363 | // "Redirect from" parameter must be relative to the root of the site. 364 | $redirect_from = wp_parse_url( $redirect->abs_redirect_from, PHP_URL_PATH ); 365 | $query_args = wp_parse_url( $redirect->abs_redirect_from, PHP_URL_QUERY ); 366 | 367 | if ( $query_args !== null ) { 368 | $redirect_from = "?{$query_args}"; 369 | } 370 | 371 | // The "redirect to" value is a post ID. Grab the appropriate URL. 372 | $redirect_to = get_permalink( (int) $redirect->redirect_to_post_id ); 373 | if ( ! $redirect_to ) { 374 | $notices[] = [ 375 | 'redirect_from' => $redirect_from, 376 | 'redirect_to' => $redirect->redirect_to_post_id, 377 | 'message' => 'Skipped - could not find redirect_to post', 378 | ]; 379 | 380 | continue; 381 | } 382 | 383 | if ( $skip_dupes ) { 384 | $has_existing_redirect = Handle_Redirects\get_redirect_post( $redirect_from ); 385 | $has_existing_redirect = ( $has_existing_redirect === null ) ? false : true; 386 | 387 | if ( $has_existing_redirect ) { 388 | if ( $verbose ) { 389 | $notices[] = [ 390 | 'redirect_from' => $redirect_from, 391 | 'redirect_to' => $redirect_to, 392 | 'message' => sprintf( 'Skipped - "redirect from" URL already exists (%s)', $redirect_from ), 393 | ]; 394 | } 395 | 396 | continue; 397 | } 398 | } 399 | 400 | // Add redirects. 401 | if ( $dry_run === false ) { 402 | $redirect = Utilities\insert_redirect( 403 | $redirect_from, 404 | $redirect_to, 405 | 301 406 | ); 407 | 408 | // Record any error notices. 409 | if ( is_wp_error( $redirect ) ) { 410 | $notices[] = [ 411 | 'redirect_from' => $redirect_from, 412 | 'redirect_to' => $redirect_to, 413 | 'message' => sprintf( 414 | 'Could not insert redirect: %s', 415 | implode( PHP_EOL, $redirect->get_error_messages() ) 416 | ), 417 | ]; 418 | 419 | // Record success notices. 420 | } elseif ( $verbose ) { 421 | $notices[] = [ 422 | 'redirect_from' => $redirect_from, 423 | 'redirect_to' => $redirect_to, 424 | 'message' => 'Successfully imported', 425 | ]; 426 | } 427 | } 428 | 429 | if ( 0 === $row % 100 ) { 430 | Utilities\clear_object_cache(); 431 | 432 | // Throttle writes. 433 | sleep( 1 ); 434 | } 435 | } 436 | 437 | $offset += 1000; 438 | } while ( $total >= 1000 && $offset < $end_offset ); 439 | 440 | $progress_bar->finish(); 441 | 442 | if ( count( $notices ) > 0 ) { 443 | WP_CLI\Utils\format_items( $format, $notices, [ 'redirect_from', 'redirect_to', 'message' ] ); 444 | } else { 445 | // phpcs:disable WordPress.XSS.EscapeOutput 446 | echo WP_CLI::colorize( '%GAll of your redirects have been imported. Nice work!%n ' ); 447 | // phpcs:enable 448 | } 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /includes/handle-redirects.php: -------------------------------------------------------------------------------- 1 | post_excerpt; 85 | if ( empty( $to_url ) ) { 86 | return false; 87 | } 88 | 89 | // If the URL is only a path, prefix it with the `home_url()`. 90 | $to_url = Utilities\prefix_path( $to_url ); 91 | 92 | // Re-append any existing query string parameters. 93 | if ( ! empty( $url_parts['query'] ) ) { 94 | $to_url = explode( '#', $to_url ); 95 | $to_url[0] .= strpos( $to_url[0], '?' ) !== false ? '&' : '?'; 96 | $to_url[0] .= $url_parts['query']; 97 | $to_url = implode( '#', array_filter( $to_url ) ); 98 | } 99 | if ( ! empty( $url_parts['fragment'] ) && strpos( $to_url, '#' ) === false ) { 100 | $to_url .= '#' . $url_parts['fragment']; 101 | } 102 | 103 | return wp_sanitize_redirect( $to_url ); 104 | } 105 | 106 | 107 | /** 108 | * Retrieves the redirect status code. 109 | * 110 | * @param string $url The URL being redirected. 111 | * 112 | * @return int|string|WP_Error 113 | */ 114 | function get_redirect_status_code( $url = '' ) { 115 | if ( ! is_string( $url ) || strlen( $url ) === 0 ) { 116 | return new WP_Error( '', 'URL needs to be a non empty string' ); 117 | } 118 | 119 | $url = Utilities\normalise_url( $url ); 120 | if ( is_wp_error( $url ) ) { 121 | return 302; 122 | } 123 | 124 | $redirect_post = get_redirect_post( $url ); 125 | 126 | if ( ! is_null( $redirect_post ) ) { 127 | $status_code = $redirect_post->post_content_filtered; 128 | if ( ! empty( $status_code ) ) { 129 | return $status_code; 130 | } 131 | } 132 | 133 | return 302; 134 | } 135 | 136 | 137 | /** 138 | * Retrieve the target URL's post ID. 139 | * 140 | * @param string $url The URL to retrieve the post for. 141 | * 142 | * @return int|null|string 143 | */ 144 | function get_redirect_post( $url ) { 145 | $url_hash = Utilities\get_url_hash( $url ); 146 | $redirect_post = get_page_by_path( $url_hash, OBJECT, [ Redirects_Post_Type\SLUG ] ); 147 | if ( 'publish' !== get_post_status( $redirect_post ) ) { 148 | return null; 149 | } 150 | 151 | return $redirect_post; 152 | } 153 | -------------------------------------------------------------------------------- /includes/post-type.php: -------------------------------------------------------------------------------- 1 | esc_html__( 'Redirects', 'hm-redirects' ), 28 | 'singular_name' => esc_html__( 'Redirect', 'hm-redirects' ), 29 | 'add_new' => esc_html__( 'Add New', 'hm-redirects' ), 30 | 'add_new_item' => esc_html__( 'Add New Redirect', 'hm-redirects' ), 31 | 'edit_item' => esc_html__( 'Edit Redirect', 'hm-redirects' ), 32 | 'new_item' => esc_html__( 'New Redirect', 'hm-redirects' ), 33 | 'view_item' => esc_html__( 'View Redirect', 'hm-redirects' ), 34 | 'search_items' => esc_html__( 'Search Redirects', 'hm-redirects' ), 35 | 'not_found' => esc_html__( 'No redirects found', 'hm-redirects' ), 36 | 'not_found_in_trash' => esc_html__( 'No redirects found in trash', 'hm-redirects' ), 37 | 'all_items' => esc_html__( 'All Redirects', 'hm-redirects' ), 38 | ]; 39 | 40 | \register_post_type( 41 | SLUG, 42 | [ 43 | 'labels' => $labels, 44 | 'show_in_feed' => false, 45 | 'supports' => [ 'title' ], 46 | 'hierarchical' => false, 47 | 'public' => false, 48 | 'show_ui' => true, 49 | 'show_in_menu' => true, 50 | 'menu_position' => 75, 51 | 'menu_icon' => 'dashicons-migrate', 52 | 'show_in_admin_bar' => true, 53 | 'show_in_nav_menus' => false, 54 | 'can_export' => true, 55 | 'has_archive' => false, 56 | 'exclude_from_search' => true, 57 | 'publicly_queryable' => false, 58 | ] 59 | ); 60 | 61 | remove_post_type_support( SLUG, 'title' ); 62 | } 63 | -------------------------------------------------------------------------------- /includes/utilities.php: -------------------------------------------------------------------------------- 1 | $post_id, 170 | 'post_content_filtered' => $status_code, 171 | 'post_excerpt' => $to, 172 | 'post_name' => get_url_hash( $from ), 173 | 'post_status' => $post->post_status ?? 'publish', 174 | 'post_title' => strtolower( $from ), 175 | 'post_type' => REDIRECTS_POST_TYPE, 176 | ], 177 | true 178 | ); 179 | 180 | add_action( 'save_post', 'HM\\Redirects\\Admin_UI\\handle_redirect_saving', 13 ); 181 | 182 | return $result; 183 | } 184 | 185 | /** 186 | * Clear all caches for memory management. 187 | */ 188 | function clear_object_cache() { 189 | global $wpdb, $wp_object_cache; 190 | 191 | $wpdb->queries = []; 192 | 193 | if ( ! is_object( $wp_object_cache ) ) { 194 | return; 195 | } 196 | 197 | $wp_object_cache->group_ops = []; 198 | $wp_object_cache->stats = []; 199 | $wp_object_cache->memcache_debug = []; 200 | $wp_object_cache->cache = []; 201 | 202 | if ( method_exists( $wp_object_cache, '__remoteset' ) ) { 203 | $wp_object_cache->__remoteset(); 204 | } 205 | } 206 | 207 | /** 208 | * Add a leading slash to a string. 209 | * 210 | * This function ensures that only a single leading slash will be present. 211 | * 212 | * @param string $string String to add leading slash to. 213 | * 214 | * @return string String with a single leading slash. 215 | */ 216 | function add_leading_slash( $string ) { 217 | $string = ltrim( $string, '\/' ); 218 | 219 | return '/' . $string; 220 | } 221 | -------------------------------------------------------------------------------- /phpcs.ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | tests/bootstrap.php 15 | 16 | 17 | 18 | 19 | tests/bootstrap.php 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | includes/class-cli-commands.php 38 | 39 | 40 | 41 | 42 | includes/class-cli-commands.php 43 | 44 | 45 | 46 | includes/class-cli-commands.php 47 | 48 | 49 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | post->create( [ 'post_type' => REDIRECTS_POST_TYPE ] ); 26 | 27 | // Nonce missing. 28 | $this->assertFalse( Admin_UI\handle_redirect_saving( $redirect_post_id ) ); 29 | 30 | // Post data missing. 31 | $_POST['hm_redirects_nonce'] = wp_create_nonce( 'hm_redirects' ); 32 | $this->assertFalse( Admin_UI\handle_redirect_saving( $redirect_post_id ) ); 33 | 34 | // All data set. 35 | $_POST['hm_redirects_from_url'] = 'http://example.com/from'; 36 | $_POST['hm_redirects_to_url'] = 'http://example.com/to'; 37 | $_POST['hm_redirects_status_code'] = '301'; 38 | $this->assertTrue( Admin_UI\handle_redirect_saving( $redirect_post_id ) ); 39 | 40 | $saved_data = get_post( $redirect_post_id ); 41 | $this->assertSame( Utilities\get_url_hash( '/from' ), $saved_data->post_name ); 42 | $this->assertSame( '/from', $saved_data->post_title ); 43 | $this->assertSame( 'http://example.com/to', $saved_data->post_excerpt ); 44 | $this->assertSame( '301', $saved_data->post_content_filtered ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/class-handle-redirects-test.php: -------------------------------------------------------------------------------- 1 | assertNull( Handle_Redirects\get_redirect_post( 'sfsgsdfgdfgdfgdf' ) ); 69 | } 70 | 71 | /** 72 | * Tests get_redirect_post. 73 | */ 74 | public function test_get_redirect_post_valid_urls() { 75 | $p = $this->factory->post->create( [ 76 | 'post_title' => 'Test Post', 77 | 'post_name' => md5( '/test/this/path' ), 78 | 'post_type' => Redirects_Post_Type\SLUG, 79 | ] ); 80 | $this->assertInstanceOf( 'WP_Post', Handle_Redirects\get_redirect_post( '/test/this/path' ) ); 81 | 82 | wp_delete_post( $p ); 83 | } 84 | 85 | /** 86 | * Tests get_redirect_uri. 87 | * 88 | * @dataProvider provider_redirect_uri_valid 89 | * 90 | * @param string $from Redirect from. 91 | * @param string $to Redirect to. 92 | * @param string $request_url Original URL. 93 | * @param string $expected_result Expected result. 94 | */ 95 | public function test_get_redirect_uri_valid_urls( $from, $to, $request_url, $expected_result ) { 96 | 97 | $p = $this->factory->post->create( 98 | [ 99 | 'post_title' => 'Test Post', 100 | 'post_name' => md5( $from ), 101 | 'post_type' => Redirects_Post_Type\SLUG, 102 | 'post_excerpt' => $to, 103 | ] 104 | ); 105 | 106 | $result = Handle_Redirects\get_redirect_uri( $request_url ); 107 | $this->assertEquals( home_url() . $expected_result, $result ); 108 | 109 | wp_delete_post( $p ); 110 | } 111 | 112 | /** 113 | * Tests get_redirect_status_code. 114 | * 115 | * @dataProvider provider_redirect_uri_valid 116 | * 117 | * @param string $original_url Original URL. 118 | * @param string $expected_result Expected result. 119 | * @param int $status_code HTTP status code. 120 | */ 121 | public function test_get_redirect_status_code( $original_url, $expected_result, $status_code ) { 122 | // make sure we catch error when argument is missing. 123 | $this->assertInstanceOf( 'WP_Error', Handle_Redirects\get_redirect_status_code() ); 124 | 125 | $this->assertEquals( 302, Handle_Redirects\get_redirect_status_code( '"^<>{}`' ) ); 126 | 127 | $p = $this->factory->post->create( 128 | [ 129 | 'post_title' => 'Test Post', 130 | 'post_name' => md5( $original_url ), 131 | 'post_type' => Redirects_Post_Type\SLUG, 132 | 'post_excerpt' => $expected_result, 133 | 'post_content_filtered' => $status_code, 134 | ] 135 | ); 136 | 137 | $this->assertEquals( $status_code, Handle_Redirects\get_redirect_status_code( $original_url ) ); 138 | 139 | wp_delete_post( $p ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/class-utilities-test.php: -------------------------------------------------------------------------------- 1 | assertEquals( $expected_result, $result ); 122 | } 123 | 124 | /** 125 | * Tests normalise_url. 126 | * 127 | * @dataProvider provider_normalised_url_invalid 128 | * 129 | * @param string $original_url Original URL. 130 | */ 131 | public function test_normalize_url_returns_error_for_invalid_urls( $original_url ) { 132 | $result = Utilities\normalise_url( $original_url ); 133 | $this->assertInstanceOf( 'WP_Error', $result ); 134 | } 135 | 136 | /** 137 | * Test `test_add_leading_slash()`. 138 | */ 139 | public function test_add_leading_slash() { 140 | $this->assertSame( '/foo', Utilities\add_leading_slash( 'foo' ) ); 141 | $this->assertSame( '/foo', Utilities\add_leading_slash( '/foo' ) ); 142 | $this->assertSame( '/foo', Utilities\add_leading_slash( '//foo' ) ); 143 | } 144 | 145 | /** 146 | * Test `sanitise_and_normalise_url()` with ASCII and non-ASCII URLs. 147 | * 148 | * @dataProvider provider_non_ascii_urls 149 | * @dataProvider provider_ascii_urls 150 | * 151 | * @param string $original_url Original URL. 152 | * @param string $expected_url Expected URL. 153 | */ 154 | public function test_sanitise_and_normalise_url( $original_url, $expected_url ) { 155 | $this->assertSame( $expected_url, Utilities\sanitise_and_normalise_url( $original_url ) ); 156 | } 157 | } 158 | --------------------------------------------------------------------------------