diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..10b7ee6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c3796c3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Code Owners + +## + +* @webdevstudios +* @thatmitchcanter +* @khleomix diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..20fb8ca --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@webdevstudios.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4c8028d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,83 @@ +# Closes + + + +## Link to test + + + +## Description + + + +## Related Tickets & Documents + + + +## Mobile & Desktop Screenshots/Recordings + + + +## AI Assistance + +- [ ] 🤖 This project was developed with the help of a LLM/AI such as Cursor, Gemini, etc. + +## Added to documentation? + +- [ ] 📜 README.md +- [ ] 📓 [ClickUp](https://documentationlink.here) +- [ ] 🙅 No documentation needed + +## Added tests? + +- [ ] 👍 Yes +- [ ] 🙅 No, because they aren't needed +- [ ] 🙋 No, because I need help + +## Testing Instructions + + + +----- + +## Reviewer's Testing Checklist + + + +As a reviewer, please verify that the relevant testing criteria are fulfilled and confirmed before approving this Pull Request. + +- [ ] **Visual Regression Testing:** Ensure that existing functionality is not negatively impacted by the changes. +- [ ] **Cross-Browser Compatibility:** Test on major browsers (Chrome, Firefox, Safari) to ensure compatibility. +- [ ] **Mobile Responsiveness:** Confirm that the changes are responsive and functional on various mobile devices. +- [ ] **Theme Compatibility:** Ensure that the changes do not adversely affect the site's theme and styling. +- [ ] **Linting:** Check that the code passes all linting checks (PHPCS, ESLint, SassLint). Check if PR passes code quality check. +- [ ] **Accessibility Testing:** Validate that the changes comply with accessibility standards. Run `npm run a11y`. +- [ ] **Security Best Practices:** Ensure that the code follows WordPress security best practices. Check if PR passes security check. +- [ ] **Documentation:** Ensure that any new features or changes are appropriately documented in the README.md or Confluence. +- [ ] **Post-Deployment Tasks:** Check if there are any tasks that need to be performed after deployment. + +## [optional] Additional Reviewer Notes or Considerations? diff --git a/.github/workflows/assertions.yml b/.github/workflows/assertions.yml new file mode 100644 index 0000000..7968edf --- /dev/null +++ b/.github/workflows/assertions.yml @@ -0,0 +1,90 @@ +name: Code Quality + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + lint-css: + name: 'Lint: CSS' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node dependencies + run: npm ci + env: + CI: true + + - name: Detect coding standard violations (stylelint) + run: npm run lint:css + + lint-js: + name: 'Lint: JS' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Check Node version + run: node -v + + - name: Install Node dependencies + run: npm ci + env: + CI: true + + - name: Detect coding standard violations (eslint) + run: npm run lint:js + + lint-php: + name: 'Lint: PHP' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + tools: cs2pr + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Configure Composer cache + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --prefer-dist --optimize-autoloader --no-progress --no-interaction + + - name: Validate composer.json + run: composer --no-interaction validate --no-check-all + + - name: Detect coding standard violations (PHPCS) + run: npm run lint:php diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..3a37381 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,46 @@ +name: Security + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + security: + name: 'Security Check' + runs-on: ubuntu-latest + + # Ensure the cron job runs only on the main repository, not forks. + if: github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'webdevstudios') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache Composer dependencies + # Automatically uses Node.js 20 after March 2024 + uses: actions/cache@v3 + with: + path: ~/.composer/cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install PHP dependencies + run: composer install --no-progress --no-suggest --prefer-dist + + - name: Run Security Check + uses: symfonycorp/security-checker-action@v5 + with: + lock: ./composer.lock + format: ansi + disable-exit-code: true + + - name: Post Security Check Summary + if: always() + run: | + echo "Security check completed. Review the output for any detected vulnerabilities." diff --git a/README.md b/README.md index d003f80..e554864 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![WebDevStudios. Your Success is Our Mission.](https://webdevstudios.com/wp-content/uploads/2024/02/wds-banner.png)](https://webdevstudios.com/contact/) + # Bluesky Feed for WordPress® Showcase your recent Bluesky posts on your WordPress® website in a variety of ways. Display your Bluesky feed using a widget, shortcode, preview the feed in the admin settings, with customizable themes and display styles. @@ -84,5 +86,6 @@ See the [LICENSE](http://www.gnu.org/licenses/gpl-2.0.txt) file for details. ## Acknowledgments -- Built with ❤️ by [Robert DeVore](https://robertdevore.com/). +- Plugin Built with ❤️ by [Robert DeVore](https://robertdevore.com/). - Icons powered by [Tabler Icons](https://tabler-icons.io/). +- Maintained by [WebDevStudios](https://webdevstudios.com). \ No newline at end of file diff --git a/composer.json b/composer.json index c4afdca..5b9b1a3 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,49 @@ { - "name":"robertdevore/bluesky-feed-for-wordpress", + "name": "webdevstudios/bluesky-feed-for-wordpress", "description": "Showcase your recent Bluesky posts on your WordPress® website in a variety of ways.", - "license": "GPL-2.0-or-later", "type": "wordpress-plugin", - "author" : [ + "config": { + "platform": { + "php": "8.0" + }, + "allow-plugins": { + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "squizlabs/php_codesniffer": true, + "wp-coding-standards/wpcs": true + }, + "sort-packages": true + }, + "authors": [ + { + "name": "WebDevStudios", + "email": "hosting@webdevstudios.com", + "role": "Developer" + }, { "name": "Robert DeVore", + "role": "Developer Emeritus" } ], + "license": "GPL-2.0-or-later", "require": { - "robertdevore/wpcom-check": "^1.0" + "composer/installers": "^2.3.0", + "php": ">=8.0" + }, + "require-dev": { + "php": ">=8.0", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "phpcompatibility/phpcompatibility-wp": "^2.1.6", + "squizlabs/php_codesniffer": "^3.12.1", + "wp-cli/wp-cli-bundle": "^2.11.0", + "wp-coding-standards/wpcs": "^3.1.0" + }, + "scripts": { + "phpcs": [ + "@php vendor/bin/phpcs --report=full,source" + ], + "phpcs-fix": [ + "@php vendor/bin/phpcbf --report=summary,source" + ] } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index cd32c91..2415c44 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,45 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "455764cd9b78270c5af3fb1bf25af96e", + "content-hash": "9e9c4f1ea2ce51361d5b605751d0915c", "packages": [ { - "name": "robertdevore/wpcom-check", - "version": "1.0.1", + "name": "composer/installers", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/robertdevore/wpcom-check.git", - "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058" + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robertdevore/wpcom-check/zipball/25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", - "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", "shasum": "" }, "require": { - "php": ">=7.4" + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" }, - "type": "library", + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", "extra": { - "wordpress-plugin": true + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true }, "autoload": { "psr-4": { - "RobertDevore\\WPComCheck\\": "src/" + "Composer\\Installers\\": "src/Composer/Installers" } }, "notification-url": "https://packagist.org/downloads/", @@ -38,35 +51,5439 @@ ], "authors": [ { - "name": "Robert DeVore", - "email": "me@robertdevore.com", - "homepage": "https://github.com/robertdevore", - "role": "Developer" + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" } ], - "description": "A utility to handle WordPress.com-specific plugin compatibility and auto-deactivation.", - "homepage": "https://github.com/robertdevore/wpcom-check", + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", "keywords": [ - "compatibility", - "deactivation", - "plugin", + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", "wordpress", - "wordpress.com" + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-24T20:46:46+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/ca-bundle", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-26T15:08:54+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-03-24T13:50:44+00:00" + }, + { + "name": "composer/composer", + "version": "2.8.9", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.2 || ^3.2", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^6.3.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^2.11 || ^3.2", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, + "branch-alias": { + "dev-main": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.8.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-13T12:01:37+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/robertdevore/wpcom-check/issues", - "source": "https://github.com/robertdevore/wpcom-check/tree/1.0.1" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.9", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } }, - "time": "2025-01-07T17:17:53+00:00" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-12T21:07:07+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "eftec/bladeone", + "version": "3.52", + "source": { + "type": "git", + "url": "https://github.com/EFTEC/BladeOne.git", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EFTEC/BladeOne/zipball/a19bf66917de0b29836983db87a455a4f6e32148", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16.1", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.5.4" + }, + "suggest": { + "eftec/bladeonehtml": "Extension to create forms", + "ext-mbstring": "This extension is used if it's active" + }, + "type": "library", + "autoload": { + "psr-4": { + "eftec\\bladeone\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jorge Patricio Castro Castillo", + "email": "jcastro@eftec.cl" + } + ], + "description": "The standalone version Blade Template Engine from Laravel in a single php file", + "homepage": "https://github.com/EFTEC/BladeOne", + "keywords": [ + "blade", + "php", + "template", + "templating", + "view" + ], + "support": { + "issues": "https://github.com/EFTEC/BladeOne/issues", + "source": "https://github.com/EFTEC/BladeOne/tree/3.52" + }, + "time": "2021-04-17T13:49:01+00:00" + }, + { + "name": "gettext/gettext", + "version": "v4.8.12", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/11af89ee6c087db3cf09ce2111a150bca5c46e12", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/view": "^5.0.x-dev", + "phpunit/phpunit": "^4.8|^5.7|^6.5", + "squizlabs/php_codesniffer": "^3.0", + "symfony/yaml": "~2", + "twig/extensions": "*", + "twig/twig": "^1.31|^2.0" + }, + "suggest": { + "illuminate/view": "Is necessary if you want to use the Blade extractor", + "symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator", + "twig/extensions": "Is necessary if you want to use the Twig extractor", + "twig/twig": "Is necessary if you want to use the Twig extractor" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/oscarotero/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/oscarotero/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v4.8.12" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "time": "2024-05-18T10:25:07+00:00" + }, + { + "name": "gettext/languages", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/0b0b0851c55168e1dfb14305735c64019732b5f1", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "bin": [ + "bin/export-plural-rules", + "bin/import-cldr-data" + ], + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.12.1" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "time": "2025-03-19T11:14:02+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.4.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "1.2.0", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" + }, + "time": "2025-06-03T18:27:04+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.1", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "time": "2024-11-28T04:54:44+00:00" + }, + { + "name": "mck89/peast", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/mck89/peast.git", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mck89/peast/zipball/3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17.0-dev" + } + }, + "autoload": { + "psr-4": { + "Peast\\": "lib/Peast/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marco Marchiò", + "email": "marco.mm89@gmail.com" + } + ], + "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.17.0" + }, + "time": "2025-03-07T19:44:14+00:00" + }, + { + "name": "nb/oxymel", + "version": "v0.1.0", + "source": { + "type": "git", + "url": "https://github.com/nb/oxymel.git", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nb/oxymel/zipball/cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "type": "library", + "autoload": { + "psr-0": { + "Oxymel": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikolay Bachiyski", + "email": "nb@nikolay.bg", + "homepage": "http://extrapolate.me/" + } + ], + "description": "A sweet XML builder", + "homepage": "https://github.com/nb/oxymel", + "keywords": [ + "xml" + ], + "support": { + "issues": "https://github.com/nb/oxymel/issues", + "source": "https://github.com/nb/oxymel/tree/master" + }, + "time": "2013-02-24T15:01:54+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:30:46+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.7", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-05-12T16:38:37+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-14T07:40:39+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "phpcs4", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-12T04:32:33+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-06-17T22:17:01+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-22T13:05:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-28T13:32:08+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-10T20:33:58+00:00" + }, + { + "name": "wp-cli/cache-command", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cache-command.git", + "reference": "14f76b0bc8f9fa0a680e9c70e18fcf627774d055" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cache-command/zipball/14f76b0bc8f9fa0a680e9c70e18fcf627774d055", + "reference": "14f76b0bc8f9fa0a680e9c70e18fcf627774d055", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "cache", + "cache add", + "cache decr", + "cache delete", + "cache flush", + "cache flush-group", + "cache get", + "cache incr", + "cache patch", + "cache pluck", + "cache replace", + "cache set", + "cache supports", + "cache type", + "transient", + "transient delete", + "transient get", + "transient list", + "transient patch", + "transient pluck", + "transient set", + "transient type" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "cache-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manages object and transient caches.", + "homepage": "https://github.com/wp-cli/cache-command", + "support": { + "issues": "https://github.com/wp-cli/cache-command/issues", + "source": "https://github.com/wp-cli/cache-command/tree/v2.2.0" + }, + "time": "2025-05-06T01:43:20+00:00" + }, + { + "name": "wp-cli/checksum-command", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/checksum-command.git", + "reference": "39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe", + "reference": "39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "core verify-checksums", + "plugin verify-checksums" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "checksum-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Verifies file integrity by comparing to published checksums.", + "homepage": "https://github.com/wp-cli/checksum-command", + "support": { + "issues": "https://github.com/wp-cli/checksum-command/issues", + "source": "https://github.com/wp-cli/checksum-command/tree/v2.3.1" + }, + "time": "2025-04-10T11:02:20+00:00" + }, + { + "name": "wp-cli/config-command", + "version": "v2.3.8", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/config-command.git", + "reference": "994b3dc9e8284fc978366920d5c5ae0dde3a004e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/config-command/zipball/994b3dc9e8284fc978366920d5c5ae0dde3a004e", + "reference": "994b3dc9e8284fc978366920d5c5ae0dde3a004e", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12", + "wp-cli/wp-config-transformer": "^1.4.0" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4.2.8" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "config", + "config edit", + "config delete", + "config create", + "config get", + "config has", + "config is-true", + "config list", + "config path", + "config set", + "config shuffle-salts" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "config-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + }, + { + "name": "Alain Schlesser", + "email": "alain.schlesser@gmail.com", + "homepage": "https://www.alainschlesser.com" + } + ], + "description": "Generates and reads the wp-config.php file.", + "homepage": "https://github.com/wp-cli/config-command", + "support": { + "issues": "https://github.com/wp-cli/config-command/issues", + "source": "https://github.com/wp-cli/config-command/tree/v2.3.8" + }, + "time": "2025-04-11T09:37:43+00:00" + }, + { + "name": "wp-cli/core-command", + "version": "v2.1.20", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/core-command.git", + "reference": "83e4692784a815bb7f5df10b72204f237b5224b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/core-command/zipball/83e4692784a815bb7f5df10b72204f237b5224b9", + "reference": "83e4692784a815bb7f5df10b72204f237b5224b9", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4 || ^2 || ^3", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/checksum-command": "^1 || ^2", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "core", + "core check-update", + "core download", + "core install", + "core is-installed", + "core multisite-convert", + "core multisite-install", + "core update", + "core update-db", + "core version" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "core-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Downloads, installs, updates, and manages a WordPress installation.", + "homepage": "https://github.com/wp-cli/core-command", + "support": { + "issues": "https://github.com/wp-cli/core-command/issues", + "source": "https://github.com/wp-cli/core-command/tree/v2.1.20" + }, + "time": "2025-04-16T11:23:00+00:00" + }, + { + "name": "wp-cli/cron-command", + "version": "v2.3.2", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cron-command.git", + "reference": "6f450028a75ebd275f12cad62959a0709bf3e7c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cron-command/zipball/6f450028a75ebd275f12cad62959a0709bf3e7c1", + "reference": "6f450028a75ebd275f12cad62959a0709bf3e7c1", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/eval-command": "^2.0", + "wp-cli/server-command": "^2.0", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "cron", + "cron test", + "cron event", + "cron event delete", + "cron event list", + "cron event run", + "cron event schedule", + "cron schedule", + "cron schedule list", + "cron event unschedule" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "cron-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules.", + "homepage": "https://github.com/wp-cli/cron-command", + "support": { + "issues": "https://github.com/wp-cli/cron-command/issues", + "source": "https://github.com/wp-cli/cron-command/tree/v2.3.2" + }, + "time": "2025-04-02T11:55:20+00:00" + }, + { + "name": "wp-cli/db-command", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/db-command.git", + "reference": "f857c91454d7092fa672bc388512a51752d9264a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/f857c91454d7092fa672bc388512a51752d9264a", + "reference": "f857c91454d7092fa672bc388512a51752d9264a", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "db", + "db clean", + "db create", + "db drop", + "db reset", + "db check", + "db optimize", + "db prefix", + "db repair", + "db cli", + "db query", + "db export", + "db import", + "db search", + "db tables", + "db size", + "db columns" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "db-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Performs basic database operations using credentials stored in wp-config.php.", + "homepage": "https://github.com/wp-cli/db-command", + "support": { + "issues": "https://github.com/wp-cli/db-command/issues", + "source": "https://github.com/wp-cli/db-command/tree/v2.1.3" + }, + "time": "2025-04-10T11:02:04+00:00" + }, + { + "name": "wp-cli/embed-command", + "version": "v2.0.18", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/embed-command.git", + "reference": "52f59a1dacf1d4a1c68fd685f27909e1f493816b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/embed-command/zipball/52f59a1dacf1d4a1c68fd685f27909e1f493816b", + "reference": "52f59a1dacf1d4a1c68fd685f27909e1f493816b", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "embed", + "embed fetch", + "embed provider", + "embed provider list", + "embed provider match", + "embed handler", + "embed handler list", + "embed cache", + "embed cache clear", + "embed cache find", + "embed cache trigger" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "embed-command.php" + ], + "psr-4": { + "WP_CLI\\Embeds\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Birchler", + "homepage": "https://pascalbirchler.com/" + } + ], + "description": "Inspects oEmbed providers, clears embed cache, and more.", + "homepage": "https://github.com/wp-cli/embed-command", + "support": { + "issues": "https://github.com/wp-cli/embed-command/issues", + "source": "https://github.com/wp-cli/embed-command/tree/v2.0.18" + }, + "time": "2025-04-10T11:01:32+00:00" + }, + { + "name": "wp-cli/entity-command", + "version": "v2.8.4", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/entity-command.git", + "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/213611f8ab619ca137d983e9b987f7fbf1ac21d4", + "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/cache-command": "^1 || ^2", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/media-command": "^1.1 || ^2", + "wp-cli/super-admin-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "comment", + "comment approve", + "comment count", + "comment create", + "comment delete", + "comment exists", + "comment generate", + "comment get", + "comment list", + "comment meta", + "comment meta add", + "comment meta delete", + "comment meta get", + "comment meta list", + "comment meta patch", + "comment meta pluck", + "comment meta update", + "comment recount", + "comment spam", + "comment status", + "comment trash", + "comment unapprove", + "comment unspam", + "comment untrash", + "comment update", + "menu", + "menu create", + "menu delete", + "menu item", + "menu item add-custom", + "menu item add-post", + "menu item add-term", + "menu item delete", + "menu item list", + "menu item update", + "menu list", + "menu location", + "menu location assign", + "menu location list", + "menu location remove", + "network meta", + "network meta add", + "network meta delete", + "network meta get", + "network meta list", + "network meta patch", + "network meta pluck", + "network meta update", + "option", + "option add", + "option delete", + "option get", + "option list", + "option patch", + "option pluck", + "option update", + "option set-autoload", + "option get-autoload", + "post", + "post create", + "post delete", + "post edit", + "post exists", + "post generate", + "post get", + "post list", + "post meta", + "post meta add", + "post meta clean-duplicates", + "post meta delete", + "post meta get", + "post meta list", + "post meta patch", + "post meta pluck", + "post meta update", + "post term", + "post term add", + "post term list", + "post term remove", + "post term set", + "post update", + "post url-to-id", + "post-type", + "post-type get", + "post-type list", + "site", + "site activate", + "site archive", + "site create", + "site generate", + "site deactivate", + "site delete", + "site empty", + "site list", + "site mature", + "site meta", + "site meta add", + "site meta delete", + "site meta get", + "site meta list", + "site meta patch", + "site meta pluck", + "site meta update", + "site option", + "site private", + "site public", + "site spam", + "site unarchive", + "site unmature", + "site unspam", + "taxonomy", + "taxonomy get", + "taxonomy list", + "term", + "term create", + "term delete", + "term generate", + "term get", + "term list", + "term meta", + "term meta add", + "term meta delete", + "term meta get", + "term meta list", + "term meta patch", + "term meta pluck", + "term meta update", + "term recount", + "term update", + "user", + "user add-cap", + "user add-role", + "user application-password", + "user application-password create", + "user application-password delete", + "user application-password exists", + "user application-password get", + "user application-password list", + "user application-password record-usage", + "user application-password update", + "user create", + "user delete", + "user exists", + "user generate", + "user get", + "user import-csv", + "user list", + "user list-caps", + "user meta", + "user meta add", + "user meta delete", + "user meta get", + "user meta list", + "user meta patch", + "user meta pluck", + "user meta update", + "user remove-cap", + "user remove-role", + "user reset-password", + "user session", + "user session destroy", + "user session list", + "user set-role", + "user signup", + "user signup activate", + "user signup delete", + "user signup get", + "user signup list", + "user spam", + "user term", + "user term add", + "user term list", + "user term remove", + "user term set", + "user unspam", + "user update" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "entity-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manage WordPress comments, menus, options, posts, sites, terms, and users.", + "homepage": "https://github.com/wp-cli/entity-command", + "support": { + "issues": "https://github.com/wp-cli/entity-command/issues", + "source": "https://github.com/wp-cli/entity-command/tree/v2.8.4" + }, + "time": "2025-05-06T16:12:49+00:00" + }, + { + "name": "wp-cli/eval-command", + "version": "v2.2.6", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/eval-command.git", + "reference": "20ec428a7b9bc604fab0bd33ee8fa20662650455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/eval-command/zipball/20ec428a7b9bc604fab0bd33ee8fa20662650455", + "reference": "20ec428a7b9bc604fab0bd33ee8fa20662650455", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "eval", + "eval-file" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "eval-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Executes arbitrary PHP code or files.", + "homepage": "https://github.com/wp-cli/eval-command", + "support": { + "issues": "https://github.com/wp-cli/eval-command/issues", + "source": "https://github.com/wp-cli/eval-command/tree/v2.2.6" + }, + "time": "2024-11-24T17:28:06+00:00" + }, + { + "name": "wp-cli/export-command", + "version": "v2.1.14", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/export-command.git", + "reference": "2af32bf12c1bccd6561a215dbbafc2f272647ee8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/export-command/zipball/2af32bf12c1bccd6561a215dbbafc2f272647ee8", + "reference": "2af32bf12c1bccd6561a215dbbafc2f272647ee8", + "shasum": "" + }, + "require": { + "nb/oxymel": "~0.1.0", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/import-command": "^1 || ^2", + "wp-cli/media-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "export" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "export-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Exports WordPress content to a WXR file.", + "homepage": "https://github.com/wp-cli/export-command", + "support": { + "issues": "https://github.com/wp-cli/export-command/issues", + "source": "https://github.com/wp-cli/export-command/tree/v2.1.14" + }, + "time": "2025-04-02T15:29:08+00:00" + }, + { + "name": "wp-cli/extension-command", + "version": "v2.1.24", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/extension-command.git", + "reference": "d21a2f504ac43a86b6b08697669b5b0844748133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/d21a2f504ac43a86b6b08697669b5b0844748133", + "reference": "d21a2f504ac43a86b6b08697669b5b0844748133", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4 || ^2 || ^3", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/cache-command": "^2.0", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/language-command": "^2.0", + "wp-cli/scaffold-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4.3.7" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "plugin", + "plugin activate", + "plugin deactivate", + "plugin delete", + "plugin get", + "plugin install", + "plugin is-installed", + "plugin list", + "plugin path", + "plugin search", + "plugin status", + "plugin toggle", + "plugin uninstall", + "plugin update", + "theme", + "theme activate", + "theme delete", + "theme disable", + "theme enable", + "theme get", + "theme install", + "theme is-installed", + "theme list", + "theme mod", + "theme mod get", + "theme mod set", + "theme mod remove", + "theme path", + "theme search", + "theme status", + "theme update", + "theme mod list" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "extension-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + }, + { + "name": "Alain Schlesser", + "email": "alain.schlesser@gmail.com", + "homepage": "https://www.alainschlesser.com" + } + ], + "description": "Manages plugins and themes, including installs, activations, and updates.", + "homepage": "https://github.com/wp-cli/extension-command", + "support": { + "issues": "https://github.com/wp-cli/extension-command/issues", + "source": "https://github.com/wp-cli/extension-command/tree/v2.1.24" + }, + "time": "2025-05-06T19:17:53+00:00" + }, + { + "name": "wp-cli/i18n-command", + "version": "v2.6.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/i18n-command.git", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/5e73d417398993625331a9f69f6c2ef60f234070", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070", + "shasum": "" + }, + "require": { + "eftec/bladeone": "3.52", + "gettext/gettext": "^4.8", + "mck89/peast": "^1.13.11", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/scaffold-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4.3.9" + }, + "suggest": { + "ext-json": "Used for reading and generating JSON translation files", + "ext-mbstring": "Used for calculating include/exclude matches in code extraction" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "i18n", + "i18n make-pot", + "i18n make-json", + "i18n make-mo", + "i18n make-php", + "i18n update-po" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "i18n-command.php" + ], + "psr-4": { + "WP_CLI\\I18n\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Birchler", + "homepage": "https://pascalbirchler.com/" + } + ], + "description": "Provides internationalization tools for WordPress projects.", + "homepage": "https://github.com/wp-cli/i18n-command", + "support": { + "issues": "https://github.com/wp-cli/i18n-command/issues", + "source": "https://github.com/wp-cli/i18n-command/tree/v2.6.5" + }, + "time": "2025-04-25T21:49:29+00:00" + }, + { + "name": "wp-cli/import-command", + "version": "v2.0.14", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/import-command.git", + "reference": "b2c48f3e51683e825738df62bf8ccc7004c5f0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/import-command/zipball/b2c48f3e51683e825738df62bf8ccc7004c5f0f9", + "reference": "b2c48f3e51683e825738df62bf8ccc7004c5f0f9", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/export-command": "^1 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "import" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "import-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Imports content from a given WXR file.", + "homepage": "https://github.com/wp-cli/import-command", + "support": { + "issues": "https://github.com/wp-cli/import-command/issues", + "source": "https://github.com/wp-cli/import-command/tree/v2.0.14" + }, + "time": "2025-04-02T16:47:25+00:00" + }, + { + "name": "wp-cli/language-command", + "version": "v2.0.23", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/language-command.git", + "reference": "7221cc39d2b14fd39e55aa7884889f26eec2f822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/language-command/zipball/7221cc39d2b14fd39e55aa7884889f26eec2f822", + "reference": "7221cc39d2b14fd39e55aa7884889f26eec2f822", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "language", + "language core", + "language core activate", + "language core is-installed", + "language core install", + "language core list", + "language core uninstall", + "language core update", + "language plugin", + "language plugin is-installed", + "language plugin install", + "language plugin list", + "language plugin uninstall", + "language plugin update", + "language theme", + "language theme is-installed", + "language theme install", + "language theme list", + "language theme uninstall", + "language theme update", + "site switch-language" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "language-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Installs, activates, and manages language packs.", + "homepage": "https://github.com/wp-cli/language-command", + "support": { + "issues": "https://github.com/wp-cli/language-command/issues", + "source": "https://github.com/wp-cli/language-command/tree/v2.0.23" + }, + "time": "2025-04-10T11:09:04+00:00" + }, + { + "name": "wp-cli/maintenance-mode-command", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/maintenance-mode-command.git", + "reference": "b947e094e00b7b68c6376ec9bd03303515864062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/b947e094e00b7b68c6376ec9bd03303515864062", + "reference": "b947e094e00b7b68c6376ec9bd03303515864062", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "maintenance-mode", + "maintenance-mode activate", + "maintenance-mode deactivate", + "maintenance-mode status", + "maintenance-mode is-active" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "maintenance-mode-command.php" + ], + "psr-4": { + "WP_CLI\\MaintenanceMode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thrijith Thankachan", + "email": "thrijith13@gmail.com", + "homepage": "https://thrijith.com" + } + ], + "description": "Activates, deactivates or checks the status of the maintenance mode of a site.", + "homepage": "https://github.com/wp-cli/maintenance-mode-command", + "support": { + "issues": "https://github.com/wp-cli/maintenance-mode-command/issues", + "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.1.3" + }, + "time": "2024-11-24T17:26:30+00:00" + }, + { + "name": "wp-cli/media-command", + "version": "v2.2.2", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/media-command.git", + "reference": "a810ea0e68473fce6a234e67c6c5f19bb820a753" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/media-command/zipball/a810ea0e68473fce6a234e67c6c5f19bb820a753", + "reference": "a810ea0e68473fce6a234e67c6c5f19bb820a753", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^2.0", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "media", + "media import", + "media regenerate", + "media image-size" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "media-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Imports files as attachments, regenerates thumbnails, or lists registered image sizes.", + "homepage": "https://github.com/wp-cli/media-command", + "support": { + "issues": "https://github.com/wp-cli/media-command/issues", + "source": "https://github.com/wp-cli/media-command/tree/v2.2.2" + }, + "time": "2025-04-11T09:28:29+00:00" + }, + { + "name": "wp-cli/mustache", + "version": "v2.14.99", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/mustache.php.git", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/mustache.php/zipball/ca23b97ac35fbe01c160549eb634396183d04a59", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "replace": { + "mustache/mustache": "^2.14.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.19.3", + "yoast/phpunit-polyfills": "^2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "source": "https://github.com/wp-cli/mustache.php/tree/v2.14.99" + }, + "time": "2025-05-06T16:15:37+00:00" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "support": { + "source": "https://github.com/wp-cli/spyc/tree/autoload" + }, + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/package-command", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/package-command.git", + "reference": "682d8c6bb30c782c3b09c015478c7cbe1cc727a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/package-command/zipball/682d8c6bb30c782c3b09c015478c7cbe1cc727a9", + "reference": "682d8c6bb30c782c3b09c015478c7cbe1cc727a9", + "shasum": "" + }, + "require": { + "composer/composer": "^2.2.25", + "ext-json": "*", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/scaffold-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "package", + "package browse", + "package install", + "package list", + "package update", + "package uninstall" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "package-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists, installs, and removes WP-CLI packages.", + "homepage": "https://github.com/wp-cli/package-command", + "support": { + "issues": "https://github.com/wp-cli/package-command/issues", + "source": "https://github.com/wp-cli/package-command/tree/v2.6.0" + }, + "time": "2025-04-11T09:28:45+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.12.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "shasum": "" + }, + "require": { + "php": ">= 5.6.0" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.11.x-dev" + } + }, + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "support": { + "issues": "https://github.com/wp-cli/php-cli-tools/issues", + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.12.5" + }, + "time": "2025-03-26T16:13:46+00:00" + }, + { + "name": "wp-cli/process", + "version": "v5.9.99", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/process.git", + "reference": "f0aec5ca26a702d3157e3a19982b662521ac2b81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/process/zipball/f0aec5ca26a702d3157e3a19982b662521ac2b81", + "reference": "f0aec5ca26a702d3157e3a19982b662521ac2b81", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "replace": { + "symfony/process": "^5.4.47" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/wp-cli/process/tree/v5.9.99" + }, + "time": "2025-05-06T21:26:50+00:00" + }, + { + "name": "wp-cli/rewrite-command", + "version": "v2.0.15", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/rewrite-command.git", + "reference": "277ec689b7c268680ff429f52558508622c9b34c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/rewrite-command/zipball/277ec689b7c268680ff429f52558508622c9b34c", + "reference": "277ec689b7c268680ff429f52558508622c9b34c", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "rewrite", + "rewrite flush", + "rewrite list", + "rewrite structure" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "rewrite-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists or flushes the site's rewrite rules, updates the permalink structure.", + "homepage": "https://github.com/wp-cli/rewrite-command", + "support": { + "issues": "https://github.com/wp-cli/rewrite-command/issues", + "source": "https://github.com/wp-cli/rewrite-command/tree/v2.0.15" + }, + "time": "2025-04-02T12:09:09+00:00" + }, + { + "name": "wp-cli/role-command", + "version": "v2.0.16", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/role-command.git", + "reference": "ed57fb5436b4d47954b07e56c734d19deb4fc491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/role-command/zipball/ed57fb5436b4d47954b07e56c734d19deb4fc491", + "reference": "ed57fb5436b4d47954b07e56c734d19deb4fc491", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "role", + "role create", + "role delete", + "role exists", + "role list", + "role reset", + "cap", + "cap add", + "cap list", + "cap remove" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "role-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Adds, removes, lists, and resets roles and capabilities.", + "homepage": "https://github.com/wp-cli/role-command", + "support": { + "issues": "https://github.com/wp-cli/role-command/issues", + "source": "https://github.com/wp-cli/role-command/tree/v2.0.16" + }, + "time": "2025-04-02T12:24:15+00:00" + }, + { + "name": "wp-cli/scaffold-command", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/scaffold-command.git", + "reference": "b4238ea12e768b3f15d10339a53a8642f82e1d2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/b4238ea12e768b3f15d10339a53a8642f82e1d2b", + "reference": "b4238ea12e768b3f15d10339a53a8642f82e1d2b", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "scaffold", + "scaffold underscores", + "scaffold block", + "scaffold child-theme", + "scaffold plugin", + "scaffold plugin-tests", + "scaffold post-type", + "scaffold taxonomy", + "scaffold theme-tests" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "scaffold-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Generates code for post types, taxonomies, blocks, plugins, child themes, etc.", + "homepage": "https://github.com/wp-cli/scaffold-command", + "support": { + "issues": "https://github.com/wp-cli/scaffold-command/issues", + "source": "https://github.com/wp-cli/scaffold-command/tree/v2.5.0" + }, + "time": "2025-04-11T09:29:34+00:00" + }, + { + "name": "wp-cli/search-replace-command", + "version": "v2.1.8", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/search-replace-command.git", + "reference": "65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057", + "reference": "65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "search-replace" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "search-replace-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Searches/replaces strings in the database.", + "homepage": "https://github.com/wp-cli/search-replace-command", + "support": { + "issues": "https://github.com/wp-cli/search-replace-command/issues", + "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.8" + }, + "time": "2025-04-02T13:07:50+00:00" + }, + { + "name": "wp-cli/server-command", + "version": "v2.0.15", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/server-command.git", + "reference": "80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/server-command/zipball/80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71", + "reference": "80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "server" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "server-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Launches PHP's built-in web server for a specific WordPress installation.", + "homepage": "https://github.com/wp-cli/server-command", + "support": { + "issues": "https://github.com/wp-cli/server-command/issues", + "source": "https://github.com/wp-cli/server-command/tree/v2.0.15" + }, + "time": "2025-04-10T11:03:13+00:00" + }, + { + "name": "wp-cli/shell-command", + "version": "v2.0.16", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/shell-command.git", + "reference": "3af53a9f4b240e03e77e815b2ee10f229f1aa591" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/shell-command/zipball/3af53a9f4b240e03e77e815b2ee10f229f1aa591", + "reference": "3af53a9f4b240e03e77e815b2ee10f229f1aa591", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "shell" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "shell-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Opens an interactive PHP console for running and testing PHP code.", + "homepage": "https://github.com/wp-cli/shell-command", + "support": { + "issues": "https://github.com/wp-cli/shell-command/issues", + "source": "https://github.com/wp-cli/shell-command/tree/v2.0.16" + }, + "time": "2025-04-11T09:39:33+00:00" + }, + { + "name": "wp-cli/super-admin-command", + "version": "v2.0.16", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/super-admin-command.git", + "reference": "54ac063c384743ee414806d42cb8c61c6aa1fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/super-admin-command/zipball/54ac063c384743ee414806d42cb8c61c6aa1fa8e", + "reference": "54ac063c384743ee414806d42cb8c61c6aa1fa8e", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "super-admin", + "super-admin add", + "super-admin list", + "super-admin remove" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "super-admin-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists, adds, or removes super admin users on a multisite installation.", + "homepage": "https://github.com/wp-cli/super-admin-command", + "support": { + "issues": "https://github.com/wp-cli/super-admin-command/issues", + "source": "https://github.com/wp-cli/super-admin-command/tree/v2.0.16" + }, + "time": "2025-04-02T13:07:32+00:00" + }, + { + "name": "wp-cli/widget-command", + "version": "v2.1.12", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/widget-command.git", + "reference": "73084053f7b32d92583e44d870b81f287beea6a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/widget-command/zipball/73084053f7b32d92583e44d870b81f287beea6a9", + "reference": "73084053f7b32d92583e44d870b81f287beea6a9", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "widget", + "widget add", + "widget deactivate", + "widget delete", + "widget list", + "widget move", + "widget reset", + "widget update", + "sidebar", + "sidebar list" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "widget-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Adds, moves, and removes widgets; lists sidebars.", + "homepage": "https://github.com/wp-cli/widget-command", + "support": { + "issues": "https://github.com/wp-cli/widget-command/issues", + "source": "https://github.com/wp-cli/widget-command/tree/v2.1.12" + }, + "time": "2025-04-11T09:29:37+00:00" + }, + { + "name": "wp-cli/wp-cli", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/03d30d4138d12b4bffd8b507b82e56e129e0523f", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.6 || ^7.0 || ^8.0", + "symfony/finder": ">2.7", + "wp-cli/mustache": "^2.14.99", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/php-cli-tools": "~0.12.4" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.2 || ^2", + "wp-cli/extension-command": "^1.1 || ^2", + "wp-cli/package-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4.3.10" + }, + "suggest": { + "ext-readline": "Include for a better --prompt implementation", + "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" + }, + "bin": [ + "bin/wp", + "bin/wp.bat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.12.x-dev" + } + }, + "autoload": { + "psr-0": { + "WP_CLI\\": "php/" + }, + "classmap": [ + "php/class-wp-cli.php", + "php/class-wp-cli-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI framework", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli/issues", + "source": "https://github.com/wp-cli/wp-cli" + }, + "time": "2025-05-07T01:16:12+00:00" + }, + { + "name": "wp-cli/wp-cli-bundle", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli-bundle.git", + "reference": "d639a3dab65f4b935b21c61ea3662bf3258a03a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/d639a3dab65f4b935b21c61ea3662bf3258a03a5", + "reference": "d639a3dab65f4b935b21c61ea3662bf3258a03a5", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "wp-cli/cache-command": "^2", + "wp-cli/checksum-command": "^2.1", + "wp-cli/config-command": "^2.1", + "wp-cli/core-command": "^2.1", + "wp-cli/cron-command": "^2", + "wp-cli/db-command": "^2", + "wp-cli/embed-command": "^2", + "wp-cli/entity-command": "^2", + "wp-cli/eval-command": "^2", + "wp-cli/export-command": "^2", + "wp-cli/extension-command": "^2.1", + "wp-cli/i18n-command": "^2", + "wp-cli/import-command": "^2", + "wp-cli/language-command": "^2", + "wp-cli/maintenance-mode-command": "^2", + "wp-cli/media-command": "^2", + "wp-cli/package-command": "^2.1", + "wp-cli/process": "5.9.99", + "wp-cli/rewrite-command": "^2", + "wp-cli/role-command": "^2", + "wp-cli/scaffold-command": "^2", + "wp-cli/search-replace-command": "^2", + "wp-cli/server-command": "^2", + "wp-cli/shell-command": "^2", + "wp-cli/super-admin-command": "^2", + "wp-cli/widget-command": "^2", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/wp-cli-tests": "^4" + }, + "suggest": { + "psy/psysh": "Enhanced `wp shell` functionality" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.12.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI bundle package with default commands.", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli-bundle/issues", + "source": "https://github.com/wp-cli/wp-cli-bundle" + }, + "time": "2025-05-07T02:15:53+00:00" + }, + { + "name": "wp-cli/wp-config-transformer", + "version": "v1.4.2", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-config-transformer.git", + "reference": "b78cab1159b43eb5ee097e2cfafe5eab573d2a8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/b78cab1159b43eb5ee097e2cfafe5eab573d2a8a", + "reference": "b78cab1159b43eb5ee097e2cfafe5eab573d2a8a", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/WPConfigTransformer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frankie Jarrett", + "email": "fjarrett@gmail.com" + } + ], + "description": "Programmatically edit a wp-config.php file.", + "homepage": "https://github.com/wp-cli/wp-config-transformer", + "support": { + "issues": "https://github.com/wp-cli/wp-config-transformer/issues", + "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.4.2" + }, + "time": "2025-03-31T08:37:05+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, + "platform": { + "php": ">=8.0" + }, + "platform-dev": { + "php": ">=8.0" + }, + "platform-overrides": { + "php": "8.0" + }, "plugin-api-version": "2.6.0" } diff --git a/vendor/bin/composer b/vendor/bin/composer new file mode 100755 index 0000000..fd55d73 --- /dev/null +++ b/vendor/bin/composer @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/composer/composer/bin/composer'); + } +} + +return include __DIR__ . '/..'.'/composer/composer/bin/composer'; diff --git a/vendor/bin/export-plural-rules b/vendor/bin/export-plural-rules new file mode 100755 index 0000000..158aa2b --- /dev/null +++ b/vendor/bin/export-plural-rules @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/gettext/languages/bin/export-plural-rules'); + } +} + +return include __DIR__ . '/..'.'/gettext/languages/bin/export-plural-rules'; diff --git a/vendor/bin/import-cldr-data b/vendor/bin/import-cldr-data new file mode 100755 index 0000000..a3a9762 --- /dev/null +++ b/vendor/bin/import-cldr-data @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/gettext/languages/bin/import-cldr-data'); + } +} + +return include __DIR__ . '/..'.'/gettext/languages/bin/import-cldr-data'; diff --git a/vendor/bin/jsonlint b/vendor/bin/jsonlint new file mode 100755 index 0000000..ca6e04d --- /dev/null +++ b/vendor/bin/jsonlint @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/seld/jsonlint/bin/jsonlint'); + } +} + +return include __DIR__ . '/..'.'/seld/jsonlint/bin/jsonlint'; diff --git a/vendor/bin/phpcbf b/vendor/bin/phpcbf new file mode 100755 index 0000000..1c0c79c --- /dev/null +++ b/vendor/bin/phpcbf @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf'); + } +} + +return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf'; diff --git a/vendor/bin/phpcs b/vendor/bin/phpcs new file mode 100755 index 0000000..04e658c --- /dev/null +++ b/vendor/bin/phpcs @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs'); + } +} + +return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs'; diff --git a/vendor/bin/validate-json b/vendor/bin/validate-json new file mode 100755 index 0000000..8be90f4 --- /dev/null +++ b/vendor/bin/validate-json @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'); + } +} + +return include __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'; diff --git a/vendor/bin/wp b/vendor/bin/wp new file mode 100755 index 0000000..1dfb4f9 --- /dev/null +++ b/vendor/bin/wp @@ -0,0 +1,37 @@ +#!/usr/bin/env sh + +# Support bash to support `source` with fallback on $0 if this does not run with bash +# https://stackoverflow.com/a/35006505/6512 +selfArg="$BASH_SOURCE" +if [ -z "$selfArg" ]; then + selfArg="$0" +fi + +self=$(realpath $selfArg 2> /dev/null) +if [ -z "$self" ]; then + self="$selfArg" +fi + +dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../wp-cli/wp-cli/bin' && pwd) + +if [ -d /proc/cygdrive ]; then + case $(which php) in + $(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=$(cygpath -m "$dir"); + ;; + esac +fi + +export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)" + +# If bash is sourcing this file, we have to source the target as well +bashSource="$BASH_SOURCE" +if [ -n "$bashSource" ]; then + if [ "$bashSource" != "$0" ]; then + source "${dir}/wp" "$@" + return + fi +fi + +exec "${dir}/wp" "$@" diff --git a/vendor/bin/wp.bat b/vendor/bin/wp.bat new file mode 100755 index 0000000..6460c03 --- /dev/null +++ b/vendor/bin/wp.bat @@ -0,0 +1,37 @@ +#!/usr/bin/env sh + +# Support bash to support `source` with fallback on $0 if this does not run with bash +# https://stackoverflow.com/a/35006505/6512 +selfArg="$BASH_SOURCE" +if [ -z "$selfArg" ]; then + selfArg="$0" +fi + +self=$(realpath $selfArg 2> /dev/null) +if [ -z "$self" ]; then + self="$selfArg" +fi + +dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../wp-cli/wp-cli/bin' && pwd) + +if [ -d /proc/cygdrive ]; then + case $(which php) in + $(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=$(cygpath -m "$dir"); + ;; + esac +fi + +export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)" + +# If bash is sourcing this file, we have to source the target as well +bashSource="$BASH_SOURCE" +if [ -n "$bashSource" ]; then + if [ "$bashSource" != "$0" ]; then + source "${dir}/wp.bat" "$@" + return + fi +fi + +exec "${dir}/wp.bat" "$@" diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 07b32ed..6d29bff 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -32,6 +32,11 @@ class InstalledVersions */ private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** * @var bool|null */ @@ -309,6 +314,12 @@ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; } /** @@ -325,7 +336,9 @@ private static function getInstalled() $copiedLocalDir = false; if (self::$canGetVendors) { + $selfDir = strtr(__DIR__, '\\', '/'); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { @@ -333,11 +346,14 @@ private static function getInstalled() $required = require $vendorDir.'/composer/installed.php'; self::$installedByVendor[$vendorDir] = $required; $installed[] = $required; - if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { self::$installed = $required; - $copiedLocalDir = true; + self::$installedIsLocalDir = true; } } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } } } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 0fb0a2c..0ef6c57 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,5 +6,164 @@ $baseDir = dirname($vendorDir); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Cache_Command' => $vendorDir . '/wp-cli/cache-command/src/Cache_Command.php', + 'Capabilities_Command' => $vendorDir . '/wp-cli/role-command/src/Capabilities_Command.php', + 'Checksum_Base_Command' => $vendorDir . '/wp-cli/checksum-command/src/Checksum_Base_Command.php', + 'Checksum_Core_Command' => $vendorDir . '/wp-cli/checksum-command/src/Checksum_Core_Command.php', + 'Checksum_Plugin_Command' => $vendorDir . '/wp-cli/checksum-command/src/Checksum_Plugin_Command.php', + 'Comment_Command' => $vendorDir . '/wp-cli/entity-command/src/Comment_Command.php', + 'Comment_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Comment_Meta_Command.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Config_Command' => $vendorDir . '/wp-cli/config-command/src/Config_Command.php', + 'Core_Command' => $vendorDir . '/wp-cli/core-command/src/Core_Command.php', + 'Core_Command_Namespace' => $vendorDir . '/wp-cli/checksum-command/src/Core_Command_Namespace.php', + 'Core_Language_Command' => $vendorDir . '/wp-cli/language-command/src/Core_Language_Command.php', + 'Cron_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Command.php', + 'Cron_Event_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Event_Command.php', + 'Cron_Schedule_Command' => $vendorDir . '/wp-cli/cron-command/src/Cron_Schedule_Command.php', + 'DB_Command' => $vendorDir . '/wp-cli/db-command/src/DB_Command.php', + 'EvalFile_Command' => $vendorDir . '/wp-cli/eval-command/src/EvalFile_Command.php', + 'Eval_Command' => $vendorDir . '/wp-cli/eval-command/src/Eval_Command.php', + 'Export_Command' => $vendorDir . '/wp-cli/export-command/src/Export_Command.php', + 'Import_Command' => $vendorDir . '/wp-cli/import-command/src/Import_Command.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Language_Namespace' => $vendorDir . '/wp-cli/language-command/src/Language_Namespace.php', + 'Media_Command' => $vendorDir . '/wp-cli/media-command/src/Media_Command.php', + 'Menu_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Command.php', + 'Menu_Item_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Item_Command.php', + 'Menu_Location_Command' => $vendorDir . '/wp-cli/entity-command/src/Menu_Location_Command.php', + 'Network_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Network_Meta_Command.php', + 'Network_Namespace' => $vendorDir . '/wp-cli/entity-command/src/Network_Namespace.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'Option_Command' => $vendorDir . '/wp-cli/entity-command/src/Option_Command.php', + 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', + 'PHPCSUtils\\BackCompat\\BCFile' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', + 'PHPCSUtils\\BackCompat\\BCTokens' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', + 'PHPCSUtils\\BackCompat\\Helper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', + 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', + 'PHPCSUtils\\Exceptions\\LogicException' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/LogicException.php', + 'PHPCSUtils\\Exceptions\\MissingArgumentError' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/MissingArgumentError.php', + 'PHPCSUtils\\Exceptions\\OutOfBoundsStackPtr' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/OutOfBoundsStackPtr.php', + 'PHPCSUtils\\Exceptions\\RuntimeException' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/RuntimeException.php', + 'PHPCSUtils\\Exceptions\\TestFileNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', + 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', + 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', + 'PHPCSUtils\\Exceptions\\TypeError' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TypeError.php', + 'PHPCSUtils\\Exceptions\\UnexpectedTokenType' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/UnexpectedTokenType.php', + 'PHPCSUtils\\Exceptions\\ValueError' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/ValueError.php', + 'PHPCSUtils\\Fixers\\SpacesFixer' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', + 'PHPCSUtils\\Internal\\Cache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrList' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', + 'PHPCSUtils\\Internal\\NoFileCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', + 'PHPCSUtils\\Internal\\StableCollections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', + 'PHPCSUtils\\TestUtils\\ConfigDouble' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/ConfigDouble.php', + 'PHPCSUtils\\TestUtils\\RulesetDouble' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/RulesetDouble.php', + 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', + 'PHPCSUtils\\Tokens\\Collections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', + 'PHPCSUtils\\Tokens\\TokenHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', + 'PHPCSUtils\\Utils\\Arrays' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', + 'PHPCSUtils\\Utils\\Conditions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', + 'PHPCSUtils\\Utils\\Constants' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Constants.php', + 'PHPCSUtils\\Utils\\Context' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', + 'PHPCSUtils\\Utils\\ControlStructures' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', + 'PHPCSUtils\\Utils\\FileInfo' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FileInfo.php', + 'PHPCSUtils\\Utils\\FilePath' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FilePath.php', + 'PHPCSUtils\\Utils\\FunctionDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', + 'PHPCSUtils\\Utils\\GetTokensAsString' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', + 'PHPCSUtils\\Utils\\Lists' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', + 'PHPCSUtils\\Utils\\MessageHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', + 'PHPCSUtils\\Utils\\Namespaces' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', + 'PHPCSUtils\\Utils\\NamingConventions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', + 'PHPCSUtils\\Utils\\Numbers' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', + 'PHPCSUtils\\Utils\\ObjectDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', + 'PHPCSUtils\\Utils\\Operators' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', + 'PHPCSUtils\\Utils\\Orthography' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', + 'PHPCSUtils\\Utils\\Parentheses' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', + 'PHPCSUtils\\Utils\\PassedParameters' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', + 'PHPCSUtils\\Utils\\Scopes' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', + 'PHPCSUtils\\Utils\\TextStrings' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', + 'PHPCSUtils\\Utils\\TypeString' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TypeString.php', + 'PHPCSUtils\\Utils\\UseStatements' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', + 'PHPCSUtils\\Utils\\Variables' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', + 'Package_Command' => $vendorDir . '/wp-cli/package-command/src/Package_Command.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Plugin_AutoUpdates_Command' => $vendorDir . '/wp-cli/extension-command/src/Plugin_AutoUpdates_Command.php', + 'Plugin_Command' => $vendorDir . '/wp-cli/extension-command/src/Plugin_Command.php', + 'Plugin_Command_Namespace' => $vendorDir . '/wp-cli/checksum-command/src/Plugin_Command_Namespace.php', + 'Plugin_Language_Command' => $vendorDir . '/wp-cli/language-command/src/Plugin_Language_Command.php', + 'Post_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Command.php', + 'Post_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Meta_Command.php', + 'Post_Term_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Term_Command.php', + 'Post_Type_Command' => $vendorDir . '/wp-cli/entity-command/src/Post_Type_Command.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Rewrite_Command' => $vendorDir . '/wp-cli/rewrite-command/src/Rewrite_Command.php', + 'Role_Command' => $vendorDir . '/wp-cli/role-command/src/Role_Command.php', + 'Scaffold_Command' => $vendorDir . '/wp-cli/scaffold-command/src/Scaffold_Command.php', + 'Search_Replace_Command' => $vendorDir . '/wp-cli/search-replace-command/src/Search_Replace_Command.php', + 'Server_Command' => $vendorDir . '/wp-cli/server-command/src/Server_Command.php', + 'Shell_Command' => $vendorDir . '/wp-cli/shell-command/src/Shell_Command.php', + 'Sidebar_Command' => $vendorDir . '/wp-cli/widget-command/src/Sidebar_Command.php', + 'Signup_Command' => $vendorDir . '/wp-cli/entity-command/src/Signup_Command.php', + 'Site_Command' => $vendorDir . '/wp-cli/entity-command/src/Site_Command.php', + 'Site_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Site_Meta_Command.php', + 'Site_Option_Command' => $vendorDir . '/wp-cli/entity-command/src/Site_Option_Command.php', + 'Site_Switch_Language_Command' => $vendorDir . '/wp-cli/language-command/src/Site_Switch_Language_Command.php', + 'Stringable' => $vendorDir . '/marc-mabe/php-enum/stubs/Stringable.php', + 'Super_Admin_Command' => $vendorDir . '/wp-cli/super-admin-command/src/Super_Admin_Command.php', + 'Taxonomy_Command' => $vendorDir . '/wp-cli/entity-command/src/Taxonomy_Command.php', + 'Term_Command' => $vendorDir . '/wp-cli/entity-command/src/Term_Command.php', + 'Term_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/Term_Meta_Command.php', + 'Theme_AutoUpdates_Command' => $vendorDir . '/wp-cli/extension-command/src/Theme_AutoUpdates_Command.php', + 'Theme_Command' => $vendorDir . '/wp-cli/extension-command/src/Theme_Command.php', + 'Theme_Language_Command' => $vendorDir . '/wp-cli/language-command/src/Theme_Language_Command.php', + 'Theme_Mod_Command' => $vendorDir . '/wp-cli/extension-command/src/Theme_Mod_Command.php', + 'Transient_Command' => $vendorDir . '/wp-cli/cache-command/src/Transient_Command.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'User_Application_Password_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Application_Password_Command.php', + 'User_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Command.php', + 'User_Meta_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Meta_Command.php', + 'User_Session_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Session_Command.php', + 'User_Term_Command' => $vendorDir . '/wp-cli/entity-command/src/User_Term_Command.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'WP_CLI' => $vendorDir . '/wp-cli/wp-cli/php/class-wp-cli.php', + 'WP_CLI\\CommandWithDBObject' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php', + 'WP_CLI\\CommandWithMeta' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php', + 'WP_CLI\\CommandWithTerms' => $vendorDir . '/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.php', + 'WP_CLI\\CommandWithTranslation' => $vendorDir . '/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.php', + 'WP_CLI\\CommandWithUpgrade' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.php', + 'WP_CLI\\Core\\CoreUpgrader' => $vendorDir . '/wp-cli/core-command/src/WP_CLI/Core/CoreUpgrader.php', + 'WP_CLI\\Core\\NonDestructiveCoreUpgrader' => $vendorDir . '/wp-cli/core-command/src/WP_CLI/Core/NonDestructiveCoreUpgrader.php', + 'WP_CLI\\DestructivePluginUpgrader' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php', + 'WP_CLI\\DestructiveThemeUpgrader' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php', + 'WP_CLI\\Fetchers\\Plugin' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php', + 'WP_CLI\\Fetchers\\Theme' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php', + 'WP_CLI\\Fetchers\\UnfilteredPlugin' => $vendorDir . '/wp-cli/checksum-command/src/WP_CLI/Fetchers/UnfilteredPlugin.php', + 'WP_CLI\\JsonManipulator' => $vendorDir . '/wp-cli/package-command/src/WP_CLI/JsonManipulator.php', + 'WP_CLI\\LanguagePackUpgrader' => $vendorDir . '/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php', + 'WP_CLI\\Package\\Compat\\Min_Composer_1_10\\NullIOMethodsTrait' => $vendorDir . '/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_1_10/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\Compat\\Min_Composer_2_3\\NullIOMethodsTrait' => $vendorDir . '/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_2_3/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\Compat\\NullIOMethodsTrait' => $vendorDir . '/wp-cli/package-command/src/WP_CLI/Package/Compat/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\ComposerIO' => $vendorDir . '/wp-cli/package-command/src/WP_CLI/Package/ComposerIO.php', + 'WP_CLI\\ParsePluginNameInput' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/ParsePluginNameInput.php', + 'WP_CLI\\ParseThemeNameInput' => $vendorDir . '/wp-cli/extension-command/src/WP_CLI/ParseThemeNameInput.php', + 'WP_CLI\\SearchReplacer' => $vendorDir . '/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.php', + 'WP_CLI\\Shell\\REPL' => $vendorDir . '/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php', + 'WP_CLI_Command' => $vendorDir . '/wp-cli/wp-cli/php/class-wp-cli-command.php', + 'WP_Export_Base_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Base_Writer.php', + 'WP_Export_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Exception.php', + 'WP_Export_File_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_File_Writer.php', + 'WP_Export_Oxymel' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Oxymel.php', + 'WP_Export_Query' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Query.php', + 'WP_Export_Returner' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Returner.php', + 'WP_Export_Split_Files_Writer' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php', + 'WP_Export_Term_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Export_Term_Exception.php', + 'WP_Export_WXR_Formatter' => $vendorDir . '/wp-cli/export-command/src/WP_Export_WXR_Formatter.php', + 'WP_Export_XML_Over_HTTP' => $vendorDir . '/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.php', + 'WP_Iterator_Exception' => $vendorDir . '/wp-cli/export-command/src/WP_Iterator_Exception.php', + 'WP_Map_Iterator' => $vendorDir . '/wp-cli/export-command/src/WP_Map_Iterator.php', + 'WP_Post_IDs_Iterator' => $vendorDir . '/wp-cli/export-command/src/WP_Post_IDs_Iterator.php', + 'Widget_Command' => $vendorDir . '/wp-cli/widget-command/src/Widget_Command.php', ); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..f651bea --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,47 @@ + $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '3937806105cc8e221b8fa8db5b70d2f2' => $vendorDir . '/wp-cli/mustangostang-spyc/includes/functions.php', + 'be01b9b16925dcb22165c40b46681ac6' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/cli.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', + 'ac949ce40a981819ba132473518a9a31' => $vendorDir . '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php', + '8a0ad02df6a5087f2c380f8fd52db273' => $vendorDir . '/wp-cli/cache-command/cache-command.php', + 'b66d29757fcb2fb7a9608d068e3716b0' => $vendorDir . '/wp-cli/checksum-command/checksum-command.php', + '5deaf6ce9c8bbdfb65104c7e938d5875' => $vendorDir . '/wp-cli/config-command/config-command.php', + '68c39b88215b6cf7a0da164166670ef9' => $vendorDir . '/wp-cli/core-command/core-command.php', + '7654e00bf0e632580764400bd8293a9c' => $vendorDir . '/wp-cli/cron-command/cron-command.php', + 'c65f753375faee349b7adc48c2ee7cc2' => $vendorDir . '/wp-cli/db-command/db-command.php', + '021d3a13471556f0b57038d679f7f8ea' => $vendorDir . '/wp-cli/embed-command/embed-command.php', + 'f3f0199a3ecd9f501d0a3b361bd2f61c' => $vendorDir . '/wp-cli/entity-command/entity-command.php', + 'f958dca3f412fd7975da1700912a9321' => $vendorDir . '/wp-cli/eval-command/eval-command.php', + '5c6ec5cff8f9d625772c8ed147f6b894' => $vendorDir . '/wp-cli/export-command/export-command.php', + '3f201033d5aceb2293314273be88f7c6' => $vendorDir . '/wp-cli/extension-command/extension-command.php', + 'ffb465a494c3101218c4417180c2c9a2' => $vendorDir . '/wp-cli/i18n-command/i18n-command.php', + '30cbb6e4122dc988e494c6b9c0438233' => $vendorDir . '/wp-cli/import-command/import-command.php', + 'ace0d205db7f4135ec32132a0076d555' => $vendorDir . '/wp-cli/language-command/language-command.php', + '1c88c1eff05217a8cac80c64c9ac2080' => $vendorDir . '/wp-cli/maintenance-mode-command/maintenance-mode-command.php', + '5e099d3cac677dd2bec1003ea7707745' => $vendorDir . '/wp-cli/media-command/media-command.php', + 'ba366f96f4fddbdef61ad7a862b44f61' => $vendorDir . '/wp-cli/package-command/package-command.php', + 'f399c1c8d0c787d5c94c09884cdd9762' => $vendorDir . '/wp-cli/rewrite-command/rewrite-command.php', + '080fadd667195d055c5a23386f270261' => $vendorDir . '/wp-cli/role-command/role-command.php', + 'd979c11fe80ba96ae3037b43429fe546' => $vendorDir . '/wp-cli/scaffold-command/scaffold-command.php', + '8ecb13f8bbc22b1b34d12b14ec01077a' => $vendorDir . '/wp-cli/search-replace-command/search-replace-command.php', + '9f04dd0aa5d67ec75a75c88c345a079e' => $vendorDir . '/wp-cli/server-command/server-command.php', + '129d58fa8151374aceb8571bcaa97504' => $vendorDir . '/wp-cli/shell-command/shell-command.php', + '8519779bbb65eeb842af2f629ce7b6f8' => $vendorDir . '/wp-cli/super-admin-command/super-admin-command.php', + '1f05372afcc7d0c51a305cef1d56dd01' => $vendorDir . '/wp-cli/widget-command/widget-command.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php index 15a2ff3..0bbd07f 100644 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -6,4 +6,8 @@ $baseDir = dirname($vendorDir); return array( + 'cli' => array($vendorDir . '/wp-cli/php-cli-tools/lib'), + 'WP_CLI\\' => array($vendorDir . '/wp-cli/wp-cli/php'), + 'Oxymel' => array($vendorDir . '/nb/oxymel'), + 'Mustache' => array($vendorDir . '/wp-cli/mustache/src'), ); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 932ad07..04fb07d 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,5 +6,43 @@ $baseDir = dirname($vendorDir); return array( - 'RobertDevore\\WPComCheck\\' => array($vendorDir . '/robertdevore/wpcom-check/src'), + 'eftec\\bladeone\\' => array($vendorDir . '/eftec/bladeone/lib'), + 'WP_CLI\\MaintenanceMode\\' => array($vendorDir . '/wp-cli/maintenance-mode-command/src'), + 'WP_CLI\\I18n\\' => array($vendorDir . '/wp-cli/i18n-command/src'), + 'WP_CLI\\Embeds\\' => array($vendorDir . '/wp-cli/embed-command/src'), + 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/wp-cli/process'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'), + 'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'), + 'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'), + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Peast\\' => array($vendorDir . '/mck89/peast/lib/Peast'), + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'), + 'Mustangostang\\' => array($vendorDir . '/wp-cli/mustangostang-spyc/src'), + 'MabeEnum\\' => array($vendorDir . '/marc-mabe/php-enum/src'), + 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), + 'Gettext\\Languages\\' => array($vendorDir . '/gettext/languages/src'), + 'Gettext\\' => array($vendorDir . '/gettext/gettext/src'), + 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), + 'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'), + 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), + 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), + 'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'), + 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'), + 'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'), + 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), + 'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 0e59854..0c1e744 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -33,6 +33,18 @@ public static function getLoader() $loader->register(true); + $filesToLoad = \Composer\Autoload\ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + return $loader; } } diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 66b223d..6975dac 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -6,22 +6,467 @@ class ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e { + public static $files = array ( + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '3937806105cc8e221b8fa8db5b70d2f2' => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/includes/functions.php', + 'be01b9b16925dcb22165c40b46681ac6' => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib/cli/cli.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', + 'ac949ce40a981819ba132473518a9a31' => __DIR__ . '/..' . '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php', + '8a0ad02df6a5087f2c380f8fd52db273' => __DIR__ . '/..' . '/wp-cli/cache-command/cache-command.php', + 'b66d29757fcb2fb7a9608d068e3716b0' => __DIR__ . '/..' . '/wp-cli/checksum-command/checksum-command.php', + '5deaf6ce9c8bbdfb65104c7e938d5875' => __DIR__ . '/..' . '/wp-cli/config-command/config-command.php', + '68c39b88215b6cf7a0da164166670ef9' => __DIR__ . '/..' . '/wp-cli/core-command/core-command.php', + '7654e00bf0e632580764400bd8293a9c' => __DIR__ . '/..' . '/wp-cli/cron-command/cron-command.php', + 'c65f753375faee349b7adc48c2ee7cc2' => __DIR__ . '/..' . '/wp-cli/db-command/db-command.php', + '021d3a13471556f0b57038d679f7f8ea' => __DIR__ . '/..' . '/wp-cli/embed-command/embed-command.php', + 'f3f0199a3ecd9f501d0a3b361bd2f61c' => __DIR__ . '/..' . '/wp-cli/entity-command/entity-command.php', + 'f958dca3f412fd7975da1700912a9321' => __DIR__ . '/..' . '/wp-cli/eval-command/eval-command.php', + '5c6ec5cff8f9d625772c8ed147f6b894' => __DIR__ . '/..' . '/wp-cli/export-command/export-command.php', + '3f201033d5aceb2293314273be88f7c6' => __DIR__ . '/..' . '/wp-cli/extension-command/extension-command.php', + 'ffb465a494c3101218c4417180c2c9a2' => __DIR__ . '/..' . '/wp-cli/i18n-command/i18n-command.php', + '30cbb6e4122dc988e494c6b9c0438233' => __DIR__ . '/..' . '/wp-cli/import-command/import-command.php', + 'ace0d205db7f4135ec32132a0076d555' => __DIR__ . '/..' . '/wp-cli/language-command/language-command.php', + '1c88c1eff05217a8cac80c64c9ac2080' => __DIR__ . '/..' . '/wp-cli/maintenance-mode-command/maintenance-mode-command.php', + '5e099d3cac677dd2bec1003ea7707745' => __DIR__ . '/..' . '/wp-cli/media-command/media-command.php', + 'ba366f96f4fddbdef61ad7a862b44f61' => __DIR__ . '/..' . '/wp-cli/package-command/package-command.php', + 'f399c1c8d0c787d5c94c09884cdd9762' => __DIR__ . '/..' . '/wp-cli/rewrite-command/rewrite-command.php', + '080fadd667195d055c5a23386f270261' => __DIR__ . '/..' . '/wp-cli/role-command/role-command.php', + 'd979c11fe80ba96ae3037b43429fe546' => __DIR__ . '/..' . '/wp-cli/scaffold-command/scaffold-command.php', + '8ecb13f8bbc22b1b34d12b14ec01077a' => __DIR__ . '/..' . '/wp-cli/search-replace-command/search-replace-command.php', + '9f04dd0aa5d67ec75a75c88c345a079e' => __DIR__ . '/..' . '/wp-cli/server-command/server-command.php', + '129d58fa8151374aceb8571bcaa97504' => __DIR__ . '/..' . '/wp-cli/shell-command/shell-command.php', + '8519779bbb65eeb842af2f629ce7b6f8' => __DIR__ . '/..' . '/wp-cli/super-admin-command/super-admin-command.php', + '1f05372afcc7d0c51a305cef1d56dd01' => __DIR__ . '/..' . '/wp-cli/widget-command/widget-command.php', + ); + public static $prefixLengthsPsr4 = array ( + 'e' => + array ( + 'eftec\\bladeone\\' => 15, + ), + 'W' => + array ( + 'WP_CLI\\MaintenanceMode\\' => 23, + 'WP_CLI\\I18n\\' => 12, + 'WP_CLI\\Embeds\\' => 14, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\Console\\' => 26, + 'Seld\\Signal\\' => 12, + 'Seld\\PharUtils\\' => 15, + 'Seld\\JsonLint\\' => 14, + ), 'R' => array ( - 'RobertDevore\\WPComCheck\\' => 24, + 'React\\Promise\\' => 14, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'Peast\\' => 6, + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 57, + ), + 'M' => + array ( + 'Mustangostang\\' => 14, + 'MabeEnum\\' => 9, + ), + 'J' => + array ( + 'JsonSchema\\' => 11, + ), + 'G' => + array ( + 'Gettext\\Languages\\' => 18, + 'Gettext\\' => 8, + ), + 'C' => + array ( + 'Composer\\XdebugHandler\\' => 23, + 'Composer\\Spdx\\' => 14, + 'Composer\\Semver\\' => 16, + 'Composer\\Pcre\\' => 14, + 'Composer\\MetadataMinifier\\' => 26, + 'Composer\\Installers\\' => 20, + 'Composer\\ClassMapGenerator\\' => 27, + 'Composer\\CaBundle\\' => 18, + 'Composer\\' => 9, ), ); public static $prefixDirsPsr4 = array ( - 'RobertDevore\\WPComCheck\\' => + 'eftec\\bladeone\\' => + array ( + 0 => __DIR__ . '/..' . '/eftec/bladeone/lib', + ), + 'WP_CLI\\MaintenanceMode\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/maintenance-mode-command/src', + ), + 'WP_CLI\\I18n\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/i18n-command/src', + ), + 'WP_CLI\\Embeds\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/embed-command/src', + ), + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/process', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Seld\\Signal\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/signal-handler/src', + ), + 'Seld\\PharUtils\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/phar-utils/src', + ), + 'Seld\\JsonLint\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint', + ), + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Peast\\' => + array ( + 0 => __DIR__ . '/..' . '/mck89/peast/lib/Peast', + ), + 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => + array ( + 0 => __DIR__ . '/..' . '/dealerdirect/phpcodesniffer-composer-installer/src', + ), + 'Mustangostang\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/src', + ), + 'MabeEnum\\' => + array ( + 0 => __DIR__ . '/..' . '/marc-mabe/php-enum/src', + ), + 'JsonSchema\\' => + array ( + 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', + ), + 'Gettext\\Languages\\' => + array ( + 0 => __DIR__ . '/..' . '/gettext/languages/src', + ), + 'Gettext\\' => + array ( + 0 => __DIR__ . '/..' . '/gettext/gettext/src', + ), + 'Composer\\XdebugHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', + ), + 'Composer\\Spdx\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/spdx-licenses/src', + ), + 'Composer\\Semver\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/semver/src', + ), + 'Composer\\Pcre\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/pcre/src', + ), + 'Composer\\MetadataMinifier\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/metadata-minifier/src', + ), + 'Composer\\Installers\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers', + ), + 'Composer\\ClassMapGenerator\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/class-map-generator/src', + ), + 'Composer\\CaBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', + ), + 'Composer\\' => + array ( + 0 => __DIR__ . '/..' . '/composer/composer/src/Composer', + ), + ); + + public static $prefixesPsr0 = array ( + 'c' => + array ( + 'cli' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib', + ), + ), + 'W' => + array ( + 'WP_CLI\\' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/wp-cli/php', + ), + ), + 'O' => + array ( + 'Oxymel' => + array ( + 0 => __DIR__ . '/..' . '/nb/oxymel', + ), + ), + 'M' => array ( - 0 => __DIR__ . '/..' . '/robertdevore/wpcom-check/src', + 'Mustache' => + array ( + 0 => __DIR__ . '/..' . '/wp-cli/mustache/src', + ), ), ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Cache_Command' => __DIR__ . '/..' . '/wp-cli/cache-command/src/Cache_Command.php', + 'Capabilities_Command' => __DIR__ . '/..' . '/wp-cli/role-command/src/Capabilities_Command.php', + 'Checksum_Base_Command' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/Checksum_Base_Command.php', + 'Checksum_Core_Command' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/Checksum_Core_Command.php', + 'Checksum_Plugin_Command' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/Checksum_Plugin_Command.php', + 'Comment_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Comment_Command.php', + 'Comment_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Comment_Meta_Command.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Config_Command' => __DIR__ . '/..' . '/wp-cli/config-command/src/Config_Command.php', + 'Core_Command' => __DIR__ . '/..' . '/wp-cli/core-command/src/Core_Command.php', + 'Core_Command_Namespace' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/Core_Command_Namespace.php', + 'Core_Language_Command' => __DIR__ . '/..' . '/wp-cli/language-command/src/Core_Language_Command.php', + 'Cron_Command' => __DIR__ . '/..' . '/wp-cli/cron-command/src/Cron_Command.php', + 'Cron_Event_Command' => __DIR__ . '/..' . '/wp-cli/cron-command/src/Cron_Event_Command.php', + 'Cron_Schedule_Command' => __DIR__ . '/..' . '/wp-cli/cron-command/src/Cron_Schedule_Command.php', + 'DB_Command' => __DIR__ . '/..' . '/wp-cli/db-command/src/DB_Command.php', + 'EvalFile_Command' => __DIR__ . '/..' . '/wp-cli/eval-command/src/EvalFile_Command.php', + 'Eval_Command' => __DIR__ . '/..' . '/wp-cli/eval-command/src/Eval_Command.php', + 'Export_Command' => __DIR__ . '/..' . '/wp-cli/export-command/src/Export_Command.php', + 'Import_Command' => __DIR__ . '/..' . '/wp-cli/import-command/src/Import_Command.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'Language_Namespace' => __DIR__ . '/..' . '/wp-cli/language-command/src/Language_Namespace.php', + 'Media_Command' => __DIR__ . '/..' . '/wp-cli/media-command/src/Media_Command.php', + 'Menu_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Menu_Command.php', + 'Menu_Item_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Menu_Item_Command.php', + 'Menu_Location_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Menu_Location_Command.php', + 'Network_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Network_Meta_Command.php', + 'Network_Namespace' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Network_Namespace.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'Option_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Option_Command.php', + 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', + 'PHPCSUtils\\BackCompat\\BCFile' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', + 'PHPCSUtils\\BackCompat\\BCTokens' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', + 'PHPCSUtils\\BackCompat\\Helper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', + 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', + 'PHPCSUtils\\Exceptions\\LogicException' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/LogicException.php', + 'PHPCSUtils\\Exceptions\\MissingArgumentError' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/MissingArgumentError.php', + 'PHPCSUtils\\Exceptions\\OutOfBoundsStackPtr' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/OutOfBoundsStackPtr.php', + 'PHPCSUtils\\Exceptions\\RuntimeException' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/RuntimeException.php', + 'PHPCSUtils\\Exceptions\\TestFileNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', + 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', + 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', + 'PHPCSUtils\\Exceptions\\TypeError' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TypeError.php', + 'PHPCSUtils\\Exceptions\\UnexpectedTokenType' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/UnexpectedTokenType.php', + 'PHPCSUtils\\Exceptions\\ValueError' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/ValueError.php', + 'PHPCSUtils\\Fixers\\SpacesFixer' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', + 'PHPCSUtils\\Internal\\Cache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrList' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', + 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', + 'PHPCSUtils\\Internal\\NoFileCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', + 'PHPCSUtils\\Internal\\StableCollections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', + 'PHPCSUtils\\TestUtils\\ConfigDouble' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/ConfigDouble.php', + 'PHPCSUtils\\TestUtils\\RulesetDouble' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/RulesetDouble.php', + 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', + 'PHPCSUtils\\Tokens\\Collections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', + 'PHPCSUtils\\Tokens\\TokenHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', + 'PHPCSUtils\\Utils\\Arrays' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', + 'PHPCSUtils\\Utils\\Conditions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', + 'PHPCSUtils\\Utils\\Constants' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Constants.php', + 'PHPCSUtils\\Utils\\Context' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', + 'PHPCSUtils\\Utils\\ControlStructures' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', + 'PHPCSUtils\\Utils\\FileInfo' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FileInfo.php', + 'PHPCSUtils\\Utils\\FilePath' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FilePath.php', + 'PHPCSUtils\\Utils\\FunctionDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', + 'PHPCSUtils\\Utils\\GetTokensAsString' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', + 'PHPCSUtils\\Utils\\Lists' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', + 'PHPCSUtils\\Utils\\MessageHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', + 'PHPCSUtils\\Utils\\Namespaces' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', + 'PHPCSUtils\\Utils\\NamingConventions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', + 'PHPCSUtils\\Utils\\Numbers' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', + 'PHPCSUtils\\Utils\\ObjectDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', + 'PHPCSUtils\\Utils\\Operators' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', + 'PHPCSUtils\\Utils\\Orthography' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', + 'PHPCSUtils\\Utils\\Parentheses' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', + 'PHPCSUtils\\Utils\\PassedParameters' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', + 'PHPCSUtils\\Utils\\Scopes' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', + 'PHPCSUtils\\Utils\\TextStrings' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', + 'PHPCSUtils\\Utils\\TypeString' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TypeString.php', + 'PHPCSUtils\\Utils\\UseStatements' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', + 'PHPCSUtils\\Utils\\Variables' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', + 'Package_Command' => __DIR__ . '/..' . '/wp-cli/package-command/src/Package_Command.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Plugin_AutoUpdates_Command' => __DIR__ . '/..' . '/wp-cli/extension-command/src/Plugin_AutoUpdates_Command.php', + 'Plugin_Command' => __DIR__ . '/..' . '/wp-cli/extension-command/src/Plugin_Command.php', + 'Plugin_Command_Namespace' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/Plugin_Command_Namespace.php', + 'Plugin_Language_Command' => __DIR__ . '/..' . '/wp-cli/language-command/src/Plugin_Language_Command.php', + 'Post_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Post_Command.php', + 'Post_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Post_Meta_Command.php', + 'Post_Term_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Post_Term_Command.php', + 'Post_Type_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Post_Type_Command.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Rewrite_Command' => __DIR__ . '/..' . '/wp-cli/rewrite-command/src/Rewrite_Command.php', + 'Role_Command' => __DIR__ . '/..' . '/wp-cli/role-command/src/Role_Command.php', + 'Scaffold_Command' => __DIR__ . '/..' . '/wp-cli/scaffold-command/src/Scaffold_Command.php', + 'Search_Replace_Command' => __DIR__ . '/..' . '/wp-cli/search-replace-command/src/Search_Replace_Command.php', + 'Server_Command' => __DIR__ . '/..' . '/wp-cli/server-command/src/Server_Command.php', + 'Shell_Command' => __DIR__ . '/..' . '/wp-cli/shell-command/src/Shell_Command.php', + 'Sidebar_Command' => __DIR__ . '/..' . '/wp-cli/widget-command/src/Sidebar_Command.php', + 'Signup_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Signup_Command.php', + 'Site_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Site_Command.php', + 'Site_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Site_Meta_Command.php', + 'Site_Option_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Site_Option_Command.php', + 'Site_Switch_Language_Command' => __DIR__ . '/..' . '/wp-cli/language-command/src/Site_Switch_Language_Command.php', + 'Stringable' => __DIR__ . '/..' . '/marc-mabe/php-enum/stubs/Stringable.php', + 'Super_Admin_Command' => __DIR__ . '/..' . '/wp-cli/super-admin-command/src/Super_Admin_Command.php', + 'Taxonomy_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Taxonomy_Command.php', + 'Term_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Term_Command.php', + 'Term_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/Term_Meta_Command.php', + 'Theme_AutoUpdates_Command' => __DIR__ . '/..' . '/wp-cli/extension-command/src/Theme_AutoUpdates_Command.php', + 'Theme_Command' => __DIR__ . '/..' . '/wp-cli/extension-command/src/Theme_Command.php', + 'Theme_Language_Command' => __DIR__ . '/..' . '/wp-cli/language-command/src/Theme_Language_Command.php', + 'Theme_Mod_Command' => __DIR__ . '/..' . '/wp-cli/extension-command/src/Theme_Mod_Command.php', + 'Transient_Command' => __DIR__ . '/..' . '/wp-cli/cache-command/src/Transient_Command.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'User_Application_Password_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/User_Application_Password_Command.php', + 'User_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/User_Command.php', + 'User_Meta_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/User_Meta_Command.php', + 'User_Session_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/User_Session_Command.php', + 'User_Term_Command' => __DIR__ . '/..' . '/wp-cli/entity-command/src/User_Term_Command.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'WP_CLI' => __DIR__ . '/..' . '/wp-cli/wp-cli/php/class-wp-cli.php', + 'WP_CLI\\CommandWithDBObject' => __DIR__ . '/..' . '/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php', + 'WP_CLI\\CommandWithMeta' => __DIR__ . '/..' . '/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php', + 'WP_CLI\\CommandWithTerms' => __DIR__ . '/..' . '/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.php', + 'WP_CLI\\CommandWithTranslation' => __DIR__ . '/..' . '/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.php', + 'WP_CLI\\CommandWithUpgrade' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.php', + 'WP_CLI\\Core\\CoreUpgrader' => __DIR__ . '/..' . '/wp-cli/core-command/src/WP_CLI/Core/CoreUpgrader.php', + 'WP_CLI\\Core\\NonDestructiveCoreUpgrader' => __DIR__ . '/..' . '/wp-cli/core-command/src/WP_CLI/Core/NonDestructiveCoreUpgrader.php', + 'WP_CLI\\DestructivePluginUpgrader' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php', + 'WP_CLI\\DestructiveThemeUpgrader' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php', + 'WP_CLI\\Fetchers\\Plugin' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php', + 'WP_CLI\\Fetchers\\Theme' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php', + 'WP_CLI\\Fetchers\\UnfilteredPlugin' => __DIR__ . '/..' . '/wp-cli/checksum-command/src/WP_CLI/Fetchers/UnfilteredPlugin.php', + 'WP_CLI\\JsonManipulator' => __DIR__ . '/..' . '/wp-cli/package-command/src/WP_CLI/JsonManipulator.php', + 'WP_CLI\\LanguagePackUpgrader' => __DIR__ . '/..' . '/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php', + 'WP_CLI\\Package\\Compat\\Min_Composer_1_10\\NullIOMethodsTrait' => __DIR__ . '/..' . '/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_1_10/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\Compat\\Min_Composer_2_3\\NullIOMethodsTrait' => __DIR__ . '/..' . '/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_2_3/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\Compat\\NullIOMethodsTrait' => __DIR__ . '/..' . '/wp-cli/package-command/src/WP_CLI/Package/Compat/NullIOMethodsTrait.php', + 'WP_CLI\\Package\\ComposerIO' => __DIR__ . '/..' . '/wp-cli/package-command/src/WP_CLI/Package/ComposerIO.php', + 'WP_CLI\\ParsePluginNameInput' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/ParsePluginNameInput.php', + 'WP_CLI\\ParseThemeNameInput' => __DIR__ . '/..' . '/wp-cli/extension-command/src/WP_CLI/ParseThemeNameInput.php', + 'WP_CLI\\SearchReplacer' => __DIR__ . '/..' . '/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.php', + 'WP_CLI\\Shell\\REPL' => __DIR__ . '/..' . '/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php', + 'WP_CLI_Command' => __DIR__ . '/..' . '/wp-cli/wp-cli/php/class-wp-cli-command.php', + 'WP_Export_Base_Writer' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Base_Writer.php', + 'WP_Export_Exception' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Exception.php', + 'WP_Export_File_Writer' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_File_Writer.php', + 'WP_Export_Oxymel' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Oxymel.php', + 'WP_Export_Query' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Query.php', + 'WP_Export_Returner' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Returner.php', + 'WP_Export_Split_Files_Writer' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php', + 'WP_Export_Term_Exception' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_Term_Exception.php', + 'WP_Export_WXR_Formatter' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_WXR_Formatter.php', + 'WP_Export_XML_Over_HTTP' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.php', + 'WP_Iterator_Exception' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Iterator_Exception.php', + 'WP_Map_Iterator' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Map_Iterator.php', + 'WP_Post_IDs_Iterator' => __DIR__ . '/..' . '/wp-cli/export-command/src/WP_Post_IDs_Iterator.php', + 'Widget_Command' => __DIR__ . '/..' . '/wp-cli/widget-command/src/Widget_Command.php', ); public static function getInitializer(ClassLoader $loader) @@ -29,6 +474,7 @@ public static function getInitializer(ClassLoader $loader) return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e::$prefixesPsr0; $loader->classMap = ComposerStaticInit455764cd9b78270c5af3fb1bf25af96e::$classMap; }, null, ClassLoader::class); diff --git a/vendor/composer/ca-bundle/LICENSE b/vendor/composer/ca-bundle/LICENSE new file mode 100644 index 0000000..c5b5220 --- /dev/null +++ b/vendor/composer/ca-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2016 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/ca-bundle/README.md b/vendor/composer/ca-bundle/README.md new file mode 100644 index 0000000..d8205ec --- /dev/null +++ b/vendor/composer/ca-bundle/README.md @@ -0,0 +1,85 @@ +composer/ca-bundle +================== + +Small utility library that lets you find a path to the system CA bundle, +and includes a fallback to the Mozilla CA bundle. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/ca-bundle +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Basic usage +----------- + +### `Composer\CaBundle\CaBundle` + +- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback +- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file +- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use +- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse() +- `CaBundle::reset()`: Resets the static caches + + +#### To use with curl + +```php +$curl = curl_init("https://example.org/"); + +$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); +if (is_dir($caPathOrFile)) { + curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile); +} else { + curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile); +} + +$result = curl_exec($curl); +``` + +#### To use with php streams + +```php +$opts = array( + 'http' => array( + 'method' => "GET" + ) +); + +$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(); +if (is_dir($caPathOrFile)) { + $opts['ssl']['capath'] = $caPathOrFile; +} else { + $opts['ssl']['cafile'] = $caPathOrFile; +} + +$context = stream_context_create($opts); +$result = file_get_contents('https://example.com', false, $context); +``` + +#### To use with Guzzle + +```php +$client = new \GuzzleHttp\Client([ + \GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath() +]); +``` + +License +------- + +composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/ca-bundle/composer.json b/vendor/composer/ca-bundle/composer.json new file mode 100644 index 0000000..c2ce2bb --- /dev/null +++ b/vendor/composer/ca-bundle/composer.json @@ -0,0 +1,54 @@ +{ + "name": "composer/ca-bundle", + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "type": "library", + "license": "MIT", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8 || ^9", + "phpstan/phpstan": "^1.10", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\CaBundle\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "@php phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/ca-bundle/res/cacert.pem b/vendor/composer/ca-bundle/res/cacert.pem new file mode 100644 index 0000000..4acd8e5 --- /dev/null +++ b/vendor/composer/ca-bundle/res/cacert.pem @@ -0,0 +1,3480 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue May 20 03:12:02 2025 GMT +## +## Find updated versions here: https://curl.se/docs/caextract.html +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 8944ec6b572b577daee4fc681a425881f841ec2660e4cb5f0eee727f84620697 +## + + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- + +BJCA Global Root CA1 +==================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG +EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK +Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG +A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD +DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm +CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS +sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn +P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW +yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj +eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn +MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b +OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh +GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK +H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB +AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ +dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 +60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh +TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW +4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp +GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx +4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps +3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S +SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= +-----END CERTIFICATE----- + +BJCA Global Root CA2 +==================== +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD +TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg +R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE +BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC +SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl +SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK +/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI +1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 +W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g +UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx +eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot +6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 +Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW +pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M +MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE +SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 +Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 +3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft +nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 +uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq +ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs +vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c +Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif +BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 +lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo +KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH ++VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 +5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM +3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck +jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf +Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W +NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ +o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ +oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc +1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM +6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V +rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx +7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC +e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W +Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp +M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf +hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr +eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE +VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t +Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx +cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF +1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa +MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd +gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O +HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm +YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr +dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ +iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN +lM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +Telekom Security TLS ECC Root 2020 +================================== +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl +a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz +NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg +R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG +SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 +2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC +MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ +Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU +ga/sf+Rn27iQ7t0l +-----END CERTIFICATE----- + +Telekom Security TLS RSA Root 2023 +================================== +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU +ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy +NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp +dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC +KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP +GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx +UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo +l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 +FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v +zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg +rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML +KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S +WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ +sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp +kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy +/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 +mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz +aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa +oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 +wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE +HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 +o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +FIRMAPROFESIONAL CA ROOT-A WEB +============================== +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF +UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4 +MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2 +WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h +bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM +IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6 +iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg +st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD +Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB +/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL +cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ +pYXFuXqUPoeovQA= +-----END CERTIFICATE----- + +TWCA CYBER Root CA +================== +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s +Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh +V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT +o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT +Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK +/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH +IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM +fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF +2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR +wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83 +QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN +c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x +X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR +IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq +/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R +FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe +Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv +It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl +IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +SecureSign Root CA12 +==================== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3 +emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc +J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl +fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF +EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef +NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC +AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi +LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce +mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS +vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga +aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +SecureSign Root CA14 +==================== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh +1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn +bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb +1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa +/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE +kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx +jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz +ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0 +dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY +AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq +YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA +ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds +Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG +FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q +nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/ +OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa +OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO +pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN +eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S +-----END CERTIFICATE----- + +SecureSign Root CA15 +==================== +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE +BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1 +cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV +BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj +dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G +dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB +2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J +fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ +SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT +cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z +sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ +WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 +++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL +QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv +x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV +MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX +/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 +CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr +d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv +U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 +nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij +YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff +/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP +pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB +WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ +5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt +n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +D-TRUST EV Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 +sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ +MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf +vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM +lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 +YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 +7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo +nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa +QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF +AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS +Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 +QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD +UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V +4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo +dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi +TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi +S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ +HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L ++KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- diff --git a/vendor/composer/ca-bundle/src/CaBundle.php b/vendor/composer/ca-bundle/src/CaBundle.php new file mode 100644 index 0000000..2d6b48c --- /dev/null +++ b/vendor/composer/ca-bundle/src/CaBundle.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\CaBundle; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Process\PhpProcess; + +/** + * @author Chris Smith + * @author Jordi Boggiano + */ +class CaBundle +{ + /** @var string|null */ + private static $caPath; + /** @var array */ + private static $caFileValidity = array(); + + /** + * Returns the system CA bundle path, or a path to the bundled one + * + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @param LoggerInterface $logger optional logger for information about which CA files were loaded + * @return string path to a CA bundle file or directory + */ + public static function getSystemCaRootBundlePath(?LoggerInterface $logger = null) + { + if (self::$caPath !== null) { + return self::$caPath; + } + $caBundlePaths = array(); + + // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE'); + + // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR'); + + $caBundlePaths[] = ini_get('openssl.cafile'); + $caBundlePaths[] = ini_get('openssl.capath'); + + $otherLocations = array( + '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) + '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) + '/usr/ssl/certs/ca-bundle.crt', // Cygwin + '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package + '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) + '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? + '/etc/ssl/cert.pem', // OpenBSD + '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package + '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package + '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package + '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package + '/etc/pki/tls/certs', + '/etc/ssl/certs', // FreeBSD + ); + + $caBundlePaths = array_merge($caBundlePaths, $otherLocations); + + foreach ($caBundlePaths as $caBundle) { + if ($caBundle && self::caFileUsable($caBundle, $logger)) { + return self::$caPath = $caBundle; + } + + if ($caBundle && self::caDirUsable($caBundle, $logger)) { + return self::$caPath = $caBundle; + } + } + + return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort + } + + /** + * Returns the path to the bundled CA file + * + * In case you don't want to trust the user or the system, you can use this directly + * + * @return string path to a CA bundle file + */ + public static function getBundledCaBundlePath() + { + $caBundleFile = __DIR__.'/../res/cacert.pem'; + + // cURL does not understand 'phar://' paths + // see https://github.com/composer/ca-bundle/issues/10 + if (0 === strpos($caBundleFile, 'phar://')) { + $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-'); + if (false === $tempCaBundleFile) { + throw new \RuntimeException('Could not create a temporary file to store the bundled CA file'); + } + + file_put_contents( + $tempCaBundleFile, + file_get_contents($caBundleFile) + ); + + register_shutdown_function(function() use ($tempCaBundleFile) { + @unlink($tempCaBundleFile); + }); + + $caBundleFile = $tempCaBundleFile; + } + + return $caBundleFile; + } + + /** + * Validates a CA file using opensl_x509_parse only if it is safe to use + * + * @param string $filename + * @param LoggerInterface $logger optional logger for information about which CA files were loaded + * + * @return bool + */ + public static function validateCaFile($filename, ?LoggerInterface $logger = null) + { + static $warned = false; + + if (isset(self::$caFileValidity[$filename])) { + return self::$caFileValidity[$filename]; + } + + $contents = file_get_contents($filename); + + if (is_string($contents) && strlen($contents) > 0) { + $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents); + if (null === $contents) { + // regex extraction failed + $isValid = false; + } else { + $isValid = (bool) openssl_x509_parse($contents); + } + } else { + $isValid = false; + } + + if ($logger) { + $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid')); + } + + return self::$caFileValidity[$filename] = $isValid; + } + + /** + * Test if it is safe to use the PHP function openssl_x509_parse(). + * + * This checks if OpenSSL extensions is vulnerable to remote code execution + * via the exploit documented as CVE-2013-6420. + * + * @return bool + */ + public static function isOpensslParseSafe() + { + return true; + } + + /** + * Resets the static caches + * @return void + */ + public static function reset() + { + self::$caFileValidity = array(); + self::$caPath = null; + } + + /** + * @param string $name + * @return string|false + */ + private static function getEnvVariable($name) + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return false; + } + + /** + * @param string|false $certFile + * @param LoggerInterface|null $logger + * @return bool + */ + private static function caFileUsable($certFile, ?LoggerInterface $logger = null) + { + return $certFile + && self::isFile($certFile, $logger) + && self::isReadable($certFile, $logger) + && self::validateCaFile($certFile, $logger); + } + + /** + * @param string|false $certDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function caDirUsable($certDir, ?LoggerInterface $logger = null) + { + return $certDir + && self::isDir($certDir, $logger) + && self::isReadable($certDir, $logger) + && self::glob($certDir . '/*', $logger); + } + + /** + * @param string $certFile + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isFile($certFile, ?LoggerInterface $logger = null) + { + $isFile = @is_file($certFile); + if (!$isFile && $logger) { + $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile)); + } + + return $isFile; + } + + /** + * @param string $certDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isDir($certDir, ?LoggerInterface $logger = null) + { + $isDir = @is_dir($certDir); + if (!$isDir && $logger) { + $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir)); + } + + return $isDir; + } + + /** + * @param string $certFileOrDir + * @param LoggerInterface|null $logger + * @return bool + */ + private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null) + { + $isReadable = @is_readable($certFileOrDir); + if (!$isReadable && $logger) { + $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir)); + } + + return $isReadable; + } + + /** + * @param string $pattern + * @param LoggerInterface|null $logger + * @return bool + */ + private static function glob($pattern, ?LoggerInterface $logger = null) + { + $certs = glob($pattern); + if ($certs === false) { + if ($logger) { + $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern)); + } + return false; + } + + if (count($certs) === 0) { + if ($logger) { + $logger->debug(sprintf("No CA files found for pattern: %s", $pattern)); + } + return false; + } + + return true; + } +} diff --git a/vendor/composer/class-map-generator/LICENSE b/vendor/composer/class-map-generator/LICENSE new file mode 100644 index 0000000..43bd2de --- /dev/null +++ b/vendor/composer/class-map-generator/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2022 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/class-map-generator/README.md b/vendor/composer/class-map-generator/README.md new file mode 100644 index 0000000..ff27e2e --- /dev/null +++ b/vendor/composer/class-map-generator/README.md @@ -0,0 +1,66 @@ +composer/class-map-generator +============================ + +Utilities to generate class maps and scan PHP code. + +[![Continuous Integration](https://github.com/composer/class-map-generator/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/class-map-generator/actions) + + +Installation +------------ + +Install the latest version with: + +```bash +composer require composer/class-map-generator +``` + + +Requirements +------------ + +* PHP 7.2 is required. + + +Basic usage +----------- + +If all you want is to scan a directory and extract a classmap with all +classes/interfaces/traits/enums mapped to their paths, you can simply use: + + +```php +use Composer\ClassMapGenerator\ClassMapGenerator; + +$map = ClassMapGenerator::createMap('path/to/scan'); +foreach ($map as $symbol => $path) { + // do your thing +} +``` + +For more advanced usage, you can instantiate a generator object and call scanPaths one or more time +then call getClassMap to get a ClassMap object containing the resulting map + eventual warnings. + +```php +use Composer\ClassMapGenerator\ClassMapGenerator; + +$generator = new ClassMapGenerator; +$generator->scanPaths('path/to/scan'); +$generator->scanPaths('path/to/scan2'); + +$classMap = $generator->getClassMap(); +$classMap->sort(); // optionally sort classes alphabetically +foreach ($classMap->getMap() as $symbol => $path) { + // do your thing +} + +foreach ($classMap->getAmbiguousClasses() as $symbol => $paths) { + // warn user about ambiguous class resolution +} +``` + + +License +------- + +composer/class-map-generator is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/class-map-generator/composer.json b/vendor/composer/class-map-generator/composer.json new file mode 100644 index 0000000..496912c --- /dev/null +++ b/vendor/composer/class-map-generator/composer.json @@ -0,0 +1,48 @@ +{ + "name": "composer/class-map-generator", + "description": "Utilities to scan PHP code and generate class maps.", + "type": "library", + "license": "MIT", + "keywords": [ + "classmap" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7", + "composer/pcre": "^2.1 || ^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "symfony/filesystem": "^5.4 || ^6" + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "@php phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/class-map-generator/src/ClassMap.php b/vendor/composer/class-map-generator/src/ClassMap.php new file mode 100644 index 0000000..797efc6 --- /dev/null +++ b/vendor/composer/class-map-generator/src/ClassMap.php @@ -0,0 +1,191 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class ClassMap implements \Countable +{ + /** + * @var array + */ + public $map = []; + + /** + * @var array> + */ + private $ambiguousClasses = []; + + /** + * @var array> + */ + private $psrViolations = []; + + /** + * Returns the class map, which is a list of paths indexed by class name + * + * @return array + */ + public function getMap(): array + { + return $this->map; + } + + /** + * Returns warning strings containing details about PSR-0/4 violations that were detected + * + * Violations are for ex a class which is in the wrong file/directory and thus should not be + * found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files. + * + * This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type + * always accepts every class as it finds it. + * + * @return string[] + */ + public function getPsrViolations(): array + { + if (\count($this->psrViolations) === 0) { + return []; + } + + return array_map(static function (array $violation): string { + return $violation['warning']; + }, array_merge(...array_values($this->psrViolations))); + } + + /** + * A map of class names to their list of ambiguous paths + * + * This occurs when the same class can be found in several files + * + * To get the path the class is being mapped to, call getClassPath + * + * By default, paths that contain test(s), fixture(s), example(s) or stub(s) are ignored + * as those are typically not problematic when they're dummy classes in the tests folder. + * If you want to get these back as well you can pass false to $duplicatesFilter. Or + * you can pass your own pattern to exclude if you need to change the default. + * + * @param non-empty-string|false $duplicatesFilter + * + * @return array> + */ + public function getAmbiguousClasses($duplicatesFilter = '{/(test|fixture|example|stub)s?/}i'): array + { + if (false === $duplicatesFilter) { + return $this->ambiguousClasses; + } + + if (true === $duplicatesFilter) { + throw new \InvalidArgumentException('$duplicatesFilter should be false or a string with a valid regex, got true.'); + } + + $ambiguousClasses = []; + foreach ($this->ambiguousClasses as $class => $paths) { + $paths = array_filter($paths, function ($path) use ($duplicatesFilter) { + return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/')); + }); + if (\count($paths) > 0) { + $ambiguousClasses[$class] = array_values($paths); + } + } + + return $ambiguousClasses; + } + + /** + * Sorts the class map alphabetically by class names + */ + public function sort(): void + { + ksort($this->map); + } + + /** + * @param class-string $className + * @param non-empty-string $path + */ + public function addClass(string $className, string $path): void + { + unset($this->psrViolations[strtr($path, '\\', '/')]); + + $this->map[$className] = $path; + } + + /** + * @param class-string $className + * @return non-empty-string + */ + public function getClassPath(string $className): string + { + if (!isset($this->map[$className])) { + throw new \OutOfBoundsException('Class '.$className.' is not present in the map'); + } + + return $this->map[$className]; + } + + /** + * @param class-string $className + */ + public function hasClass(string $className): bool + { + return isset($this->map[$className]); + } + + public function addPsrViolation(string $warning, string $className, string $path): void + { + $path = rtrim(strtr($path, '\\', '/'), '/'); + + $this->psrViolations[$path][] = ['warning' => $warning, 'className' => $className]; + } + + public function clearPsrViolationsByPath(string $pathPrefix): void + { + $pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/'); + + foreach ($this->psrViolations as $path => $violations) { + if ($path === $pathPrefix || 0 === \strpos($path, $pathPrefix.'/')) { + unset($this->psrViolations[$path]); + } + } + } + + /** + * @param class-string $className + * @param non-empty-string $path + */ + public function addAmbiguousClass(string $className, string $path): void + { + $this->ambiguousClasses[$className][] = $path; + } + + public function count(): int + { + return \count($this->map); + } + + /** + * Get the raw psr violations + * + * This is a map of filepath to an associative array of the warning string + * and the offending class name. + * @return array> + */ + public function getRawPsrViolations(): array + { + return $this->psrViolations; + } +} diff --git a/vendor/composer/class-map-generator/src/ClassMapGenerator.php b/vendor/composer/class-map-generator/src/ClassMapGenerator.php new file mode 100644 index 0000000..2bfa1ed --- /dev/null +++ b/vendor/composer/class-map-generator/src/ClassMapGenerator.php @@ -0,0 +1,351 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file was initially based on a version from the Symfony package. + * + * (c) Fabien Potencier + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; +use Symfony\Component\Finder\Finder; +use Composer\IO\IOInterface; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + * @author Jordi Boggiano + */ +class ClassMapGenerator +{ + /** + * @var list + */ + private $extensions; + + /** + * @var FileList|null + */ + private $scannedFiles = null; + + /** + * @var ClassMap + */ + private $classMap; + + /** + * @var non-empty-string + */ + private $streamWrappersRegex; + + /** + * @param list $extensions File extensions to scan for classes in the given paths + */ + public function __construct(array $extensions = ['php', 'inc']) + { + $this->extensions = $extensions; + $this->classMap = new ClassMap; + $this->streamWrappersRegex = sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers()))); + } + + /** + * When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice + * + * You can provide your own FileList instance or use the default one if you pass no argument + * + * @return $this + */ + public function avoidDuplicateScans(?FileList $scannedFiles = null): self + { + $this->scannedFiles = $scannedFiles ?? new FileList; + + return $this; + } + + /** + * Iterate over all files in the given directory searching for classes + * + * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) + * @return array A class map array + * + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public static function createMap($path): array + { + $generator = new self(); + + $generator->scanPaths($path); + + return $generator->getClassMap()->getMap(); + } + + public function getClassMap(): ClassMap + { + return $this->classMap; + } + + /** + * Iterate over all files in the given directory searching for classes + * + * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance) + * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap + * @param 'classmap'|'psr-0'|'psr-4' $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap + * @param string|null $namespace Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading + * @param array $excludedDirs Optional dirs to exclude from search relative to $path + * + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void + { + if (!in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) { + throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"'); + } + + if ('classmap' !== $autoloadType) { + if (!is_string($path)) { + throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type'); + } + if (!is_string($namespace)) { + throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type'); + } + $basePath = $path; + } + + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path) || strpos($path, '*') !== false) { + $path = Finder::create() + ->files() + ->followLinks() + ->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/') + ->in($path) + ->exclude($excludedDirs); + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder' + ); + } + } + + $cwd = realpath(self::getCwd()); + + foreach ($path as $file) { + $filePath = $file->getPathname(); + if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) { + continue; + } + + $isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath); + if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) { + $filePath = $cwd . '/' . $filePath; + $filePath = self::normalizePath($filePath); + } else { + $filePath = Preg::replace('{(?getPathname()); + } + + $realPath = $isStreamWrapperPath + ? $filePath + : realpath($filePath); + + // fallback just in case but this really should not happen + if (false === $realPath) { + throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false'); + } + + // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings + // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already + if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) { + continue; + } + + // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved + if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) { + continue; + } + // check non-realpath of file for directories symlink in project dir + if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) { + continue; + } + + $classes = PhpFileParser::findClasses($filePath); + if ('classmap' !== $autoloadType && isset($namespace)) { + $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath); + + // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later + if (\count($classes) > 0 && $this->scannedFiles !== null) { + $this->scannedFiles->add($realPath); + } + } elseif ($this->scannedFiles !== null) { + // classmap autoload rules always collect all classes so for these we definitely do not want to scan again + $this->scannedFiles->add($realPath); + } + + foreach ($classes as $class) { + if (!$this->classMap->hasClass($class)) { + $this->classMap->addClass($class, $filePath); + } elseif ($filePath !== $this->classMap->getClassPath($class)) { + $this->classMap->addAmbiguousClass($class, $filePath); + } + } + } + } + + /** + * Remove classes which could not have been loaded by namespace autoloaders + * + * @param array $classes found classes in given file + * @param string $filePath current file + * @param string $baseNamespace prefix of given autoload mapping + * @param 'psr-0'|'psr-4' $namespaceType + * @param string $basePath root directory of given autoload mapping + * @return array valid classes + * + * @throws \InvalidArgumentException When namespaceType is neither psr-0 nor psr-4 + */ + private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array + { + $validClasses = []; + $rejectedClasses = []; + + $realSubPath = substr($filePath, strlen($basePath) + 1); + $dotPosition = strrpos($realSubPath, '.'); + $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition); + + foreach ($classes as $class) { + // transform class name to file path and validate + if ('psr-0' === $namespaceType) { + $namespaceLength = strrpos($class, '\\'); + if (false !== $namespaceLength) { + $namespace = substr($class, 0, $namespaceLength + 1); + $className = substr($class, $namespaceLength + 1); + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) + . str_replace('_', DIRECTORY_SEPARATOR, $className); + } else { + $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class); + } + } elseif ('psr-4' === $namespaceType) { + $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class; + $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace); + } else { + throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"'); + } + if ($subPath === $realSubPath) { + $validClasses[] = $class; + } else { + $rejectedClasses[] = $class; + } + } + // warn only if no valid classes, else silently skip invalid + if (\count($validClasses) === 0) { + $cwd = realpath(self::getCwd()); + if ($cwd === false) { + $cwd = self::getCwd(); + } + $cwd = self::normalizePath($cwd); + $shortPath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($filePath), 1); + $shortBasePath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($basePath), 1); + + foreach ($rejectedClasses as $class) { + $this->classMap->addPsrViolation("Class $class located in $shortPath does not comply with $namespaceType autoloading standard (rule: $baseNamespace => $shortBasePath). Skipping.", $class, $filePath); + } + + return []; + } + + return $validClasses; + } + + /** + * Checks if the given path is absolute + * + * @see Composer\Util\Filesystem::isAbsolutePath + * + * @param string $path + * @return bool + */ + private static function isAbsolutePath(string $path) + { + return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; + } + + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @see Composer\Util\Filesystem::normalizePath + * + * @param string $path Path to the file or directory + * @return string + */ + private static function normalizePath(string $path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = ''; + + // extract windows UNC paths e.g. \\foo\bar + if (strpos($path, '//') === 0 && \strlen($path) > 2) { + $absolute = '//'; + $path = substr($path, 2); + } + + // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: + if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, \strlen($prefix)); + } + + if (strpos($path, '/') === 0) { + $absolute = '/'; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { + array_pop($parts); + $up = !(\count($parts) === 0 || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + // ensure c: is normalized to C: + $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return strtoupper((string) $m[0]); }, $prefix); + + return $prefix.$absolute.implode('/', $parts); + } + + /** + * @see Composer\Util\Platform::getCwd + */ + private static function getCwd(): string + { + $cwd = getcwd(); + + if (false === $cwd) { + throw new \RuntimeException('Could not determine the current working directory'); + } + + return $cwd; + } +} diff --git a/vendor/composer/class-map-generator/src/FileList.php b/vendor/composer/class-map-generator/src/FileList.php new file mode 100644 index 0000000..f0bfcb7 --- /dev/null +++ b/vendor/composer/class-map-generator/src/FileList.php @@ -0,0 +1,42 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +/** + * Contains a list of files which were scanned to generate a classmap + * + * @author Jordi Boggiano + */ +class FileList +{ + /** + * @var array + */ + public $files = []; + + /** + * @param non-empty-string $path + */ + public function add(string $path): void + { + $this->files[$path] = true; + } + + /** + * @param non-empty-string $path + */ + public function contains(string $path): bool + { + return isset($this->files[$path]); + } +} diff --git a/vendor/composer/class-map-generator/src/PhpFileCleaner.php b/vendor/composer/class-map-generator/src/PhpFileCleaner.php new file mode 100644 index 0000000..b6222aa --- /dev/null +++ b/vendor/composer/class-map-generator/src/PhpFileCleaner.php @@ -0,0 +1,248 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + * @internal + */ +class PhpFileCleaner +{ + /** @var array */ + private static $typeConfig; + + /** @var non-empty-string */ + private static $restPattern; + + /** + * @readonly + * @var string + */ + private $contents; + + /** + * @readonly + * @var int + */ + private $len; + + /** + * @readonly + * @var int + */ + private $maxMatches; + + /** @var int */ + private $index = 0; + + /** + * @param string[] $types + */ + public static function setTypeConfig(array $types): void + { + foreach ($types as $type) { + self::$typeConfig[$type[0]] = array( + 'name' => $type, + 'length' => \strlen($type), + 'pattern' => '{.\b(?])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', + ); + } + + self::$restPattern = '{[^?"\'contents = $contents; + $this->len = \strlen($this->contents); + $this->maxMatches = $maxMatches; + } + + public function clean(): string + { + $clean = ''; + + while ($this->index < $this->len) { + $this->skipToPhp(); + $clean .= 'index < $this->len) { + $char = $this->contents[$this->index]; + if ($char === '?' && $this->peek('>')) { + $clean .= '?>'; + $this->index += 2; + continue 2; + } + + if ($char === '"') { + $this->skipString('"'); + $clean .= 'null'; + continue; + } + + if ($char === "'") { + $this->skipString("'"); + $clean .= 'null'; + continue; + } + + if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) { + $this->index += \strlen($match[0]); + $this->skipHeredoc($match[2]); + $clean .= 'null'; + continue; + } + + if ($char === '/') { + if ($this->peek('/')) { + $this->skipToNewline(); + continue; + } + if ($this->peek('*')) { + $this->skipComment(); + continue; + } + } + + if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) { + $type = self::$typeConfig[$char]; + if ( + \substr($this->contents, $this->index, $type['length']) === $type['name'] + && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1) + ) { + $clean .= $match[0]; + + return $clean; + } + } + + $this->index += 1; + if ($this->match(self::$restPattern, $match)) { + $clean .= $char . $match[0]; + $this->index += \strlen($match[0]); + } else { + $clean .= $char; + } + } + } + + return $clean; + } + + private function skipToPhp(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '<' && $this->peek('?')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipString(string $delimiter): void + { + $this->index += 1; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { + $this->index += 2; + continue; + } + if ($this->contents[$this->index] === $delimiter) { + $this->index += 1; + break; + } + $this->index += 1; + } + } + + private function skipComment(): void + { + $this->index += 2; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '*' && $this->peek('/')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipToNewline(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") { + return; + } + $this->index += 1; + } + } + + private function skipHeredoc(string $delimiter): void + { + $firstDelimiterChar = $delimiter[0]; + $delimiterLength = \strlen($delimiter); + $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A'; + + while ($this->index < $this->len) { + // check if we find the delimiter after some spaces/tabs + switch ($this->contents[$this->index]) { + case "\t": + case " ": + $this->index += 1; + continue 2; + case $firstDelimiterChar: + if ( + \substr($this->contents, $this->index, $delimiterLength) === $delimiter + && $this->match($delimiterPattern) + ) { + $this->index += $delimiterLength; + + return; + } + break; + } + + // skip the rest of the line + while ($this->index < $this->len) { + $this->skipToNewline(); + + // skip newlines + while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) { + $this->index += 1; + } + + break; + } + } + } + + private function peek(string $char): bool + { + return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; + } + + /** + * @param non-empty-string $regex + * @param null|array $match + * @param-out array $match + */ + private function match(string $regex, ?array &$match = null): bool + { + return Preg::isMatchStrictGroups($regex, $this->contents, $match, 0, $this->index); + } +} diff --git a/vendor/composer/class-map-generator/src/PhpFileParser.php b/vendor/composer/class-map-generator/src/PhpFileParser.php new file mode 100644 index 0000000..5a6875d --- /dev/null +++ b/vendor/composer/class-map-generator/src/PhpFileParser.php @@ -0,0 +1,159 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\ClassMapGenerator; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class PhpFileParser +{ + /** + * Extract the classes in the given file + * + * @param string $path The file to check + * @throws \RuntimeException + * @return list The found classes + */ + public static function findClasses(string $path): array + { + $extraTypes = self::getExtraTypes(); + + if (!function_exists('php_strip_whitespace')) { + throw new \RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.'); + } + // Use @ here instead of Silencer to actively suppress 'unhelpful' output + // @link https://github.com/composer/composer/pull/4886 + $contents = @php_strip_whitespace($path); + if ('' === $contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!self::isReadable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim((string) file_get_contents($path))) { + // The input file was really empty and thus contains no classes + return array(); + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + // return early if there is no chance of matching anything in this file + Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches); + if (0 === \count($matches)) { + return array(); + } + + $p = new PhpFileCleaner($contents, count($matches[0])); + $contents = $p->clean(); + unset($p); + + Preg::matchAll('{ + (?: + \b(?])(?Pclass|interface|trait'.$extraTypes.') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = array(); + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') { + $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', (string) $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + assert(is_string($name)); + // skip anon classes extending/implementing + if ($name === 'extends' || $name === 'implements') { + continue; + } + if ($name[0] === ':') { + // This is an XHP class, https://github.com/facebook/xhp + $name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1); + } elseif (strtolower((string) $matches['type'][$i]) === 'enum') { + // something like: + // enum Foo: int { HERP = '123'; } + // The regex above captures the colon, which isn't part of + // the class name. + // or: + // enum Foo:int { HERP = '123'; } + // The regex above captures the colon and type, which isn't part of + // the class name. + $colonPos = strrpos($name, ':'); + if (false !== $colonPos) { + $name = substr($name, 0, $colonPos); + } + } + /** @var class-string */ + $className = ltrim($namespace . $name, '\\'); + $classes[] = $className; + } + } + + return $classes; + } + + /** + * @return string + */ + private static function getExtraTypes(): string + { + static $extraTypes = null; + + if (null === $extraTypes) { + $extraTypes = ''; + if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) { + $extraTypes .= '|enum'; + } + + $extraTypesArray = array_filter(explode('|', $extraTypes), function (string $type) { + return $type !== ''; + }); + PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray)); + } + + return $extraTypes; + } + + /** + * Cross-platform safe version of is_readable() + * + * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts + * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + * + * @see Composer\Util\Filesystem::isReadable + * + * @param string $path + * @return bool + */ + private static function isReadable(string $path) + { + if (is_readable($path)) { + return true; + } + + if (is_file($path)) { + return false !== @file_get_contents($path, false, null, 0, 1); + } + + // assume false otherwise + return false; + } +} diff --git a/vendor/composer/composer/LICENSE b/vendor/composer/composer/LICENSE new file mode 100644 index 0000000..62ecfd8 --- /dev/null +++ b/vendor/composer/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/composer/composer/bin/compile b/vendor/composer/composer/bin/compile new file mode 100755 index 0000000..1e2e4be --- /dev/null +++ b/vendor/composer/composer/bin/compile @@ -0,0 +1,44 @@ +#!/usr/bin/env php +compile(); +} catch (\Exception $e) { + echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL; + exit(1); +} diff --git a/vendor/composer/composer/bin/composer b/vendor/composer/composer/bin/composer new file mode 100755 index 0000000..4f6d08f --- /dev/null +++ b/vendor/composer/composer/bin/composer @@ -0,0 +1,99 @@ +#!/usr/bin/env php +check(); +unset($xdebug); + +if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) { + echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL; + exit(1); +} +if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { + echo 'The iconv OR mbstring extension is required and both are missing.' + .PHP_EOL.'Install either of them or recompile php without --disable-iconv.' + .PHP_EOL.'Aborting.'.PHP_EOL; + exit(1); +} + +if (function_exists('ini_set')) { + // check if error logging is on, but to an empty destination - for the CLI SAPI, that means stderr + $logsToSapiDefault = ('' === ini_get('error_log') && (bool) ini_get('log_errors')); + // on the CLI SAPI, ensure errors are displayed on stderr, either via display_errors or via error_log + if (PHP_SAPI === 'cli') { + @ini_set('display_errors', $logsToSapiDefault ? '0' : 'stderr'); + } + + // Set user defined memory limit + if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) { + @ini_set('memory_limit', $memoryLimit); + } else { + $memoryInBytes = function ($value) { + $unit = strtolower(substr($value, -1, 1)); + $value = (int) $value; + switch($unit) { + case 'g': + $value *= 1024; + // no break (cumulative multiplier) + case 'm': + $value *= 1024; + // no break (cumulative multiplier) + case 'k': + $value *= 1024; + } + + return $value; + }; + + $memoryLimit = trim(ini_get('memory_limit')); + // Increase memory_limit if it is lower than 1.5GB + if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { + @ini_set('memory_limit', '1536M'); + } + unset($memoryInBytes); + } + unset($memoryLimit); +} + +// Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER +// see https://github.com/php/php-src/issues/7896 +if (PHP_VERSION_ID >= 70113 && (PHP_VERSION_ID < 80016 || (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103)) && Platform::isWindows()) { + foreach ($_SERVER as $serverVar => $serverVal) { + if (($serverVal = getenv($serverVar)) !== false) { + $_SERVER[$serverVar] = $serverVal; + } + } +} + +Platform::putEnv('COMPOSER_BINARY', realpath($_SERVER['argv'][0])); + +ErrorHandler::register(); + +// run the command application +$application = new Application(); +$application->run(); diff --git a/vendor/composer/composer/composer.json b/vendor/composer/composer/composer.json new file mode 100644 index 0000000..2d6b132 --- /dev/null +++ b/vendor/composer/composer/composer.json @@ -0,0 +1,110 @@ +{ + "name": "composer/composer", + "type": "library", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "keywords": [ + "package", + "dependency", + "autoload" + ], + "homepage": "https://getcomposer.org/", + "license": "MIT", + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^6.3.1", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3", + "react/promise": "^2.11 || ^3.2", + "composer/pcre": "^2.2 || ^3.2", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "seld/signal-handler": "^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1", + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "config": { + "platform": { + "php": "7.2.5" + }, + "platform-check": false + }, + "extra": { + "branch-alias": { + "dev-main": "2.8-dev" + }, + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Test\\": "tests/Composer/Test/" + }, + "exclude-from-classmap": [ + "tests/Composer/Test/Fixtures/", + "tests/Composer/Test/Autoload/Fixtures", + "tests/Composer/Test/Autoload/MinimumVersionSupport", + "tests/Composer/Test/Plugin/Fixtures" + ] + }, + "bin": [ + "bin/composer" + ], + "scripts": { + "compile": "@php -dphar.readonly=0 bin/compile", + "test": "@php simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse --configuration=phpstan/config.neon" + }, + "scripts-descriptions": { + "compile": "Compile composer.phar", + "test": "Run all tests", + "phpstan": "Runs PHPStan" + }, + "support": { + "issues": "https://github.com/composer/composer/issues", + "irc": "ircs://irc.libera.chat:6697/composer", + "security": "https://github.com/composer/composer/security/policy" + } +} diff --git a/vendor/composer/composer/composer.lock b/vendor/composer/composer/composer.lock new file mode 100644 index 0000000..cb45182 --- /dev/null +++ b/vendor/composer/composer/composer.lock @@ -0,0 +1,2479 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "346c859c80684f1cd23f57dc04f917b4", + "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f65c239c970e7f072f067ab78646e9f0b2935175", + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.6" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-03-06T14:30:56+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-03-24T13:50:44+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/ebb81df8f52b40172d14062ae96a06939d80a069", + "reference": "ebb81df8f52b40172d14062ae96a06939d80a069", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/2.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:24:47+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.9", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-12T21:07:07+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/35d262c94959571e8736db1e5c9bc36ab94ae900", + "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "1.2.0", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.1" + }, + "time": "2025-04-04T13:08:07+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.1", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "time": "2024-11-28T04:54:44+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-22T13:05:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-28T13:32:08+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:36:42+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-10T20:33:58+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.12.25", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e310849a19e02b8bfcbb63147f495d8f872dd96f", + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-04-27T12:20:45+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1" + }, + "time": "2024-09-11T15:52:35+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/72a6721c9b64b3e4c9db55abbc38f790b318267e", + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.2" + }, + "time": "2024-12-17T17:20:49+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "b564ca479e7e735f750aaac4935af965572a7845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b564ca479e7e735f750aaac4935af965572a7845", + "reference": "b564ca479e7e735f750aaac4935af965572a7845", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12.4" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.2" + }, + "time": "2025-01-19T13:02:24+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "1.4.15", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "78b6b5a62f56731d938031c8f59817ed83b2328a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/78b6b5a62f56731d938031c8f59817ed83b2328a", + "reference": "78b6b5a62f56731d938031c8f59817ed83b2328a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.12" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^8.5.29 || ^9.5", + "psr/container": "1.0 || 1.1.1", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.15" + }, + "time": "2025-03-28T12:01:24+00:00" + }, + { + "name": "symfony/phpunit-bridge", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "6106ae85a0e3ed509d339b7f924788c9cc4e7cfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/6106ae85a0e3ed509d339b7f924788c9cc4e7cfb", + "reference": "6106ae85a0e3ed509d339b7f924788c9cc4e7cfb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "url": "https://github.com/sebastianbergmann/phpunit", + "name": "phpunit/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/bin/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-09T08:35:42+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.2.5 || ^8.0" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "7.2.5" + }, + "plugin-api-version": "2.6.0" +} diff --git a/vendor/composer/composer/phpstan/rules.neon b/vendor/composer/composer/phpstan/rules.neon new file mode 100644 index 0000000..6dae5cf --- /dev/null +++ b/vendor/composer/composer/phpstan/rules.neon @@ -0,0 +1,14 @@ +# Composer-specific PHPStan extensions +# +# These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon' +# in your phpstan config + +services: + - + class: Composer\PHPStan\ConfigReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: Composer\PHPStan\RuleReasonDataReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/vendor/composer/composer/res/composer-lock-schema.json b/vendor/composer/composer/res/composer-lock-schema.json new file mode 100644 index 0000000..b1ef31c --- /dev/null +++ b/vendor/composer/composer/res/composer-lock-schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Lock File", + "type": "object", + "required": [ "content-hash", "packages", "packages-dev" ], + "additionalProperties": true, + "properties": { + "_readme": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational text for humans reading the file" + }, + "content-hash": { + "type": "string", + "description": "Hash of all relevant properties of the composer.json that was used to create this lock file." + }, + "packages": { + "type": "array", + "description": "An array of packages that are required.", + "items": { + "$ref": "./composer-schema.json", + "required": ["name", "version"] + } + }, + "packages-dev": { + "type": "array", + "description": "An array of packages that are required in require-dev.", + "items": { + "$ref": "./composer-schema.json" + } + }, + "aliases": { + "type": "array", + "description": "Inline aliases defined in the root package.", + "items": { + "type": "object", + "required": [ "package", "version", "alias", "alias_normalized" ], + "properties": { + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "alias_normalized": { + "type": "string" + } + } + } + }, + "minimum-stability": { + "type": "string", + "description": "The minimum-stability used to generate this lock file." + }, + "stability-flags": { + "type": "object", + "description": "Root package stability flags changing the minimum-stability for specific packages.", + "additionalProperties": { + "type": "integer" + } + }, + "prefer-stable": { + "type": "boolean", + "description": "Whether the --prefer-stable flag was used when building this lock file." + }, + "prefer-lowest": { + "type": "boolean", + "description": "Whether the --prefer-lowest flag was used when building this lock file." + }, + "platform": { + "type": "object", + "description": "Platform requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-dev": { + "type": "object", + "description": "Platform dev-requirements of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "platform-overrides": { + "type": "object", + "description": "Platform config overrides of the root package.", + "additionalProperties": { + "type": "string" + } + }, + "plugin-api-version": { + "type": "string", + "description": "The composer-plugin-api version that was used to generate this lock file." + } + } +} diff --git a/vendor/composer/composer/res/composer-repository-schema.json b/vendor/composer/composer/res/composer-repository-schema.json new file mode 100644 index 0000000..223f63a --- /dev/null +++ b/vendor/composer/composer/res/composer-repository-schema.json @@ -0,0 +1,204 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Package Repository", + "type": "object", + "oneOf": [ + { "required": [ "packages" ] }, + { "required": [ "providers" ] }, + { "required": [ "provider-includes", "providers-url" ] }, + { "required": [ "metadata-url" ] } + ], + "properties": { + "packages": { + "type": ["object", "array"], + "description": "A hashmap of package names in the form of /.", + "additionalProperties": { "$ref": "#/definitions/versions" } + }, + "metadata-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'." + }, + "available-packages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here." + }, + "available-package-patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here." + }, + "security-advisories": { + "type": "array", + "items": { + "type": "object", + "required": ["metadata", "api-url"], + "properties": { + "metadata": { + "type": "boolean", + "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL." + }, + "api-url": { + "type": "string", + "description": "Endpoint to call to retrieve security advisories data." + } + } + } + }, + "metadata-changes-url": { + "type": "string", + "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'." + }, + "providers-api": { + "type": "string", + "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'." + }, + "notify-batch": { + "type": "string", + "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." + }, + "search": { + "type": "string", + "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." + }, + "list": { + "type": "string", + "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as a warning when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the warning should be shown." + } + } + } + }, + "infos": { + "type": "array", + "items": { + "type": "object", + "required": ["message", "versions"], + "properties": { + "message": { + "type": "string", + "description": "A message that will be output by Composer as info when this source is consulted." + }, + "versions": { + "type": "string", + "description": "A version constraint to limit to which Composer versions the info should be shown." + } + } + } + }, + "providers-url": { + "type": "string", + "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." + }, + "provider-includes": { + "type": "object", + "description": "DEPRECATED: A hashmap of provider listings.", + "additionalProperties": { "$ref": "#/definitions/provider" } + }, + "providers": { + "type": "object", + "description": "DEPRECATED: A hashmap of package names in the form of /.", + "additionalProperties": { "$ref": "#/definitions/provider" } + }, + "warning": { + "type": "string", + "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted." + }, + "warning-versions": { + "type": "string", + "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown." + }, + "info": { + "type": "string", + "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted." + }, + "info-versions": { + "type": "string", + "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown." + } + }, + "definitions": { + "versions": { + "type": "object", + "description": "A hashmap of versions and their metadata.", + "additionalProperties": { "$ref": "#/definitions/version" } + }, + "version": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/package" }, + { "$ref": "#/definitions/metapackage" } + ] + }, + "package-base": { + "properties": { + "name": { "type": "string" }, + "type": { "type": "string" }, + "version": { "type": "string" }, + "version_normalized": { + "type": "string", + "description": "Normalized version, optional but can save computational time on client side." + }, + "autoload": { "type": "object" }, + "require": { "type": "object" }, + "replace": { "type": "object" }, + "conflict": { "type": "object" }, + "provide": { "type": "object" }, + "time": { "type": "string" } + }, + "additionalProperties": true + }, + "package": { + "allOf": [ + { "$ref": "#/definitions/package-base" }, + { + "properties": { + "dist": { "type": "object" }, + "source": { "type": "object" } + } + }, + { "oneOf": [ + { "required": [ "name", "version", "source" ] }, + { "required": [ "name", "version", "dist" ] } + ] } + ] + }, + "metapackage": { + "allOf": [ + { "$ref": "#/definitions/package-base" }, + { + "properties": { + "type": { "type": "string", "enum": [ "metapackage" ] } + }, + "required": [ "name", "version", "type" ] + } + ] + }, + "provider": { + "type": "object", + "properties": { + "sha256": { + "type": "string", + "description": "Hash value that can be used to validate the resource." + } + } + } + } +} diff --git a/vendor/composer/composer/res/composer-schema.json b/vendor/composer/composer/res/composer-schema.json new file mode 100644 index 0000000..7487570 --- /dev/null +++ b/vendor/composer/composer/res/composer-schema.json @@ -0,0 +1,1210 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Composer Package", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Package name, including 'vendor-name/' prefix.", + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$" + }, + "description": { + "type": "string", + "description": "Short package description." + }, + "license": { + "type": ["string", "array"], + "description": "License name. Or an array of license names." + }, + "type": { + "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "abandoned": { + "type": ["boolean", "string"], + "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false." + }, + "version": { + "type": "string", + "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.", + "pattern": "^[vV]?\\d+(?:[.-]\\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\\d+)*+)?)?(?:[.-]?[dD][eE][vV]|\\.x-dev)?(?:\\+.*)?$|^dev-.*$" + }, + "default-branch": { + "type": ["boolean"], + "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false." + }, + "non-feature-branches": { + "type": ["array"], + "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", + "items": { + "type": "string" + } + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "description": "A tag/keyword that this package relates to." + } + }, + "readme": { + "type": "string", + "description": "Relative path to the readme document." + }, + "time": { + "type": "string", + "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format." + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the project.", + "format": "uri" + }, + "support": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Email address for support.", + "format": "email" + }, + "issues": { + "type": "string", + "description": "URL to the issue tracker.", + "format": "uri" + }, + "forum": { + "type": "string", + "description": "URL to the forum.", + "format": "uri" + }, + "wiki": { + "type": "string", + "description": "URL to the wiki.", + "format": "uri" + }, + "irc": { + "type": "string", + "description": "IRC channel for support, as irc://server/channel.", + "format": "uri" + }, + "chat": { + "type": "string", + "description": "URL to the support chat.", + "format": "uri" + }, + "source": { + "type": "string", + "description": "URL to browse or download the sources.", + "format": "uri" + }, + "docs": { + "type": "string", + "description": "URL to the documentation.", + "format": "uri" + }, + "rss": { + "type": "string", + "description": "URL to the RSS feed.", + "format": "uri" + }, + "security": { + "type": "string", + "description": "URL to the vulnerability disclosure policy (VDP).", + "format": "uri" + } + } + }, + "funding": { + "type": "array", + "description": "A list of options to fund the development and maintenance of the package.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of funding or platform through which funding is possible." + }, + "url": { + "type": "string", + "description": "URL to a website with details on funding and a way to fund the package.", + "format": "uri" + } + } + } + }, + "source": { + "$ref": "#/definitions/source" + }, + "dist": { + "$ref": "#/definitions/dist" + }, + "_comment": { + "type": ["array", "string"], + "description": "A key to store comments in" + }, + "require": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.", + "additionalProperties": { + "type": "string" + } + }, + "require-dev": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", + "additionalProperties": { + "type": "string" + } + }, + "replace": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.", + "additionalProperties": { + "type": "string" + } + }, + "conflict": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.", + "additionalProperties": { + "type": "string" + } + }, + "provide": { + "type": "object", + "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", + "additionalProperties": { + "type": "string" + } + }, + "suggest": { + "type": "object", + "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", + "additionalProperties": { + "type": "string" + } + }, + "repositories": { + "type": ["object", "array"], + "description": "A set of additional repositories where packages can be found.", + "additionalProperties": { + "anyOf": [ + { "$ref": "#/definitions/repository" }, + { "type": "boolean", "enum": [false] } + ] + }, + "items": { + "anyOf": [ + { "$ref": "#/definitions/repository" }, + { + "type": "object", + "additionalProperties": { "type": "boolean", "enum": [false] }, + "minProperties": 1, + "maxProperties": 1 + } + ] + } + }, + "minimum-stability": { + "type": ["string"], + "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.", + "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"] + }, + "prefer-stable": { + "type": ["boolean"], + "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." + }, + "autoload": { + "$ref": "#/definitions/autoload" + }, + "autoload-dev": { + "type": "object", + "description": "Description of additional autoload rules for development purpose (eg. a test suite).", + "properties": { + "psr-0": { + "type": "object", + "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "psr-4": { + "type": "object", + "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "classmap": { + "type": "array", + "description": "This is an array of paths that contain classes to be included in the class-map generation process." + }, + "files": { + "type": "array", + "description": "This is an array of files that are always required on every request." + } + } + }, + "target-dir": { + "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", + "type": "string" + }, + "include-path": { + "type": ["array"], + "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", + "items": { + "type": "string" + } + }, + "bin": { + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", + "items": { + "type": "string" + } + }, + "archive": { + "type": ["object"], + "description": "Options for creating package archives for distribution.", + "properties": { + "name": { + "type": "string", + "description": "A base name for archive." + }, + "exclude": { + "type": "array", + "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark." + } + } + }, + "php-ext": { + "type": "object", + "description": "Settings for PHP extension packages.", + "properties": { + "extension-name": { + "type": "string", + "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.", + "example": "ext-xdebug" + }, + "priority": { + "type": "integer", + "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", + "minimum": 10, + "maximum": 99, + "example": 80, + "default": 80 + }, + "support-zts": { + "type": "boolean", + "description": "Does this package support Zend Thread Safety", + "example": false, + "default": true + }, + "support-nts": { + "type": "boolean", + "description": "Does this package support non-Thread Safe mode", + "example": false, + "default": true + }, + "build-path": { + "type": ["string", "null"], + "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.", + "example": "my-extension-source", + "default": null + }, + "download-url-method": { + "type": "string", + "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.", + "enum": ["composer-default", "pre-packaged-source"], + "example": "composer-default" + }, + "os-families": { + "type": "array", + "minItems": 1, + "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.", + "items": { + "type": "string", + "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], + "description": "The name of the OS family to mark as compatible." + } + }, + "os-families-exclude": { + "type": "array", + "minItems": 1, + "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.", + "items": { + "type": "string", + "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], + "description": "The name of the OS family to exclude." + } + }, + "configure-options": { + "type": "array", + "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.", + "example": "without-xdebug-compression", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" + }, + "needs-value": { + "type": "boolean", + "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=), otherwise it is a flag without a value (e.g. --enable-some-feature).", + "example": false, + "default": false + }, + "description": { + "type": "string", + "description": "The description of what the flag does or means.", + "example": "Disable compression through zlib" + } + } + } + } + }, + "allOf": [ + { + "not": { + "required": ["os-families", "os-families-exclude"] + } + } + ] + }, + "config": { + "type": "object", + "description": "Composer options.", + "properties": { + "platform": { + "type": "object", + "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.", + "additionalProperties": { + "type": ["string", "boolean"] + } + }, + "allow-plugins": { + "type": ["object", "boolean"], + "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.", + "additionalProperties": { + "type": ["boolean"] + } + }, + "process-timeout": { + "type": "integer", + "description": "The timeout in seconds for process executions, defaults to 300 (5mins)." + }, + "use-include-path": { + "type": "boolean", + "description": "If true, the Composer autoloader will also look for classes in the PHP include path." + }, + "use-parent-dir": { + "type": ["string", "boolean"], + "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt." + }, + "preferred-install": { + "type": ["string", "object"], + "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.", + "additionalProperties": { + "type": ["string"] + } + }, + "audit": { + "type": "object", + "description": "Security audit configuration options", + "properties": { + "ignore": { + "anyOf": [ + { + "type": "object", + "description": "A list of advisory ids, remote ids or CVE ids (keys) and the explanations (values) for why they're being ignored. The listed items are reported but let the audit command pass.", + "additionalProperties": { + "type": ["string", "string"] + } + }, + { + "type": "array", + "description": "A set of advisory ids, remote ids or CVE ids that are reported but let the audit command pass.", + "items": { + "type": "string" + } + } + ] + }, + "abandoned": { + "enum": ["ignore", "report", "fail"], + "description": "Whether abandoned packages should be ignored, reported as problems or cause an audit failure." + } + } + }, + "notify-on-install": { + "type": "boolean", + "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true." + }, + "github-protocols": { + "type": "array", + "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].", + "items": { + "type": "string" + } + }, + "github-oauth": { + "type": "object", + "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"\"}.", + "additionalProperties": { + "type": "string" + } + }, + "gitlab-oauth": { + "type": "object", + "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"\", \"refresh-token\":\"\", \"token\":\"\"}}.", + "additionalProperties": { + "type": ["string", "object"], + "required": [ "token"], + "properties": { + "expires-at": { + "type": "integer", + "description": "The expiration date for this GitLab token" + }, + "refresh-token": { + "type": "string", + "description": "The refresh token used for GitLab authentication" + }, + "token": { + "type": "string", + "description": "The token used for GitLab authentication" + } + } + } + }, + "gitlab-token": { + "type": "object", + "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"\"}, or an object with username and token keys.", + "additionalProperties": { + "type": ["string", "object"], + "required": ["username", "token"], + "properties": { + "username": { + "type": "string", + "description": "The username used for GitLab authentication" + }, + "token": { + "type": "string", + "description": "The token used for GitLab authentication" + } + } + } + }, + "gitlab-protocol": { + "enum": ["git", "http", "https"], + "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos." + }, + "bearer": { + "type": "object", + "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"\"}.", + "additionalProperties": { + "type": "string" + } + }, + "disable-tls": { + "type": "boolean", + "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini." + }, + "secure-http": { + "type": "boolean", + "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative." + }, + "secure-svn-domains": { + "type": "array", + "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.", + "items": { + "type": "string" + } + }, + "cafile": { + "type": "string", + "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." + }, + "capath": { + "type": "string", + "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." + }, + "http-basic": { + "type": "object", + "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.", + "additionalProperties": { + "type": "object", + "required": ["username", "password"], + "properties": { + "username": { + "type": "string", + "description": "The username used for HTTP Basic authentication" + }, + "password": { + "type": "string", + "description": "The password used for HTTP Basic authentication" + } + } + } + }, + "store-auths": { + "type": ["string", "boolean"], + "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt." + }, + "vendor-dir": { + "type": "string", + "description": "The location where all packages are installed, defaults to \"vendor\"." + }, + "bin-dir": { + "type": "string", + "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." + }, + "data-dir": { + "type": "string", + "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes." + }, + "cache-dir": { + "type": "string", + "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows." + }, + "cache-files-dir": { + "type": "string", + "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"." + }, + "cache-repo-dir": { + "type": "string", + "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"." + }, + "cache-vcs-dir": { + "type": "string", + "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"." + }, + "cache-ttl": { + "type": "integer", + "description": "The default cache time-to-live, defaults to 15552000 (6 months)." + }, + "cache-files-ttl": { + "type": "integer", + "description": "The cache time-to-live for files, defaults to the value of cache-ttl." + }, + "cache-files-maxsize": { + "type": ["string", "integer"], + "description": "The cache max size for the files cache, defaults to \"300MiB\"." + }, + "cache-read-only": { + "type": ["boolean"], + "description": "Whether to use the Composer cache in read-only mode." + }, + "bin-compat": { + "enum": ["auto", "full", "proxy", "symlink"], + "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)." + }, + "discard-changes": { + "type": ["string", "boolean"], + "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." + }, + "autoloader-suffix": { + "type": "string", + "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated." + }, + "optimize-autoloader": { + "type": "boolean", + "description": "Always optimize when dumping the autoloader." + }, + "prepend-autoloader": { + "type": "boolean", + "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." + }, + "classmap-authoritative": { + "type": "boolean", + "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." + }, + "apcu-autoloader": { + "type": "boolean", + "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false." + }, + "github-domains": { + "type": "array", + "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", + "items": { + "type": "string" + } + }, + "github-expose-hostname": { + "type": "boolean", + "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname." + }, + "gitlab-domains": { + "type": "array", + "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].", + "items": { + "type": "string" + } + }, + "bitbucket-oauth": { + "type": "object", + "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.", + "additionalProperties": { + "type": "object", + "required": ["consumer-key", "consumer-secret"], + "properties": { + "consumer-key": { + "type": "string", + "description": "The consumer-key used for OAuth authentication" + }, + "consumer-secret": { + "type": "string", + "description": "The consumer-secret used for OAuth authentication" + }, + "access-token": { + "type": "string", + "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it." + }, + "access-token-expiration": { + "type": "integer", + "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it." + } + } + } + }, + "use-github-api": { + "type": "boolean", + "description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository." + }, + "archive-format": { + "type": "string", + "description": "The default archiving format when not provided on cli, defaults to \"tar\"." + }, + "archive-dir": { + "type": "string", + "description": "The default archive path when not provided on cli, defaults to \".\"." + }, + "htaccess-protect": { + "type": "boolean", + "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." + }, + "sort-packages": { + "type": "boolean", + "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." + }, + "lock": { + "type": "boolean", + "description": "Defaults to true. If set to false, Composer will not create a composer.lock file." + }, + "platform-check": { + "type": ["boolean", "string"], + "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." + }, + "bump-after-update": { + "type": ["string", "boolean"], + "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped." + }, + "allow-missing-requirements": { + "type": ["boolean"], + "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json." + } + } + }, + "extra": { + "type": ["object", "array"], + "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", + "additionalProperties": true + }, + "scripts": { + "type": ["object"], + "description": "Script listeners that will be executed before/after some events.", + "properties": { + "pre-install-cmd": { + "type": ["array", "string"], + "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands." + }, + "post-install-cmd": { + "type": ["array", "string"], + "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-update-cmd": { + "type": ["array", "string"], + "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands." + }, + "post-update-cmd": { + "type": ["array", "string"], + "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-status-cmd": { + "type": ["array", "string"], + "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands." + }, + "post-status-cmd": { + "type": ["array", "string"], + "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands." + }, + "pre-package-install": { + "type": ["array", "string"], + "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands." + }, + "post-package-install": { + "type": ["array", "string"], + "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands." + }, + "pre-package-update": { + "type": ["array", "string"], + "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands." + }, + "post-package-update": { + "type": ["array", "string"], + "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands." + }, + "pre-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands." + }, + "post-package-uninstall": { + "type": ["array", "string"], + "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands." + }, + "pre-autoload-dump": { + "type": ["array", "string"], + "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands." + }, + "post-autoload-dump": { + "type": ["array", "string"], + "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands." + }, + "post-root-package-install": { + "type": ["array", "string"], + "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands." + }, + "post-create-project-cmd": { + "type": ["array", "string"], + "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands." + } + } + }, + "scripts-descriptions": { + "type": ["object"], + "description": "Descriptions for custom commands, shown in console help.", + "additionalProperties": { + "type": "string" + } + }, + "scripts-aliases": { + "type": ["object"], + "description": "Aliases for custom commands.", + "additionalProperties": { + "type": "array" + } + } + }, + "definitions": { + "authors": { + "type": "array", + "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ "name"], + "properties": { + "name": { + "type": "string", + "description": "Full name of the author." + }, + "email": { + "type": "string", + "description": "Email address of the author.", + "format": "email" + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the author.", + "format": "uri" + }, + "role": { + "type": "string", + "description": "Author's role in the project." + } + } + } + }, + "autoload": { + "type": "object", + "description": "Description of how the package can be autoloaded.", + "properties": { + "psr-0": { + "type": "object", + "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "psr-4": { + "type": "object", + "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } + }, + "classmap": { + "type": "array", + "description": "This is an array of paths that contain classes to be included in the class-map generation process." + }, + "files": { + "type": "array", + "description": "This is an array of files that are always required on every request." + }, + "exclude-from-classmap": { + "type": "array", + "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]" + } + } + }, + "repository": { + "type": "object", + "anyOf": [ + { "$ref": "#/definitions/composer-repository" }, + { "$ref": "#/definitions/vcs-repository" }, + { "$ref": "#/definitions/path-repository" }, + { "$ref": "#/definitions/artifact-repository" }, + { "$ref": "#/definitions/pear-repository" }, + { "$ref": "#/definitions/package-repository" } + ] + }, + "composer-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { "type": "string", "enum": ["composer"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "additionalProperties": true + }, + "allow_ssl_downgrade": { "type": "boolean" }, + "force-lazy-providers": { "type": "boolean" } + } + }, + "vcs-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "no-api": { "type": "boolean" }, + "secure-http": { "type": "boolean" }, + "svn-cache-credentials": { "type": "boolean" }, + "trunk-path": { "type": ["string", "boolean"] }, + "branches-path": { "type": ["string", "boolean"] }, + "tags-path": { "type": ["string", "boolean"] }, + "package-path": { "type": "string" }, + "depot": { "type": "string" }, + "branch": { "type": "string" }, + "unique_perforce_client_name": { "type": "string" }, + "p4user": { "type": "string" }, + "p4password": { "type": "string" } + } + }, + "path-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { "type": "string", "enum": ["path"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "object", + "properties": { + "reference": { "type": ["string"], "enum": ["none", "config", "auto"] }, + "symlink": { "type": ["boolean", "null"] }, + "relative": { "type": ["boolean"] }, + "versions": { "type": "object", "additionalProperties": { "type": "string" } } + }, + "additionalProperties": true + } + } + }, + "artifact-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { "type": "string", "enum": ["artifact"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "pear-repository": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { "type": "string", "enum": ["pear"] }, + "url": { "type": "string" }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "vendor-alias": { "type": "string" } + } + }, + "package-repository": { + "type": "object", + "required": ["type", "package"], + "properties": { + "type": { "type": "string", "enum": ["package"] }, + "canonical": { "type": "boolean" }, + "only": { + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "package": { + "oneOf": [ + { "$ref": "#/definitions/inline-package" }, + { + "type": "array", + "items": { "$ref": "#/definitions/inline-package" } + } + ] + } + } + }, + "inline-package": { + "type": "object", + "required": ["name", "version"], + "properties": { + "name": { + "type": "string", + "description": "Package name, including 'vendor-name/' prefix." + }, + "type": { + "type": "string" + }, + "target-dir": { + "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", + "type": "string" + }, + "description": { + "type": "string" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "homepage": { + "type": "string", + "format": "uri" + }, + "version": { + "type": "string" + }, + "time": { + "type": "string" + }, + "license": { + "type": [ + "string", + "array" + ] + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "require": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "replace": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "conflict": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "provide": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "require-dev": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "suggest": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "extra": { + "type": ["object", "array"], + "additionalProperties": true + }, + "autoload": { + "$ref": "#/definitions/autoload" + }, + "archive": { + "type": ["object"], + "properties": { + "exclude": { + "type": "array" + } + } + }, + "bin": { + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", + "items": { + "type": "string" + } + }, + "include-path": { + "type": ["array"], + "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", + "items": { + "type": "string" + } + }, + "source": { + "$ref": "#/definitions/source" + }, + "dist": { + "$ref": "#/definitions/dist" + } + }, + "additionalProperties": true + }, + "source": { + "type": "object", + "required": ["type", "url", "reference"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "mirrors": { + "type": "array" + } + } + }, + "dist": { + "type": "object", + "required": ["type", "url"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + }, + "mirrors": { + "type": "array" + } + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/Auditor.php b/vendor/composer/composer/src/Composer/Advisory/Auditor.php new file mode 100644 index 0000000..485b332 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/Auditor.php @@ -0,0 +1,428 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\IO\ConsoleIO; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; +use Composer\Repository\RepositorySet; +use Composer\Util\PackageInfo; +use InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @internal + */ +class Auditor +{ + public const FORMAT_TABLE = 'table'; + + public const FORMAT_PLAIN = 'plain'; + + public const FORMAT_JSON = 'json'; + + public const FORMAT_SUMMARY = 'summary'; + + public const FORMATS = [ + self::FORMAT_TABLE, + self::FORMAT_PLAIN, + self::FORMAT_JSON, + self::FORMAT_SUMMARY, + ]; + + public const ABANDONED_IGNORE = 'ignore'; + public const ABANDONED_REPORT = 'report'; + public const ABANDONED_FAIL = 'fail'; + + /** @internal */ + public const ABANDONEDS = [ + self::ABANDONED_IGNORE, + self::ABANDONED_REPORT, + self::ABANDONED_FAIL, + ]; + + /** Values to determine the audit result. */ + public const STATUS_OK = 0; + public const STATUS_VULNERABLE = 1; + public const STATUS_ABANDONED = 2; + + /** + * @param PackageInterface[] $packages + * @param self::FORMAT_* $format The format that will be used to output audit results. + * @param bool $warningOnly If true, outputs a warning. If false, outputs an error. + * @param string[] $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. + * @param self::ABANDONED_* $abandoned + * @param array $ignoredSeverities List of ignored severity levels + * + * @return int-mask A bitmask of STATUS_* constants or 0 on success + * @throws InvalidArgumentException If no packages are passed in + */ + public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = []): int + { + $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY); + // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above + // and ignores are set then we need to query again the full data to make sure it can be filtered + if (count($allAdvisories) > 0 && $ignoreList !== [] && $format === self::FORMAT_SUMMARY) { + $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, false); + } + ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities); + + $abandonedCount = 0; + $affectedPackagesCount = count($advisories); + if ($abandoned === self::ABANDONED_IGNORE) { + $abandonedPackages = []; + } else { + $abandonedPackages = $this->filterAbandonedPackages($packages); + if ($abandoned === self::ABANDONED_FAIL) { + $abandonedCount = count($abandonedPackages); + } + } + + $auditBitmask = $this->calculateBitmask(0 < $affectedPackagesCount, 0 < $abandonedCount); + + if (self::FORMAT_JSON === $format) { + $json = ['advisories' => $advisories]; + if ($ignoredAdvisories !== []) { + $json['ignored-advisories'] = $ignoredAdvisories; + } + $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array { + $carry[$package->getPrettyName()] = $package->getReplacementPackage(); + + return $carry; + }, []); + + $io->write(JsonFile::encode($json)); + + return $auditBitmask; + } + + $errorOrWarn = $warningOnly ? 'warning' : 'error'; + if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) { + $passes = [ + [$ignoredAdvisories, "Found %d ignored security vulnerability advisor%s affecting %d package%s%s"], + [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s"], + ]; + foreach ($passes as [$advisoriesToOutput, $message]) { + [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); + if ($pkgCount > 0) { + $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies'; + $pkgPlurality = $pkgCount === 1 ? '' : 's'; + $punctuation = $format === 'summary' ? '.' : ':'; + $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation)); + $this->outputAdvisories($io, $advisoriesToOutput, $format); + } + } + + if ($format === self::FORMAT_SUMMARY) { + $io->writeError('Run "composer audit" for a full list of advisories.'); + } + } else { + $io->writeError('No security vulnerability advisories found.'); + } + + if (count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) { + $this->outputAbandonedPackages($io, $abandonedPackages, $format); + } + + return $auditBitmask; + } + + /** + * @param array $packages + * @return array + */ + private function filterAbandonedPackages(array $packages): array + { + return array_filter($packages, static function (PackageInterface $pkg): bool { + return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned(); + }); + } + + /** + * @phpstan-param array> $allAdvisories + * @param array|array $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities. + * @param array $ignoredSeverities List of ignored severity levels + * @phpstan-return array{advisories: array>, ignoredAdvisories: array>} + */ + private function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array + { + if ($ignoreList === [] && $ignoredSeverities === []) { + return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []]; + } + + if (\count($ignoreList) > 0 && !\array_is_list($ignoreList)) { + $ignoredIds = array_keys($ignoreList); + } else { + $ignoredIds = $ignoreList; + } + + $advisories = []; + $ignored = []; + $ignoreReason = null; + + foreach ($allAdvisories as $package => $pkgAdvisories) { + foreach ($pkgAdvisories as $advisory) { + $isActive = true; + + if (in_array($advisory->advisoryId, $ignoredIds, true)) { + $isActive = false; + $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null; + } + + if ($advisory instanceof SecurityAdvisory) { + if (in_array($advisory->severity, $ignoredSeverities, true)) { + $isActive = false; + $ignoreReason = "Ignored via --ignore-severity={$advisory->severity}"; + } + + if (in_array($advisory->cve, $ignoredIds, true)) { + $isActive = false; + $ignoreReason = $ignoreList[$advisory->cve] ?? null; + } + + foreach ($advisory->sources as $source) { + if (in_array($source['remoteId'], $ignoredIds, true)) { + $isActive = false; + $ignoreReason = $ignoreList[$source['remoteId']] ?? null; + break; + } + } + } + + if ($isActive) { + $advisories[$package][] = $advisory; + continue; + } + + // Partial security advisories only used in summary mode + // and in that case we do not need to cast the object. + if ($advisory instanceof SecurityAdvisory) { + $advisory = $advisory->toIgnoredAdvisory($ignoreReason); + } + + $ignored[$package][] = $advisory; + } + } + + return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored]; + } + + /** + * @param array> $advisories + * @return array{int, int} Count of affected packages and total count of advisories + */ + private function countAdvisories(array $advisories): array + { + $count = 0; + foreach ($advisories as $packageAdvisories) { + $count += count($packageAdvisories); + } + + return [count($advisories), $count]; + } + + /** + * @param array> $advisories + * @param self::FORMAT_* $format The format that will be used to output audit results. + */ + private function outputAdvisories(IOInterface $io, array $advisories, string $format): void + { + switch ($format) { + case self::FORMAT_TABLE: + if (!($io instanceof ConsoleIO)) { + throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); + } + $this->outputAdvisoriesTable($io, $advisories); + + return; + case self::FORMAT_PLAIN: + $this->outputAdvisoriesPlain($io, $advisories); + + return; + case self::FORMAT_SUMMARY: + + return; + default: + throw new InvalidArgumentException('Invalid format "'.$format.'".'); + } + } + + /** + * @param array> $advisories + */ + private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void + { + foreach ($advisories as $packageAdvisories) { + foreach ($packageAdvisories as $advisory) { + $headers = [ + 'Package', + 'Severity', + 'CVE', + 'Title', + 'URL', + 'Affected versions', + 'Reported at', + ]; + $row = [ + $advisory->packageName, + $this->getSeverity($advisory), + $this->getCVE($advisory), + $advisory->title, + $this->getURL($advisory), + $advisory->affectedVersions->getPrettyString(), + $advisory->reportedAt->format(DATE_ATOM), + ]; + if ($advisory->cve === null) { + $headers[] = 'Advisory ID'; + $row[] = $advisory->advisoryId; + } + if ($advisory instanceof IgnoredSecurityAdvisory) { + $headers[] = 'Ignore reason'; + $row[] = $advisory->ignoreReason ?? 'None specified'; + } + $io->getTable() + ->setHorizontal() + ->setHeaders($headers) + ->addRow($row) + ->setColumnWidth(1, 80) + ->setColumnMaxWidth(1, 80) + ->render(); + } + } + } + + /** + * @param array> $advisories + */ + private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void + { + $error = []; + $firstAdvisory = true; + foreach ($advisories as $packageAdvisories) { + foreach ($packageAdvisories as $advisory) { + if (!$firstAdvisory) { + $error[] = '--------'; + } + $error[] = "Package: ".$advisory->packageName; + $error[] = "Severity: ".$this->getSeverity($advisory); + $error[] = "CVE: ".$this->getCVE($advisory); + if ($advisory->cve === null) { + $error[] = "Advisory ID: ".$advisory->advisoryId; + } + $error[] = "Title: ".OutputFormatter::escape($advisory->title); + $error[] = "URL: ".$this->getURL($advisory); + $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString()); + $error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM); + if ($advisory instanceof IgnoredSecurityAdvisory) { + $error[] = "Ignore reason: ".($advisory->ignoreReason ?? 'None specified'); + } + $firstAdvisory = false; + } + } + $io->writeError($error); + } + + /** + * @param array $packages + * @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format + */ + private function outputAbandonedPackages(IOInterface $io, array $packages, string $format): void + { + $io->writeError(sprintf('Found %d abandoned package%s:', count($packages), count($packages) > 1 ? 's' : '')); + + if ($format === self::FORMAT_PLAIN) { + foreach ($packages as $pkg) { + $replacement = $pkg->getReplacementPackage() !== null + ? 'Use '.$pkg->getReplacementPackage().' instead' + : 'No replacement was suggested'; + $io->writeError(sprintf( + '%s is abandoned. %s.', + $this->getPackageNameWithLink($pkg), + $replacement + )); + } + + return; + } + + if (!($io instanceof ConsoleIO)) { + throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); + } + + $table = $io->getTable() + ->setHeaders(['Abandoned Package', 'Suggested Replacement']) + ->setColumnWidth(1, 80) + ->setColumnMaxWidth(1, 80); + + foreach ($packages as $pkg) { + $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none'; + $table->addRow([$this->getPackageNameWithLink($pkg), $replacement]); + } + + $table->render(); + } + + private function getPackageNameWithLink(PackageInterface $package): string + { + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + + return $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + } + + private function getSeverity(SecurityAdvisory $advisory): string + { + if ($advisory->severity === null) { + return ''; + } + + return $advisory->severity; + } + + private function getCVE(SecurityAdvisory $advisory): string + { + if ($advisory->cve === null) { + return 'NO CVE'; + } + + return ''.$advisory->cve.''; + } + + private function getURL(SecurityAdvisory $advisory): string + { + if ($advisory->link === null) { + return ''; + } + + return 'link).'>'.OutputFormatter::escape($advisory->link).''; + } + + /** + * @return int-mask + */ + private function calculateBitmask(bool $hasVulnerablePackages, bool $hasAbandonedPackages): int + { + $bitmask = self::STATUS_OK; + + if ($hasVulnerablePackages) { + $bitmask |= self::STATUS_VULNERABLE; + } + + if ($hasAbandonedPackages) { + $bitmask |= self::STATUS_ABANDONED; + } + + return $bitmask; + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php new file mode 100644 index 0000000..3d8b56a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Semver\Constraint\ConstraintInterface; +use DateTimeImmutable; + +class IgnoredSecurityAdvisory extends SecurityAdvisory +{ + /** + * @var string|null + * @readonly + */ + public $ignoreReason; + + /** + * @param non-empty-array $sources + */ + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null) + { + parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity); + + $this->ignoreReason = $ignoreReason; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + if ($this->ignoreReason === NULL) { + unset($data['ignoreReason']); + } + + return $data; + } + +} diff --git a/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php new file mode 100644 index 0000000..2867e9b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php @@ -0,0 +1,71 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\VersionParser; +use JsonSerializable; + +class PartialSecurityAdvisory implements JsonSerializable +{ + /** + * @var string + * @readonly + */ + public $advisoryId; + + /** + * @var string + * @readonly + */ + public $packageName; + + /** + * @var ConstraintInterface + * @readonly + */ + public $affectedVersions; + + /** + * @param array $data + * @return SecurityAdvisory|PartialSecurityAdvisory + */ + public static function create(string $packageName, array $data, VersionParser $parser): self + { + $constraint = $parser->parseConstraints($data['affectedVersions']); + if (isset($data['title'], $data['sources'], $data['reportedAt'])) { + return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null); + } + + return new self($packageName, $data['advisoryId'], $constraint); + } + + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions) + { + $this->advisoryId = $advisoryId; + $this->packageName = $packageName; + $this->affectedVersions = $affectedVersions; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = (array) $this; + $data['affectedVersions'] = $data['affectedVersions']->getPrettyString(); + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php b/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php new file mode 100644 index 0000000..a3d58b4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php @@ -0,0 +1,101 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Advisory; + +use Composer\Semver\Constraint\ConstraintInterface; +use DateTimeImmutable; + +class SecurityAdvisory extends PartialSecurityAdvisory +{ + /** + * @var string + * @readonly + */ + public $title; + + /** + * @var string|null + * @readonly + */ + public $cve; + + /** + * @var string|null + * @readonly + */ + public $link; + + /** + * @var DateTimeImmutable + * @readonly + */ + public $reportedAt; + + /** + * @var non-empty-array + * @readonly + */ + public $sources; + + /** + * @var string|null + * @readonly + */ + public $severity; + + /** + * @param non-empty-array $sources + */ + public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null) + { + parent::__construct($packageName, $advisoryId, $affectedVersions); + + $this->title = $title; + $this->sources = $sources; + $this->reportedAt = $reportedAt; + $this->cve = $cve; + $this->link = $link; + $this->severity = $severity; + } + + /** + * @internal + */ + public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisory + { + return new IgnoredSecurityAdvisory( + $this->packageName, + $this->advisoryId, + $this->affectedVersions, + $this->title, + $this->sources, + $this->reportedAt, + $this->cve, + $this->link, + $ignoreReason, + $this->severity + ); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $data = parent::jsonSerialize(); + $data['reportedAt'] = $data['reportedAt']->format(DATE_RFC3339); + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php b/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php new file mode 100644 index 0000000..b86c3b6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php @@ -0,0 +1,1422 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +use Composer\ClassMapGenerator\ClassMap; +use Composer\ClassMapGenerator\ClassMapGenerator; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer\InstallationManager; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Semver\Constraint\Bound; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Script\ScriptEvents; +use Composer\Util\PackageSorter; +use Composer\Json\JsonFile; +use Composer\Package\Locker; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * @author Igor Wiedler + * @author Jordi Boggiano + */ +class AutoloadGenerator +{ + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var ?bool + */ + private $devMode = null; + + /** + * @var bool + */ + private $classMapAuthoritative = false; + + /** + * @var bool + */ + private $apcu = false; + + /** + * @var string|null + */ + private $apcuPrefix; + + /** + * @var bool + */ + private $dryRun = false; + + /** + * @var bool + */ + private $runScripts = false; + + /** + * @var PlatformRequirementFilterInterface + */ + private $platformRequirementFilter; + + public function __construct(EventDispatcher $eventDispatcher, ?IOInterface $io = null) + { + $this->eventDispatcher = $eventDispatcher; + $this->io = $io ?? new NullIO(); + + $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } + + /** + * @return void + */ + public function setDevMode(bool $devMode = true) + { + $this->devMode = $devMode; + } + + /** + * Whether generated autoloader considers the class map authoritative. + * + * @return void + */ + public function setClassMapAuthoritative(bool $classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Whether generated autoloader considers APCu caching. + * + * @return void + */ + public function setApcu(bool $apcu, ?string $apcuPrefix = null) + { + $this->apcu = $apcu; + $this->apcuPrefix = $apcuPrefix; + } + + /** + * Whether to run scripts or not + * + * @return void + */ + public function setRunScripts(bool $runScripts = true) + { + $this->runScripts = $runScripts; + } + + /** + * Whether to run in drymode or not + */ + public function setDryRun(bool $dryRun = true): void + { + $this->dryRun = $dryRun; + } + + /** + * Whether platform requirements should be ignored. + * + * If this is set to true, the platform check file will not be generated + * If this is set to false, the platform check file will be generated with all requirements + * If this is set to string[], those packages will be ignored from the platform check file + * + * @param bool|string[] $ignorePlatformReqs + * @return void + * + * @deprecated use setPlatformRequirementFilter instead + */ + public function setIgnorePlatformRequirements($ignorePlatformReqs) + { + trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); + + $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); + } + + /** + * @return void + */ + public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter) + { + $this->platformRequirementFilter = $platformRequirementFilter; + } + + /** + * @return ClassMap + * @throws \Seld\JsonLint\ParsingException + * @throws \RuntimeException + */ + public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false) + { + if ($this->classMapAuthoritative) { + // Force scanPsrPackages when classmap is authoritative + $scanPsrPackages = true; + } + + // auto-set devMode based on whether dev dependencies are installed or not + if (null === $this->devMode) { + // we assume no-dev mode if no vendor dir is present or it is too old to contain dev information + $this->devMode = false; + + $installedJson = new JsonFile($config->get('vendor-dir').'/composer/installed.json'); + if ($installedJson->exists()) { + $installedJson = $installedJson->read(); + if (isset($installedJson['dev'])) { + $this->devMode = $installedJson['dev']; + } + } + } + + if ($this->runScripts) { + // set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners + if (!isset($_SERVER['COMPOSER_DEV_MODE'])) { + Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); + } + + $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], [ + 'optimize' => $scanPsrPackages, + ]); + } + + $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); + $classMapGenerator->avoidDuplicateScans(); + + $filesystem = new Filesystem(); + $filesystem->ensureDirectoryExists($config->get('vendor-dir')); + // Do not remove double realpath() calls. + // Fixes failing Windows realpath() implementation. + // See https://bugs.php.net/bug.php?id=72738 + $basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd()))); + $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir')))); + $useGlobalIncludePath = $config->get('use-include-path'); + $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; + $targetDir = $vendorPath.'/'.$targetDir; + $filesystem->ensureDirectoryExists($targetDir); + + $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); + $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true); + + $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true); + $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); + + $namespacesFile = <<getDevPackageNames(); + $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages()); + if ($this->devMode) { + // if dev mode is enabled, then we do not filter any dev packages out so disable this entirely + $filteredDevPackages = false; + } else { + // if the list of dev package names is available we use that straight, otherwise pass true which means use legacy algo to figure them out + $filteredDevPackages = $devPackageNames ?: true; + } + $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages); + + // Process the 'psr-0' base directories. + foreach ($autoloads['psr-0'] as $namespace => $paths) { + $exportedPaths = []; + foreach ($paths as $path) { + $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + } + $exportedPrefix = var_export($namespace, true); + $namespacesFile .= " $exportedPrefix => "; + $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n"; + } + $namespacesFile .= ");\n"; + + // Process the 'psr-4' base directories. + foreach ($autoloads['psr-4'] as $namespace => $paths) { + $exportedPaths = []; + foreach ($paths as $path) { + $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); + } + $exportedPrefix = var_export($namespace, true); + $psr4File .= " $exportedPrefix => "; + $psr4File .= "array(".implode(', ', $exportedPaths)."),\n"; + } + $psr4File .= ");\n"; + + // add custom psr-0 autoloading if the root package has a target dir + $targetDirLoader = null; + $mainAutoload = $rootPackage->getAutoload(); + if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) { + $levels = substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1; + $prefixes = implode(', ', array_map(static function ($prefix): string { + return var_export($prefix, true); + }, array_keys($mainAutoload['psr-0']))); + $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true); + + $targetDirLoader = <<scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); + } + + if ($scanPsrPackages) { + $namespacesToScan = []; + + // Scan the PSR-0/4 directories for class files, and add them to the class map + foreach (['psr-4', 'psr-0'] as $psrType) { + foreach ($autoloads[$psrType] as $namespace => $paths) { + $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType]; + } + } + + krsort($namespacesToScan); + + foreach ($namespacesToScan as $namespace => $groups) { + foreach ($groups as $group) { + foreach ($group['paths'] as $dir) { + $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); + if (!is_dir($dir)) { + continue; + } + + // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it + if (str_contains($vendorPath, $dir.'/')) { + $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/'])); + } else { + $exclusionRegex = $this->buildExclusionRegex($dir, $excluded); + } + + $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace); + } + } + } + } + + $classMap = $classMapGenerator->getClassMap(); + if ($strictAmbiguous) { + $ambiguousClasses = $classMap->getAmbiguousClasses(false); + } else { + $ambiguousClasses = $classMap->getAmbiguousClasses(); + } + foreach ($ambiguousClasses as $className => $ambiguousPaths) { + if (count($ambiguousPaths) > 1) { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$className.'"'. + ' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' + ); + } else { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$className.'"'. + ' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' + ); + } + } + if (\count($ambiguousClasses) > 0) { + $this->io->writeError('To resolve ambiguity in classes not under your control you can ignore them by path using exclude-files-from-classmap'); + } + + // output PSR violations which are not coming from the vendor dir + $classMap->clearPsrViolationsByPath($vendorPath); + foreach ($classMap->getPsrViolations() as $msg) { + $this->io->writeError("$msg"); + } + + $classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php'); + $classMap->sort(); + + $classmapFile = <<getMap() as $className => $path) { + $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; + $classmapFile .= ' '.var_export($className, true).' => '.$pathCode; + } + $classmapFile .= ");\n"; + + if ('' === $suffix) { + $suffix = null; + } + if (null === $suffix) { + $suffix = $config->get('autoloader-suffix'); + + // carry over existing autoload.php's suffix if possible and none is configured + if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) { + $content = (string) file_get_contents($vendorPath.'/autoload.php'); + if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { + $suffix = $match[1]; + } + } + + if (null === $suffix) { + $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16)); + } + } + + if ($this->dryRun) { + return $classMap; + } + + $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile); + $includePathFilePath = $targetDir.'/include_paths.php'; + if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { + $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents); + } elseif (file_exists($includePathFilePath)) { + unlink($includePathFilePath); + } + $includeFilesFilePath = $targetDir.'/autoload_files.php'; + if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { + $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); + } elseif (file_exists($includeFilesFilePath)) { + unlink($includeFilesFilePath); + } + $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)); + $checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter); + $platformCheckContent = null; + if ($checkPlatform) { + $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames); + if (null === $platformCheckContent) { + $checkPlatform = false; + } + } + if ($checkPlatform) { + $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $platformCheckContent); + } elseif (file_exists($targetDir.'/platform_check.php')) { + unlink($targetDir.'/platform_check.php'); + } + $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); + $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform)); + + $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); + $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); + + if ($this->runScripts) { + $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], [ + 'optimize' => $scanPsrPackages, + ]); + } + + return $classMap; + } + + /** + * @param array $excluded + * @return non-empty-string|null + */ + private function buildExclusionRegex(string $dir, array $excluded): ?string + { + if ([] === $excluded) { + return null; + } + + // filter excluded patterns here to only use those matching $dir + // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work + // if $dir does not exist, it should anyway not find anything there so no trouble + if (file_exists($dir)) { + // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other + $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/')); + foreach ($excluded as $index => $pattern) { + // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character + $pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern); + // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it + if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) { + unset($excluded[$index]); + } + } + } + + return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null; + } + + /** + * @param PackageInterface[] $packages + * @return non-empty-array + */ + public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages) + { + // build package => install path map + $packageMap = [[$rootPackage, '']]; + + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + $this->validatePackage($package); + $packageMap[] = [ + $package, + $installationManager->getInstallPath($package), + ]; + } + + return $packageMap; + } + + /** + * @return void + * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings. + */ + protected function validatePackage(PackageInterface $package) + { + $autoload = $package->getAutoload(); + if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { + $name = $package->getName(); + $package->getTargetDir(); + throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'."); + } + if (!empty($autoload['psr-4'])) { + foreach ($autoload['psr-4'] as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr($namespace, -1)) { + throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'."); + } + } + } + } + + /** + * Compiles an ordered list of namespace => path mappings + * + * @param non-empty-array $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages) + * @param RootPackageInterface $rootPackage root package instance + * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages + * @return array + * @phpstan-return array{ + * 'psr-0': array>, + * 'psr-4': array>, + * 'classmap': array, + * 'files': array, + * 'exclude-from-classmap': array, + * } + */ + public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false) + { + $rootPackageMap = array_shift($packageMap); + if (is_array($filteredDevPackages)) { + $packageMap = array_filter($packageMap, static function ($item) use ($filteredDevPackages): bool { + return !in_array($item[0]->getName(), $filteredDevPackages, true); + }); + } elseif ($filteredDevPackages) { + $packageMap = $this->filterPackageMap($packageMap, $rootPackage); + } + $sortedPackageMap = $this->sortPackageMap($packageMap); + $sortedPackageMap[] = $rootPackageMap; + $reverseSortedMap = array_reverse($sortedPackageMap); + + // reverse-sorted means root first, then dependents, then their dependents, etc. + // which makes sense to allow root to override classmap or psr-0/4 entries with higher precedence rules + $psr0 = $this->parseAutoloadsType($reverseSortedMap, 'psr-0', $rootPackage); + $psr4 = $this->parseAutoloadsType($reverseSortedMap, 'psr-4', $rootPackage); + $classmap = $this->parseAutoloadsType($reverseSortedMap, 'classmap', $rootPackage); + + // sorted (i.e. dependents first) for files to ensure that dependencies are loaded/available once a file is included + $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage); + // using sorted here but it does not really matter as all are excluded equally + $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage); + + krsort($psr0); + krsort($psr4); + + return [ + 'psr-0' => $psr0, + 'psr-4' => $psr4, + 'classmap' => $classmap, + 'files' => $files, + 'exclude-from-classmap' => $exclude, + ]; + } + + /** + * Registers an autoloader based on an autoload-map returned by parseAutoloads + * + * @param array $autoloads see parseAutoloads return value + * @return ClassLoader + */ + public function createLoader(array $autoloads, ?string $vendorDir = null) + { + $loader = new ClassLoader($vendorDir); + + if (isset($autoloads['psr-0'])) { + foreach ($autoloads['psr-0'] as $namespace => $path) { + $loader->add($namespace, $path); + } + } + + if (isset($autoloads['psr-4'])) { + foreach ($autoloads['psr-4'] as $namespace => $path) { + $loader->addPsr4($namespace, $path); + } + } + + if (isset($autoloads['classmap'])) { + $excluded = []; + if (!empty($autoloads['exclude-from-classmap'])) { + $excluded = $autoloads['exclude-from-classmap']; + } + + $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); + $classMapGenerator->avoidDuplicateScans(); + + foreach ($autoloads['classmap'] as $dir) { + try { + $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); + } catch (\RuntimeException $e) { + $this->io->writeError(''.$e->getMessage().''); + } + } + + $loader->addClassMap($classMapGenerator->getClassMap()->getMap()); + } + + return $loader; + } + + /** + * @param array $packageMap + * @return ?string + */ + protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) + { + $includePaths = []; + + foreach ($packageMap as $item) { + [$package, $installPath] = $item; + + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + + if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) { + $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); + } + + foreach ($package->getIncludePaths() as $includePath) { + $includePath = trim($includePath, '/'); + $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath; + } + } + + if (\count($includePaths) === 0) { + return null; + } + + $includePathsCode = ''; + foreach ($includePaths as $path) { + $includePathsCode .= " " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; + } + + return << $files + * @return ?string + */ + protected function getIncludeFilesFile(array $files, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) + { + // Get the path to each file, and make sure these paths are unique. + $files = array_map( + function (string $functionFile) use ($filesystem, $basePath, $vendorPath): string { + return $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile); + }, + $files + ); + $uniqueFiles = array_unique($files); + if (count($uniqueFiles) < count($files)) { + $this->io->writeError('The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:'); + foreach (array_unique(array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) { + $this->io->writeError(' - '.$duplicateFile.''); + } + } + unset($uniqueFiles); + + $filesCode = ''; + + foreach ($files as $fileIdentifier => $functionFile) { + $filesCode .= ' ' . var_export($fileIdentifier, true) . ' => ' . $functionFile . ",\n"; + } + + if (!$filesCode) { + return null; + } + + return <<isAbsolutePath($path)) { + $path = $basePath . '/' . $path; + } + $path = $filesystem->normalizePath($path); + + $baseDir = ''; + if (strpos($path.'/', $vendorPath.'/') === 0) { + $path = (string) substr($path, strlen($vendorPath)); + $baseDir = '$vendorDir . '; + } else { + $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true)); + if (!$filesystem->isAbsolutePath($path)) { + $baseDir = '$baseDir . '; + $path = '/' . $path; + } + } + + if (Preg::isMatch('{\.phar([\\\\/]|$)}', $path)) { + $baseDir = "'phar://' . " . $baseDir; + } + + return $baseDir . var_export($path, true); + } + + /** + * @param array $packageMap + * @param bool|'php-only' $checkPlatform + * @param string[] $devPackageNames + * @return ?string + */ + protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames) + { + $lowestPhpVersion = Bound::zero(); + $requiredPhp64bit = false; + $requiredExtensions = []; + $extensionProviders = []; + + foreach ($packageMap as $item) { + $package = $item[0]; + foreach (array_merge($package->getReplaces(), $package->getProvides()) as $link) { + if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { + $extensionProviders[$match[1]][] = $link->getConstraint(); + } + } + } + + foreach ($packageMap as $item) { + $package = $item[0]; + // skip dev dependencies platform requirements as platform-check really should only be a production safeguard + if (in_array($package->getName(), $devPackageNames, true)) { + continue; + } + + foreach ($package->getRequires() as $link) { + if ($this->platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } + + if (in_array($link->getTarget(), ['php', 'php-64bit'], true)) { + $constraint = $link->getConstraint(); + if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) { + $lowestPhpVersion = $constraint->getLowerBound(); + } + } + + if ('php-64bit' === $link->getTarget()) { + $requiredPhp64bit = true; + } + + if ($checkPlatform === true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { + // skip extension checks if they have a valid provider/replacer + if (isset($extensionProviders[$match[1]])) { + foreach ($extensionProviders[$match[1]] as $provided) { + if ($provided->matches($link->getConstraint())) { + continue 2; + } + } + } + + if ($match[1] === 'zend-opcache') { + $match[1] = 'zend opcache'; + } + + $extension = var_export($match[1], true); + if ($match[1] === 'pcntl' || $match[1] === 'readline') { + $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; + } else { + $requiredExtensions[$extension] = "extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; + } + } + } + } + + ksort($requiredExtensions); + + $formatToPhpVersionId = static function (Bound $bound): int { + if ($bound->isZero()) { + return 0; + } + + if ($bound->isPositiveInfinity()) { + return 99999; + } + + $version = str_replace('-', '.', $bound->getVersion()); + $chunks = array_map('intval', explode('.', $version)); + + return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2]; + }; + + $formatToHumanReadable = static function (Bound $bound) { + if ($bound->isZero()) { + return 0; + } + + if ($bound->isPositiveInfinity()) { + return 99999; + } + + $version = str_replace('-', '.', $bound->getVersion()); + $chunks = explode('.', $version); + $chunks = array_slice($chunks, 0, 3); + + return implode('.', $chunks); + }; + + $requiredPhp = ''; + $requiredPhpError = ''; + if (!$lowestPhpVersion->isZero()) { + $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>'; + $requiredPhp = 'PHP_VERSION_ID '.$operator.' '.$formatToPhpVersionId($lowestPhpVersion); + $requiredPhpError = '"'.$operator.' '.$formatToHumanReadable($lowestPhpVersion).'"'; + } + + if ($requiredPhp) { + $requiredPhp = <<classMapAuthoritative) { + $file .= <<<'CLASSMAPAUTHORITATIVE' + $loader->setClassMapAuthoritative(true); + +CLASSMAPAUTHORITATIVE; + } + + if ($this->apcu) { + $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true); + $file .= <<setApcuPrefix($apcuPrefix); + +APCU; + } + + if ($useGlobalIncludePath) { + $file .= <<<'INCLUDEPATH' + $loader->setUseIncludePath(true); + +INCLUDEPATH; + } + + if ($targetDirLoader) { + $file .= <<register($prependAutoloader); + + +REGISTER_LOADER; + + if ($useIncludeFiles) { + $file .= << \$file) { + \$requireFile(\$fileIdentifier, \$file); + } + + +INCLUDE_FILES; + } + + $file .= << $path) { + $loader->set($namespace, $path); + } + + $map = require $targetDir . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + /** + * @var string $vendorDir + * @var string $baseDir + */ + $classMap = require $targetDir . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $filesystem = new Filesystem(); + + $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + + $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); + $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1); + + $initializer = ''; + $prefix = "\0Composer\Autoload\ClassLoader\0"; + $prefixLen = strlen($prefix); + if (file_exists($targetDir . '/autoload_files.php')) { + $maps = ['files' => require $targetDir . '/autoload_files.php']; + } else { + $maps = []; + } + + foreach ((array) $loader as $prop => $value) { + if (!is_array($value) || \count($value) === 0 || !str_starts_with($prop, $prefix)) { + continue; + } + $maps[substr($prop, $prefixLen)] = $value; + } + + foreach ($maps as $prop => $value) { + $value = strtr( + var_export($value, true), + [ + $absoluteVendorPathCode => $vendorPathCode, + $absoluteVendorPharPathCode => $vendorPharPathCode, + $absoluteAppBaseDirCode => $appBaseDirCode, + $absoluteAppBaseDirPharCode => $appBaseDirPharCode, + ] + ); + $value = ltrim(Preg::replace('/^ */m', ' $0$0', $value)); + + $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); + if ('files' !== $prop) { + $initializer .= " \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n"; + } + } + + return $file . << $packageMap + * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files' + * @return array|array>|array + */ + protected function parseAutoloadsType(array $packageMap, string $type, RootPackageInterface $rootPackage) + { + $autoloads = []; + + foreach ($packageMap as $item) { + [$package, $installPath] = $item; + + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + + $autoload = $package->getAutoload(); + if ($this->devMode && $package === $rootPackage) { + $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); + } + + // skip misconfigured packages + if (!isset($autoload[$type]) || !is_array($autoload[$type])) { + continue; + } + if (null !== $package->getTargetDir() && $package !== $rootPackage) { + $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); + } + + foreach ($autoload[$type] as $namespace => $paths) { + if (in_array($type, ['psr-4', 'psr-0'], true)) { + // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed + $namespace = ltrim($namespace, '\\'); + } + foreach ((array) $paths as $path) { + if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) { + // remove target-dir from file paths of the root package + if ($package === $rootPackage) { + $targetDir = str_replace('\\', '[\\\\/]', preg_quote(str_replace(['/', '\\'], '', $package->getTargetDir()))); + $path = ltrim(Preg::replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/'); + } else { + // add target-dir from file paths that don't have it + $path = $package->getTargetDir() . '/' . $path; + } + } + + if ($type === 'exclude-from-classmap') { + // first escape user input + $path = Preg::replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/'))); + + // add support for wildcards * and ** + $path = strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']); + + // add support for up-level relative paths + $updir = null; + $path = Preg::replaceCallback( + '{^((?:(?:\\\\\\.){1,2}+/)+)}', + static function ($matches) use (&$updir): string { + // undo preg_quote for the matched string + $updir = str_replace('\\.', '.', $matches[1]); + + return ''; + }, + $path + ); + if (empty($installPath)) { + $installPath = strtr(Platform::getCwd(), '\\', '/'); + } + + $resolvedPath = realpath($installPath . '/' . $updir); + if (false === $resolvedPath) { + continue; + } + $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)'; + continue; + } + + $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path; + + if ($type === 'files') { + $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath; + continue; + } + if ($type === 'classmap') { + $autoloads[] = $relativePath; + continue; + } + + $autoloads[$namespace][] = $relativePath; + } + } + } + + return $autoloads; + } + + /** + * @return string + */ + protected function getFileIdentifier(PackageInterface $package, string $path) + { + // TODO composer v3 change this to sha1 or xxh3? Possibly not worth the potential breakage though + return hash('md5', $package->getName() . ':' . $path); + } + + /** + * Filters out dev-dependencies + * + * @param array $packageMap + * @return array + */ + protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage) + { + $packages = []; + $include = []; + $replacedBy = []; + + foreach ($packageMap as $item) { + $package = $item[0]; + $name = $package->getName(); + $packages[$name] = $package; + foreach ($package->getReplaces() as $replace) { + $replacedBy[$replace->getTarget()] = $name; + } + } + + $add = static function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy): void { + foreach ($package->getRequires() as $link) { + $target = $link->getTarget(); + if (isset($replacedBy[$target])) { + $target = $replacedBy[$target]; + } + if (!isset($include[$target])) { + $include[$target] = true; + if (isset($packages[$target])) { + $add($packages[$target]); + } + } + } + }; + $add($rootPackage); + + return array_filter( + $packageMap, + static function ($item) use ($include): bool { + $package = $item[0]; + foreach ($package->getNames() as $name) { + if (isset($include[$name])) { + return true; + } + } + + return false; + } + ); + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight are sorted alphabetically + * + * @param array $packageMap + * @return array + */ + protected function sortPackageMap(array $packageMap) + { + $packages = []; + $paths = []; + + foreach ($packageMap as $item) { + [$package, $path] = $item; + $name = $package->getName(); + $packages[$name] = $package; + $paths[$name] = $path; + } + + $sortedPackages = PackageSorter::sortPackages($packages); + + $sortedPackageMap = []; + + foreach ($sortedPackages as $package) { + $name = $package->getName(); + $sortedPackageMap[] = [$packages[$name], $paths[$name]]; + } + + return $sortedPackageMap; + } +} + +function composerRequire(string $fileIdentifier, string $file): void +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php b/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php b/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php new file mode 100644 index 0000000..316757f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php @@ -0,0 +1,98 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file is copied from the Symfony package. + * + * (c) Fabien Potencier + */ + +namespace Composer\Autoload; + +use Composer\ClassMapGenerator\FileList; +use Composer\IO\IOInterface; + +/** + * ClassMapGenerator + * + * @author Gyula Sallai + * @author Jordi Boggiano + * + * @deprecated Since Composer 2.4.0 use the composer/class-map-generator package instead + */ +class ClassMapGenerator +{ + /** + * Generate a class map file + * + * @param \Traversable|array $dirs Directories or a single path to search in + * @param string $file The name of the class map file + */ + public static function dump(iterable $dirs, string $file): void + { + $maps = []; + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf('|string|array<\SplFileInfo> $path The path to search in or an iterator + * @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap + * @param ?IOInterface $io IO object + * @param null|string $namespace Optional namespace prefix to filter by + * @param null|'psr-0'|'psr-4'|'classmap' $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules + * @param array $scannedFiles + * @return array A class map array + * @throws \RuntimeException When the path is neither an existing file nor directory + */ + public static function createMap($path, ?string $excluded = null, ?IOInterface $io = null, ?string $namespace = null, ?string $autoloadType = null, array &$scannedFiles = []): array + { + $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(['php', 'inc', 'hh']); + $fileList = new FileList(); + $fileList->files = $scannedFiles; + $generator->avoidDuplicateScans($fileList); + + $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace); + + $classMap = $generator->getClassMap(); + + $scannedFiles = $fileList->files; + + if ($io !== null) { + foreach ($classMap->getPsrViolations() as $msg) { + $io->writeError("$msg"); + } + + foreach ($classMap->getAmbiguousClasses() as $class => $paths) { + if (count($paths) > 1) { + $io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found '. (count($paths) + 1) .'x: in "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' + ); + } else { + $io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found in both "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' + ); + } + } + } + + return $classMap->getMap(); + } +} diff --git a/vendor/composer/composer/src/Composer/Cache.php b/vendor/composer/composer/src/Composer/Cache.php new file mode 100644 index 0000000..e18715f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Cache.php @@ -0,0 +1,389 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Finder\Finder; + +/** + * Reads/writes to a filesystem cache + * + * @author Jordi Boggiano + */ +class Cache +{ + /** @var bool|null */ + private static $cacheCollected = null; + /** @var IOInterface */ + private $io; + /** @var string */ + private $root; + /** @var ?bool */ + private $enabled = null; + /** @var string */ + private $allowlist; + /** @var Filesystem */ + private $filesystem; + /** @var bool */ + private $readOnly; + + /** + * @param string $cacheDir location of the cache + * @param string $allowlist List of characters that are allowed in path names (used in a regex character class) + * @param Filesystem $filesystem optional filesystem instance + * @param bool $readOnly whether the cache is in readOnly mode + */ + public function __construct(IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = false) + { + $this->io = $io; + $this->root = rtrim($cacheDir, '/\\') . '/'; + $this->allowlist = $allowlist; + $this->filesystem = $filesystem ?: new Filesystem(); + $this->readOnly = $readOnly; + + if (!self::isUsable($cacheDir)) { + $this->enabled = false; + } + } + + /** + * @return void + */ + public function setReadOnly(bool $readOnly) + { + $this->readOnly = $readOnly; + } + + /** + * @return bool + */ + public function isReadOnly() + { + return $this->readOnly; + } + + /** + * @return bool + */ + public static function isUsable(string $path) + { + return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); + } + + /** + * @return bool + */ + public function isEnabled() + { + if ($this->enabled === null) { + $this->enabled = true; + + if ( + !$this->readOnly + && ( + (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true)) + || !is_writable($this->root) + ) + ) { + $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.'); + $this->enabled = false; + } + } + + return $this->enabled; + } + + /** + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * @return string|false + */ + public function read(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + + return file_get_contents($this->root . $file); + } + } + + return false; + } + + /** + * @return bool + */ + public function write(string $file, string $contents) + { + $wasEnabled = $this->enabled === true; + + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + + $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); + + $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp'; + try { + return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); + } catch (\ErrorException $e) { + // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to + // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 + if ($wasEnabled) { + clearstatcache(); + $this->enabled = null; + return $this->write($file, $contents); + } + + $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); + if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { + // Remove partial file. + unlink($tempFileName); + + $message = sprintf( + 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available', + $tempFileName, + $m[1], + $m[2], + function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown' + ); + + $this->io->writeError($message); + + return false; + } + + throw $e; + } + } + + return false; + } + + /** + * Copy a file into the cache + * + * @return bool + */ + public function copyFrom(string $file, string $source) + { + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); + + if (!file_exists($source)) { + $this->io->writeError(''.$source.' does not exist, can not write into cache'); + } elseif ($this->io->isDebug()) { + $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); + } + + return $this->filesystem->copy($source, $this->root . $file); + } + + return false; + } + + /** + * Copy a file out of the cache + * + * @return bool + */ + public function copyTo(string $file, string $target) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + try { + touch($this->root . $file, (int) filemtime($this->root . $file), time()); + } catch (\ErrorException $e) { + // fallback in case the above failed due to incorrect ownership + // see https://github.com/composer/composer/issues/4070 + Silencer::call('touch', $this->root . $file); + } + + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + + return $this->filesystem->copy($this->root . $file, $target); + } + } + + return false; + } + + /** + * @return bool + */ + public function gcIsNecessary() + { + if (self::$cacheCollected) { + return false; + } + + self::$cacheCollected = true; + if (Platform::getEnv('COMPOSER_TEST_SUITE')) { + return false; + } + + if (Platform::isInputCompletionProcess()) { + return false; + } + + return !random_int(0, 50); + } + + /** + * @return bool + */ + public function remove(string $file) + { + if ($this->isEnabled() && !$this->readOnly) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return $this->filesystem->unlink($this->root . $file); + } + } + + return false; + } + + /** + * @return bool + */ + public function clear() + { + if ($this->isEnabled() && !$this->readOnly) { + $this->filesystem->emptyDirectory($this->root); + + return true; + } + + return false; + } + + /** + * @return int|false + * @phpstan-return int<0, max>|false + */ + public function getAge(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) { + return abs(time() - $mtime); + } + } + + return false; + } + + /** + * @return bool + */ + public function gc(int $ttl, int $maxSize) + { + if ($this->isEnabled() && !$this->readOnly) { + $expire = new \DateTime(); + $expire->modify('-'.$ttl.' seconds'); + + $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); + foreach ($finder as $file) { + $this->filesystem->unlink($file->getPathname()); + } + + $totalSize = $this->filesystem->size($this->root); + if ($totalSize > $maxSize) { + $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); + while ($totalSize > $maxSize && $iterator->valid()) { + $filepath = $iterator->current()->getPathname(); + $totalSize -= $this->filesystem->size($filepath); + $this->filesystem->unlink($filepath); + $iterator->next(); + } + } + + self::$cacheCollected = true; + + return true; + } + + return false; + } + + public function gcVcsCache(int $ttl): bool + { + if ($this->isEnabled()) { + $expire = new \DateTime(); + $expire->modify('-'.$ttl.' seconds'); + + $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s')); + foreach ($finder as $file) { + $this->filesystem->removeDirectory($file->getPathname()); + } + + self::$cacheCollected = true; + + return true; + } + + return false; + } + + /** + * @return string|false + */ + public function sha1(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return hash_file('sha1', $this->root . $file); + } + } + + return false; + } + + /** + * @return string|false + */ + public function sha256(string $file) + { + if ($this->isEnabled()) { + $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return hash_file('sha256', $this->root . $file); + } + } + + return false; + } + + /** + * @return Finder + */ + protected function getFinder() + { + return Finder::create()->in($this->root)->files(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/AboutCommand.php b/vendor/composer/composer/src/Composer/Command/AboutCommand.php new file mode 100644 index 0000000..b4bd229 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/AboutCommand.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class AboutCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('about') + ->setDescription('Shows a short information about Composer') + ->setHelp( + <<php composer.phar about +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composerVersion = Composer::getVersion(); + + $this->getIO()->write( + <<Composer - Dependency Manager for PHP - version $composerVersion +Composer is a dependency manager tracking local dependencies of your projects and libraries. +See https://getcomposer.org/ for more information. +EOT + ); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php b/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php new file mode 100644 index 0000000..b71f4e2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ArchiveCommand.php @@ -0,0 +1,212 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Composer; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\RepositorySet; +use Composer\Script\ScriptEvents; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Util\Filesystem; +use Composer\Util\Loop; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Creates an archive of a package for distribution. + * + * @author Nils Adermann + */ +class ArchiveCommand extends BaseCommand +{ + use CompletionTrait; + + private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip']; + + protected function configure(): void + { + $this + ->setName('archive') + ->setDescription('Creates an archive of this composer package') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()), + new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS), + new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), + new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' + .' Note that the format will be appended.'), + new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package'), + ]) + ->setHelp( + <<archive command creates an archive of the specified format +containing the files and directories of the Composer project or the specified +package in the specified version and writes it to the specified directory. + +php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]] + +Read more at https://getcomposer.org/doc/03-cli.md#archive +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + $config = null; + + if ($composer) { + $config = $composer->getConfig(); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); + $eventDispatcher = $composer->getEventDispatcher(); + $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); + $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); + } + + if (!$config) { + $config = Factory::createConfig(); + } + + $format = $input->getOption('format') ?? $config->get('archive-format'); + $dir = $input->getOption('dir') ?? $config->get('archive-dir'); + + $returnCode = $this->archive( + $this->getIO(), + $config, + $input->getArgument('package'), + $input->getArgument('version'), + $format, + $dir, + $input->getOption('file'), + $input->getOption('ignore-filters'), + $composer + ); + + if (0 === $returnCode && $composer) { + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD); + } + + return $returnCode; + } + + /** + * @throws \Exception + */ + protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int + { + if ($composer) { + $archiveManager = $composer->getArchiveManager(); + } else { + $factory = new Factory; + $process = new ProcessExecutor(); + $httpDownloader = Factory::createHttpDownloader($io, $config); + $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process); + $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process)); + } + + if ($packageName) { + $package = $this->selectPackage($io, $packageName, $version); + + if (!$package) { + return 1; + } + } else { + $package = $this->requireComposer()->getPackage(); + } + + $io->writeError('Creating the archive into "'.$dest.'".'); + $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters); + $fs = new Filesystem; + $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, true); + + $io->writeError('Created: ', false); + $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath); + + return 0; + } + + /** + * @return (BasePackage&CompletePackageInterface)|false + */ + protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null) + { + $io->writeError('Searching for the specified package.'); + + if ($composer = $this->tryComposer()) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $repo = new CompositeRepository(array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories())); + $minStability = $composer->getPackage()->getMinimumStability(); + } else { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); + $repo = new CompositeRepository($defaultRepos); + $minStability = 'stable'; + } + + if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) { + $minStability = VersionParser::normalizeStability($match[1]); + $version = (string) substr($version, 0, -strlen($match[0])); + } + + $repoSet = new RepositorySet($minStability); + $repoSet->addRepository($repo); + $parser = new VersionParser(); + $constraint = $version !== null ? $parser->parseConstraints($version) : null; + $packages = $repoSet->findPackages(strtolower($packageName), $constraint); + + if (count($packages) > 1) { + $versionSelector = new VersionSelector($repoSet); + $package = $versionSelector->findBestCandidate(strtolower($packageName), $version, $minStability); + if ($package === false) { + $package = reset($packages); + } + + $io->writeError('Found multiple matches, selected '.$package->getPrettyString().'.'); + $io->writeError('Alternatives were '.implode(', ', array_map(static function ($p): string { + return $p->getPrettyString(); + }, $packages)).'.'); + $io->writeError('Please use a more specific constraint to pick a different package.'); + } elseif (count($packages) === 1) { + $package = reset($packages); + $io->writeError('Found an exact match '.$package->getPrettyString().'.'); + } else { + $io->writeError('Could not find a package matching '.$packageName.'.'); + + return false; + } + + if (!$package instanceof CompletePackageInterface) { + throw new \LogicException('Expected a CompletePackageInterface instance but found '.get_class($package)); + } + if (!$package instanceof BasePackage) { + throw new \LogicException('Expected a BasePackage instance but found '.get_class($package)); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/AuditCommand.php b/vendor/composer/composer/src/Composer/Command/AuditCommand.php new file mode 100644 index 0000000..e4a2094 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/AuditCommand.php @@ -0,0 +1,115 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Repository\RepositorySet; +use Composer\Repository\RepositoryUtils; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepository; +use Composer\Advisory\Auditor; +use Composer\Console\Input\InputOption; + +class AuditCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('audit') + ->setDescription('Checks for security vulnerability advisories for installed packages') + ->setDefinition([ + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'), + new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS), + new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']), + ]) + ->setHelp( + <<audit command checks for security vulnerability advisories for installed packages. + +If you do not want to include dev dependencies in the audit you can omit them with --no-dev + +Read more at https://getcomposer.org/doc/03-cli.md#audit +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + $packages = $this->getPackages($composer, $input); + + if (count($packages) === 0) { + $this->getIO()->writeError('No packages - skipping audit.'); + + return 0; + } + + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + foreach ($composer->getRepositoryManager()->getRepositories() as $repo) { + $repoSet->addRepository($repo); + } + + $auditConfig = $composer->getConfig()->get('audit'); + + $abandoned = $input->getOption('abandoned'); + if ($abandoned !== null && !in_array($abandoned, Auditor::ABANDONEDS, true)) { + throw new \InvalidArgumentException('--audit must be one of '.implode(', ', Auditor::ABANDONEDS).'.'); + } + + $abandoned = $abandoned ?? $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL; + + $ignoreSeverities = $input->getOption('ignore-severity') ?? []; + + return min(255, $auditor->audit( + $this->getIO(), + $repoSet, + $packages, + $this->getAuditFormat($input, 'format'), + false, + $auditConfig['ignore'] ?? [], + $abandoned, + $ignoreSeverities + )); + + } + + /** + * @return PackageInterface[] + */ + private function getPackages(Composer $composer, InputInterface $input): array + { + if ($input->getOption('locked')) { + if (!$composer->getLocker()->isLocked()) { + throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); + } + $locker = $composer->getLocker(); + + return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); + } + + $rootPkg = $composer->getPackage(); + $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]); + + if ($input->getOption('no-dev')) { + return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg); + } + + return $installedRepo->getPackages(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BaseCommand.php b/vendor/composer/composer/src/Composer/Command/BaseCommand.php new file mode 100644 index 0000000..85c99f7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BaseCommand.php @@ -0,0 +1,471 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Config; +use Composer\Console\Application; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Plugin\PreCommandRunEvent; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginEvents; +use Composer\Advisory\Auditor; +use Composer\Util\Platform; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Terminal; + +/** + * Base class for Composer commands + * + * @author Ryan Weaver + * @author Konstantin Kudryashov + */ +abstract class BaseCommand extends Command +{ + /** + * @var Composer|null + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * Gets the application instance for this command. + */ + public function getApplication(): Application + { + $application = parent::getApplication(); + if (!$application instanceof Application) { + throw new \RuntimeException('Composer commands can only work with an '.Application::class.' instance set'); + } + + return $application; + } + + /** + * @param bool $required Should be set to false, or use `requireComposer` instead + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + * @throws \RuntimeException + * @return Composer|null + * @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false + */ + public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null) + { + if ($required) { + return $this->requireComposer($disablePlugins, $disableScripts); + } + + return $this->tryComposer($disablePlugins, $disableScripts); + } + + /** + * Retrieves the default Composer\Composer instance or throws + * + * Use this instead of getComposer if you absolutely need an instance + * + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + * @throws \RuntimeException + */ + public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer + { + if (null === $this->composer) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts); + assert($this->composer instanceof Composer); + } else { + throw new \RuntimeException( + 'Could not create a Composer\Composer instance, you must inject '. + 'one if this command is not used with a Composer\Console\Application instance' + ); + } + } + + return $this->composer; + } + + /** + * Retrieves the default Composer\Composer instance or null + * + * Use this instead of getComposer(false) + * + * @param bool|null $disablePlugins If null, reads --no-plugins as default + * @param bool|null $disableScripts If null, reads --no-scripts as default + */ + public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer + { + if (null === $this->composer) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->composer = $application->getComposer(false, $disablePlugins, $disableScripts); + } + } + + return $this->composer; + } + + /** + * @return void + */ + public function setComposer(Composer $composer) + { + $this->composer = $composer; + } + + /** + * Removes the cached composer instance + * + * @return void + */ + public function resetComposer() + { + $this->composer = null; + $this->getApplication()->resetComposer(); + } + + /** + * Whether or not this command is meant to call another command. + * + * This is mainly needed to avoid duplicated warnings messages. + * + * @return bool + */ + public function isProxyCommand() + { + return false; + } + + /** + * @return IOInterface + */ + public function getIO() + { + if (null === $this->io) { + $application = parent::getApplication(); + if ($application instanceof Application) { + $this->io = $application->getIO(); + } else { + $this->io = new NullIO(); + } + } + + return $this->io; + } + + /** + * @return void + */ + public function setIO(IOInterface $io) + { + $this->io = $io; + } + + /** + * @inheritdoc + * + * Backport suggested values definition from symfony/console 6.1+ + * + * TODO drop when PHP 8.1 / symfony 6.1+ can be required + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $definition = $this->getDefinition(); + $name = (string) $input->getCompletionName(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() + && $definition->hasOption($name) + && ($option = $definition->getOption($name)) instanceof InputOption + ) { + $option->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && $definition->hasArgument($name) + && ($argument = $definition->getArgument($name)) instanceof InputArgument + ) { + $argument->complete($input, $suggestions); + } else { + parent::complete($input, $suggestions); + } + } + + /** + * @inheritDoc + * + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + // initialize a plugin-enabled Composer instance, either local or global + $disablePlugins = $input->hasParameterOption('--no-plugins'); + $disableScripts = $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + + if ($this instanceof SelfUpdateCommand) { + $disablePlugins = true; + $disableScripts = true; + } + + $composer = $this->tryComposer($disablePlugins, $disableScripts); + $io = $this->getIO(); + + if (null === $composer) { + $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts); + } + if ($composer) { + $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); + $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); + } + + if (true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) { + $input->setOption('no-progress', true); + } + + $envOptions = [ + 'COMPOSER_NO_AUDIT' => ['no-audit'], + 'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'], + 'COMPOSER_PREFER_STABLE' => ['prefer-stable'], + 'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'], + 'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'], + 'COMPOSER_WITH_DEPENDENCIES' => ['with-dependencies'], + 'COMPOSER_WITH_ALL_DEPENDENCIES' => ['with-all-dependencies'], + ]; + foreach ($envOptions as $envName => $optionNames) { + foreach ($optionNames as $optionName) { + if (true === $input->hasOption($optionName)) { + if (false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) { + $input->setOption($optionName, true); + } + } + } + } + + if (true === $input->hasOption('ignore-platform-reqs')) { + if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) { + $input->setOption('ignore-platform-reqs', true); + + $io->writeError('COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.'); + } + } + + if (true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) { + $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ'); + if (0 === count($input->getOption('ignore-platform-req')) && is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) { + $input->setOption('ignore-platform-req', explode(',', $ignorePlatformReqEnv)); + + $io->writeError('COMPOSER_IGNORE_PLATFORM_REQ is set to ignore '.$ignorePlatformReqEnv.'. You may experience unexpected errors.'); + } + } + + parent::initialize($input, $output); + } + + /** + * Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins + * + * @param mixed $config either a configuration array or a filename to read from, if null it will read from + * the default filename + * @return Composer + */ + protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer + { + $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins'); + $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts'); + + $application = parent::getApplication(); + if ($application instanceof Application && $application->getDisablePluginsByDefault()) { + $disablePlugins = true; + } + if ($application instanceof Application && $application->getDisableScriptsByDefault()) { + $disableScripts = true; + } + + return Factory::create($io, $config, $disablePlugins, $disableScripts); + } + + /** + * Returns preferSource and preferDist values based on the configuration. + * + * @return bool[] An array composed of the preferSource and preferDist values + */ + protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = false) + { + $preferSource = false; + $preferDist = false; + + switch ($config->get('preferred-install')) { + case 'source': + $preferSource = true; + break; + case 'dist': + $preferDist = true; + break; + case 'auto': + default: + // noop + break; + } + + if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) { + return [$preferSource, $preferDist]; + } + + if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) { + if ($input->getOption('prefer-source')) { + throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install'); + } + if ($input->getOption('prefer-dist')) { + throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install'); + } + switch ($input->getOption('prefer-install')) { + case 'dist': + $input->setOption('prefer-dist', true); + break; + case 'source': + $input->setOption('prefer-source', true); + break; + case 'auto': + $preferDist = false; + $preferSource = false; + break; + default: + throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got '.$input->getOption('prefer-install')); + } + } + + if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { + $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); + $preferDist = $input->getOption('prefer-dist'); + } + + return [$preferSource, $preferDist]; + } + + protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface + { + if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) { + throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.'); + } + + if (true === $input->getOption('ignore-platform-reqs')) { + return PlatformRequirementFilterFactory::ignoreAll(); + } + + $ignores = $input->getOption('ignore-platform-req'); + if (count($ignores) > 0) { + return PlatformRequirementFilterFactory::fromBoolOrList($ignores); + } + + return PlatformRequirementFilterFactory::ignoreNothing(); + } + + /** + * @param array $requirements + * + * @return array + */ + protected function formatRequirements(array $requirements) + { + $requires = []; + $requirements = $this->normalizeRequirements($requirements); + foreach ($requirements as $requirement) { + if (!isset($requirement['version'])) { + throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0'); + } + $requires[$requirement['name']] = $requirement['version']; + } + + return $requires; + } + + /** + * @param array $requirements + * + * @return list + */ + protected function normalizeRequirements(array $requirements) + { + $parser = new VersionParser(); + + return $parser->parseNameVersionPairs($requirements); + } + + /** + * @param array $table + * + * @return void + */ + protected function renderTable(array $table, OutputInterface $output) + { + $renderer = new Table($output); + $renderer->setStyle('compact'); + $renderer->setRows($table)->render(); + } + + /** + * @return int + */ + protected function getTerminalWidth() + { + $terminal = new Terminal(); + $width = $terminal->getWidth(); + + if (Platform::isWindows()) { + $width--; + } else { + $width = max(80, $width); + } + + return $width; + } + + /** + * @internal + * @param 'format'|'audit-format' $optName + * @return Auditor::FORMAT_* + */ + protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format'): string + { + if (!$input->hasOption($optName)) { + throw new \LogicException('This should not be called on a Command which has no '.$optName.' option defined.'); + } + + $val = $input->getOption($optName); + if (!in_array($val, Auditor::FORMATS, true)) { + throw new \InvalidArgumentException('--'.$optName.' must be one of '.implode(', ', Auditor::FORMATS).'.'); + } + + return $val; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php b/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php new file mode 100644 index 0000000..1f67f5b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php @@ -0,0 +1,296 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\Link; +use Composer\Package\Package; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\RootPackage; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\CompositeRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Semver\Constraint\Bound; +use Composer\Util\Platform; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Composer\Package\Version\VersionParser; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Util\PackageInfo; + +/** + * Base implementation for commands mapping dependency relationships. + * + * @author Niels Keurentjes + */ +abstract class BaseDependencyCommand extends BaseCommand +{ + protected const ARGUMENT_PACKAGE = 'package'; + protected const ARGUMENT_CONSTRAINT = 'version'; + protected const OPTION_RECURSIVE = 'recursive'; + protected const OPTION_TREE = 'tree'; + + /** @var string[] */ + protected $colors; + + /** + * Execute the command. + * + * @param bool $inverted Whether to invert matching process (why-not vs why behaviour) + * @return int Exit code of the operation. + */ + protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = false): int + { + // Emit command event on startup + $composer = $this->requireComposer(); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $repos = []; + + $repos[] = new RootPackageRepository(clone $composer->getPackage()); + + if ($input->getOption('locked')) { + $locker = $composer->getLocker(); + + if (!$locker->isLocked()) { + throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked'); + } + + $repos[] = $locker->getLockedRepository(true); + $repos[] = new PlatformRepository([], $locker->getPlatformOverrides()); + } else { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $rootPkg = $composer->getPackage(); + + if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) { + $output->writeln('No dependencies installed. Try running composer install or update, or use --locked.'); + + return 1; + } + + $repos[] = $localRepo; + + $platformOverrides = $composer->getConfig()->get('platform') ?: []; + $repos[] = new PlatformRepository([], $platformOverrides); + } + + $installedRepo = new InstalledRepository($repos); + + // Parse package name and constraint + $needle = $input->getArgument(self::ARGUMENT_PACKAGE); + $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*'; + + // Find packages that are or provide the requested package first + $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle); + if (empty($packages)) { + throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); + } + + // If the version we ask for is not installed then we need to locate it in remote repos and add it. + // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. + $matchedPackage = $installedRepo->findPackage($needle, $textConstraint); + if (!$matchedPackage) { + $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); + if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { + $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($textConstraint); + if ($constraint->getLowerBound() !== Bound::zero()) { + $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion()); + $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg])); + } + } else { + $this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.'); + } + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $extraNotice = ''; + if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) { + $extraNotice = ' (version provided by config.platform)'; + } + $this->getIO()->writeError('Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.'); + } elseif ($inverted) { + $this->getIO()->write('Package "'.$needle.'" '.$matchedPackage->getPrettyVersion().' is already installed! To find out why, run `composer why '.$needle.'`'); + return 0; + } + + // Include replaced packages for inverted lookups as they are then the actual starting point to consider + $needles = [$needle]; + if ($inverted) { + foreach ($packages as $package) { + $needles = array_merge($needles, array_map(static function (Link $link): string { + return $link->getTarget(); + }, $package->getReplaces())); + } + } + + // Parse constraint if one was supplied + if ('*' !== $textConstraint) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($textConstraint); + } else { + $constraint = null; + } + + // Parse rendering options + $renderTree = $input->getOption(self::OPTION_TREE); + $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); + + $return = $inverted ? 1 : 0; + + // Resolve dependencies + $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive); + if (empty($results)) { + $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; + $this->getIO()->writeError(sprintf( + 'There is no installed package depending on "%s"%s', + $needle, + $extra + )); + $return = $inverted ? 0 : 1; + } elseif ($renderTree) { + $this->initStyles($output); + $root = $packages[0]; + $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : '')); + $this->printTree($results); + } else { + $this->printTable($output, $results); + } + + if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) { + $composerCommand = 'update'; + + foreach ($composer->getPackage()->getRequires() as $rootRequirement) { + if ($rootRequirement->getTarget() === $needle) { + $composerCommand = 'require'; + break; + } + } + + foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) { + if ($rootRequirement->getTarget() === $needle) { + $composerCommand = 'require --dev'; + break; + } + } + + $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.'); + } + + return $return; + } + + /** + * Assembles and prints a bottom-up table of the dependencies. + * + * @param array{PackageInterface, Link, array|false}[] $results + */ + protected function printTable(OutputInterface $output, array $results): void + { + $table = []; + $doubles = []; + do { + $queue = []; + $rows = []; + foreach ($results as $result) { + /** + * @var PackageInterface $package + * @var Link $link + */ + [$package, $link, $children] = $result; + $unique = (string) $link; + if (isset($doubles[$unique])) { + continue; + } + $doubles[$unique] = true; + $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion(); + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())]; + if (is_array($children)) { + $queue = array_merge($queue, $children); + } + } + $results = $queue; + $table = array_merge($rows, $table); + } while (\count($results) > 0); + + $this->renderTable($table, $output); + } + + /** + * Init styles for tree + */ + protected function initStyles(OutputInterface $output): void + { + $this->colors = [ + 'green', + 'yellow', + 'cyan', + 'magenta', + 'blue', + ]; + + foreach ($this->colors as $color) { + $style = new OutputFormatterStyle($color); + $output->getFormatter()->setStyle($color, $style); + } + } + + /** + * Recursively prints a tree of the selected results. + * + * @param array{PackageInterface, Link, array|false}[] $results Results to be printed at this level. + * @param string $prefix Prefix of the current tree level. + * @param int $level Current level of recursion. + */ + protected function printTree(array $results, string $prefix = '', int $level = 1): void + { + $count = count($results); + $idx = 0; + foreach ($results as $result) { + [$package, $link, $children] = $result; + + $color = $this->colors[$level % count($this->colors)]; + $prevColor = $this->colors[($level - 1) % count($this->colors)]; + $isLast = (++$idx === $count); + $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion(); + $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); + $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); + $packageText = rtrim(sprintf('<%s>%s %s', $color, $nameWithLink, $versionText)); + $linkText = sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); + $circularWarn = $children === false ? '(circular dependency aborted here)' : ''; + $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); + if (is_array($children)) { + $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); + } + } + } + + private function writeTreeLine(string $line): void + { + $io = $this->getIO(); + if (!$io->isDecorated()) { + $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); + } + + $io->write($line); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/BumpCommand.php b/vendor/composer/composer/src/Composer/Command/BumpCommand.php new file mode 100644 index 0000000..4570ce2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/BumpCommand.php @@ -0,0 +1,260 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Locker; +use Composer\Package\Version\VersionBumper; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Repository\PlatformRepository; +use Composer\Util\Silencer; + +/** + * @author Jordi Boggiano + */ +final class BumpCommand extends BaseCommand +{ + private const ERROR_GENERIC = 1; + private const ERROR_LOCK_OUTDATED = 2; + + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('bump') + ->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()), + new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'), + new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'), + ]) + ->setHelp( + <<bump command increases the lower limit of your composer.json requirements +to the currently installed versions. This helps to ensure your dependencies do not +accidentally get downgraded due to some other conflict, and can slightly improve +dependency resolution performance as it limits the amount of package versions +Composer has to look at. + +Running this blindly on libraries is **NOT** recommended as it will narrow down +your allowed dependencies, which may cause dependency hell for your users. +Running it with --dev-only on libraries may be fine however as dev requirements +are local to the library and do not affect consumers of the package. + +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + return $this->doBump( + $this->getIO(), + $input->getOption('dev-only'), + $input->getOption('no-dev-only'), + $input->getOption('dry-run'), + $input->getArgument('packages') + ); + } + + /** + * @param string[] $packagesFilter + * @throws \Seld\JsonLint\ParsingException + */ + public function doBump( + IOInterface $io, + bool $devOnly, + bool $noDevOnly, + bool $dryRun, + array $packagesFilter + ): int { + /** @readonly */ + $composerJsonPath = Factory::getComposerFile(); + + if (!Filesystem::isReadable($composerJsonPath)) { + $io->writeError(''.$composerJsonPath.' is not readable.'); + + return self::ERROR_GENERIC; + } + + $composerJson = new JsonFile($composerJsonPath); + $contents = file_get_contents($composerJson->getPath()); + if (false === $contents) { + $io->writeError(''.$composerJsonPath.' is not readable.'); + + return self::ERROR_GENERIC; + } + + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) { + $io->writeError(''.$composerJsonPath.' is not writable.'); + + return self::ERROR_GENERIC; + } + unset($contents); + + $composer = $this->requireComposer(); + if ($composer->getLocker()->isLocked()) { + if (!$composer->getLocker()->isFresh()) { + $io->writeError('The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.'); + + return self::ERROR_LOCK_OUTDATED; + } + + $repo = $composer->getLocker()->getLockedRepository(true); + } else { + $repo = $composer->getRepositoryManager()->getLocalRepository(); + } + + if ($composer->getPackage()->getType() !== 'project' && !$devOnly) { + $io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.'); + + $contents = $composerJson->read(); + if (!isset($contents['type'])) { + $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); + $io->writeError('Alternatively you can use --dev-only to only bump dependencies within "require-dev".'); + } + unset($contents); + } + + $bumper = new VersionBumper(); + $tasks = []; + if (!$devOnly) { + $tasks['require'] = $composer->getPackage()->getRequires(); + } + if (!$noDevOnly) { + $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); + } + + if (count($packagesFilter) > 0) { + // support proxied args from the update command that contain constraints together with the package names + $packagesFilter = array_map(function ($constraint) { + return Preg::replace('{[:= ].+}', '', $constraint); + }, $packagesFilter); + $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter))); + foreach ($tasks as $key => $reqs) { + foreach ($reqs as $pkgName => $link) { + if (!Preg::isMatch($pattern, $pkgName)) { + unset($tasks[$key][$pkgName]); + } + } + } + } + + $updates = []; + foreach ($tasks as $key => $reqs) { + foreach ($reqs as $pkgName => $link) { + if (PlatformRepository::isPlatformPackage($pkgName)) { + continue; + } + $currentConstraint = $link->getPrettyConstraint(); + + $package = $repo->findPackage($pkgName, '*'); + // name must be provided or replaced + if (null === $package) { + continue; + } + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $bumped = $bumper->bumpRequirement($link->getConstraint(), $package); + + if ($bumped === $currentConstraint) { + continue; + } + + $updates[$key][$pkgName] = $bumped; + } + } + + if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { + $composerDefinition = $composerJson->read(); + foreach ($updates as $key => $packages) { + foreach ($packages as $package => $version) { + $composerDefinition[$key][$package] = $version; + } + } + $composerJson->write($composerDefinition); + } + + $changeCount = array_sum(array_map('count', $updates)); + if ($changeCount > 0) { + if ($dryRun) { + $io->write('' . $composerJsonPath . ' would be updated with:'); + foreach ($updates as $requireType => $packages) { + foreach ($packages as $package => $version) { + $io->write(sprintf(' - %s.%s: %s', $requireType, $package, $version)); + } + } + } else { + $io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).'); + } + } else { + $io->write('No requirements to update in '.$composerJsonPath.'.'); + } + + if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) { + $composer->getLocker()->updateHash($composerJson); + } + + if ($dryRun && $changeCount > 0) { + return self::ERROR_GENERIC; + } + + return 0; + } + + /** + * @param array<'require'|'require-dev', array> $updates + */ + private function updateFileCleanly(JsonFile $json, array $updates): bool + { + $contents = file_get_contents($json->getPath()); + if (false === $contents) { + throw new \RuntimeException('Unable to read '.$json->getPath().' contents.'); + } + + $manipulator = new JsonManipulator($contents); + + foreach ($updates as $key => $packages) { + foreach ($packages as $package => $version) { + if (!$manipulator->addLink($key, $package, $version)) { + return false; + } + } + } + + if (false === file_put_contents($json->getPath(), $manipulator->getContents())) { + throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.'); + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php b/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php new file mode 100644 index 0000000..e252100 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php @@ -0,0 +1,214 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Json\JsonFile; + +class CheckPlatformReqsCommand extends BaseCommand +{ + protected function configure(): void + { + $this->setName('check-platform-reqs') + ->setDescription('Check that platform requirements are satisfied') + ->setDefinition([ + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'), + new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + ]) + ->setHelp( + <<php composer.phar check-platform-reqs + +EOT + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $requires = []; + $removePackages = []; + if ($input->getOption('lock')) { + $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements using the lock file'); + $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); + } else { + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + // fallback to lockfile if installed repo is empty + if (!$installedRepo->getPackages()) { + $this->getIO()->writeError('No vendor dir present, checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements from the lock file'); + $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); + } else { + if ($input->getOption('no-dev')) { + $removePackages = $installedRepo->getDevPackageNames(); + } + + $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements for packages in the vendor dir'); + } + } + if (!$input->getOption('no-dev')) { + foreach ($composer->getPackage()->getDevRequires() as $require => $link) { + $requires[$require] = [$link]; + } + } + + $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]); + foreach ($installedRepo->getPackages() as $package) { + if (in_array($package->getName(), $removePackages, true)) { + continue; + } + foreach ($package->getRequires() as $require => $link) { + $requires[$require][] = $link; + } + } + + ksort($requires); + + $installedRepo->addRepository(new PlatformRepository([], [])); + + $results = []; + $exitCode = 0; + + /** + * @var Link[] $links + */ + foreach ($requires as $require => $links) { + if (PlatformRepository::isPlatformPackage($require)) { + $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require); + if ($candidates) { + $reqResults = []; + foreach ($candidates as $candidate) { + $candidateConstraint = null; + if ($candidate->getName() === $require) { + $candidateConstraint = new Constraint('=', $candidate->getVersion()); + $candidateConstraint->setPrettyString($candidate->getPrettyVersion()); + } else { + foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { + if ($link->getTarget() === $require) { + $candidateConstraint = $link->getConstraint(); + break; + } + } + } + + // safety check for phpstan, but it should not be possible to get a candidate out of findPackagesWithReplacersAndProviders without a constraint matching $require + if (!$candidateConstraint) { + continue; + } + + foreach ($links as $link) { + if (!$link->getConstraint()->matches($candidateConstraint)) { + $reqResults[] = [ + $candidate->getName() === $require ? $candidate->getPrettyName() : $require, + $candidateConstraint->getPrettyString(), + $link, + 'failed', + $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', + ]; + + // skip to next candidate + continue 2; + } + } + + $results[] = [ + $candidate->getName() === $require ? $candidate->getPrettyName() : $require, + $candidateConstraint->getPrettyString(), + null, + 'success', + $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', + ]; + + // candidate matched, skip to next requirement + continue 2; + } + + // show the first error from every failed candidate + $results = array_merge($results, $reqResults); + $exitCode = max($exitCode, 1); + + continue; + } + + $results[] = [ + $require, + 'n/a', + $links[0], + 'missing', + '', + ]; + + $exitCode = max($exitCode, 2); + } + } + + $this->printTable($output, $results, $input->getOption('format')); + + return $exitCode; + } + + /** + * @param mixed[] $results + */ + protected function printTable(OutputInterface $output, array $results, string $format): void + { + $rows = []; + foreach ($results as $result) { + /** + * @var Link|null $link + */ + [$platformPackage, $version, $link, $status, $provider] = $result; + + if ('json' === $format) { + $rows[] = [ + "name" => $platformPackage, + "version" => $version, + "status" => strip_tags($status), + "failed_requirement" => $link instanceof Link ? [ + 'source' => $link->getSource(), + 'type' => $link->getDescription(), + 'target' => $link->getTarget(), + 'constraint' => $link->getPrettyConstraint(), + ] : null, + "provider" => $provider === '' ? null : strip_tags($provider), + ]; + } else { + $rows[] = [ + $platformPackage, + $version, + $link, + $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '', + rtrim($status.' '.$provider), + ]; + } + } + + if ('json' === $format) { + $this->getIO()->write(JsonFile::encode($rows)); + } else { + $this->renderTable($rows, $output); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php b/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php new file mode 100644 index 0000000..77ed517 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php @@ -0,0 +1,107 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Cache; +use Composer\Factory; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author David Neilsen + */ +class ClearCacheCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('clear-cache') + ->setAliases(['clearcache', 'cc']) + ->setDescription('Clears composer\'s internal package cache') + ->setDefinition([ + new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear'), + ]) + ->setHelp( + <<clear-cache deletes all cached packages from composer's +cache directory. + +Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + if ($composer !== null) { + $config = $composer->getConfig(); + } else { + $config = Factory::createConfig(); + } + + $io = $this->getIO(); + + $cachePaths = [ + 'cache-vcs-dir' => $config->get('cache-vcs-dir'), + 'cache-repo-dir' => $config->get('cache-repo-dir'), + 'cache-files-dir' => $config->get('cache-files-dir'), + 'cache-dir' => $config->get('cache-dir'), + ]; + + foreach ($cachePaths as $key => $cachePath) { + // only individual dirs get garbage collected + if ($key === 'cache-dir' && $input->getOption('gc')) { + continue; + } + + $cachePath = realpath($cachePath); + if (!$cachePath) { + $io->writeError("Cache directory does not exist ($key): $cachePath"); + + continue; + } + $cache = new Cache($io, $cachePath); + $cache->setReadOnly($config->get('cache-read-only')); + if (!$cache->isEnabled()) { + $io->writeError("Cache is not enabled ($key): $cachePath"); + + continue; + } + + if ($input->getOption('gc')) { + $io->writeError("Garbage-collecting cache ($key): $cachePath"); + if ($key === 'cache-files-dir') { + $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); + } elseif ($key === 'cache-repo-dir') { + $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024 /* 1GB, this should almost never clear anything that is not outdated */); + } elseif ($key === 'cache-vcs-dir') { + $cache->gcVcsCache($config->get('cache-ttl')); + } + } else { + $io->writeError("Clearing cache ($key): $cachePath"); + $cache->clear(); + } + } + + if ($input->getOption('gc')) { + $io->writeError('All caches garbage-collected.'); + } else { + $io->writeError('All caches cleared.'); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CompletionTrait.php b/vendor/composer/composer/src/Composer/Command/CompletionTrait.php new file mode 100644 index 0000000..444d695 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CompletionTrait.php @@ -0,0 +1,244 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Symfony\Component\Console\Completion\CompletionInput; + +/** + * Adds completion to arguments and options. + * + * @internal + */ +trait CompletionTrait +{ + /** + * @see BaseCommand::requireComposer() + */ + abstract public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer; + + /** + * Suggestion values for "prefer-install" option + * + * @return list + */ + private function suggestPreferInstall(): array + { + return ['dist', 'source', 'auto']; + } + + /** + * Suggest package names from root requirements. + */ + private function suggestRootRequirement(): \Closure + { + return function (CompletionInput $input): array { + $composer = $this->requireComposer(); + + return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires())); + }; + } + + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackage(bool $includeRootPackage = true, bool $includePlatformPackages = false): \Closure + { + return function (CompletionInput $input) use ($includeRootPackage, $includePlatformPackages): array { + $composer = $this->requireComposer(); + $installedRepos = []; + + if ($includeRootPackage) { + $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); + } + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $platformHint = []; + if ($includePlatformPackages) { + if ($locker->isLocked()) { + $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides()); + } else { + $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform')); + } + if ($input->getCompletionValue() === '') { + // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes + $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99]; + foreach ($platformRepo->getPackages() as $pkg) { + foreach ($hintsToFind as $hintPrefix => $hintCount) { + if (str_starts_with($pkg->getName(), $hintPrefix)) { + if ($hintCount === 0 || $hintCount >= 99) { + $platformHint[] = $pkg->getName(); + $hintsToFind[$hintPrefix]++; + } elseif ($hintCount === 1) { + unset($hintsToFind[$hintPrefix]); + $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...'; + } + continue 2; + } + } + } + } else { + $installedRepos[] = $platformRepo; + } + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_merge( + array_map(static function (PackageInterface $package) { + return $package->getName(); + }, $installedRepo->getPackages()), + $platformHint + ); + }; + } + + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure + { + return function (CompletionInput $input) use ($includeRootPackage): array { + $composer = $this->requireComposer(); + $installedRepos = []; + + if ($includeRootPackage) { + $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); + } + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_values(array_unique( + array_map(static function (PackageInterface $package) { + return $package->getType(); + }, $installedRepo->getPackages()) + )); + }; + } + + /** + * Suggest package names available on all configured repositories. + */ + private function suggestAvailablePackage(int $max = 99): \Closure + { + return function (CompletionInput $input) use ($max): array { + if ($max < 1) { + return []; + } + + $composer = $this->requireComposer(); + $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + + $results = []; + $showVendors = false; + if (!str_contains($input->getCompletionValue(), '/')) { + $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR); + $showVendors = true; + } + + // if we get a single vendor, we expand it into its contents already + if (\count($results) <= 1) { + $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); + $showVendors = false; + } + + $results = array_column($results, 'name'); + + if ($showVendors) { + $results = array_map(static function (string $name): string { + return $name.'/'; + }, $results); + + // sort shorter results first to avoid auto-expanding the completion to a longer string than needed + usort($results, static function (string $a, string $b) { + $lenA = \strlen($a); + $lenB = \strlen($b); + if ($lenA === $lenB) { + return $a <=> $b; + } + + return $lenA - $lenB; + }); + + $pinned = []; + + // ensure if the input is an exact match that it is always in the result set + $completionInput = $input->getCompletionValue().'/'; + if (false !== ($exactIndex = array_search($completionInput, $results, true))) { + $pinned[] = $completionInput; + array_splice($results, $exactIndex, 1); + } + + return array_merge($pinned, array_slice($results, 0, $max - \count($pinned))); + } + + return array_slice($results, 0, $max); + }; + } + + /** + * Suggest package names available on all configured repositories or + * platform packages from the ones available on the currently-running PHP + */ + private function suggestAvailablePackageInclPlatform(): \Closure + { + return function (CompletionInput $input): array { + if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) { + $matches = $this->suggestPlatformPackage()($input); + } else { + $matches = []; + } + + return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input)); + }; + } + + /** + * Suggest platform packages from the ones available on the currently-running PHP + */ + private function suggestPlatformPackage(): \Closure + { + return function (CompletionInput $input): array { + $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform')); + + $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*'); + + return array_filter(array_map(static function (PackageInterface $package) { + return $package->getName(); + }, $repos->getPackages()), static function (string $name) use ($pattern): bool { + return Preg::isMatch($pattern, $name); + }); + }; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ConfigCommand.php b/vendor/composer/composer/src/Composer/Command/ConfigCommand.php new file mode 100644 index 0000000..de3bd36 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ConfigCommand.php @@ -0,0 +1,1135 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Advisory\Auditor; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Config; +use Composer\Config\JsonConfigSource; +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Semver\VersionParser; +use Composer\Package\BasePackage; + +/** + * @author Joshua Estes + * @author Jordi Boggiano + */ +class ConfigCommand extends BaseCommand +{ + /** + * List of additional configurable package-properties + * + * @var string[] + */ + protected const CONFIGURABLE_PACKAGE_PROPERTIES = [ + 'name', + 'type', + 'description', + 'homepage', + 'version', + 'minimum-stability', + 'prefer-stable', + 'keywords', + 'license', + 'repositories', + 'suggest', + 'extra', + ]; + + /** + * @var Config + */ + protected $config; + + /** + * @var JsonFile + */ + protected $configFile; + + /** + * @var JsonConfigSource + */ + protected $configSource; + + /** + * @var JsonFile + */ + protected $authConfigFile; + + /** + * @var JsonConfigSource + */ + protected $authConfigSource; + + protected function configure(): void + { + $this + ->setName('config') + ->setDescription('Sets config options') + ->setDefinition([ + new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), + new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), + new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), + new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), + new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), + new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'), + new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* keys in combination with --json'), + new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), + new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), + new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), + new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), + ]) + ->setHelp( + <<%command.full_name% bin-dir bin/ + +To read a config setting: + + %command.full_name% bin-dir + Outputs: bin + +To edit the global config.json file: + + %command.full_name% --global + +To add a repository: + + %command.full_name% repositories.foo vcs https://bar.com + +To remove a repository (repo is a short alias for repositories): + + %command.full_name% --unset repo.foo + +To disable packagist: + + %command.full_name% repo.packagist false + +You can alter repositories in the global config.json file by passing in the +--global option. + +To add or edit suggested packages you can use: + + %command.full_name% suggest.package reason for the suggestion + +To add or edit extra properties you can use: + + %command.full_name% extra.property value + +Or to add a complex value you can use json with: + + %command.full_name% extra.property --json '{"foo":true, "bar": []}' + +To edit the file in an external editor: + + %command.full_name% --editor + +To choose your editor you can set the "EDITOR" env variable. + +To get a list of configuration values in the file: + + %command.full_name% --list + +You can always pass more than one option. As an example, if you want to edit the +global config.json file. + + %command.full_name% --editor --global + +Read more at https://getcomposer.org/doc/03-cli.md#config +EOT + ) + ; + } + + /** + * @throws \Exception + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + parent::initialize($input, $output); + + if ($input->getOption('global') && null !== $input->getOption('file')) { + throw new \RuntimeException('--file and --global can not be combined'); + } + + $io = $this->getIO(); + $this->config = Factory::createConfig($io); + + $configFile = $this->getComposerConfigFile($input, $this->config); + + // Create global composer.json if this was invoked using `composer global config` + if ( + ($configFile === 'composer.json' || $configFile === './composer.json') + && !file_exists($configFile) + && realpath(Platform::getCwd()) === realpath($this->config->get('home')) + ) { + file_put_contents($configFile, "{\n}\n"); + } + + $this->configFile = new JsonFile($configFile, null, $io); + $this->configSource = new JsonConfigSource($this->configFile); + + $authConfigFile = $this->getAuthConfigFile($input, $this->config); + + $this->authConfigFile = new JsonFile($authConfigFile, null, $io); + $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); + + // Initialize the global file if it's not there, ignoring any warnings or notices + if ($input->getOption('global') && !$this->configFile->exists()) { + touch($this->configFile->getPath()); + $this->configFile->write(['config' => new \ArrayObject]); + Silencer::call('chmod', $this->configFile->getPath(), 0600); + } + if ($input->getOption('global') && !$this->authConfigFile->exists()) { + touch($this->authConfigFile->getPath()); + $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject]); + Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); + } + + if (!$this->configFile->exists()) { + throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile)); + } + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Open file in editor + if (true === $input->getOption('editor')) { + $editor = Platform::getEnv('EDITOR'); + if (false === $editor || '' === $editor) { + if (Platform::isWindows()) { + $editor = 'notepad'; + } else { + foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) { + if (exec('which '.$candidate)) { + $editor = $candidate; + break; + } + } + } + } else { + $editor = escapeshellcmd($editor); + } + + $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); + system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); + + return 0; + } + + if (false === $input->getOption('global')) { + $this->config->merge($this->configFile->read(), $this->configFile->getPath()); + $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath()); + } + + $this->getIO()->loadConfiguration($this->config); + + // List the configuration of the file settings + if (true === $input->getOption('list')) { + $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source')); + + return 0; + } + + $settingKey = $input->getArgument('setting-key'); + if (!is_string($settingKey)) { + return 0; + } + + // If the user enters in a config variable, parse it and save to file + if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) { + throw new \RuntimeException('You can not combine a setting value with --unset'); + } + + // show the value if no value is provided + if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { + $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; + $propertiesDefaults = [ + 'type' => 'library', + 'description' => '', + 'homepage' => '', + 'minimum-stability' => 'stable', + 'prefer-stable' => false, + 'keywords' => [], + 'license' => [], + 'suggest' => [], + 'extra' => [], + ]; + $rawData = $this->configFile->read(); + $data = $this->config->all(); + $source = $this->config->getSourceOfValue($settingKey); + + if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { + if (!isset($matches[1])) { + $value = $data['repositories'] ?? []; + } else { + if (!isset($data['repositories'][$matches[1]])) { + throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined'); + } + + $value = $data['repositories'][$matches[1]]; + } + } elseif (strpos($settingKey, '.')) { + $bits = explode('.', $settingKey); + if ($bits[0] === 'extra' || $bits[0] === 'suggest') { + $data = $rawData; + } else { + $data = $data['config']; + } + $match = false; + foreach ($bits as $bit) { + $key = isset($key) ? $key.'.'.$bit : $bit; + $match = false; + if (isset($data[$key])) { + $match = true; + $data = $data[$key]; + unset($key); + } + } + + if (!$match) { + throw new \RuntimeException($settingKey.' is not defined.'); + } + + $value = $data; + } elseif (isset($data['config'][$settingKey])) { + $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); + // ensure we get {} output for properties which are objects + if ($value === []) { + $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); + if ( + isset($schema['properties']['config']['properties'][$settingKey]['type']) + && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true) + ) { + $value = new \stdClass; + } + } + } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) { + $value = $rawData[$settingKey]; + $source = $this->configFile->getPath(); + } elseif (isset($propertiesDefaults[$settingKey])) { + $value = $propertiesDefaults[$settingKey]; + $source = 'defaults'; + } else { + throw new \RuntimeException($settingKey.' is not defined'); + } + + if (is_array($value) || is_object($value) || is_bool($value)) { + $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + $sourceOfConfigValue = ''; + if ($input->getOption('source')) { + $sourceOfConfigValue = ' (' . $source . ')'; + } + + $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET); + + return 0; + } + + $values = $input->getArgument('setting-value'); // what the user is trying to add/change + + $booleanValidator = static function ($val): bool { + return in_array($val, ['true', 'false', '1', '0'], true); + }; + $booleanNormalizer = static function ($val): bool { + return $val !== 'false' && (bool) $val; + }; + + // handle config values + $uniqueConfigValues = [ + 'process-timeout' => ['is_numeric', 'intval'], + 'use-include-path' => [$booleanValidator, $booleanNormalizer], + 'use-github-api' => [$booleanValidator, $booleanNormalizer], + 'preferred-install' => [ + static function ($val): bool { + return in_array($val, ['auto', 'source', 'dist'], true); + }, + static function ($val) { + return $val; + }, + ], + 'gitlab-protocol' => [ + static function ($val): bool { + return in_array($val, ['git', 'http', 'https'], true); + }, + static function ($val) { + return $val; + }, + ], + 'store-auths' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'notify-on-install' => [$booleanValidator, $booleanNormalizer], + 'vendor-dir' => ['is_string', static function ($val) { + return $val; + }], + 'bin-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-dir' => ['is_string', static function ($val) { + return $val; + }], + 'archive-format' => ['is_string', static function ($val) { + return $val; + }], + 'data-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-files-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-repo-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-vcs-dir' => ['is_string', static function ($val) { + return $val; + }], + 'cache-ttl' => ['is_numeric', 'intval'], + 'cache-files-ttl' => ['is_numeric', 'intval'], + 'cache-files-maxsize' => [ + static function ($val): bool { + return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val); + }, + static function ($val) { + return $val; + }, + ], + 'bin-compat' => [ + static function ($val): bool { + return in_array($val, ['auto', 'full', 'proxy', 'symlink']); + }, + static function ($val) { + return $val; + }, + ], + 'discard-changes' => [ + static function ($val): bool { + return in_array($val, ['stash', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('stash' === $val) { + return 'stash'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'autoloader-suffix' => ['is_string', static function ($val) { + return $val === 'null' ? null : $val; + }], + 'sort-packages' => [$booleanValidator, $booleanNormalizer], + 'optimize-autoloader' => [$booleanValidator, $booleanNormalizer], + 'classmap-authoritative' => [$booleanValidator, $booleanNormalizer], + 'apcu-autoloader' => [$booleanValidator, $booleanNormalizer], + 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], + 'disable-tls' => [$booleanValidator, $booleanNormalizer], + 'secure-http' => [$booleanValidator, $booleanNormalizer], + 'bump-after-update' => [ + static function ($val): bool { + return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('dev' === $val || 'no-dev' === $val) { + return $val; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'cafile' => [ + static function ($val): bool { + return file_exists($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'capath' => [ + static function ($val): bool { + return is_dir($val) && Filesystem::isReadable($val); + }, + static function ($val) { + return $val === 'null' ? null : $val; + }, + ], + 'github-expose-hostname' => [$booleanValidator, $booleanNormalizer], + 'htaccess-protect' => [$booleanValidator, $booleanNormalizer], + 'lock' => [$booleanValidator, $booleanNormalizer], + 'allow-plugins' => [$booleanValidator, $booleanNormalizer], + 'platform-check' => [ + static function ($val): bool { + return in_array($val, ['php-only', 'true', 'false', '1', '0'], true); + }, + static function ($val) { + if ('php-only' === $val) { + return 'php-only'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'use-parent-dir' => [ + static function ($val): bool { + return in_array($val, ['true', 'false', 'prompt'], true); + }, + static function ($val) { + if ('prompt' === $val) { + return 'prompt'; + } + + return $val !== 'false' && (bool) $val; + }, + ], + 'audit.abandoned' => [ + static function ($val): bool { + return in_array($val, [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true); + }, + static function ($val) { + return $val; + }, + ], + ]; + $multiConfigValues = [ + 'github-protocols' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + foreach ($vals as $val) { + if (!in_array($val, ['git', 'https', 'ssh'])) { + return 'valid protocols include: git, https, ssh'; + } + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'github-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'gitlab-domains' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'audit.ignore' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + ]; + + // allow unsetting audit config entirely + if ($input->getOption('unset') && $settingKey === 'audit') { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { + if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } + + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + if (isset($uniqueConfigValues[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + if (isset($multiConfigValues[$settingKey])) { + $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); + + return 0; + } + // handle preferred-install per-package config + if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + [$validator] = $uniqueConfigValues['preferred-install']; + if (!$validator($values[0])) { + throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist'); + } + + $this->configSource->addConfigSetting($settingKey, $values[0]); + + return 0; + } + + // handle allow-plugins config setting elements true or false to add/remove + if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + if (true !== $booleanValidator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value', + $values[0] + )); + } + + $normalizedValue = $booleanNormalizer($values[0]); + + $this->configSource->addConfigSetting($settingKey, $normalizedValue); + + return 0; + } + + // handle properties + $uniqueProps = [ + 'name' => ['is_string', static function ($val) { + return $val; + }], + 'type' => ['is_string', static function ($val) { + return $val; + }], + 'description' => ['is_string', static function ($val) { + return $val; + }], + 'homepage' => ['is_string', static function ($val) { + return $val; + }], + 'version' => ['is_string', static function ($val) { + return $val; + }], + 'minimum-stability' => [ + static function ($val): bool { + return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]); + }, + static function ($val): string { + return VersionParser::normalizeStability($val); + }, + ], + 'prefer-stable' => [$booleanValidator, $booleanNormalizer], + ]; + $multiProps = [ + 'keywords' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + 'license' => [ + static function ($vals) { + if (!is_array($vals)) { + return 'array expected'; + } + + return true; + }, + static function ($vals) { + return $vals; + }, + ], + ]; + + if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) { + throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); + } + if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + if (isset($uniqueProps[$settingKey])) { + $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); + + return 0; + } + if (isset($multiProps[$settingKey])) { + $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); + + return 0; + } + + // handle repositories + if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeRepository($matches[1]); + + return 0; + } + + if (2 === count($values)) { + $this->configSource->addRepository($matches[1], [ + 'type' => $values[0], + 'url' => $values[1], + ], $input->getOption('append')); + + return 0; + } + + if (1 === count($values)) { + $value = strtolower($values[0]); + if (true === $booleanValidator($value)) { + if (false === $booleanNormalizer($value)) { + $this->configSource->addRepository($matches[1], false, $input->getOption('append')); + + return 0; + } + } else { + $value = JsonFile::parseJson($values[0]); + $this->configSource->addRepository($matches[1], $value, $input->getOption('append')); + + return 0; + } + } + + throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); + } + + // handle extra + if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $value = $values[0]; + if ($input->getOption('json')) { + $value = JsonFile::parseJson($value); + if ($input->getOption('merge')) { + $currentValue = $this->configFile->read(); + $bits = explode('.', $settingKey); + foreach ($bits as $bit) { + $currentValue = $currentValue[$bit] ?? null; + } + if (is_array($currentValue) && is_array($value)) { + if (array_is_list($currentValue) && array_is_list($value)) { + $value = array_merge($currentValue, $value); + } else { + $value = $value + $currentValue; + } + } + } + } + $this->configSource->addProperty($settingKey, $value); + + return 0; + } + + // handle suggest + if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, implode(' ', $values)); + + return 0; + } + + // handle unsetting extra/suggest + if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + // handle platform + if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]); + + return 0; + } + + // handle unsetting platform + if ($settingKey === 'platform' && $input->getOption('unset')) { + $this->configSource->removeConfigSetting($settingKey); + + return 0; + } + + // handle auth + if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|bearer)\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + + return 0; + } + + if ($matches[1] === 'bitbucket-oauth') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]); + } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) { + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); + } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) { + if (1 !== count($values)) { + throw new \RuntimeException('Too many arguments, expected only one token'); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); + } elseif ($matches[1] === 'http-basic') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]); + } + + return 0; + } + + // handle script + if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); + + return 0; + } + + // handle unsetting other top level properties + if ($input->getOption('unset')) { + $this->configSource->removeProperty($settingKey); + + return 0; + } + + throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (1 !== count($values)) { + throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); + } + + if (true !== $validation = $validator($values[0])) { + throw new \RuntimeException(sprintf( + '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''), + $values[0] + )); + } + + $normalizedValue = $normalizer($values[0]); + + if ($key === 'disable-tls') { + if (!$normalizedValue && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } elseif ($normalizedValue && !$this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); + } + } + + call_user_func([$this->configSource, $method], $key, $normalizedValue); + } + + /** + * @param array{callable, callable} $callbacks Validator and normalizer callbacks + * @param array $values + */ + protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void + { + [$validator, $normalizer] = $callbacks; + if (true !== $validation = $validator($values)) { + throw new \RuntimeException(sprintf( + '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''), + json_encode($values) + )); + } + + call_user_func([$this->configSource, $method], $key, $normalizer($values)); + } + + /** + * Display the contents of the file in a pretty formatted way + * + * @param array $contents + * @param array $rawContents + */ + protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void + { + $origK = $k; + $io = $this->getIO(); + foreach ($contents as $key => $value) { + if ($k === null && !in_array($key, ['config', 'repositories'])) { + continue; + } + + $rawVal = $rawContents[$key] ?? null; + + if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { + $k .= Preg::replace('{^config\.}', '', $key . '.'); + $this->listConfiguration($value, $rawVal, $output, $k, $showSource); + $k = $origK; + + continue; + } + + if (is_array($value)) { + $value = array_map(static function ($val) { + return is_array($val) ? json_encode($val) : $val; + }, $value); + + $value = '['.implode(', ', $value).']'; + } + + if (is_bool($value)) { + $value = var_export($value, true); + } + + $source = ''; + if ($showSource) { + $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')'; + } + + if (null !== $k && 0 === strpos($k, 'repositories')) { + $link = 'https://getcomposer.org/doc/05-repositories.md'; + } else { + $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k); + $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id))); + $id = Preg::replace('{-+}', '-', $id); + $link = 'https://getcomposer.org/doc/06-config.md#' . $id; + } + if (is_string($rawVal) && $rawVal !== $value) { + $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')' . $source, true, IOInterface::QUIET); + } else { + $io->write('[' . $k . $key . '] ' . $value . '' . $source, true, IOInterface::QUIET); + } + } + } + + /** + * Get the local composer.json, global config.json, or the file passed by the user + */ + private function getComposerConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/config.json') + : ($input->getOption('file') ?: Factory::getComposerFile()) + ; + } + + /** + * Get the local auth.json or global auth.json, or if the user passed in a file to use, + * the corresponding auth.json + */ + private function getAuthConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/auth.json') + : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json' + ; + } + + /** + * Suggest setting-keys, while taking given options in account. + */ + private function suggestSettingKeys(): \Closure + { + return function (CompletionInput $input): array { + if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { + return []; + } + + // initialize configuration + $config = Factory::createConfig(); + + // load configuration + $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); + if ($configFile->exists()) { + $config->merge($configFile->read(), $configFile->getPath()); + } + + // load auth-configuration + $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); + if ($authConfigFile->exists()) { + $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); + } + + // collect all configuration setting-keys + $rawConfig = $config->raw(); + $keys = array_merge( + $this->flattenSettingKeys($rawConfig['config']), + $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.') + ); + + // if unsetting … + if ($input->getOption('unset')) { + // … keep only the currently customized setting-keys … + $sources = [$configFile->getPath(), $authConfigFile->getPath()]; + $keys = array_filter( + $keys, + static function (string $key) use ($config, $sources): bool { + return in_array($config->getSourceOfValue($key), $sources, true); + } + ); + + // … else if showing or setting a value … + } else { + // … add all configurable package-properties, no matter if it exist + $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); + + // it would be nice to distinguish between showing and setting + // a value, but that makes the implementation much more complex + // and partially impossible because symfony's implementation + // does not complete arguments followed by other arguments + } + + // add all existing configurable package-properties + if ($configFile->exists()) { + $properties = array_filter( + $configFile->read(), + static function (string $key): bool { + return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true); + }, + ARRAY_FILTER_USE_KEY + ); + + $keys = array_merge( + $keys, + $this->flattenSettingKeys($properties) + ); + } + + // filter settings-keys by completion value + $completionValue = $input->getCompletionValue(); + + if ($completionValue !== '') { + $keys = array_filter( + $keys, + static function (string $key) use ($completionValue): bool { + return str_starts_with($key, $completionValue); + } + ); + } + + sort($keys); + + return array_unique($keys); + }; + } + + /** + * build a flat list of dot-separated setting-keys from given config + * + * @param array $config + * @return string[] + */ + private function flattenSettingKeys(array $config, string $prefix = ''): array + { + $keys = []; + foreach ($config as $key => $value) { + $keys[] = [$prefix . $key]; + // array-lists must not be added to completion + // sub-keys of repository-keys must not be added to completion + if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') { + $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); + } + } + + return array_merge(...$keys); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php b/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php new file mode 100644 index 0000000..368516f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php @@ -0,0 +1,494 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Config; +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer; +use Composer\Installer\ProjectInstaller; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\Package\Version\VersionSelector; +use Composer\Package\AliasPackage; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginBlockedException; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\RepositorySet; +use Composer\Script\ScriptEvents; +use Composer\Util\Silencer; +use Composer\Console\Input\InputArgument; +use Seld\Signal\SignalHandler; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; +use Composer\Json\JsonFile; +use Composer\Config\JsonConfigSource; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Package\Version\VersionParser; +use Composer\Advisory\Auditor; + +/** + * Install a package as new project into new directory. + * + * @author Benjamin Eberlei + * @author Jordi Boggiano + * @author Tobias Munk + * @author Nils Adermann + */ +class CreateProjectCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @var SuggestedPackagesReporter + */ + protected $suggestedPackagesReporter; + + protected function configure(): void + { + $this + ->setName('create-project') + ->setDescription('Creates new project from a package into given directory') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()), + new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), + new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), + new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'), + new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), + new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), + new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), + new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'), + new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'), + ]) + ->setHelp( + <<create-project command creates a new project from a given +package into a new directory. If executed without params and in a directory +with a composer.json file it installs the packages for the current project. + +You can use this command to bootstrap new projects or setup a clean +version-controlled installation for developers of your project. + +php composer.phar create-project vendor/project target-directory [version] + +You can also specify the version with the package name using = or : as separator. + +php composer.phar create-project vendor/project:version target-directory + +To install unstable packages, either specify the version you want, or use the +--stability=dev (where dev can be one of RC, beta, alpha or dev). + +To setup a developer workable version you should create the project using the source +controlled code by appending the '--prefer-source' flag. + +To install a package from another repository than the default one you +can pass the '--repository=https://myrepository.org' flag. + +Read more at https://getcomposer.org/doc/03-cli.md#create-project +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = Factory::createConfig(); + $io = $this->getIO(); + + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, true); + + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); + } + if ($input->getOption('no-custom-installers')) { + $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $input->setOption('no-plugins', true); + } + + if ($input->isInteractive() && $input->getOption('ask')) { + $package = $input->getArgument('package'); + if (null === $package) { + throw new \RuntimeException('Not enough arguments (missing: "package").'); + } + $parts = explode("/", strtolower($package), 2); + $input->setArgument('directory', $io->ask('New project directory ['.array_pop($parts).']: ')); + } + + return $this->installProject( + $io, + $config, + $input, + $input->getArgument('package'), + $input->getArgument('directory'), + $input->getArgument('version'), + $input->getOption('stability'), + $preferSource, + $preferDist, + !$input->getOption('no-dev'), + \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'), + $input->getOption('no-plugins'), + $input->getOption('no-scripts'), + $input->getOption('no-progress'), + $input->getOption('no-install'), + $this->getPlatformRequirementFilter($input), + !$input->getOption('no-secure-http'), + $input->getOption('add-repository') + ); + } + + /** + * @param string|array|null $repositories + * + * @throws \Exception + */ + public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $noInstall = false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = true, bool $addRepository = false): int + { + $oldCwd = Platform::getCwd(); + + if ($repositories !== null && !is_array($repositories)) { + $repositories = (array) $repositories; + } + + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + // we need to manually load the configuration to pass the auth credentials to the io interface! + $io->loadConfiguration($config); + + $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); + + if ($packageName !== null) { + $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); + } else { + $installedFromVcs = false; + } + + if ($repositories !== null && $addRepository && is_file('composer.lock')) { + unlink('composer.lock'); + } + + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts); + + // add the repository to the composer.json and use it for the install run later + if ($repositories !== null && $addRepository) { + foreach ($repositories as $index => $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, true); + $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories(); + $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig); + $configSource = new JsonConfigSource(new JsonFile('composer.json')); + + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + $configSource->addRepository('packagist.org', false); + } else { + $configSource->addRepository($name, $repoConfig, false); + } + + $composer = $this->createComposerInstance($input, $io, null, $disablePlugins); + } + } + + $process = $composer->getLoop()->getProcessExecutor(); + $fs = new Filesystem($process); + + // dispatch event + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); + + // use the new config including the newly installed project + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + // install dependencies of the created project + if ($noInstall === false) { + $composer->getInstallationManager()->setOutputProgress(!$noProgress); + + $installer = Installer::create($io, $composer); + $installer->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode($installDevPackages) + ->setPlatformRequirementFilter($platformRequirementFilter) + ->setSuggestedPackagesReporter($this->suggestedPackagesReporter) + ->setOptimizeAutoloader($config->get('optimize-autoloader')) + ->setClassMapAuthoritative($config->get('classmap-authoritative')) + ->setApcuAutoloader($config->get('apcu-autoloader')) + ->setAudit(!$input->getOption('no-audit')) + ->setAuditFormat($this->getAuditFormat($input)); + + if (!$composer->getLocker()->isLocked()) { + $installer->setUpdate(true); + } + + if ($disablePlugins) { + $installer->disablePlugins(); + } + + try { + $status = $installer->run(); + if (0 !== $status) { + return $status; + } + } catch (PluginBlockedException $e) { + $io->writeError('Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.'); + $io->writeError('You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.'); + throw $e; + } + } + + $hasVcs = $installedFromVcs; + if ( + !$input->getOption('keep-vcs') + && $installedFromVcs + && ( + $input->getOption('remove-vcs') + || !$io->isInteractive() + || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? ') + ) + ) { + $finder = new Finder(); + $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(false)->ignoreDotFiles(false); + foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) { + $finder->name($vcsName); + } + + try { + $dirs = iterator_to_array($finder); + unset($finder); + foreach ($dirs as $dir) { + if (!$fs->removeDirectory((string) $dir)) { + throw new \RuntimeException('Could not remove '.$dir); + } + } + } catch (\Exception $e) { + $io->writeError('An error occurred while removing the VCS metadata: '.$e->getMessage().''); + } + + $hasVcs = false; + } + + // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone + if (!$hasVcs) { + $package = $composer->getPackage(); + $configSource = new JsonConfigSource(new JsonFile('composer.json')); + foreach (BasePackage::$supportedLinkTypes as $type => $meta) { + foreach ($package->{'get'.$meta['method']}() as $link) { + if ($link->getPrettyConstraint() === 'self.version') { + $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion()); + } + } + } + } + + // dispatch event + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); + + chdir($oldCwd); + + return 0; + } + + /** + * @param array|null $repositories + * + * @throws \Exception + */ + protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool + { + $parser = new VersionParser(); + $requirements = $parser->parseNameVersionPairs([$packageName]); + $name = strtolower($requirements[0]['name']); + if (!$packageVersion && isset($requirements[0]['version'])) { + $packageVersion = $requirements[0]['version']; + } + + // if no directory was specified, use the 2nd part of the package name + if (null === $directory) { + $parts = explode("/", $name, 2); + $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts); + } + $directory = rtrim($directory, '/\\'); + + $process = new ProcessExecutor($io); + $fs = new Filesystem($process); + if (!$fs->isAbsolutePath($directory)) { + $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory; + } + if ('' === $directory) { + throw new \UnexpectedValueException('Got an empty target directory, something went wrong'); + } + + // set the base dir to ensure $config->all() below resolves the correct absolute paths to vendor-dir etc + $config->setBaseDir($directory); + if (!$secureHttp) { + $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); + } + + $io->writeError('Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"'); + + if (file_exists($directory)) { + if (!is_dir($directory)) { + throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.'); + } + if (!$fs->isDirEmpty($directory)) { + throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.'); + } + } + + if (null === $stability) { + if (null === $packageVersion) { + $stability = 'stable'; + } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) { + $stability = $match[1]; + } else { + $stability = VersionParser::parseStability($packageVersion); + } + } + + $stability = VersionParser::normalizeStability($stability); + + if (!isset(BasePackage::STABILITIES[$stability])) { + throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES))); + } + + $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); + $config = $composer->getConfig(); + // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all() + $config->setBaseDir($directory); + $rm = $composer->getRepositoryManager(); + + $repositorySet = new RepositorySet($stability); + if (null === $repositories) { + $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm))); + } else { + foreach ($repositories as $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + continue; + } + + // disable symlinking for the root package by default as that most likely makes no sense + if (($repoConfig['type'] ?? null) === 'path' && !isset($repoConfig['options']['symlink'])) { + $repoConfig['options']['symlink'] = false; + } + + $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm)); + } + } + + $platformOverrides = $config->get('platform'); + $platformRepo = new PlatformRepository([], $platformOverrides); + + // find the latest version if there are multiple + $versionSelector = new VersionSelector($repositorySet, $platformRepo); + $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io); + + if (!$package) { + $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability"); + if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) { + throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version, PHP extensions and Composer version.'); + } + + throw new \InvalidArgumentException($errorMessage .'.'); + } + + // handler Ctrl+C aborts gracefully + @mkdir($directory, 0777, true); + if (false !== ($realDir = realpath($directory))) { + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $fs = new Filesystem(); + $fs->removeDirectory($realDir); + $handler->exitWithLastSignal(); + }); + } + + // avoid displaying 9999999-dev as version if default-branch was selected + if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); + + if ($disablePlugins) { + $io->writeError('Plugins have been disabled.'); + } + + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $dm = $composer->getDownloadManager(); + $dm->setPreferSource($preferSource) + ->setPreferDist($preferDist); + + $projectInstaller = new ProjectInstaller($directory, $dm, $fs); + $im = $composer->getInstallationManager(); + $im->setOutputProgress(!$noProgress); + $im->addInstaller($projectInstaller); + $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]); + $im->notifyInstalls($io); + + // collect suggestions + $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); + + $installedFromVcs = 'source' === $package->getInstallationSource(); + + $io->writeError('Created project in ' . $directory . ''); + chdir($directory); + + // ensure that the env var being set does not interfere with create-project + // as it is probably not meant to be used here, so we do not use it if a composer.json can be found + // in the project + if (file_exists($directory.'/composer.json') && Platform::getEnv('COMPOSER') !== false) { + Platform::clearEnv('COMPOSER'); + } + + Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion()); + + // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install + if (isset($signalHandler)) { + $signalHandler->unregister(); + } + + return $installedFromVcs; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DependsCommand.php b/vendor/composer/composer/src/Composer/Command/DependsCommand.php new file mode 100644 index 0000000..07e58c1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DependsCommand.php @@ -0,0 +1,58 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; + +/** + * @author Niels Keurentjes + */ +class DependsCommand extends BaseDependencyCommand +{ + use CompletionTrait; + + /** + * Configure command metadata. + */ + protected function configure(): void + { + $this + ->setName('depends') + ->setAliases(['why']) + ->setDescription('Shows which packages cause the given package to be installed') + ->setDefinition([ + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true, true)), + new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), + ]) + ->setHelp( + <<php composer.phar depends composer/composer + +Read more at https://getcomposer.org/doc/03-cli.md#depends-why +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + return parent::doExecute($input, $output); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php b/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php new file mode 100644 index 0000000..8ed8f94 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php @@ -0,0 +1,959 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Advisory\Auditor; +use Composer\Composer; +use Composer\Factory; +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\IO\BufferIO; +use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; +use Composer\Package\Locker; +use Composer\Package\RootPackage; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Repository\ComposerRepository; +use Composer\Repository\FilesystemRepository; +use Composer\Repository\PlatformRepository; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\RepositorySet; +use Composer\Repository\RootPackageRepository; +use Composer\Util\ConfigValidator; +use Composer\Util\Git; +use Composer\Util\IniHelper; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\StreamContextFactory; +use Composer\Util\Platform; +use Composer\SelfUpdate\Keys; +use Composer\SelfUpdate\Versions; +use Composer\IO\NullIO; +use Composer\Package\CompletePackageInterface; +use Composer\XdebugHandler\XdebugHandler; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\ExecutableFinder; +use Composer\Util\Http\ProxyManager; +use Composer\Util\Http\RequestProxy; + +/** + * @author Jordi Boggiano + */ +class DiagnoseCommand extends BaseCommand +{ + /** @var HttpDownloader */ + protected $httpDownloader; + + /** @var ProcessExecutor */ + protected $process; + + /** @var int */ + protected $exitCode = 0; + + protected function configure(): void + { + $this + ->setName('diagnose') + ->setDescription('Diagnoses the system to identify common errors') + ->setHelp( + <<diagnose command checks common errors to help debugging problems. + +The process exit code will be 1 in case of warnings and 2 for errors. + +Read more at https://getcomposer.org/doc/03-cli.md#diagnose +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->tryComposer(); + $io = $this->getIO(); + + if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $io->write('Checking composer.json: ', false); + $this->outputResult($this->checkComposerSchema()); + + if ($composer->getLocker()->isLocked()) { + $io->write('Checking composer.lock: ', false); + $this->outputResult($this->checkComposerLockSchema($composer->getLocker())); + } + + $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io); + } else { + $this->process = new ProcessExecutor($io); + } + + if ($composer) { + $config = $composer->getConfig(); + } else { + $config = Factory::createConfig(); + } + + $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); + $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO); + + $this->httpDownloader = Factory::createHttpDownloader($io, $config); + + $io->write('Checking platform settings: ', false); + $this->outputResult($this->checkPlatform()); + + $io->write('Checking git settings: ', false); + $this->outputResult($this->checkGit()); + + $io->write('Checking http connectivity to packagist: ', false); + $this->outputResult($this->checkHttp('http', $config)); + + $io->write('Checking https connectivity to packagist: ', false); + $this->outputResult($this->checkHttp('https', $config)); + + foreach ($config->getRepositories() as $repo) { + if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) { + $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader); + $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl'); + if (PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } + $url = $reflMethod->invoke($composerRepo); + if (!str_starts_with($url, 'http')) { + continue; + } + if (str_starts_with($url, 'https://repo.packagist.org')) { + continue; + } + $io->write('Checking connectivity to ' . $repo['url'].': ', false); + $this->outputResult($this->checkComposerRepo($url, $config)); + } + } + + $proxyManager = ProxyManager::getInstance(); + $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https']; + try { + foreach ($protos as $proto) { + $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org'); + if ($proxy->getStatus() !== '') { + $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP'; + $io->write('Checking '.$type.' proxy with '.$proto.': ', false); + $this->outputResult($this->checkHttpProxy($proxy, $proto)); + } + } + } catch (TransportException $e) { + $io->write('Checking HTTP proxy: ', false); + $status = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + $this->outputResult(is_string($status) ? $status : $e); + } + + if (count($oauth = $config->get('github-oauth')) > 0) { + foreach ($oauth as $domain => $token) { + $io->write('Checking '.$domain.' oauth access: ', false); + $this->outputResult($this->checkGithubOauth($domain, $token)); + } + } else { + $io->write('Checking github.com rate limit: ', false); + try { + $rate = $this->getGithubRateLimit('github.com'); + if (!is_array($rate)) { + $this->outputResult($rate); + } elseif (10 > $rate['remaining']) { + $io->write('WARNING'); + $io->write(sprintf( + 'GitHub has a rate limit on their API. ' + . 'You currently have %u ' + . 'out of %u requests left.' . PHP_EOL + . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL + . ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens', + $rate['remaining'], + $rate['limit'] + )); + } else { + $this->outputResult(true); + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $e->getCode() === 401) { + $this->outputResult('The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it'); + } else { + $this->outputResult($e); + } + } + } + + $io->write('Checking disk free space: ', false); + $this->outputResult($this->checkDiskSpace($config)); + + if (strpos(__FILE__, 'phar:') === 0) { + $io->write('Checking pubkeys: ', false); + $this->outputResult($this->checkPubKeys($config)); + + $io->write('Checking Composer version: ', false); + $this->outputResult($this->checkVersion($config)); + } + + $io->write('Checking Composer and its dependencies for vulnerabilities: ', false); + $this->outputResult($this->checkComposerAudit($config)); + + $io->write(sprintf('Composer version: %s', Composer::getVersion())); + + $platformOverrides = $config->get('platform') ?: []; + $platformRepo = new PlatformRepository([], $platformOverrides); + $phpPkg = $platformRepo->findPackage('php', '*'); + $phpVersion = $phpPkg->getPrettyVersion(); + if ($phpPkg instanceof CompletePackageInterface && str_contains((string) $phpPkg->getDescription(), 'overridden')) { + $phpVersion .= ' - ' . $phpPkg->getDescription(); + } + + $io->write(sprintf('PHP version: %s', $phpVersion)); + + if (defined('PHP_BINARY')) { + $io->write(sprintf('PHP binary path: %s', PHP_BINARY)); + } + + $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? ''.OPENSSL_VERSION_TEXT.'' : 'missing')); + $io->write('curl version: ' . $this->getCurlVersion()); + + $finder = new ExecutableFinder; + $hasSystemUnzip = (bool) $finder->find('unzip'); + $bin7zip = ''; + if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\Program Files\7-Zip'])) { + $bin7zip = '7z'; + } + if (!Platform::isWindows() && !$hasSystem7zip && $hasSystem7zip = (bool) $finder->find('7zz')) { + $bin7zip = '7zz'; + } + + $io->write( + 'zip: ' . (extension_loaded('zip') ? 'extension present' : 'extension not loaded') + . ', ' . ($hasSystemUnzip ? 'unzip present' : 'unzip not available') + . ', ' . ($hasSystem7zip ? '7-Zip present ('.$bin7zip.')' : '7-Zip not available') + . (($hasSystem7zip || $hasSystemUnzip) && !function_exists('proc_open') ? ', proc_open is disabled or not present, unzip/7-z will not be usable' : '') + ); + + return $this->exitCode; + } + + /** + * @return string|true + */ + private function checkComposerSchema() + { + $validator = new ConfigValidator($this->getIO()); + [$errors, , $warnings] = $validator->validate(Factory::getComposerFile()); + + if ($errors || $warnings) { + $messages = [ + 'error' => $errors, + 'warning' => $warnings, + ]; + + $output = ''; + foreach ($messages as $style => $msgs) { + foreach ($msgs as $msg) { + $output .= '<' . $style . '>' . $msg . '' . PHP_EOL; + } + } + + return rtrim($output); + } + + return true; + } + + /** + * @return string|true + */ + private function checkComposerLockSchema(Locker $locker) + { + $json = $locker->getJsonFile(); + + try { + $json->validateSchema(JsonFile::LOCK_SCHEMA); + } catch (JsonValidationException $e) { + $output = ''; + foreach ($e->getErrors() as $error) { + $output .= ''.$error.''.PHP_EOL; + } + + return trim($output); + } + + return true; + } + + private function checkGit(): string + { + if (!function_exists('proc_open')) { + return 'proc_open is not available, git cannot be used'; + } + + $this->process->execute(['git', 'config', 'color.ui'], $output); + if (strtolower(trim($output)) === 'always') { + return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; + } + + $gitVersion = Git::getVersion($this->process); + if (null === $gitVersion) { + return 'No git process found'; + } + + if (version_compare('2.24.0', $gitVersion, '>')) { + return 'Your git version ('.$gitVersion.') is too old and possibly will cause issues. Please upgrade to git 2.24 or above'; + } + + return 'OK git version '.$gitVersion.''; + } + + /** + * @return string|string[]|true + */ + private function checkHttp(string $proto, Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $result = []; + if ($proto === 'https' && $config->get('disable-tls') === true) { + $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; + } + + try { + $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json'); + } catch (TransportException $e) { + $hints = HttpDownloader::getExceptionHints($e); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $result[] = $hint; + } + } + + $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; + } + + if (isset($tlsWarning)) { + $result[] = $tlsWarning; + } + + if (count($result) > 0) { + return $result; + } + + return true; + } + + /** + * @return string|string[]|true + */ + private function checkComposerRepo(string $url, Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $result = []; + if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) { + $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; + } + + try { + $this->httpDownloader->get($url); + } catch (TransportException $e) { + $hints = HttpDownloader::getExceptionHints($e); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $result[] = $hint; + } + } + + $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; + } + + if (isset($tlsWarning)) { + $result[] = $tlsWarning; + } + + if (count($result) > 0) { + return $result; + } + + return true; + } + + /** + * @return string|\Exception + */ + private function checkHttpProxy(RequestProxy $proxy, string $protocol) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + try { + $proxyStatus = $proxy->getStatus(); + + if ($proxy->isExcludedByNoProxy()) { + return 'SKIP Because repo.packagist.org is '.$proxyStatus.''; + } + + $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson(); + if (isset($json['provider-includes'])) { + $hash = reset($json['provider-includes']); + $hash = $hash['sha256']; + $path = str_replace('%hash%', $hash, key($json['provider-includes'])); + $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); + + if (hash('sha256', $provider) !== $hash) { + return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; + } + } + + return 'OK '.$proxyStatus.''; + } catch (\Exception $e) { + return $e; + } + } + + /** + * @return string|\Exception + */ + private function checkGithubOauth(string $domain, string $token) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); + try { + $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; + + $response = $this->httpDownloader->get($url, [ + 'retry-auth-failure' => false, + ]); + + $expiration = $response->getHeader('github-authentication-token-expiration'); + + if ($expiration === null) { + return 'OK does not expire'; + } + + return 'OK expires on '. $expiration .''; + } catch (\Exception $e) { + if ($e instanceof TransportException && $e->getCode() === 401) { + return 'The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it'; + } + + return $e; + } + } + + /** + * @param string $token + * @throws TransportException + * @return mixed|string + */ + private function getGithubRateLimit(string $domain, ?string $token = null) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + if ($token) { + $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); + } + + $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; + $data = $this->httpDownloader->get($url, ['retry-auth-failure' => false])->decodeJson(); + + return $data['resources']['core']; + } + + /** + * @return string|true + */ + private function checkDiskSpace(Config $config) + { + if (!function_exists('disk_free_space')) { + return true; + } + + $minSpaceFree = 1024 * 1024; + if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) + || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) + ) { + return 'The disk hosting '.$dir.' is full'; + } + + return true; + } + + /** + * @return string[]|true + */ + private function checkPubKeys(Config $config) + { + $home = $config->get('home'); + $errors = []; + $io = $this->getIO(); + + if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) { + $io->write(''); + } + + if (file_exists($home.'/keys.tags.pub')) { + $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub')); + } else { + $errors[] = 'Missing pubkey for tags verification'; + } + + if (file_exists($home.'/keys.dev.pub')) { + $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub')); + } else { + $errors[] = 'Missing pubkey for dev verification'; + } + + if ($errors) { + $errors[] = 'Run composer self-update --update-keys to set them up'; + } + + return $errors ?: true; + } + + /** + * @return string|\Exception|true + */ + private function checkVersion(Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $versionsUtil = new Versions($config, $this->httpDownloader); + try { + $latest = $versionsUtil->getLatest(); + } catch (\Exception $e) { + return $e; + } + + if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { + return 'You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')'; + } + + return true; + } + + /** + * @return string|true + */ + private function checkComposerAudit(Config $config) + { + $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json'); + if (!$installedJson->exists()) { + return 'Could not find Composer\'s installed.json, this must be a non-standard Composer installation.'; + } + + $localRepo = new FilesystemRepository($installedJson); + $version = Composer::getVersion(); + $packages = $localRepo->getCanonicalPackages(); + if ($version !== '@package_version@') { + $versionParser = new VersionParser(); + $normalizedVersion = $versionParser->normalize($version); + $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version); + $packages[] = $rootPkg; + } + $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader)); + + try { + $io = new BufferIO(); + $result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, true, [], Auditor::ABANDONED_IGNORE); + } catch (\Throwable $e) { + return 'Failed performing audit: '.$e->getMessage().''; + } + + if ($result > 0) { + return 'Audit found some issues:' . PHP_EOL . $io->getOutput(); + } + + return true; + } + + private function getCurlVersion(): string + { + if (extension_loaded('curl')) { + if (!HttpDownloader::isCurlEnabled()) { + return 'disabled via disable_functions, using php streams fallback, which reduces performance'; + } + + $version = curl_version(); + $hasZstd = isset($version['features']) && defined('CURL_VERSION_ZSTD') && 0 !== ($version['features'] & CURL_VERSION_ZSTD); + + return ''.$version['version'].' '. + 'libz '.($version['libz_version'] ?? 'missing').' '. + 'brotli '.($version['brotli_version'] ?? 'missing').' '. + 'zstd '.($hasZstd ? 'supported' : 'missing').' '. + 'ssl '.($version['ssl_version'] ?? 'missing').''; + } + + return 'missing, using php streams fallback, which reduces performance'; + } + + /** + * @param bool|string|string[]|\Exception $result + */ + private function outputResult($result): void + { + $io = $this->getIO(); + if (true === $result) { + $io->write('OK'); + + return; + } + + $hadError = false; + $hadWarning = false; + if ($result instanceof \Exception) { + $result = '['.get_class($result).'] '.$result->getMessage().''; + } + + if (!$result) { + // falsey results should be considered as an error, even if there is nothing to output + $hadError = true; + } else { + if (!is_array($result)) { + $result = [$result]; + } + foreach ($result as $message) { + if (false !== strpos($message, '')) { + $hadError = true; + } elseif (false !== strpos($message, '')) { + $hadWarning = true; + } + } + } + + if ($hadError) { + $io->write('FAIL'); + $this->exitCode = max($this->exitCode, 2); + } elseif ($hadWarning) { + $io->write('WARNING'); + $this->exitCode = max($this->exitCode, 1); + } + + if ($result) { + foreach ($result as $message) { + $io->write(trim($message)); + } + } + } + + /** + * @return string|true + */ + private function checkPlatform() + { + $output = ''; + $out = static function ($msg, $style) use (&$output): void { + $output .= '<'.$style.'>'.$msg.''.PHP_EOL; + }; + + // code below taken from getcomposer.org/installer, any changes should be made there and replicated here + $errors = []; + $warnings = []; + $displayIniMessage = false; + + $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage(); + $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; + + if (!function_exists('json_decode')) { + $errors['json'] = true; + } + + if (!extension_loaded('Phar')) { + $errors['phar'] = true; + } + + if (!extension_loaded('filter')) { + $errors['filter'] = true; + } + + if (!extension_loaded('hash')) { + $errors['hash'] = true; + } + + if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { + $errors['iconv_mbstring'] = true; + } + + if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { + $errors['allow_url_fopen'] = true; + } + + if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { + $errors['ioncube'] = ioncube_loader_version(); + } + + if (\PHP_VERSION_ID < 70205) { + $errors['php'] = PHP_VERSION; + } + + if (!extension_loaded('openssl')) { + $errors['openssl'] = true; + } + + if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { + $warnings['openssl_version'] = true; + } + + if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { + $warnings['apc_cli'] = true; + } + + if (!extension_loaded('zlib')) { + $warnings['zlib'] = true; + } + + ob_start(); + phpinfo(INFO_GENERAL); + $phpinfo = ob_get_clean(); + if (is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { + $configure = $match[1]; + + if (str_contains($configure, '--enable-sigchild')) { + $warnings['sigchild'] = true; + } + + if (str_contains($configure, '--with-curlwrappers')) { + $warnings['curlwrappers'] = true; + } + } + + if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) { + $warnings['xdebug_profile'] = true; + } elseif (XdebugHandler::isXdebugActive()) { + $warnings['xdebug_loaded'] = true; + } + + if (defined('PHP_WINDOWS_VERSION_BUILD') + && (version_compare(PHP_VERSION, '7.2.23', '<') + || (version_compare(PHP_VERSION, '7.3.0', '>=') + && version_compare(PHP_VERSION, '7.3.10', '<')))) { + $warnings['onedrive'] = PHP_VERSION; + } + + if (extension_loaded('uopz') + && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN) + || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) { + $warnings['uopz'] = true; + } + + if (!empty($errors)) { + foreach ($errors as $error => $current) { + switch ($error) { + case 'json': + $text = PHP_EOL."The json extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-json"; + break; + + case 'phar': + $text = PHP_EOL."The phar extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-phar"; + break; + + case 'filter': + $text = PHP_EOL."The filter extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-filter"; + break; + + case 'hash': + $text = PHP_EOL."The hash extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-hash"; + break; + + case 'iconv_mbstring': + $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL; + $text .= "Install either of them or recompile php without --disable-iconv"; + break; + + case 'php': + $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher."; + break; + + case 'allow_url_fopen': + $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; + $text .= " allow_url_fopen = On"; + $displayIniMessage = true; + break; + + case 'ioncube': + $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL; + $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL; + $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; + $displayIniMessage = true; + break; + + case 'openssl': + $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL; + $text .= "If possible you should enable it or recompile php with --with-openssl"; + break; + + default: + throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error)); + } + $out($text, 'error'); + } + + $output .= PHP_EOL; + } + + if (!empty($warnings)) { + foreach ($warnings as $warning => $current) { + switch ($warning) { + case 'apc_cli': + $text = "The apc.enable_cli setting is incorrect.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; + $text .= " apc.enable_cli = Off"; + $displayIniMessage = true; + break; + + case 'zlib': + $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL; + $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL; + $displayIniMessage = true; + break; + + case 'sigchild': + $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL; + $text .= "Recompile it without this flag if possible, see also:".PHP_EOL; + $text .= " https://bugs.php.net/bug.php?id=22999"; + break; + + case 'curlwrappers': + $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL; + $text .= " Recompile it without this flag if possible"; + break; + + case 'openssl_version': + // Attempt to parse version number out, fallback to whole string value. + $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true); + $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT; + + $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; + $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; + break; + + case 'xdebug_loaded': + $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; + $text .= " Disabling it when using Composer is recommended."; + break; + + case 'xdebug_profile': + $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL; + $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL; + $text .= " xdebug.profiler_enabled = 0"; + $displayIniMessage = true; + break; + + case 'onedrive': + $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL; + $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL; + break; + + case 'uopz': + $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL; + $text .= "Disabling it when using Composer is recommended."; + break; + + default: + throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning)); + } + $out($text, 'comment'); + } + } + + if ($displayIniMessage) { + $out($iniMessage, 'comment'); + } + + if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) { + $warnings['ipresolve'] = true; + $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment'); + } + + return count($warnings) === 0 && count($errors) === 0 ? true : $output; + } + + /** + * Check if allow_url_fopen is ON + * + * @return string|true + */ + private function checkConnectivity() + { + if (!ini_get('allow_url_fopen')) { + return 'SKIP Because allow_url_fopen is missing.'; + } + + return true; + } + + /** + * @return string|true + */ + private function checkConnectivityAndComposerNetworkHttpEnablement() + { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + + $result = $this->checkComposerNetworkHttpEnablement(); + if ($result !== true) { + return $result; + } + + return true; + } + + /** + * Check if Composer network is enabled for HTTP/S + * + * @return string|true + */ + private function checkComposerNetworkHttpEnablement() + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + return 'SKIP Network is disabled by COMPOSER_DISABLE_NETWORK.'; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php b/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php new file mode 100644 index 0000000..cc0d7bf --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php @@ -0,0 +1,150 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\AliasPackage; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class DumpAutoloadCommand extends BaseCommand +{ + /** + * @return void + */ + protected function configure() + { + $this + ->setName('dump-autoload') + ->setAliases(['dumpautoload']) + ->setDescription('Dumps the autoloader') + ->setDefinition([ + new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), + new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'), + new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'), + ]) + ->setHelp( + <<php composer.phar dump-autoload + +Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $installationManager = $composer->getInstallationManager(); + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $package = $composer->getPackage(); + $config = $composer->getConfig(); + + $missingDependencies = false; + foreach ($localRepo->getCanonicalPackages() as $localPkg) { + $installPath = $installationManager->getInstallPath($localPkg); + if ($installPath !== null && file_exists($installPath) === false) { + $missingDependencies = true; + $this->getIO()->write('Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies'); + + break; + } + } + + $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader'); + + if ($input->getOption('strict-psr') && !$optimize && !$authoritative) { + throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); + } + if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) { + throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); + } + + if ($authoritative) { + $this->getIO()->write('Generating optimized autoload files (authoritative)'); + } elseif ($optimize) { + $this->getIO()->write('Generating optimized autoload files'); + } else { + $this->getIO()->write('Generating autoload files'); + } + + $generator = $composer->getAutoloadGenerator(); + if ($input->getOption('dry-run')) { + $generator->setDryRun(true); + } + if ($input->getOption('no-dev')) { + $generator->setDevMode(false); + } + if ($input->getOption('dev')) { + if ($input->getOption('no-dev')) { + throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.'); + } + $generator->setDevMode(true); + } + $generator->setClassMapAuthoritative($authoritative); + $generator->setRunScripts(true); + $generator->setApcu($apcu, $apcuPrefix); + $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); + $classMap = $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + 'composer', + $optimize, + null, + $composer->getLocker(), + $input->getOption('strict-ambiguous') + ); + $numberOfClasses = count($classMap); + + if ($authoritative) { + $this->getIO()->write('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); + } elseif ($optimize) { + $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes'); + } else { + $this->getIO()->write('Generated autoload files'); + } + + if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) { + return 1; + } + + if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) { + return 2; + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ExecCommand.php b/vendor/composer/composer/src/Composer/Command/ExecCommand.php new file mode 100644 index 0000000..e8c7c96 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ExecCommand.php @@ -0,0 +1,153 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; + +/** + * @author Davey Shafik + */ +class ExecCommand extends BaseCommand +{ + /** + * @return void + */ + protected function configure() + { + $this + ->setName('exec') + ->setDescription('Executes a vendored binary/script') + ->setDefinition([ + new InputOption('list', 'l', InputOption::VALUE_NONE), + new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () { + return $this->getBinaries(false); + }), + new InputArgument( + 'args', + InputArgument::IS_ARRAY | InputArgument::OPTIONAL, + 'Arguments to pass to the binary. Use -- to separate from composer arguments' + ), + ]) + ->setHelp( + <<getBinaries(false); + if (count($binaries) === 0) { + return; + } + + if ($input->getArgument('binary') !== null || $input->getOption('list')) { + return; + } + + $io = $this->getIO(); + /** @var int $binary */ + $binary = $io->select( + 'Binary to run: ', + $binaries, + '', + 1, + 'Invalid binary name "%s"' + ); + + $input->setArgument('binary', $binaries[$binary]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + if ($input->getOption('list') || null === $input->getArgument('binary')) { + $bins = $this->getBinaries(true); + if ([] === $bins) { + $binDir = $composer->getConfig()->get('bin-dir'); + + throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); + } + + $this->getIO()->write( + <<Available binaries: +EOT + ); + + foreach ($bins as $bin) { + $this->getIO()->write( + <<- $bin +EOT + ); + } + + return 0; + } + + $binary = $input->getArgument('binary'); + + $dispatcher = $composer->getEventDispatcher(); + $dispatcher->addListener('__exec_command', $binary); + + // If the CWD was modified, we restore it to what it was initially, as it was + // most likely modified by the global command, and we want exec to run in the local working directory + // not the global one + if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== false) { + try { + chdir($this->getApplication()->getInitialWorkingDirectory()); + } catch (\Exception $e) { + throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e); + } + } + + return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args')); + } + + /** + * @return list + */ + private function getBinaries(bool $forDisplay): array + { + $composer = $this->requireComposer(); + $binDir = $composer->getConfig()->get('bin-dir'); + $bins = glob($binDir . '/*'); + $localBins = $composer->getPackage()->getBinaries(); + if ($forDisplay) { + $localBins = array_map(static function ($e) { + return "$e (local)"; + }, $localBins); + } + + $binaries = []; + foreach (array_merge($bins, $localBins) as $bin) { + // skip .bat copies + if (isset($previousBin) && $bin === $previousBin.'.bat') { + continue; + } + + $previousBin = $bin; + $binaries[] = basename($bin); + } + + return $binaries; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/FundCommand.php b/vendor/composer/composer/src/Composer/Command/FundCommand.php new file mode 100644 index 0000000..44e355e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/FundCommand.php @@ -0,0 +1,151 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Json\JsonFile; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Semver\Constraint\MatchAllConstraint; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + * @author Jordi Boggiano + */ +class FundCommand extends BaseCommand +{ + protected function configure(): void + { + $this->setName('fund') + ->setDescription('Discover how to help fund the maintenance of your dependencies') + ->setDefinition([ + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']), + ]) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $repo = $composer->getRepositoryManager()->getLocalRepository(); + $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + $fundings = []; + + $packagesToLoad = []; + foreach ($repo->getPackages() as $package) { + if ($package instanceof AliasPackage) { + continue; + } + $packagesToLoad[$package->getName()] = new MatchAllConstraint(); + } + + // load all packages dev versions in parallel + $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []); + + // collect funding data from default branches + foreach ($result['packages'] as $package) { + if ( + !$package instanceof AliasPackage + && $package instanceof CompletePackageInterface + && $package->isDefaultBranch() + && $package->getFunding() + && isset($packagesToLoad[$package->getName()]) + ) { + $fundings = $this->insertFundingData($fundings, $package); + unset($packagesToLoad[$package->getName()]); + } + } + + // collect funding from installed packages if none was found in the default branch above + foreach ($repo->getPackages() as $package) { + if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) { + continue; + } + + if ($package instanceof CompletePackageInterface && $package->getFunding()) { + $fundings = $this->insertFundingData($fundings, $package); + } + } + + ksort($fundings); + + $io = $this->getIO(); + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + if ($fundings && $format === 'text') { + $prev = null; + + $io->write('The following packages were found in your dependencies which publish funding information:'); + + foreach ($fundings as $vendor => $links) { + $io->write(''); + $io->write(sprintf("%s", $vendor)); + foreach ($links as $url => $packages) { + $line = sprintf(' %s', implode(', ', $packages)); + + if ($prev !== $line) { + $io->write($line); + $prev = $line; + } + + $io->write(sprintf(' %s', OutputFormatter::escape($url), $url)); + } + } + + $io->write(""); + $io->write("Please consider following these links and sponsoring the work of package authors!"); + $io->write("Thank you!"); + } elseif ($format === 'json') { + $io->write(JsonFile::encode($fundings)); + } else { + $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); + } + + return 0; + } + + /** + * @param mixed[] $fundings + * @return mixed[] + */ + private function insertFundingData(array $fundings, CompletePackageInterface $package): array + { + foreach ($package->getFunding() as $fundingOption) { + [$vendor, $packageName] = explode('/', $package->getPrettyName()); + // ignore malformed funding entries + if (empty($fundingOption['url'])) { + continue; + } + $url = $fundingOption['url']; + if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) { + $url = 'https://github.com/sponsors/'.$match[1]; + } + $fundings[$vendor][$url][] = $packageName; + } + + return $fundings; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/GlobalCommand.php b/vendor/composer/composer/src/Composer/Command/GlobalCommand.php new file mode 100644 index 0000000..2841c2a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/GlobalCommand.php @@ -0,0 +1,169 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class GlobalCommand extends BaseCommand +{ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $application = $this->getApplication(); + if ($input->mustSuggestArgumentValuesFor('command-name')) { + $suggestions->suggestValues(array_values(array_filter( + array_map(static function (Command $command) { + return $command->isHidden() ? null : $command->getName(); + }, $application->all()), function (?string $cmd) { + return $cmd !== null; + } + ))); + + return; + } + + if ($application->has($commandName = $input->getArgument('command-name'))) { + $input = $this->prepareSubcommandInput($input, true); + $input = CompletionInput::fromString($input->__toString(), 2); + $command = $application->find($commandName); + $command->mergeApplicationDefinition(); + + $input->bind($command->getDefinition()); + $command->complete($input, $suggestions); + } + } + + protected function configure(): void + { + $this + ->setName('global') + ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)') + ->setDefinition([ + new InputArgument('command-name', InputArgument::REQUIRED, ''), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + ]) + ->setHelp( + <<\AppData\Roaming\Composer on Windows +and /home//.composer on unix systems. + +If your system uses freedesktop.org standards, then it will first check +XDG_CONFIG_HOME or default to /home//.config/composer + +Note: This path may vary depending on customizations to bin-dir in +composer.json or the environmental variable COMPOSER_BIN_DIR. + +Read more at https://getcomposer.org/doc/03-cli.md#global +EOT + ) + ; + } + + /** + * @throws \Symfony\Component\Console\Exception\ExceptionInterface + */ + public function run(InputInterface $input, OutputInterface $output): int + { + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + // extract real command name + $tokens = Preg::split('{\s+}', $input->__toString()); + $args = []; + foreach ($tokens as $token) { + if ($token && $token[0] !== '-') { + $args[] = $token; + if (count($args) >= 2) { + break; + } + } + } + + // show help for this command if no command was found + if (count($args) < 2) { + return parent::run($input, $output); + } + + $input = $this->prepareSubcommandInput($input); + + return $this->getApplication()->run($input, $output); + } + + private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput + { + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + // The COMPOSER env var should not apply to the global execution scope + if (Platform::getEnv('COMPOSER')) { + Platform::clearEnv('COMPOSER'); + } + + // change to global dir + $config = Factory::createConfig(); + $home = $config->get('home'); + + if (!is_dir($home)) { + $fs = new Filesystem(); + $fs->ensureDirectoryExists($home); + if (!is_dir($home)) { + throw new \RuntimeException('Could not create home directory'); + } + } + + try { + chdir($home); + } catch (\Exception $e) { + throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e); + } + if (!$quiet) { + $this->getIO()->writeError('Changed current directory to '.$home.''); + } + + // create new input without "global" command prefix + $input = new StringInput(Preg::replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); + $this->getApplication()->resetComposer(); + + return $input; + } + + /** + * @inheritDoc + */ + public function isProxyCommand(): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/HomeCommand.php b/vendor/composer/composer/src/Composer/Command/HomeCommand.php new file mode 100644 index 0000000..3547fae --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/HomeCommand.php @@ -0,0 +1,165 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Package\CompletePackageInterface; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Robert Schönthal + */ +class HomeCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @inheritDoc + */ + protected function configure(): void + { + $this + ->setName('browse') + ->setAliases(['home']) + ->setDescription('Opens the package\'s repository URL or homepage in your browser') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()), + new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), + new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), + ]) + ->setHelp( + <<initializeRepos(); + $io = $this->getIO(); + $return = 0; + + $packages = $input->getArgument('packages'); + if (count($packages) === 0) { + $io->writeError('No package specified, opening homepage for the root package'); + $packages = [$this->requireComposer()->getPackage()->getName()]; + } + + foreach ($packages as $packageName) { + $handled = false; + $packageExists = false; + foreach ($repos as $repo) { + foreach ($repo->findPackages($packageName) as $package) { + $packageExists = true; + if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) { + $handled = true; + break 2; + } + } + } + + if (!$packageExists) { + $return = 1; + $io->writeError('Package '.$packageName.' not found'); + } + + if (!$handled) { + $return = 1; + $io->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); + } + } + + return $return; + } + + private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly): bool + { + $support = $package->getSupport(); + $url = $support['source'] ?? $package->getSourceUrl(); + if (!$url || $showHomepage) { + $url = $package->getHomepage(); + } + + if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) { + return false; + } + + if ($showOnly) { + $this->getIO()->write(sprintf('%s', $url)); + } else { + $this->openBrowser($url); + } + + return true; + } + + /** + * opens a url in your system default browser + */ + private function openBrowser(string $url): void + { + $process = new ProcessExecutor($this->getIO()); + if (Platform::isWindows()) { + $process->execute(['start', '"web"', 'explorer', $url], $output); + + return; + } + + $linux = $process->execute(['which', 'xdg-open'], $output); + $osx = $process->execute(['which', 'open'], $output); + + if (0 === $linux) { + $process->execute(['xdg-open', $url], $output); + } elseif (0 === $osx) { + $process->execute(['open', $url], $output); + } else { + $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); + } + } + + /** + * Initializes repositories + * + * Returns an array of repos in order they should be checked in + * + * @return RepositoryInterface[] + */ + private function initializeRepos(): array + { + $composer = $this->tryComposer(); + + if ($composer) { + return array_merge( + [new RootPackageRepository(clone $composer->getPackage())], // root package + [$composer->getRepositoryManager()->getLocalRepository()], // installed packages + $composer->getRepositoryManager()->getRepositories() // remotes + ); + } + + return RepositoryFactory::defaultReposWithDefaultManager($this->getIO()); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/InitCommand.php b/vendor/composer/composer/src/Composer/Command/InitCommand.php new file mode 100644 index 0000000..9a49d59 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/InitCommand.php @@ -0,0 +1,650 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Json\JsonFile; +use Composer\Json\JsonValidationException; +use Composer\Package\BasePackage; +use Composer\Package\Package; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Spdx\SpdxLicenses; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; +use Symfony\Component\Console\Helper\FormatterHelper; + +/** + * @author Justin Rainbow + * @author Jordi Boggiano + */ +class InitCommand extends BaseCommand +{ + use CompletionTrait; + use PackageDiscoveryTrait; + + /** @var array */ + private $gitConfig; + + /** + * @inheritDoc + * + * @return void + */ + protected function configure() + { + $this + ->setName('init') + ->setDescription('Creates a basic composer.json file in current directory') + ->setDefinition([ + new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), + new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), + new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), + new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), + new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), + new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'), + new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), + new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), + new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'), + ]) + ->setHelp( + <<init command creates a basic composer.json file +in the current directory. + +php composer.phar init + +Read more at https://getcomposer.org/doc/03-cli.md#init +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + + $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; + $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), function ($val) { return $val !== null && $val !== []; }); + + if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $options['name'])) { + throw new \InvalidArgumentException( + 'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' + ); + } + + if (isset($options['author'])) { + $options['authors'] = $this->formatAuthors($options['author']); + unset($options['author']); + } + + $repositories = $input->getOption('repository'); + if (count($repositories) > 0) { + $config = Factory::createConfig($io); + foreach ($repositories as $repo) { + $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true); + } + } + + if (isset($options['stability'])) { + $options['minimum-stability'] = $options['stability']; + unset($options['stability']); + } + + $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass; + if ([] === $options['require']) { + $options['require'] = new \stdClass; + } + + if (isset($options['require-dev'])) { + $options['require-dev'] = $this->formatRequirements($options['require-dev']); + if ([] === $options['require-dev']) { + $options['require-dev'] = new \stdClass; + } + } + + // --autoload - create autoload object + $autoloadPath = null; + if (isset($options['autoload'])) { + $autoloadPath = $options['autoload']; + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + $options['autoload'] = (object) [ + 'psr-4' => [ + $namespace . '\\' => $autoloadPath, + ], + ]; + } + + $file = new JsonFile(Factory::getComposerFile()); + $json = JsonFile::encode($options); + + if ($input->isInteractive()) { + $io->writeError(['', $json, '']); + if (!$io->askConfirmation('Do you confirm generation [yes]? ')) { + $io->writeError('Command aborted'); + + return 1; + } + } else { + if (json_encode($options) === '{"require":{}}') { + throw new \RuntimeException('You have to run this command in interactive mode, or specify at least some data using --name, --require, etc.'); + } + + $io->writeError('Writing '.$file->getPath()); + } + + $file->write($options); + try { + $file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + $io->writeError('Schema validation error, aborting'); + $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); + $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors); + Silencer::call('unlink', $file->getPath()); + + return 1; + } + + // --autoload - Create src folder + if ($autoloadPath) { + $filesystem = new Filesystem(); + $filesystem->ensureDirectoryExists($autoloadPath); + + // dump-autoload only for projects without added dependencies. + if (!$this->hasDependencies($options)) { + $this->runDumpAutoloadCommand($output); + } + } + + if ($input->isInteractive() && is_dir('.git')) { + $ignoreFile = realpath('.gitignore'); + + if (false === $ignoreFile) { + $ignoreFile = realpath('.') . '/.gitignore'; + } + + if (!$this->hasVendorIgnore($ignoreFile)) { + $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; + + if ($io->askConfirmation($question)) { + $this->addVendorIgnore($ignoreFile); + } + } + } + + $question = 'Would you like to install dependencies now [yes]? '; + if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) { + $this->updateDependencies($output); + } + + // --autoload - Show post-install configuration info + if ($autoloadPath) { + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + + $io->writeError('PSR-4 autoloading configured. Use "namespace '.$namespace.';" in '.$autoloadPath); + $io->writeError('Include the Composer autoloader with: require \'vendor/autoload.php\';'); + } + + return 0; + } + + /** + * @inheritDoc + * + * @return void + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + $git = $this->getGitConfig(); + $io = $this->getIO(); + /** @var FormatterHelper $formatter */ + $formatter = $this->getHelperSet()->get('formatter'); + + // initialize repos if configured + $repositories = $input->getOption('repository'); + if (count($repositories) > 0) { + $config = Factory::createConfig($io); + $io->loadConfiguration($config); + $repoManager = RepositoryFactory::manager($io, $config); + + $repos = [new PlatformRepository]; + $createDefaultPackagistRepo = true; + foreach ($repositories as $repo) { + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); + if ( + (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) + || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) + ) { + $createDefaultPackagistRepo = false; + continue; + } + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); + } + + if ($createDefaultPackagistRepo) { + $repos[] = RepositoryFactory::createRepo($io, $config, [ + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + ], $repoManager); + } + + $this->repos = new CompositeRepository($repos); + unset($repos, $config, $repositories); + } + + $io->writeError([ + '', + $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), + '', + ]); + + // namespace + $io->writeError([ + '', + 'This command will guide you through creating your composer.json config.', + '', + ]); + + $cwd = realpath("."); + + $name = $input->getOption('name'); + if (null === $name) { + $name = basename($cwd); + $name = $this->sanitizePackageNameComponent($name); + + $vendor = $name; + if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) { + $vendor = $_SERVER['COMPOSER_DEFAULT_VENDOR']; + } elseif (isset($git['github.user'])) { + $vendor = $git['github.user']; + } elseif (!empty($_SERVER['USERNAME'])) { + $vendor = $_SERVER['USERNAME']; + } elseif (!empty($_SERVER['USER'])) { + $vendor = $_SERVER['USER']; + } elseif (get_current_user()) { + $vendor = get_current_user(); + } + + $vendor = $this->sanitizePackageNameComponent($vendor); + + $name = $vendor . '/' . $name; + } + + $name = $io->askAndValidate( + 'Package name (/) ['.$name.']: ', + static function ($value) use ($name) { + if (null === $value) { + return $name; + } + + if (!Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $value)) { + throw new \InvalidArgumentException( + 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' + ); + } + + return $value; + }, + null, + $name + ); + $input->setOption('name', $name); + + $description = $input->getOption('description') ?: null; + $description = $io->ask( + 'Description ['.$description.']: ', + $description + ); + $input->setOption('description', $description); + + if (null === $author = $input->getOption('author')) { + if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) { + $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR']; + } elseif (isset($git['user.name'])) { + $author_name = $git['user.name']; + } + + if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) { + $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL']; + } elseif (isset($git['user.email'])) { + $author_email = $git['user.email']; + } + + if (isset($author_name, $author_email)) { + $author = sprintf('%s <%s>', $author_name, $author_email); + } + } + + $author = $io->askAndValidate( + 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', + function ($value) use ($author) { + if ($value === 'n' || $value === 'no') { + return; + } + $value = $value ?: $author; + $author = $this->parseAuthorString($value ?? ''); + + if ($author['email'] === null) { + return $author['name']; + } + + return sprintf('%s <%s>', $author['name'], $author['email']); + }, + null, + $author + ); + $input->setOption('author', $author); + + $minimumStability = $input->getOption('stability') ?: null; + $minimumStability = $io->askAndValidate( + 'Minimum Stability ['.$minimumStability.']: ', + static function ($value) use ($minimumStability) { + if (null === $value) { + return $minimumStability; + } + + if (!isset(BasePackage::STABILITIES[$value])) { + throw new \InvalidArgumentException( + 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. + implode(', ', array_keys(BasePackage::STABILITIES)) + ); + } + + return $value; + }, + null, + $minimumStability + ); + $input->setOption('stability', $minimumStability); + + $type = $input->getOption('type'); + $type = $io->ask( + 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', + $type + ); + if ($type === '' || $type === false) { + $type = null; + } + $input->setOption('type', $type); + + if (null === $license = $input->getOption('license')) { + if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) { + $license = $_SERVER['COMPOSER_DEFAULT_LICENSE']; + } + } + + $license = $io->ask( + 'License ['.$license.']: ', + $license + ); + $spdx = new SpdxLicenses(); + if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') { + throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); + } + $input->setOption('license', $license); + + $io->writeError(['', 'Define your dependencies.', '']); + + // prepare to resolve dependencies + $repos = $this->getRepos(); + $preferredStability = $minimumStability ?: 'stable'; + $platformRepo = null; + if ($repos instanceof CompositeRepository) { + foreach ($repos->getRepositories() as $candidateRepo) { + if ($candidateRepo instanceof PlatformRepository) { + $platformRepo = $candidateRepo; + break; + } + } + } + + $question = 'Would you like to define your dependencies (require) interactively [yes]? '; + $require = $input->getOption('require'); + $requirements = []; + if (count($require) > 0 || $io->askConfirmation($question)) { + $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); + } + $input->setOption('require', $requirements); + + $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; + $requireDev = $input->getOption('require-dev'); + $devRequirements = []; + if (count($requireDev) > 0 || $io->askConfirmation($question)) { + $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); + } + $input->setOption('require-dev', $devRequirements); + + // --autoload - input and validation + $autoload = $input->getOption('autoload') ?: 'src/'; + $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); + $autoload = $io->askAndValidate( + 'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. ['.$autoload.', n to skip]: ', + static function ($value) use ($autoload) { + if (null === $value) { + return $autoload; + } + + if ($value === 'n' || $value === 'no') { + return; + } + + $value = $value ?: $autoload; + + if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) { + throw new \InvalidArgumentException(sprintf( + 'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/', + $value + )); + } + + return $value; + }, + null, + $autoload + ); + $input->setOption('autoload', $autoload); + } + + /** + * @return array{name: string, email: string|null} + */ + private function parseAuthorString(string $author): array + { + if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { + if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { + throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); + } + + return [ + 'name' => trim($match['name']), + 'email' => $match['email'], + ]; + } + + throw new \InvalidArgumentException( + 'Invalid author string. Must be in the formats: '. + 'Jane Doe or John Smith ' + ); + } + + /** + * @return array + */ + protected function formatAuthors(string $author): array + { + $author = $this->parseAuthorString($author); + if (null === $author['email']) { + unset($author['email']); + } + + return [$author]; + } + + /** + * Extract namespace from package's vendor name. + * + * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName" + */ + public function namespaceFromPackageName(string $packageName): ?string + { + if (!$packageName || strpos($packageName, '/') === false) { + return null; + } + + $namespace = array_map( + static function ($part): string { + $part = Preg::replace('/[^a-z0-9]/i', ' ', $part); + $part = ucwords($part); + + return str_replace(' ', '', $part); + }, + explode('/', $packageName) + ); + + return implode('\\', $namespace); + } + + /** + * @return array + */ + protected function getGitConfig(): array + { + if (null !== $this->gitConfig) { + return $this->gitConfig; + } + + $process = new ProcessExecutor($this->getIO()); + + if (0 === $process->execute(['git', 'config', '-l'], $output)) { + $this->gitConfig = []; + Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches); + foreach ($matches[1] as $key => $match) { + $this->gitConfig[$match] = $matches[2][$key]; + } + + return $this->gitConfig; + } + + return $this->gitConfig = []; + } + + /** + * Checks the local .gitignore file for the Composer vendor directory. + * + * Tested patterns include: + * "/$vendor" + * "$vendor" + * "$vendor/" + * "/$vendor/" + * "/$vendor/*" + * "$vendor/*" + */ + protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool + { + if (!file_exists($ignoreFile)) { + return false; + } + + $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor)); + + $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); + foreach ($lines as $line) { + if (Preg::isMatch($pattern, $line)) { + return true; + } + } + + return false; + } + + protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void + { + $contents = ""; + if (file_exists($ignoreFile)) { + $contents = file_get_contents($ignoreFile); + + if (strpos($contents, "\n") !== 0) { + $contents .= "\n"; + } + } + + file_put_contents($ignoreFile, $contents . $vendor. "\n"); + } + + protected function isValidEmail(string $email): bool + { + // assume it's valid if we can't validate it + if (!function_exists('filter_var')) { + return true; + } + + return false !== filter_var($email, FILTER_VALIDATE_EMAIL); + } + + private function updateDependencies(OutputInterface $output): void + { + try { + $updateCommand = $this->getApplication()->find('update'); + $this->getApplication()->resetComposer(); + $updateCommand->run(new ArrayInput([]), $output); + } catch (\Exception $e) { + $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.'); + } + } + + private function runDumpAutoloadCommand(OutputInterface $output): void + { + try { + $command = $this->getApplication()->find('dump-autoload'); + $this->getApplication()->resetComposer(); + $command->run(new ArrayInput([]), $output); + } catch (\Exception $e) { + $this->getIO()->writeError('Could not run dump-autoload.'); + } + } + + /** + * @param array> $options + */ + private function hasDependencies(array $options): bool + { + $requires = (array) $options['require']; + $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : []; + + return !empty($requires) || !empty($devRequires); + } + + private function sanitizePackageNameComponent(string $name): string + { + $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); + $name = strtolower($name); + $name = Preg::replace('{^[_.-]+|[_.-]+$|[^a-z0-9_.-]}u', '', $name); + $name = Preg::replace('{([_.-]){2,}}u', '$1', $name); + + return $name; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/InstallCommand.php b/vendor/composer/composer/src/Composer/Command/InstallCommand.php new file mode 100644 index 0000000..1d45eaf --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/InstallCommand.php @@ -0,0 +1,149 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Installer; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Advisory\Auditor; +use Composer\Util\HttpDownloader; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + * @author Ryan Weaver + * @author Konstantin Kudryashov + * @author Nils Adermann + */ +class InstallCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('install') + ->setAliases(['i']) + ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json') + ->setDefinition([ + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'), + new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), + ]) + ->setHelp( + <<install command reads the composer.lock file from +the current directory, processes it, and downloads and installs all the +libraries and dependencies outlined in that file. If the file does not +exist it will look for composer.json and do the same. + +php composer.phar install + +Read more at https://getcomposer.org/doc/03-cli.md#install-i +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); + } + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $args = $input->getArgument('packages'); + if (count($args) > 0) { + $io->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); + + return 1; + } + + if ($input->getOption('no-install')) { + $io->writeError('Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.'); + + return 1; + } + + $composer = $this->requireComposer(); + + if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) { + $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $install = Installer::create($io, $composer); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install + ->setDryRun($input->getOption('dry-run')) + ->setDownloadOnly($input->getOption('download-only')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setAudit($input->getOption('audit')) + ->setErrorOnAudit($input->getOption('audit')) + ->setAuditFormat($this->getAuditFormat($input)) + ; + + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); + } + + return $install->run(); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/LicensesCommand.php b/vendor/composer/composer/src/Composer/Command/LicensesCommand.php new file mode 100644 index 0000000..9305ece --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/LicensesCommand.php @@ -0,0 +1,153 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Console\Input\InputOption; +use Composer\Json\JsonFile; +use Composer\Package\CompletePackageInterface; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\RepositoryUtils; +use Composer\Util\PackageInfo; +use Composer\Util\PackageSorter; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Benoît Merlet + */ +class LicensesCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('licenses') + ->setDescription('Shows information about licenses of dependencies') + ->setDefinition([ + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + ]) + ->setHelp( + <<requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $root = $composer->getPackage(); + $repo = $composer->getRepositoryManager()->getLocalRepository(); + + if ($input->getOption('no-dev')) { + $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root); + } else { + $packages = $repo->getPackages(); + } + + $packages = PackageSorter::sortPackagesAlphabetically($packages); + $io = $this->getIO(); + + switch ($format = $input->getOption('format')) { + case 'text': + $io->write('Name: '.$root->getPrettyName().''); + $io->write('Version: '.$root->getFullPrettyVersion().''); + $io->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); + $io->write('Dependencies:'); + $io->write(''); + + $table = new Table($output); + $table->setStyle('compact'); + $table->setHeaders(['Name', 'Version', 'Licenses']); + foreach ($packages as $package) { + $link = PackageInfo::getViewSourceOrHomepageUrl($package); + if ($link !== null) { + $name = ''.$package->getPrettyName().''; + } else { + $name = $package->getPrettyName(); + } + + $table->addRow([ + $name, + $package->getFullPrettyVersion(), + implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none', + ]); + } + $table->render(); + break; + + case 'json': + $dependencies = []; + foreach ($packages as $package) { + $dependencies[$package->getPrettyName()] = [ + 'version' => $package->getFullPrettyVersion(), + 'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : [], + ]; + } + + $io->write(JsonFile::encode([ + 'name' => $root->getPrettyName(), + 'version' => $root->getFullPrettyVersion(), + 'license' => $root->getLicense(), + 'dependencies' => $dependencies, + ])); + break; + + case 'summary': + $usedLicenses = []; + foreach ($packages as $package) { + $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : []; + if (count($licenses) === 0) { + $licenses[] = 'none'; + } + foreach ($licenses as $licenseName) { + if (!isset($usedLicenses[$licenseName])) { + $usedLicenses[$licenseName] = 0; + } + $usedLicenses[$licenseName]++; + } + } + + // Sort licenses so that the most used license will appear first + arsort($usedLicenses, SORT_NUMERIC); + + $rows = []; + foreach ($usedLicenses as $usedLicense => $numberOfDependencies) { + $rows[] = [$usedLicense, $numberOfDependencies]; + } + + $symfonyIo = new SymfonyStyle($input, $output); + $symfonyIo->table( + ['License', 'Number of dependencies'], + $rows + ); + break; + default: + throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php b/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php new file mode 100644 index 0000000..0fea6dc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/OutdatedCommand.php @@ -0,0 +1,135 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\ArrayInput; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class OutdatedCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('outdated') + ->setDescription('Shows a list of installed packages that have updates available, including their latest version') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(false)), + new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'), + new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), + new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'), + new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'), + new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'), + new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), + ]) + ->setHelp( + <<green (=): Dependency is in the latest version and is up to date. +- yellow (~): Dependency has a new version available that includes backwards + compatibility breaks according to semver, so upgrade when you can but it + may involve work. +- red (!): Dependency has a new version that is semver-compatible and you should upgrade it. + +Read more at https://getcomposer.org/doc/03-cli.md#outdated +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $args = [ + 'command' => 'show', + '--latest' => true, + ]; + if ($input->getOption('no-interaction')) { + $args['--no-interaction'] = true; + } + if ($input->getOption('no-plugins')) { + $args['--no-plugins'] = true; + } + if ($input->getOption('no-scripts')) { + $args['--no-scripts'] = true; + } + if ($input->getOption('no-cache')) { + $args['--no-cache'] = true; + } + if (!$input->getOption('all')) { + $args['--outdated'] = true; + } + if ($input->getOption('direct')) { + $args['--direct'] = true; + } + if (null !== $input->getArgument('package')) { + $args['package'] = $input->getArgument('package'); + } + if ($input->getOption('strict')) { + $args['--strict'] = true; + } + if ($input->getOption('major-only')) { + $args['--major-only'] = true; + } + if ($input->getOption('minor-only')) { + $args['--minor-only'] = true; + } + if ($input->getOption('patch-only')) { + $args['--patch-only'] = true; + } + if ($input->getOption('locked')) { + $args['--locked'] = true; + } + if ($input->getOption('no-dev')) { + $args['--no-dev'] = true; + } + if ($input->getOption('sort-by-age')) { + $args['--sort-by-age'] = true; + } + $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req'); + if ($input->getOption('ignore-platform-reqs')) { + $args['--ignore-platform-reqs'] = true; + } + $args['--format'] = $input->getOption('format'); + $args['--ignore'] = $input->getOption('ignore'); + + $input = new ArrayInput($args); + + return $this->getApplication()->run($input, $output); + } + + /** + * @inheritDoc + */ + public function isProxyCommand(): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php b/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php new file mode 100644 index 0000000..0bbd2a4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php @@ -0,0 +1,466 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\Constraint; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @internal + */ +trait PackageDiscoveryTrait +{ + /** @var ?CompositeRepository */ + private $repos; + /** @var RepositorySet[] */ + private $repositorySets; + + protected function getRepos(): CompositeRepository + { + if (null === $this->repos) { + $this->repos = new CompositeRepository(array_merge( + [new PlatformRepository], + RepositoryFactory::defaultReposWithDefaultManager($this->getIO()) + )); + } + + return $this->repos; + } + + /** + * @param key-of|null $minimumStability + */ + private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet + { + $key = $minimumStability ?? 'default'; + + if (!isset($this->repositorySets[$key])) { + $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input)); + $repositorySet->addRepository($this->getRepos()); + } + + return $this->repositorySets[$key]; + } + + /** + * @return key-of + */ + private function getMinimumStability(InputInterface $input): string + { + if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do + return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable'); + } + + // @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there + $file = Factory::getComposerFile(); + if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) { + if (isset($composer['minimum-stability'])) { + return VersionParser::normalizeStability($composer['minimum-stability']); + } + } + + return 'stable'; + } + + /** + * @param array $requires + * + * @return array + * @throws \Exception + */ + final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = true, bool $fixed = false): array + { + if (count($requires) > 0) { + $requires = $this->normalizeRequirements($requires); + $result = []; + $io = $this->getIO(); + + foreach ($requires as $requirement) { + if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) { + $io->writeError('The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); + } + + if (!isset($requirement['version'])) { + // determine the best version automatically + [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed); + + // replace package name from packagist.org + $requirement['name'] = $name; + + if ($useBestVersionConstraint) { + $requirement['version'] = $version; + $io->writeError(sprintf( + 'Using version %s for %s', + $requirement['version'], + $requirement['name'] + )); + } else { + $requirement['version'] = 'guess'; + } + } + + $result[] = $requirement['name'] . ' ' . $requirement['version']; + } + + return $result; + } + + $versionParser = new VersionParser(); + + // Collect existing packages + $composer = $this->tryComposer(); + $installedRepo = null; + if (null !== $composer) { + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + } + $existingPackages = []; + if (null !== $installedRepo) { + foreach ($installedRepo->getPackages() as $package) { + $existingPackages[] = $package->getName(); + } + } + unset($composer, $installedRepo); + + $io = $this->getIO(); + while (null !== $package = $io->ask('Search for a package: ')) { + $matches = $this->getRepos()->search($package); + + if (count($matches) > 0) { + // Remove existing packages from search results. + foreach ($matches as $position => $foundPackage) { + if (in_array($foundPackage['name'], $existingPackages, true)) { + unset($matches[$position]); + } + } + $matches = array_values($matches); + + $exactMatch = false; + foreach ($matches as $match) { + if ($match['name'] === $package) { + $exactMatch = true; + break; + } + } + + // no match, prompt which to pick + if (!$exactMatch) { + $providers = $this->getRepos()->getProviders($package); + if (count($providers) > 0) { + array_unshift($matches, ['name' => $package, 'description' => '']); + } + + $choices = []; + foreach ($matches as $position => $foundPackage) { + $abandoned = ''; + if (isset($foundPackage['abandoned'])) { + if (is_string($foundPackage['abandoned'])) { + $replacement = sprintf('Use %s instead', $foundPackage['abandoned']); + } else { + $replacement = 'No replacement was suggested'; + } + $abandoned = sprintf('Abandoned. %s.', $replacement); + } + + $choices[] = sprintf(' %5s %s %s', "[$position]", $foundPackage['name'], $abandoned); + } + + $io->writeError([ + '', + sprintf('Found %s packages matching %s', count($matches), $package), + '', + ]); + + $io->writeError($choices); + $io->writeError(''); + + $validator = static function (string $selection) use ($matches, $versionParser) { + if ('' === $selection) { + return false; + } + + if (is_numeric($selection) && isset($matches[(int) $selection])) { + $package = $matches[(int) $selection]; + + return $package['name']; + } + + if (Preg::isMatch('{^\s*(?P[\S/]+)(?:\s+(?P\S+))?\s*$}', $selection, $packageMatches)) { + if (isset($packageMatches['version'])) { + // parsing `acme/example ~2.3` + + // validate version constraint + $versionParser->parseConstraints($packageMatches['version']); + + return $packageMatches['name'].' '.$packageMatches['version']; + } + + // parsing `acme/example` + return $packageMatches['name']; + } + + throw new \Exception('Not a valid selection'); + }; + + $package = $io->askAndValidate( + 'Enter package # to add, or the complete package name if it is not listed: ', + $validator, + 3, + '' + ); + } + + // no constraint yet, determine the best version automatically + if (false !== $package && false === strpos($package, ' ')) { + $validator = static function (string $input) { + $input = trim($input); + + return strlen($input) > 0 ? $input : false; + }; + + $constraint = $io->askAndValidate( + 'Enter the version constraint to require (or leave blank to use the latest version): ', + $validator, + 3, + '' + ); + + if (false === $constraint) { + [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability); + + $io->writeError(sprintf( + 'Using version %s for %s', + $constraint, + $package + )); + } + + $package .= ' '.$constraint; + } + + if (false !== $package) { + $requires[] = $package; + $existingPackages[] = explode(' ', $package)[0]; + } + } + } + + return $requires; + } + + /** + * Given a package name, this determines the best version to use in the require key. + * + * This returns a version with the ~ operator prefixed when possible. + * + * @throws \InvalidArgumentException + * @return array{string, string} name version + */ + private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array + { + // handle ignore-platform-reqs flag if present + if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) { + $platformRequirementFilter = $this->getPlatformRequirementFilter($input); + } else { + $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } + + // find the latest version allowed in this repo set + $repoSet = $this->getRepositorySet($input); + $versionSelector = new VersionSelector($repoSet, $platformRepo); + $effectiveMinimumStability = $this->getMinimumStability($input); + + $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO()); + + if (false === $package) { + // platform packages can not be found in the pool in versions other than the local platform's has + // so if platform reqs are ignored we just take the user's word for it + if ($platformRequirementFilter->isIgnored($name)) { + return [$name, '*']; + } + + // Check if it is a virtual package provided by others + $providers = $repoSet->getProviders($name); + if (count($providers) > 0) { + $constraint = '*'; + if ($input->isInteractive()) { + $constraint = $this->getIO()->askAndValidate('Package "'.$name.'" does not exist but is provided by '.count($providers).' packages. Which version constraint would you like to use? [*] ', static function ($value) { + $parser = new VersionParser(); + $parser->parseConstraints($value); + + return $value; + }, 3, '*'); + } + + return [$name, $constraint]; + } + + // Check whether the package requirements were the problem + if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { + throw new \InvalidArgumentException(sprintf( + 'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), + $name + )); + } + // Check whether the minimum stability was the problem but the package exists + if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { + // we must first verify if a valid package would be found in a lower priority repository + if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { + throw new \InvalidArgumentException( + 'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.' + ); + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.', + $name, + $effectiveMinimumStability + )); + } + // Check whether the PHP version was the problem for all versions + if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { + $additional = ''; + if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) { + $additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".'; + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s', + $name, + $additional + )); + } + + // Check for similar names/typos + $similar = $this->findSimilar($name); + if (count($similar) > 0) { + if (in_array($name, $similar, true)) { + throw new \InvalidArgumentException(sprintf( + "Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.", + $name + )); + } + + if ($input->isInteractive()) { + $result = $io->select("Could not find package $name.\nPick one of these or leave empty to abort:", $similar, false, 1); + if ($result !== false) { + return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed); + } + } + + throw new \InvalidArgumentException(sprintf( + "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", + $name, + implode("\n ", $similar) + )); + } + + throw new \InvalidArgumentException(sprintf( + 'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).', + $name, + $effectiveMinimumStability + )); + } + + return [ + $package->getPrettyName(), + $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package), + ]; + } + + /** + * @return array + */ + private function findSimilar(string $package): array + { + try { + if (null === $this->repos) { + throw new \LogicException('findSimilar was called before $this->repos was initialized'); + } + $results = $this->repos->search($package); + } catch (\Throwable $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + // ignore search errors + return []; + } + $similarPackages = []; + + $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository(); + + foreach ($results as $result) { + if (null !== $installedRepo->findPackage($result['name'], '*')) { + // Ignore installed package + continue; + } + $similarPackages[$result['name']] = levenshtein($package, $result['name']); + } + asort($similarPackages); + + return array_keys(array_slice($similarPackages, 0, 5)); + } + + private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string + { + $details = []; + if (null === $platformRepo) { + return ''; + } + + foreach ($candidate->getRequires() as $link) { + if (!PlatformRepository::isPlatformPackage($link->getTarget())) { + continue; + } + $platformPkg = $platformRepo->findPackage($link->getTarget(), '*'); + if (null === $platformPkg) { + if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) { + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".'; + } else { + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.'; + } + continue; + } + if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) { + $platformPkgVersion = $platformPkg->getPrettyVersion(); + $platformExtra = $platformPkg->getExtra(); + if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) { + $platformPkgVersion .= ' ('.$platformPkg->getDescription().')'; + } + $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.'; + } + } + + if (count($details) === 0) { + return ''; + } + + return ':'.PHP_EOL.' - ' . implode(PHP_EOL.' - ', $details); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php b/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php new file mode 100644 index 0000000..49e0669 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; + +/** + * @author Niels Keurentjes + */ +class ProhibitsCommand extends BaseDependencyCommand +{ + use CompletionTrait; + + /** + * Configure command metadata. + */ + protected function configure(): void + { + $this + ->setName('prohibits') + ->setAliases(['why-not']) + ->setDescription('Shows which packages prevent the given package from being installed') + ->setDefinition([ + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()), + new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'), + new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), + new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), + ]) + ->setHelp( + <<php composer.phar prohibits composer/composer + +Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + return parent::doExecute($input, $output, true); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php b/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php new file mode 100644 index 0000000..cb7882a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ReinstallCommand.php @@ -0,0 +1,198 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Transaction; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Script\ScriptEvents; +use Composer\Util\Platform; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class ReinstallCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('reinstall') + ->setDescription('Uninstalls and reinstalls the given package names') + ->setDefinition([ + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)), + new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)), + ]) + ->setHelp( + <<reinstall command looks up installed packages by name, +uninstalls them and reinstalls them. This lets you do a clean install +of a package if you messed with its files, or if you wish to change +the installation type using --prefer-install. + +php composer.phar reinstall acme/foo "acme/bar-*" + +Read more at https://getcomposer.org/doc/03-cli.md#reinstall +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + + $composer = $this->requireComposer(); + + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $packagesToReinstall = []; + $packageNamesToReinstall = []; + if (\count($input->getOption('type')) > 0) { + if (\count($input->getArgument('packages')) > 0) { + throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.'); + } + foreach ($localRepo->getCanonicalPackages() as $package) { + if (in_array($package->getType(), $input->getOption('type'), true)) { + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } + } else { + if (\count($input->getArgument('packages')) === 0) { + throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.'); + } + foreach ($input->getArgument('packages') as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + $matched = false; + foreach ($localRepo->getCanonicalPackages() as $package) { + if (Preg::isMatch($patternRegexp, $package->getName())) { + $matched = true; + $packagesToReinstall[] = $package; + $packageNamesToReinstall[] = $package->getName(); + } + } + + if (!$matched) { + $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); + } + } + } + + if (0 === \count($packagesToReinstall)) { + $io->writeError('Found no packages to reinstall, aborting.'); + + return 1; + } + + $uninstallOperations = []; + foreach ($packagesToReinstall as $package) { + $uninstallOperations[] = new UninstallOperation($package); + } + + // make sure we have a list of install operations ordered by dependency/plugins + $presentPackages = $localRepo->getPackages(); + $resultPackages = $presentPackages; + foreach ($presentPackages as $index => $package) { + if (in_array($package->getName(), $packageNamesToReinstall, true)) { + unset($presentPackages[$index]); + } + } + $transaction = new Transaction($presentPackages, $resultPackages); + $installOperations = $transaction->getOperations(); + + // reverse-sort the uninstalls based on the install order + $installOrder = []; + foreach ($installOperations as $index => $op) { + if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) { + $installOrder[$op->getPackage()->getName()] = $index; + } + } + usort($uninstallOperations, static function ($a, $b) use ($installOrder): int { + return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()]; + }); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output); + $eventDispatcher = $composer->getEventDispatcher(); + $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $installationManager = $composer->getInstallationManager(); + $downloadManager = $composer->getDownloadManager(); + $package = $composer->getPackage(); + + $installationManager->setOutputProgress(!$input->getOption('no-progress')); + if ($input->getOption('no-plugins')) { + $installationManager->disablePlugins(); + } + + $downloadManager->setPreferSource($preferSource); + $downloadManager->setPreferDist($preferDist); + + $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : true; + + Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); + $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode); + + $installationManager->execute($localRepo, $uninstallOperations, $devMode); + $installationManager->execute($localRepo, $installOperations, $devMode); + + if (!$input->getOption('no-autoloader')) { + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $generator = $composer->getAutoloadGenerator(); + $generator->setClassMapAuthoritative($authoritative); + $generator->setApcu($apcu, $apcuPrefix); + $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); + $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + 'composer', + $optimize, + null, + $composer->getLocker() + ); + } + + $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RemoveCommand.php b/vendor/composer/composer/src/Composer/Command/RemoveCommand.php new file mode 100644 index 0000000..a686635 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RemoveCommand.php @@ -0,0 +1,317 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Config\JsonConfigSource; +use Composer\DependencyResolver\Request; +use Composer\Installer; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Json\JsonFile; +use Composer\Factory; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Package\BasePackage; +use Composer\Advisory\Auditor; + +/** + * @author Pierre du Plessis + * @author Jordi Boggiano + */ +class RemoveCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('remove') + ->setAliases(['rm', 'uninstall']) + ->setDescription('Removes a package from the require or require-dev') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), + new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)'), + new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), + new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + ]) + ->setHelp( + <<remove command removes a package from the current +list of installed packages + +php composer.phar remove + +Read more at https://getcomposer.org/doc/03-cli.md#remove-rm +EOT + ) + ; + } + + /** + * @throws \Seld\JsonLint\ParsingException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getArgument('packages') === [] && !$input->getOption('unused')) { + throw new InvalidArgumentException('Not enough arguments (missing: "packages").'); + } + + $packages = $input->getArgument('packages'); + $packages = array_map('strtolower', $packages); + + if ($input->getOption('unused')) { + $composer = $this->requireComposer(); + $locker = $composer->getLocker(); + if (!$locker->isLocked()) { + throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused'); + } + + $lockedPackages = $locker->getLockedRepository()->getPackages(); + + $required = []; + foreach (array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) { + $required[$link->getTarget()] = true; + } + + do { + $found = false; + foreach ($lockedPackages as $index => $package) { + foreach ($package->getNames() as $name) { + if (isset($required[$name])) { + foreach ($package->getRequires() as $link) { + $required[$link->getTarget()] = true; + } + $found = true; + unset($lockedPackages[$index]); + break; + } + } + } + } while ($found); + + $unused = []; + foreach ($lockedPackages as $package) { + $unused[] = $package->getName(); + } + $packages = array_merge($packages, $unused); + + if (count($packages) === 0) { + $this->getIO()->writeError('No unused packages to remove'); + + return 0; + } + } + + $file = Factory::getComposerFile(); + + $jsonFile = new JsonFile($file); + /** @var array{require?: array, require-dev?: array} $composer */ + $composer = $jsonFile->read(); + $composerBackup = file_get_contents($jsonFile->getPath()); + + $json = new JsonConfigSource($jsonFile); + + $type = $input->getOption('dev') ? 'require-dev' : 'require'; + $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; + $io = $this->getIO(); + + if ($input->getOption('update-with-dependencies')) { + $io->writeError('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.'); + } + + // make sure name checks are done case insensitively + foreach (['require', 'require-dev'] as $linkType) { + if (isset($composer[$linkType])) { + foreach ($composer[$linkType] as $name => $version) { + $composer[$linkType][strtolower($name)] = $name; + } + } + } + + $dryRun = $input->getOption('dry-run'); + $toRemove = []; + foreach ($packages as $package) { + if (isset($composer[$type][$package])) { + if ($dryRun) { + $toRemove[$type][] = $composer[$type][$package]; + } else { + $json->removeLink($type, $composer[$type][$package]); + } + } elseif (isset($composer[$altType][$package])) { + $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { + if ($dryRun) { + $toRemove[$altType][] = $composer[$altType][$package]; + } else { + $json->removeLink($altType, $composer[$altType][$package]); + } + } + } + } elseif (isset($composer[$type]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) > 0) { + foreach ($matches as $matchedPackage) { + if ($dryRun) { + $toRemove[$type][] = $matchedPackage; + } else { + $json->removeLink($type, $matchedPackage); + } + } + } elseif (isset($composer[$altType]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) > 0) { + foreach ($matches as $matchedPackage) { + $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { + if ($dryRun) { + $toRemove[$altType][] = $matchedPackage; + } else { + $json->removeLink($altType, $matchedPackage); + } + } + } + } + } else { + $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); + } + } + + $io->writeError(''.$file.' has been updated'); + + if ($input->getOption('no-update')) { + return 0; + } + + if ($composer = $this->tryComposer()) { + $composer->getPluginManager()->deactivateInstalledPlugins(); + } + + // Update packages + $this->resetComposer(); + $composer = $this->requireComposer(); + + if ($dryRun) { + $rootPackage = $composer->getPackage(); + $links = [ + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ]; + foreach ($toRemove as $type => $names) { + foreach ($names as $name) { + unset($links[$type][$name]); + } + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $allowPlugins = $composer->getConfig()->get('allow-plugins'); + $removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : []; + if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) { + if (count($allowPlugins) === count($removedPlugins)) { + $json->removeConfigSetting('allow-plugins'); + } else { + foreach ($removedPlugins as $plugin) { + $json->removeConfigSetting('allow-plugins.'.$plugin); + } + } + } + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + $flags = ''; + if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + $flags .= ' --with-all-dependencies'; + } elseif ($input->getOption('no-update-with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + $flags .= ' --with-dependencies'; + } + + $io->writeError('Running composer update '.implode(' ', $packages).$flags.''); + + $install + ->setVerbose($input->getOption('verbose')) + ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setDryRun($dryRun) + ->setAudit(!$input->getOption('no-audit')) + ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) + ; + + // if no lock is present, we do not do a partial update as + // this is not supported by the Installer + if ($composer->getLocker()->isLocked()) { + $install->setUpdateAllowList($packages); + } + + $status = $install->run(); + if ($status !== 0) { + $io->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); + file_put_contents($jsonFile->getPath(), $composerBackup); + } + + if (!$dryRun) { + foreach ($packages as $package) { + if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) { + $io->writeError('Removal failed, '.$package.' is still present, it may be required by another package. See `composer why '.$package.'`.'); + + return 2; + } + } + } + + return $status; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RequireCommand.php b/vendor/composer/composer/src/Composer/Command/RequireCommand.php new file mode 100644 index 0000000..09eab3e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RequireCommand.php @@ -0,0 +1,643 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\DependencyResolver\Request; +use Composer\Package\AliasPackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Loader\RootPackageLoader; +use Composer\Package\Locker; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionBumper; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Util\Filesystem; +use Composer\Util\PackageSorter; +use Seld\Signal\SignalHandler; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Factory; +use Composer\Installer; +use Composer\Installer\InstallerEvents; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Package\Version\VersionParser; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\BasePackage; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\IO\IOInterface; +use Composer\Advisory\Auditor; +use Composer\Util\Silencer; + +/** + * @author Jérémy Romey + * @author Jordi Boggiano + */ +class RequireCommand extends BaseCommand +{ + use CompletionTrait; + use PackageDiscoveryTrait; + + /** @var bool */ + private $newlyCreated; + /** @var bool */ + private $firstRequire; + /** @var JsonFile */ + private $json; + /** @var string */ + private $file; + /** @var string */ + private $composerBackup; + /** @var string file name */ + private $lock; + /** @var ?string contents before modification if the lock file exists */ + private $lockBackup; + /** @var bool */ + private $dependencyResolutionCompleted = false; + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('require') + ->setAliases(['r']) + ->setDescription('Adds required packages to your composer.json and installs them') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), + new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), + new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'), + new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), + new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + ]) + ->setHelp( + <<file = Factory::getComposerFile(); + $io = $this->getIO(); + + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $this->newlyCreated = !file_exists($this->file); + if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) { + $io->writeError(''.$this->file.' could not be created.'); + + return 1; + } + if (!Filesystem::isReadable($this->file)) { + $io->writeError(''.$this->file.' is not readable.'); + + return 1; + } + + if (filesize($this->file) === 0) { + file_put_contents($this->file, "{\n}\n"); + } + + $this->json = new JsonFile($this->file); + $this->lock = Factory::getLockFile($this->file); + $this->composerBackup = file_get_contents($this->json->getPath()); + $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null; + + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $this->revertComposerFile(); + $handler->exitWithLastSignal(); + }); + + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { + $io->writeError(''.$this->file.' is not writable.'); + + return 1; + } + + if ($input->getOption('fixed') === true) { + $config = $this->json->read(); + + $packageType = empty($config['type']) ? 'library' : $config['type']; + + /** + * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955 + */ + if ($packageType !== 'project' && !$input->getOption('dev')) { + $io->writeError('The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.'); + + if (!isset($config['type'])) { + $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); + } + + return 1; + } + } + + $composer = $this->requireComposer(); + $repos = $composer->getRepositoryManager()->getRepositories(); + + $platformOverrides = $composer->getConfig()->get('platform'); + // initialize $this->repos as it is used by the PackageDiscoveryTrait + $this->repos = new CompositeRepository(array_merge( + [$platformRepo = new PlatformRepository([], $platformOverrides)], + $repos + )); + + if ($composer->getPackage()->getPreferStable()) { + $preferredStability = 'stable'; + } else { + $preferredStability = $composer->getPackage()->getMinimumStability(); + } + + try { + $requirements = $this->determineRequirements( + $input, + $output, + $input->getArgument('packages'), + $platformRepo, + $preferredStability, + $input->getOption('no-update'), // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint + $input->getOption('fixed') + ); + } catch (\Exception $e) { + if ($this->newlyCreated) { + $this->revertComposerFile(); + + throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e); + } + + throw $e; + } + + $requirements = $this->formatRequirements($requirements); + + if (!$input->getOption('dev') && $io->isInteractive() && !$composer->isGlobal()) { + $devPackages = []; + $devTags = ['dev', 'testing', 'static analysis']; + $currentRequiresByKey = $this->getPackagesByRequireKey(); + foreach ($requirements as $name => $version) { + // skip packages which are already in the composer.json as those have already been decided + if (isset($currentRequiresByKey[$name])) { + continue; + } + + $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name)); + if ($pkg instanceof CompletePackageInterface) { + $pkgDevTags = array_intersect($devTags, array_map('strtolower', $pkg->getKeywords())); + if (count($pkgDevTags) > 0) { + $devPackages[] = $pkgDevTags; + } + } + } + + if (count($devPackages) === count($requirements)) { + $plural = count($requirements) > 1 ? 's' : ''; + $plural2 = count($requirements) > 1 ? 'are' : 'is'; + $plural3 = count($requirements) > 1 ? 'they are' : 'it is'; + $pkgDevTags = array_unique(array_merge(...$devPackages)); + $io->warning('The package'.$plural.' you required '.$plural2.' recommended to be placed in require-dev (because '.$plural3.' tagged as "'.implode('", "', $pkgDevTags).'") but you did not use --dev.'); + if ($io->askConfirmation('Do you want to re-run the command with --dev? [yes]? ')) { + $input->setOption('dev', true); + } + } + + unset($devPackages, $pkgDevTags); + } + + $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; + $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; + + // check which requirements need the version guessed + $requirementsToGuess = []; + foreach ($requirements as $package => $constraint) { + if ($constraint === 'guess') { + $requirements[$package] = '*'; + $requirementsToGuess[] = $package; + } + } + + // validate requirements format + $versionParser = new VersionParser(); + foreach ($requirements as $package => $constraint) { + if (strtolower($package) === $composer->getPackage()->getName()) { + $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); + + return 1; + } + if ($constraint === 'self.version') { + continue; + } + $versionParser->parseConstraints($constraint); + } + + $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey); + if (count($inconsistentRequireKeys) > 0) { + foreach ($inconsistentRequireKeys as $package) { + $io->warning(sprintf( + '%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.', + $package, + $removeKey, + $input->getOption('dev') ? 'with' : 'without', + $requireKey + )); + } + + if ($io->isInteractive()) { + if (!$io->askConfirmation(sprintf('Do you want to move %s? [no]? ', count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), false)) { + if (!$io->askConfirmation(sprintf('Do you want to re-run the command %s --dev? [yes]? ', $input->getOption('dev') ? 'without' : 'with'), true)) { + return 0; + } + + $input->setOption('dev', true); + [$requireKey, $removeKey] = [$removeKey, $requireKey]; + } + } + } + + $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); + + $this->firstRequire = $this->newlyCreated; + if (!$this->firstRequire) { + $composerDefinition = $this->json->read(); + if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) { + $this->firstRequire = true; + } + } + + if (!$input->getOption('dry-run')) { + $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); + } + + $io->writeError(''.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').''); + + if ($input->getOption('no-update')) { + return 0; + } + + $composer->getPluginManager()->deactivateInstalledPlugins(); + + try { + $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); + if ($result === 0 && count($requirementsToGuess) > 0) { + $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed')); + } + + return $result; + } catch (\Exception $e) { + if (!$this->dependencyResolutionCompleted) { + $this->revertComposerFile(); + } + throw $e; + } finally { + if ($input->getOption('dry-run') && $this->newlyCreated) { + @unlink($this->json->getPath()); + } + + $signalHandler->unregister(); + } + } + + /** + * @param array $newRequirements + * @return string[] + */ + private function getInconsistentRequireKeys(array $newRequirements, string $requireKey): array + { + $requireKeys = $this->getPackagesByRequireKey(); + $inconsistentRequirements = []; + foreach ($requireKeys as $package => $packageRequireKey) { + if (!isset($newRequirements[$package])) { + continue; + } + if ($requireKey !== $packageRequireKey) { + $inconsistentRequirements[] = $package; + } + } + + return $inconsistentRequirements; + } + + /** + * @return array + */ + private function getPackagesByRequireKey(): array + { + $composerDefinition = $this->json->read(); + $require = []; + $requireDev = []; + + if (isset($composerDefinition['require'])) { + $require = $composerDefinition['require']; + } + + if (isset($composerDefinition['require-dev'])) { + $requireDev = $composerDefinition['require-dev']; + } + + return array_merge( + array_fill_keys(array_keys($require), 'require'), + array_fill_keys(array_keys($requireDev), 'require-dev') + ); + } + + /** + * @param array $requirements + * @param 'require'|'require-dev' $requireKey + * @param 'require'|'require-dev' $removeKey + * @throws \Exception + */ + private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int + { + // Update packages + $this->resetComposer(); + $composer = $this->requireComposer(); + + $this->dependencyResolutionCompleted = false; + $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function (): void { + $this->dependencyResolutionCompleted = true; + }, 10000); + + if ($input->getOption('dry-run')) { + $rootPackage = $composer->getPackage(); + $links = [ + 'require' => $rootPackage->getRequires(), + 'require-dev' => $rootPackage->getDevRequires(), + ]; + $loader = new ArrayLoader(); + $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements); + $links[$requireKey] = array_merge($links[$requireKey], $newLinks); + foreach ($requirements as $package => $constraint) { + unset($links[$removeKey][$package]); + } + $rootPackage->setRequires($links['require']); + $rootPackage->setDevRequires($links['require-dev']); + + // extract stability flags & references as they weren't present when loading the unmodified composer.json + $references = $rootPackage->getReferences(); + $references = RootPackageLoader::extractReferences($requirements, $references); + $rootPackage->setReferences($references); + $stabilityFlags = $rootPackage->getStabilityFlags(); + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags); + $rootPackage->setStabilityFlags($stabilityFlags); + unset($stabilityFlags, $references); + } + + $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + $flags = ''; + if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + $flags .= ' --with-all-dependencies'; + } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + $flags .= ' --with-dependencies'; + } + + $io->writeError('Running composer update '.implode(' ', array_keys($requirements)).$flags.''); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input); + + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setAudit(!$input->getOption('no-audit')) + ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) + ; + + // if no lock is present, or the file is brand new, we do not do a + // partial update as this is not supported by the Installer + if (!$this->firstRequire && $composer->getLocker()->isLocked()) { + $install->setUpdateAllowList(array_keys($requirements)); + } + + $status = $install->run(); + if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) { + if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) { + foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) { + if (!isset($req['version'])) { + $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require '.$req['name'].':*" to figure out if any version is installable, or "composer require '.$req['name'].':^2.1" if you know which you need.'); + break; + } + } + } + $this->revertComposerFile(); + } + + return $status; + } + + /** + * @param list $requirementsToUpdate + */ + private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed): int + { + $composer = $this->requireComposer(); + $locker = $composer->getLocker(); + $requirements = []; + $versionSelector = new VersionSelector(new RepositorySet()); + $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(true) : $composer->getRepositoryManager()->getLocalRepository(); + foreach ($requirementsToUpdate as $packageName) { + $package = $repo->findPackage($packageName, '*'); + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + if (!$package instanceof PackageInterface) { + continue; + } + + if ($fixed) { + $requirements[$packageName] = $package->getPrettyVersion(); + } else { + $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package); + } + $this->getIO()->writeError(sprintf( + 'Using version %s for %s', + $requirements[$packageName], + $packageName + )); + + if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) { + $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state'); + if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (Y) or would you rather abort (n) the whole operation [Y,n]? ')) { + $this->revertComposerFile(); + + return 1; + } + } + } + + if (!$dryRun) { + $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); + if ($locker->isLocked() && $composer->getConfig()->get('lock')) { + $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); + $locker->updateHash($this->json, function (array $lockData) use ($stabilityFlags) { + foreach ($stabilityFlags as $packageName => $flag) { + $lockData['stability-flags'][$packageName] = $flag; + } + + return $lockData; + }); + } + } + + return 0; + } + + /** + * @param array $new + */ + private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): void + { + if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) { + return; + } + + $composerDefinition = $this->json->read(); + foreach ($new as $package => $version) { + $composerDefinition[$requireKey][$package] = $version; + unset($composerDefinition[$removeKey][$package]); + if (isset($composerDefinition[$removeKey]) && count($composerDefinition[$removeKey]) === 0) { + unset($composerDefinition[$removeKey]); + } + } + $this->json->write($composerDefinition); + } + + /** + * @param array $new + */ + private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): bool + { + $contents = file_get_contents($json->getPath()); + + $manipulator = new JsonManipulator($contents); + + foreach ($new as $package => $constraint) { + if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) { + return false; + } + if (!$manipulator->removeSubNode($removeKey, $package)) { + return false; + } + } + + $manipulator->removeMainKeyIfEmpty($removeKey); + + file_put_contents($json->getPath(), $manipulator->getContents()); + + return true; + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + } + + private function revertComposerFile(): void + { + $io = $this->getIO(); + + if ($this->newlyCreated) { + $io->writeError("\n".'Installation failed, deleting '.$this->file.'.'); + unlink($this->json->getPath()); + if (file_exists($this->lock)) { + unlink($this->lock); + } + } else { + $msg = ' to its '; + if ($this->lockBackup) { + $msg = ' and '.$this->lock.' to their '; + } + $io->writeError("\n".'Installation failed, reverting '.$this->file.$msg.'original content.'); + file_put_contents($this->json->getPath(), $this->composerBackup); + if ($this->lockBackup) { + file_put_contents($this->lock, $this->lockBackup); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php b/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php new file mode 100644 index 0000000..8283dbe --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/RunScriptCommand.php @@ -0,0 +1,187 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Script\Event as ScriptEvent; +use Composer\Script\ScriptEvents; +use Composer\Util\ProcessExecutor; +use Composer\Util\Platform; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Fabien Potencier + */ +class RunScriptCommand extends BaseCommand +{ + /** + * @var string[] Array with command events + */ + protected $scriptEvents = [ + ScriptEvents::PRE_INSTALL_CMD, + ScriptEvents::POST_INSTALL_CMD, + ScriptEvents::PRE_UPDATE_CMD, + ScriptEvents::POST_UPDATE_CMD, + ScriptEvents::PRE_STATUS_CMD, + ScriptEvents::POST_STATUS_CMD, + ScriptEvents::POST_ROOT_PACKAGE_INSTALL, + ScriptEvents::POST_CREATE_PROJECT_CMD, + ScriptEvents::PRE_ARCHIVE_CMD, + ScriptEvents::POST_ARCHIVE_CMD, + ScriptEvents::PRE_AUTOLOAD_DUMP, + ScriptEvents::POST_AUTOLOAD_DUMP, + ]; + + protected function configure(): void + { + $this + ->setName('run-script') + ->setAliases(['run']) + ->setDescription('Runs the scripts defined in composer.json') + ->setDefinition([ + new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () { + return array_map(static function ($script) { return $script['name']; }, $this->getScripts()); + }), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), + ]) + ->setHelp( + <<run-script command runs scripts defined in composer.json: + +php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script-run +EOT + ) + ; + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + $scripts = $this->getScripts(); + if (count($scripts) === 0) { + return; + } + + if ($input->getArgument('script') !== null || $input->getOption('list')) { + return; + } + + $options = []; + foreach ($scripts as $script) { + $options[$script['name']] = $script['description']; + } + $io = $this->getIO(); + $script = $io->select( + 'Script to run: ', + $options, + '', + 1, + 'Invalid script name "%s"' + ); + + $input->setArgument('script', $script); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getOption('list')) { + return $this->listScripts($output); + } + + $script = $input->getArgument('script'); + if ($script === null) { + throw new \RuntimeException('Missing required argument "script"'); + } + + if (!in_array($script, $this->scriptEvents)) { + if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { + throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script)); + } + } + + $composer = $this->requireComposer(); + $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); + $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); + $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); + if (!$hasListeners) { + throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script)); + } + + $args = $input->getArgument('args'); + + if (null !== $timeout = $input->getOption('timeout')) { + if (!ctype_digit($timeout)) { + throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever'); + } + // Override global timeout set before in Composer by environment or config + ProcessExecutor::setTimeout((int) $timeout); + } + + Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); + + return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); + } + + protected function listScripts(OutputInterface $output): int + { + $scripts = $this->getScripts(); + if (count($scripts) === 0) { + return 0; + } + + $io = $this->getIO(); + $io->writeError('scripts:'); + $table = []; + foreach ($scripts as $script) { + $table[] = [' '.$script['name'], $script['description']]; + } + + $this->renderTable($table, $output); + + return 0; + } + + /** + * @return list + */ + private function getScripts(): array + { + $scripts = $this->requireComposer()->getPackage()->getScripts(); + if (count($scripts) === 0) { + return []; + } + + $result = []; + foreach ($scripts as $name => $script) { + $description = ''; + try { + $cmd = $this->getApplication()->find($name); + if ($cmd instanceof ScriptAliasCommand) { + $description = $cmd->getDescription(); + } + } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) { + // ignore scripts that have no command associated, like native Composer script listeners + } + $result[] = ['name' => $name, 'description' => $description]; + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php b/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php new file mode 100644 index 0000000..f2de686 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php @@ -0,0 +1,89 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Pcre\Preg; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + */ +class ScriptAliasCommand extends BaseCommand +{ + /** @var string */ + private $script; + /** @var string */ + private $description; + /** @var string[] */ + private $aliases; + + /** + * @param string[] $aliases + */ + public function __construct(string $script, ?string $description, array $aliases = []) + { + $this->script = $script; + $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json'; + $this->aliases = $aliases; + + foreach ($this->aliases as $alias) { + if (!is_string($alias)) { + throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings'); + } + } + + $this->ignoreValidationErrors(); + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName($this->script) + ->setDescription($this->description) + ->setAliases($this->aliases) + ->setDefinition([ + new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), + new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), + ]) + ->setHelp( + <<run-script command runs scripts defined in composer.json: + +php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script-run +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $args = $input->getArguments(); + + // TODO remove for Symfony 6+ as it is then in the interface + if (!method_exists($input, '__toString')) { // @phpstan-ignore-line + throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); + } + + return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args'], ['script-alias-input' => Preg::replace('{^\S+ ?}', '', $input->__toString(), 1)]); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SearchCommand.php b/vendor/composer/composer/src/Composer/Command/SearchCommand.php new file mode 100644 index 0000000..d95c941 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SearchCommand.php @@ -0,0 +1,127 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\Json\JsonFile; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; + +/** + * @author Robert Schönthal + */ +class SearchCommand extends BaseCommand +{ + protected function configure(): void + { + $this + ->setName('search') + ->setDescription('Searches for packages') + ->setDefinition([ + new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'), + new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'), + new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), + ]) + ->setHelp( + <<php composer.phar search symfony composer + +Read more at https://getcomposer.org/doc/03-cli.md#search +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // init repos + $platformRepo = new PlatformRepository; + $io = $this->getIO(); + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + if (!($composer = $this->tryComposer())) { + $composer = $this->createComposerInstance($input, $this->getIO(), []); + } + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $installedRepo = new CompositeRepository([$localRepo, $platformRepo]); + $repos = new CompositeRepository(array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories())); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $mode = RepositoryInterface::SEARCH_FULLTEXT; + if ($input->getOption('only-name') === true) { + if ($input->getOption('only-vendor') === true) { + throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together'); + } + $mode = RepositoryInterface::SEARCH_NAME; + } elseif ($input->getOption('only-vendor') === true) { + $mode = RepositoryInterface::SEARCH_VENDOR; + } + + $type = $input->getOption('type'); + + $query = implode(' ', $input->getArgument('tokens')); + if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) { + $query = preg_quote($query); + } + + $results = $repos->search($query, $mode, $type); + + if (\count($results) > 0 && $format === 'text') { + $width = $this->getTerminalWidth(); + + $nameLength = 0; + foreach ($results as $result) { + $nameLength = max(strlen($result['name']), $nameLength); + } + $nameLength += 1; + foreach ($results as $result) { + $description = $result['description'] ?? ''; + $warning = !empty($result['abandoned']) ? '! Abandoned ! ' : ''; + $remaining = $width - $nameLength - strlen($warning) - 2; + if (strlen($description) > $remaining) { + $description = substr($description, 0, $remaining - 3) . '...'; + } + + $link = $result['url'] ?? null; + if ($link !== null) { + $io->write(''.$result['name'].''. str_repeat(' ', $nameLength - strlen($result['name'])) . $warning . $description); + } else { + $io->write(str_pad($result['name'], $nameLength, ' ') . $warning . $description); + } + } + } elseif ($format === 'json') { + $io->write(JsonFile::encode($results)); + } + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php b/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php new file mode 100644 index 0000000..1bf2e57 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php @@ -0,0 +1,646 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Factory; +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\SelfUpdate\Keys; +use Composer\SelfUpdate\Versions; +use Composer\IO\IOInterface; +use Composer\Downloader\FilesystemException; +use Composer\Downloader\TransportException; +use Phar; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; + +/** + * @author Igor Wiedler + * @author Kevin Ran + * @author Jordi Boggiano + */ +class SelfUpdateCommand extends BaseCommand +{ + private const HOMEPAGE = 'getcomposer.org'; + private const OLD_INSTALL_EXT = '-old.phar'; + + protected function configure(): void + { + $this + ->setName('self-update') + ->setAliases(['selfupdate']) + ->setDescription('Updates composer.phar to the latest version') + ->setDefinition([ + new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'), + new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'), + new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'), + new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'), + new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'), + new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), + new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'), + new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'), + new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'), + new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), + ]) + ->setHelp( + <<self-update command checks getcomposer.org for newer +versions of composer and if found, installs the latest. + +php composer.phar self-update + +Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate +EOT + ) + ; + } + + /** + * @throws FilesystemException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($_SERVER['argv'][0] === 'Standard input code') { + return 1; + } + + // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file + // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252 + class_exists('Composer\Util\Platform'); + class_exists('Composer\Downloader\FilesystemException'); + + $config = Factory::createConfig(); + + if ($config->get('disable-tls') === true) { + $baseUrl = 'http://' . self::HOMEPAGE; + } else { + $baseUrl = 'https://' . self::HOMEPAGE; + } + + $io = $this->getIO(); + $httpDownloader = Factory::createHttpDownloader($io, $config); + + $versionsUtil = new Versions($config, $httpDownloader); + + // switch channel if requested + $requestedChannel = null; + foreach (Versions::CHANNELS as $channel) { + if ($input->getOption($channel)) { + $requestedChannel = $channel; + $versionsUtil->setChannel($channel, $io); + break; + } + } + + if ($input->getOption('set-channel-only')) { + return 0; + } + + $cacheDir = $config->get('cache-dir'); + $rollbackDir = $config->get('data-dir'); + $home = $config->get('home'); + $localFilename = Phar::running(false); + if ('' === $localFilename) { + throw new \RuntimeException('Could not determine the location of the composer.phar file as it appears you are not running this code from a phar archive.'); + } + + if ($input->getOption('update-keys')) { + $this->fetchKeys($io, $config); + + return 0; + } + + // ensure composer.phar location is accessible + if (!file_exists($localFilename)) { + throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible'); + } + + // check if current dir is writable and if not try the cache dir from settings + $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir; + + // check for permissions in local filesystem before start connection process + if (!is_writable($tmpDir)) { + throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); + } + + // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $composerUser = posix_getpwuid(posix_geteuid()); + $homeDirOwnerId = fileowner($home); + if (is_array($composerUser) && $homeDirOwnerId !== false) { + $homeOwner = posix_getpwuid($homeDirOwnerId); + if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) { + $io->writeError('You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"'); + } + } + } + + if ($input->getOption('rollback')) { + return $this->rollback($output, $rollbackDir, $localFilename); + } + + if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') { + $input->setArgument('version', null); + } + + $latest = $versionsUtil->getLatest(); + $latestStable = $versionsUtil->getLatest('stable'); + try { + $latestPreview = $versionsUtil->getLatest('preview'); + } catch (\UnexpectedValueException $e) { + $latestPreview = $latestStable; + } + $latestVersion = $latest['version']; + $updateVersion = $input->getArgument('version') ?? $latestVersion; + $currentMajorVersion = Preg::replace('{^(\d+).*}', '$1', Composer::getVersion()); + $updateMajorVersion = Preg::replace('{^(\d+).*}', '$1', $updateVersion); + $previewMajorVersion = Preg::replace('{^(\d+).*}', '$1', $latestPreview['version']); + + if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) { + // if requesting stable channel and no specific version, avoid automatically upgrading to the next major + // simply output a warning that the next major stable is available and let users upgrade to it manually + if ($currentMajorVersion < $updateMajorVersion) { + $skippedVersion = $updateVersion; + + $versionsUtil->setChannel($currentMajorVersion); + + $latest = $versionsUtil->getLatest(); + $latestStable = $versionsUtil->getLatest('stable'); + $latestVersion = $latest['version']; + $updateVersion = $latestVersion; + + $io->writeError('A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.''); + } elseif ($currentMajorVersion < $previewMajorVersion) { + // promote next major version if available in preview + $io->writeError('A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.'); + } + } + + $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel; + if (is_numeric($effectiveChannel) && strpos($latestStable['version'], $effectiveChannel) !== 0) { + $io->writeError('Warning: You forced the install of '.$latestVersion.' via --'.$effectiveChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); + } + if (isset($latest['eol'])) { + $io->writeError('Warning: Version '.$latestVersion.' is EOL / End of Life. '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); + } + + if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { + $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); + + return 1; + } + + $channelString = $versionsUtil->getChannel(); + if (is_numeric($channelString)) { + $channelString .= '.x'; + } + + if (Composer::VERSION === $updateVersion) { + $io->writeError( + sprintf( + 'You are already using the latest available Composer version %s (%s channel).', + $updateVersion, + $channelString + ) + ); + + // remove all backups except for the most recent, if any + if ($input->getOption('clean-backups')) { + $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir)); + } + + return 0; + } + + $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp'.random_int(0, 10000000).'.phar'; + $backupFile = sprintf( + '%s/%s-%s%s', + $rollbackDir, + strtr(Composer::RELEASE_DATE, ' :', '_-'), + Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION), + self::OLD_INSTALL_EXT + ); + + $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion); + + $io->write(sprintf("Upgrading to version %s (%s channel).", $updateVersion, $channelString)); + $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); + try { + $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody(); + } catch (TransportException $e) { + if ($e->getStatusCode() === 404) { + throw new \InvalidArgumentException('Version "'.$updateVersion.'" could not be found.', 0, $e); + } + throw $e; + } + $io->writeError(' ', false); + $httpDownloader->copy($remoteFilename, $tempFilename); + $io->writeError(''); + + if (!file_exists($tempFilename) || null === $signature || '' === $signature) { + $io->writeError('The download of the new composer version failed for an unexpected reason'); + + return 1; + } + + // verify phar signature + if (!extension_loaded('openssl') && $config->get('disable-tls')) { + $io->writeError('Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls'); + } else { + if (!extension_loaded('openssl')) { + throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. ' + . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + + $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); + if (!file_exists($sigFile)) { + file_put_contents( + $home.'/keys.dev.pub', + <<getOption('clean-backups')) { + $this->cleanBackups($rollbackDir); + } + + if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { + @unlink($tempFilename); + + return 1; + } + + if (file_exists($backupFile)) { + $io->writeError(sprintf( + 'Use composer self-update --rollback to return to version %s', + Composer::VERSION + )); + } else { + $io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); + } + + return 0; + } + + /** + * @throws \Exception + */ + protected function fetchKeys(IOInterface $io, Config $config): void + { + if (!$io->isInteractive()) { + throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); + } + + $io->write('Open https://composer.github.io/pubkeys.html to find the latest keys'); + + $validator = static function ($value): string { + $value = (string) $value; + if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) { + throw new \UnexpectedValueException('Invalid input'); + } + + return trim($value)."\n"; + }; + + $devKey = ''; + while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { + $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); + while ($line = $io->ask('', '')) { + $devKey .= trim($line)."\n"; + if (trim($line) === '-----END PUBLIC KEY-----') { + break; + } + } + } + file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]); + $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); + + $tagsKey = ''; + while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { + $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); + while ($line = $io->ask('', '')) { + $tagsKey .= trim($line)."\n"; + if (trim($line) === '-----END PUBLIC KEY-----') { + break; + } + } + } + file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]); + $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); + + $io->write('Public keys stored in '.$config->get('home')); + } + + /** + * @throws FilesystemException + */ + protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename): int + { + $rollbackVersion = $this->getLastBackupVersion($rollbackDir); + if (null === $rollbackVersion) { + throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"'); + } + + $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; + + if (!is_file($oldFile)) { + throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found'); + } + if (!Filesystem::isReadable($oldFile)) { + throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read'); + } + + $io = $this->getIO(); + $io->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); + if (!$this->setLocalPhar($localFilename, $oldFile)) { + return 1; + } + + return 0; + } + + /** + * Checks if the downloaded/rollback phar is valid then moves it + * + * @param string $localFilename The composer.phar location + * @param string $newFilename The downloaded or backup phar + * @param string $backupTarget The filename to use for the backup + * @throws FilesystemException If the file cannot be moved + * @return bool Whether the phar is valid and has been moved + */ + protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null): bool + { + $io = $this->getIO(); + $perms = @fileperms($localFilename); + if ($perms !== false) { + @chmod($newFilename, $perms); + } + + // check phar validity + if (!$this->validatePhar($newFilename, $error)) { + $io->writeError('The '.($backupTarget !== null ? 'update' : 'backup').' file is corrupted ('.$error.')'); + + if ($backupTarget !== null) { + $io->writeError('Please re-run the self-update command to try again.'); + } + + return false; + } + + // copy current file into backups dir + if ($backupTarget !== null) { + @copy($localFilename, $backupTarget); + } + + try { + if (Platform::isWindows()) { + // use copy to apply permissions from the destination directory + // as rename uses source permissions and may block other users + copy($newFilename, $localFilename); + @unlink($newFilename); + } else { + rename($newFilename, $localFilename); + } + + return true; + } catch (\Exception $e) { + // see if we can run this operation as an Admin on Windows + if (!is_writable(dirname($localFilename)) + && $io->isInteractive() + && $this->isWindowsNonAdminUser()) { + return $this->tryAsWindowsAdmin($localFilename, $newFilename); + } + + @unlink($newFilename); + $action = 'Composer '.($backupTarget !== null ? 'update' : 'rollback'); + throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage()); + } + } + + protected function cleanBackups(string $rollbackDir, ?string $except = null): void + { + $finder = $this->getOldInstallationFinder($rollbackDir); + $io = $this->getIO(); + $fs = new Filesystem; + + foreach ($finder as $file) { + if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) { + continue; + } + $file = (string) $file; + $io->writeError('Removing: '.$file.''); + $fs->remove($file); + } + } + + protected function getLastBackupVersion(string $rollbackDir): ?string + { + $finder = $this->getOldInstallationFinder($rollbackDir); + $finder->sortByName(); + $files = iterator_to_array($finder); + + if (count($files) > 0) { + return end($files)->getBasename(self::OLD_INSTALL_EXT); + } + + return null; + } + + protected function getOldInstallationFinder(string $rollbackDir): Finder + { + return Finder::create() + ->depth(0) + ->files() + ->name('*' . self::OLD_INSTALL_EXT) + ->in($rollbackDir); + } + + /** + * Validates the downloaded/backup phar file + * + * @param string $pharFile The downloaded or backup phar + * @param null|string $error Set by method on failure + * + * Code taken from getcomposer.org/installer. Any changes should be made + * there and replicated here + * + * @throws \Exception + * @return bool If the operation succeeded + */ + protected function validatePhar(string $pharFile, ?string &$error): bool + { + if ((bool) ini_get('phar.readonly')) { + return true; + } + + try { + // Test the phar validity + $phar = new \Phar($pharFile); + // Free the variable to unlock the file + unset($phar); + $result = true; + } catch (\Exception $e) { + if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { + throw $e; + } + $error = $e->getMessage(); + $result = false; + } + + return $result; + } + + /** + * Returns true if this is a non-admin Windows user account + */ + protected function isWindowsNonAdminUser(): bool + { + if (!Platform::isWindows()) { + return false; + } + + // fltmc.exe manages filter drivers and errors without admin privileges + exec('fltmc.exe filters', $output, $exitCode); + + return $exitCode !== 0; + } + + /** + * Invokes a UAC prompt to update composer.phar as an admin + * + * Uses a .vbs script to elevate and run the cmd.exe copy command. + * + * @param string $localFilename The composer.phar location + * @param string $newFilename The downloaded or backup phar + * @return bool Whether composer.phar has been updated + */ + protected function tryAsWindowsAdmin(string $localFilename, string $newFilename): bool + { + $io = $this->getIO(); + + $io->writeError('Unable to write "'.$localFilename.'". Access is denied.'); + $helpMessage = 'Please run the self-update command as an Administrator.'; + $question = 'Complete this operation with Administrator privileges [Y,n]? '; + + if (!$io->askConfirmation($question, true)) { + $io->writeError('Operation cancelled. '.$helpMessage.''); + + return false; + } + + $tmpFile = tempnam(sys_get_temp_dir(), ''); + if (false === $tmpFile) { + $io->writeError('Operation failed.'.$helpMessage.''); + + return false; + } + $script = $tmpFile.'.vbs'; + rename($tmpFile, $script); + + $checksum = hash_file('sha256', $newFilename); + + // cmd's internal copy is fussy about backslashes + $source = str_replace('/', '\\', $newFilename); + $destination = str_replace('/', '\\', $localFilename); + + $vbs = <<writeError('Operation succeeded.'); + @unlink($newFilename); + } else { + $io->writeError('Operation failed.'.$helpMessage.''); + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ShowCommand.php b/vendor/composer/composer/src/Composer/Command/ShowCommand.php new file mode 100644 index 0000000..14c9a4c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ShowCommand.php @@ -0,0 +1,1558 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\DependencyResolver\DefaultPolicy; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\ArrayRepository; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\ComposerRepository; +use Composer\Repository\CompositeRepository; +use Composer\Repository\FilterRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryFactory; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Repository\RepositoryUtils; +use Composer\Repository\RootPackageRepository; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Semver; +use Composer\Spdx\SpdxLicenses; +use Composer\Util\PackageInfo; +use DateTimeInterface; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Robert Schönthal + * @author Jordi Boggiano + * @author Jérémy Romey + * @author Mihai Plasoianu + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-type JsonStructure array|AutoloadRules> + */ +class ShowCommand extends BaseCommand +{ + use CompletionTrait; + + /** @var VersionParser */ + protected $versionParser; + /** @var string[] */ + protected $colors; + + /** @var ?RepositorySet */ + private $repositorySet; + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('show') + ->setAliases(['info']) + ->setDescription('Shows information about packages') + ->setDefinition([ + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()), + new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), + new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), + new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'), + new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'), + new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'), + new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'), + new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), + new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'), + new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'), + new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), + new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), + new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), + new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'), + new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'), + new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), + ]) + ->setHelp( + <<getOption('available') || $input->getOption('all')) { + return $this->suggestAvailablePackageInclPlatform()($input); + } + + if ($input->getOption('platform')) { + return $this->suggestPlatformPackage()($input); + } + + return $this->suggestInstalledPackage(false)($input); + }; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->versionParser = new VersionParser; + if ($input->getOption('tree')) { + $this->initStyles($output); + } + + $composer = $this->tryComposer(); + $io = $this->getIO(); + + if ($input->getOption('installed') && !$input->getOption('self')) { + $io->writeError('You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.'); + } + + if ($input->getOption('outdated')) { + $input->setOption('latest', true); + } elseif (count($input->getOption('ignore')) > 0) { + $io->writeError('You are using the option "ignore" for action other than "outdated", it will be ignored.'); + } + + if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { + $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); + + return 1; + } + + if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { + $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); + + return 1; + } + + if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) { + $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once'); + + return 1; + } + + if ($input->getOption('tree') && $input->getOption('latest')) { + $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)'); + + return 1; + } + + if ($input->getOption('tree') && $input->getOption('path')) { + $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)'); + + return 1; + } + + $format = $input->getOption('format'); + if (!in_array($format, ['text', 'json'])) { + $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); + + return 1; + } + + $platformReqFilter = $this->getPlatformRequirementFilter($input); + + // init repos + $platformOverrides = []; + if ($composer) { + $platformOverrides = $composer->getConfig()->get('platform'); + } + $platformRepo = new PlatformRepository([], $platformOverrides); + $lockedRepo = null; + + if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) { + $package = clone $this->requireComposer()->getPackage(); + if ($input->getOption('name-only')) { + $io->write($package->getName()); + + return 0; + } + if ($input->getArgument('package')) { + throw new \InvalidArgumentException('You cannot use --self together with a package name'); + } + $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]); + } elseif ($input->getOption('platform')) { + $repos = $installedRepo = new InstalledRepository([$platformRepo]); + } elseif ($input->getOption('available')) { + $installedRepo = new InstalledRepository([$platformRepo]); + if ($composer) { + $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository()); + } else { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $repos = new CompositeRepository($defaultRepos); + $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + } + } elseif ($input->getOption('all') && $composer) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $lockedRepo = $locker->getLockedRepository(true); + $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]); + } else { + $installedRepo = new InstalledRepository([$localRepo, $platformRepo]); + } + $repos = new CompositeRepository(array_merge([new FilterRepository($installedRepo, ['canonical' => false])], $composer->getRepositoryManager()->getRepositories())); + } elseif ($input->getOption('all')) { + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); + $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $installedRepo = new InstalledRepository([$platformRepo]); + $repos = new CompositeRepository(array_merge([$installedRepo], $defaultRepos)); + } elseif ($input->getOption('locked')) { + if (!$composer || !$composer->getLocker()->isLocked()) { + throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked'); + } + $locker = $composer->getLocker(); + $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev')); + if ($input->getOption('self')) { + $lockedRepo->addPackage(clone $composer->getPackage()); + } + $repos = $installedRepo = new InstalledRepository([$lockedRepo]); + } else { + // --installed / default case + if (!$composer) { + $composer = $this->requireComposer(); + } + $rootPkg = $composer->getPackage(); + + $rootRepo = new InstalledArrayRepository(); + if ($input->getOption('self')) { + $rootRepo = new RootPackageRepository(clone $rootPkg); + } + if ($input->getOption('no-dev')) { + $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg); + $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface { + return clone $pkg; + }, $packages))]); + } else { + $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]); + } + + if (!$installedRepo->getPackages()) { + $hasNonPlatformReqs = static function (array $reqs): bool { + return (bool) array_filter(array_keys($reqs), function (string $name) { + return !PlatformRepository::isPlatformPackage($name); + }); + }; + + if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) { + $io->writeError('No dependencies installed. Try running composer install or update.'); + } + } + } + + if ($composer) { + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + } + + if ($input->getOption('latest') && null === $composer) { + $io->writeError('No composer.json found in the current directory, disabling "latest" option'); + $input->setOption('latest', false); + } + + $packageFilter = $input->getArgument('package'); + + // show single package or single version + if (isset($package)) { + $versions = [$package->getPrettyVersion() => $package->getVersion()]; + } elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) { + [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version')); + + if (isset($package) && $input->getOption('direct')) { + if (!in_array($package->getName(), $this->getRootRequires(), true)) { + throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.'); + } + } + + if (!isset($package)) { + $options = $input->getOptions(); + $hint = ''; + if ($input->getOption('locked')) { + $hint .= ' in lock file'; + } + if (isset($options['working-dir'])) { + $hint .= ' in ' . $options['working-dir'] . '/composer.json'; + } + if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) { + $hint .= ', try using --platform (-p) to show platform packages'; + } + if (!$input->getOption('all') && !$input->getOption('available')) { + $hint .= ', try using --available (-a) to show all available packages'; + } + + throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.'); + } + } + + if (isset($package)) { + assert(isset($versions)); + + $exitCode = 0; + if ($input->getOption('tree')) { + $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); + + if ('json' === $format) { + $io->write(JsonFile::encode(['installed' => [$arrayTree]])); + } else { + $this->displayPackageTree([$arrayTree]); + } + + return $exitCode; + } + + $latestPackage = null; + if ($input->getOption('latest')) { + $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter); + } + if ( + $input->getOption('outdated') + && $input->getOption('strict') + && null !== $latestPackage + && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() + && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()) + ) { + $exitCode = 1; + } + if ($input->getOption('path')) { + $io->write($package->getName(), false); + $path = $composer->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $io->write(' ' . strtok(realpath($path), "\r\n")); + } else { + $io->write(' null'); + } + + return $exitCode; + } + + if ('json' === $format) { + $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null); + } else { + $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null); + } + + return $exitCode; + } + + // show tree view if requested + if ($input->getOption('tree')) { + $rootRequires = $this->getRootRequires(); + $packages = $installedRepo->getPackages(); + usort($packages, static function (BasePackage $a, BasePackage $b): int { + return strcmp((string) $a, (string) $b); + }); + $arrayTree = []; + foreach ($packages as $package) { + if (in_array($package->getName(), $rootRequires, true)) { + $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos); + } + } + + if ('json' === $format) { + $io->write(JsonFile::encode(['installed' => $arrayTree])); + } else { + $this->displayPackageTree($arrayTree); + } + + return 0; + } + + // list packages + /** @var array> $packages */ + $packages = []; + $packageFilterRegex = null; + if (null !== $packageFilter) { + $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i'; + } + + $packageListFilter = null; + if ($input->getOption('direct')) { + $packageListFilter = $this->getRootRequires(); + } + + if ($input->getOption('path') && null === $composer) { + $io->writeError('No composer.json found in the current directory, disabling "path" option'); + $input->setOption('path', false); + } + + foreach (RepositoryUtils::flattenRepositories($repos) as $repo) { + if ($repo === $platformRepo) { + $type = 'platform'; + } elseif ($lockedRepo !== null && $repo === $lockedRepo) { + $type = 'locked'; + } elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) { + $type = 'installed'; + } else { + $type = 'available'; + } + if ($repo instanceof ComposerRepository) { + foreach ($repo->getPackageNames($packageFilter) as $name) { + $packages[$type][$name] = $name; + } + } else { + foreach ($repo->getPackages() as $package) { + if (!isset($packages[$type][$package->getName()]) + || !is_object($packages[$type][$package->getName()]) + || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<') + ) { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) { + if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) { + $packages[$type][$package->getName()] = $package; + } + } + } + } + if ($repo === $platformRepo) { + foreach ($platformRepo->getDisabledPackages() as $name => $package) { + $packages[$type][$name] = $package; + } + } + } + } + + $showAllTypes = $input->getOption('all'); + $showLatest = $input->getOption('latest'); + $showMajorOnly = $input->getOption('major-only'); + $showMinorOnly = $input->getOption('minor-only'); + $showPatchOnly = $input->getOption('patch-only'); + $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore'))); + $indent = $showAllTypes ? ' ' : ''; + /** @var PackageInterface[] $latestPackages */ + $latestPackages = []; + $exitCode = 0; + $viewData = []; + $viewMetaData = []; + + $writeVersion = false; + $writeDescription = false; + + foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) { + if (isset($packages[$type])) { + ksort($packages[$type]); + + $nameLength = $versionLength = $latestLength = $releaseDateLength = 0; + + if ($showLatest && $showVersion) { + foreach ($packages[$type] as $package) { + if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) { + $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter); + if ($latestPackage === null) { + continue; + } + + $latestPackages[$package->getPrettyName()] = $latestPackage; + } + } + } + + $writePath = !$input->getOption('name-only') && $input->getOption('path'); + $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; + $writeLatest = $writeVersion && $showLatest; + $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); + $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json'); + + $hasOutdatedPackages = false; + + if ($input->getOption('sort-by-age')) { + usort($packages[$type], function ($a, $b) { + if (is_object($a) && is_object($b)) { + return $a->getReleaseDate() <=> $b->getReleaseDate(); + } + + return 0; + }); + } + + $viewData[$type] = []; + foreach ($packages[$type] as $package) { + $packageViewData = []; + if (is_object($package)) { + $latestPackage = null; + if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { + $latestPackage = $latestPackages[$package->getPrettyName()]; + } + + // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code + $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()); + // When using --major-only, and no bigger version than current major is found then it is considered up to date + $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly); + $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName()); + if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { + continue; + } + + if ($input->getOption('outdated') || $input->getOption('strict')) { + $hasOutdatedPackages = true; + } + + $packageViewData['name'] = $package->getPrettyName(); + $packageViewData['direct-dependency'] = in_array($package->getName(), $this->getRootRequires(), true); + if ($format !== 'json' || true !== $input->getOption('name-only')) { + $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null; + $packageViewData['source'] = PackageInfo::getViewSourceUrl($package); + } + $nameLength = max($nameLength, strlen($packageViewData['name'])); + if ($writeVersion) { + $packageViewData['version'] = $package->getFullPrettyVersion(); + if ($format === 'text') { + $packageViewData['version'] = ltrim($packageViewData['version'], 'v'); + } + $versionLength = max($versionLength, strlen($packageViewData['version'])); + } + if ($writeReleaseDate) { + if ($package->getReleaseDate() !== null) { + $packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate())); + if (!str_contains($packageViewData['release-age'], ' old')) { + $packageViewData['release-age'] = 'from '.$packageViewData['release-age']; + } + $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age'])); + $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM); + } else { + $packageViewData['release-age'] = ''; + $packageViewData['release-date'] = ''; + } + } + if ($writeLatest && $latestPackage) { + $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); + if ($format === 'text') { + $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v'); + } + $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); + $latestLength = max($latestLength, strlen($packageViewData['latest'])); + + if ($latestPackage->getReleaseDate() !== null) { + $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM); + } else { + $packageViewData['latest-release-date'] = ''; + } + } elseif ($writeLatest) { + $packageViewData['latest'] = '[none matched]'; + $packageViewData['latest-status'] = 'up-to-date'; + $latestLength = max($latestLength, strlen($packageViewData['latest'])); + } + if ($writeDescription && $package instanceof CompletePackageInterface) { + $packageViewData['description'] = $package->getDescription(); + } + if ($writePath) { + $path = $composer->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $packageViewData['path'] = strtok(realpath($path), "\r\n"); + } else { + $packageViewData['path'] = null; + } + } + + $packageIsAbandoned = false; + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $replacementPackageName = $latestPackage->getReplacementPackage(); + $replacement = $replacementPackageName !== null + ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' + : 'No replacement was suggested'; + $packageWarning = sprintf( + 'Package %s is abandoned, you should avoid using it. %s.', + $package->getPrettyName(), + $replacement + ); + $packageViewData['warning'] = $packageWarning; + $packageIsAbandoned = $replacementPackageName ?? true; + } + + $packageViewData['abandoned'] = $packageIsAbandoned; + } else { + $packageViewData['name'] = $package; + $nameLength = max($nameLength, strlen($package)); + } + $viewData[$type][] = $packageViewData; + } + $viewMetaData[$type] = [ + 'nameLength' => $nameLength, + 'versionLength' => $versionLength, + 'latestLength' => $latestLength, + 'releaseDateLength' => $releaseDateLength, + 'writeLatest' => $writeLatest, + 'writeReleaseDate' => $writeReleaseDate, + ]; + if ($input->getOption('strict') && $hasOutdatedPackages) { + $exitCode = 1; + break; + } + } + } + + if ('json' === $format) { + $io->write(JsonFile::encode($viewData)); + } else { + if ($input->getOption('latest') && array_filter($viewData)) { + if (!$io->isDecorated()) { + $io->writeError('Legend:'); + $io->writeError('! patch or minor release available - update recommended'); + $io->writeError('~ major release available - update possible'); + if (!$input->getOption('outdated')) { + $io->writeError('= up to date version'); + } + } else { + $io->writeError('Color legend:'); + $io->writeError('- patch or minor release available - update recommended'); + $io->writeError('- major release available - update possible'); + if (!$input->getOption('outdated')) { + $io->writeError('- up to date version'); + } + } + } + + $width = $this->getTerminalWidth(); + + foreach ($viewData as $type => $packages) { + $nameLength = $viewMetaData[$type]['nameLength']; + $versionLength = $viewMetaData[$type]['versionLength']; + $latestLength = $viewMetaData[$type]['latestLength']; + $releaseDateLength = $viewMetaData[$type]['releaseDateLength']; + $writeLatest = $viewMetaData[$type]['writeLatest']; + $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate']; + + $versionFits = $nameLength + $versionLength + 3 <= $width; + $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width; + $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width; + $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width; + + if ($latestFits && !$io->isDecorated()) { + $latestLength += 2; + } + + if ($showAllTypes) { + if ('available' === $type) { + $io->write('' . $type . ':'); + } else { + $io->write('' . $type . ':'); + } + } + + if ($writeLatest && !$input->getOption('direct')) { + $directDeps = []; + $transitiveDeps = []; + foreach ($packages as $pkg) { + if ($pkg['direct-dependency'] ?? false) { + $directDeps[] = $pkg; + } else { + $transitiveDeps[] = $pkg; + } + } + + $io->writeError(''); + $io->writeError('Direct dependencies required in composer.json:'); + if (\count($directDeps) > 0) { + $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } else { + $io->writeError('Everything up to date'); + } + $io->writeError(''); + $io->writeError('Transitive dependencies not required in composer.json:'); + if (\count($transitiveDeps) > 0) { + $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } else { + $io->writeError('Everything up to date'); + } + } else { + if ($writeLatest && \count($packages) === 0) { + $io->writeError('All your direct dependencies are up to date'); + } else { + $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); + } + } + + if ($showAllTypes) { + $io->write(''); + } + } + } + + return $exitCode; + } + + /** + * @param array $packages + */ + private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void + { + $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription; + $padVersion = $writeLatest || $writeReleaseDate || $writeDescription; + $padLatest = $writeDescription || $writeReleaseDate; + $padReleaseDate = $writeDescription; + foreach ($packages as $package) { + $link = $package['source'] ?? $package['homepage'] ?? ''; + if ($link !== '') { + $io->write($indent . ''.$package['name'].''. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false); + } else { + $io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false); + } + if (isset($package['version']) && $writeVersion) { + $io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false); + } + if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) { + $latestVersion = $package['latest']; + $updateStatus = $package['latest-status']; + $style = $this->updateStatusToVersionStyle($updateStatus); + if (!$io->isDecorated()) { + $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion; + } + $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '', false); + if ($writeReleaseDate && isset($package['release-age'])) { + $io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false); + } + } + if (isset($package['description']) && $writeDescription) { + $description = strtok($package['description'], "\r\n"); + $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4; + if ($writeLatest) { + $remaining -= $latestLength; + } + if (strlen($description) > $remaining) { + $description = substr($description, 0, $remaining - 3) . '...'; + } + $io->write(' ' . $description, false); + } + if (array_key_exists('path', $package)) { + $io->write(' '.(is_string($package['path']) ? $package['path'] : 'null'), false); + } + $io->write(''); + if (isset($package['warning'])) { + $io->write('' . $package['warning'] . ''); + } + } + } + + /** + * @return string[] + */ + protected function getRootRequires(): array + { + $composer = $this->tryComposer(); + if ($composer === null) { + return []; + } + + $rootPackage = $composer->getPackage(); + + return array_map( + 'strtolower', + array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires())) + ); + } + + /** + * @return array|string|string[] + */ + protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package) + { + return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package)); + } + + /** + * finds a package by name and version if provided + * + * @param ConstraintInterface|string $version + * @throws \InvalidArgumentException + * @return array{CompletePackageInterface|null, array} + */ + protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null): array + { + $name = strtolower($name); + $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; + + $policy = new DefaultPolicy(); + $repositorySet = new RepositorySet('dev'); + $repositorySet->allowInstalledRepositories(); + $repositorySet->addRepository($repos); + + $matchedPackage = null; + $versions = []; + if (PlatformRepository::isPlatformPackage($name)) { + $pool = $repositorySet->createPoolWithAllPackages(); + } else { + $pool = $repositorySet->createPoolForPackage($name); + } + $matches = $pool->whatProvides($name, $constraint); + $literals = []; + foreach ($matches as $package) { + // avoid showing the 9999999-dev alias if the default branch has no branch-alias set + if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + // select an exact match if it is in the installed repo and no specific version was required + if (null === $version && $installedRepo->hasPackage($package)) { + $matchedPackage = $package; + } + + $versions[$package->getPrettyVersion()] = $package->getVersion(); + $literals[] = $package->getId(); + } + + // select preferred package according to policy rules + if (null === $matchedPackage && \count($literals) > 0) { + $preferred = $policy->selectPreferredPackages($pool, $literals); + $matchedPackage = $pool->literalToPackage($preferred[0]); + } + + if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) { + throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage)); + } + + return [$matchedPackage, $versions]; + } + + /** + * Prints package info. + * + * @param array $versions + */ + protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $io = $this->getIO(); + + $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); + $this->printLinks($package, Link::TYPE_REQUIRE); + $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)'); + + if ($package->getSuggests()) { + $io->write("\nsuggests"); + foreach ($package->getSuggests() as $suggested => $reason) { + $io->write($suggested . ' ' . $reason . ''); + } + } + + $this->printLinks($package, Link::TYPE_PROVIDE); + $this->printLinks($package, Link::TYPE_CONFLICT); + $this->printLinks($package, Link::TYPE_REPLACE); + } + + /** + * Prints package metadata. + * + * @param array $versions + */ + protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package); + + $io = $this->getIO(); + $io->write('name : ' . $package->getPrettyName()); + $io->write('descrip. : ' . $package->getDescription()); + $io->write('keywords : ' . implode(', ', $package->getKeywords() ?: [])); + $this->printVersions($package, $versions, $installedRepo); + if ($isInstalledPackage && $package->getReleaseDate() !== null) { + $io->write('released : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate())); + } + if ($latestPackage) { + $style = $this->getVersionStyle($latestPackage, $package); + $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate()); + $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . '' . $releasedTime); + } else { + $latestPackage = $package; + } + $io->write('type : ' . $package->getType()); + $this->printLicenses($package); + $io->write('homepage : ' . $package->getHomepage()); + $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); + $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); + if ($isInstalledPackage) { + $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $io->write('path : ' . realpath($path)); + } else { + $io->write('path : null'); + } + } + $io->write('names : ' . implode(', ', $package->getNames())); + + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $replacement = ($latestPackage->getReplacementPackage() !== null) + ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.' + : null; + + $io->writeError( + sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) + ); + } + + if ($package->getSupport()) { + $io->write("\nsupport"); + foreach ($package->getSupport() as $type => $value) { + $io->write('' . $type . ' : '.$value); + } + } + + if (\count($package->getAutoload()) > 0) { + $io->write("\nautoload"); + $autoloadConfig = $package->getAutoload(); + foreach ($autoloadConfig as $type => $autoloads) { + $io->write('' . $type . ''); + + if ($type === 'psr-0' || $type === 'psr-4') { + foreach ($autoloads as $name => $path) { + $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + } + } elseif ($type === 'classmap') { + $io->write(implode(', ', $autoloadConfig[$type])); + } + } + if ($package->getIncludePaths()) { + $io->write('include-path'); + $io->write(implode(', ', $package->getIncludePaths())); + } + } + } + + /** + * Prints all available versions of this package and highlights the installed one if any. + * + * @param array $versions + */ + protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo): void + { + $versions = array_keys($versions); + $versions = Semver::rsort($versions); + + // highlight installed version + if ($installedPackages = $installedRepo->findPackages($package->getName())) { + foreach ($installedPackages as $installedPackage) { + $installedVersion = $installedPackage->getPrettyVersion(); + $key = array_search($installedVersion, $versions); + if (false !== $key) { + $versions[$key] = '* ' . $installedVersion . ''; + } + } + } + + $versions = implode(', ', $versions); + + $this->getIO()->write('versions : ' . $versions); + } + + /** + * print link objects + * + * @param string $title + */ + protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null): void + { + $title = $title ?: $linkType; + $io = $this->getIO(); + if ($links = $package->{'get'.ucfirst($linkType)}()) { + $io->write("\n" . $title . ""); + + foreach ($links as $link) { + $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); + } + } + } + + /** + * Prints the licenses of a package with metadata + */ + protected function printLicenses(CompletePackageInterface $package): void + { + $spdxLicenses = new SpdxLicenses(); + + $licenses = $package->getLicense(); + $io = $this->getIO(); + + foreach ($licenses as $licenseId) { + $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url + + if (!$license) { + $out = $licenseId; + } else { + // is license OSI approved? + if ($license[1] === true) { + $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); + } else { + $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); + } + } + + $io->write('license : ' . $out); + } + } + + /** + * Prints package info in JSON format. + * + * @param array $versions + */ + protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void + { + $json = [ + 'name' => $package->getPrettyName(), + 'description' => $package->getDescription(), + 'keywords' => $package->getKeywords() ?: [], + 'type' => $package->getType(), + 'homepage' => $package->getHomepage(), + 'names' => $package->getNames(), + ]; + + $json = $this->appendVersions($json, $versions); + $json = $this->appendLicenses($json, $package); + + if ($latestPackage) { + $json['latest'] = $latestPackage->getPrettyVersion(); + } else { + $latestPackage = $package; + } + + if (null !== $package->getSourceType()) { + $json['source'] = [ + 'type' => $package->getSourceType(), + 'url' => $package->getSourceUrl(), + 'reference' => $package->getSourceReference(), + ]; + } + + if (null !== $package->getDistType()) { + $json['dist'] = [ + 'type' => $package->getDistType(), + 'url' => $package->getDistUrl(), + 'reference' => $package->getDistReference(), + ]; + } + + if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) { + $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); + if (is_string($path)) { + $path = realpath($path); + if ($path !== false) { + $json['path'] = $path; + } + } else { + $json['path'] = null; + } + + if ($package->getReleaseDate() !== null) { + $json['released'] = $package->getReleaseDate()->format(DATE_ATOM); + } + } + + if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { + $json['replacement'] = $latestPackage->getReplacementPackage(); + } + + if ($package->getSuggests()) { + $json['suggests'] = $package->getSuggests(); + } + + if ($package->getSupport()) { + $json['support'] = $package->getSupport(); + } + + $json = $this->appendAutoload($json, $package); + + if ($package->getIncludePaths()) { + $json['include_path'] = $package->getIncludePaths(); + } + + $json = $this->appendLinks($json, $package); + + $this->getIO()->write(JsonFile::encode($json)); + } + + /** + * @param JsonStructure $json + * @param array $versions + * @return JsonStructure + */ + private function appendVersions(array $json, array $versions): array + { + uasort($versions, 'version_compare'); + $versions = array_keys(array_reverse($versions)); + $json['versions'] = $versions; + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLicenses(array $json, CompletePackageInterface $package): array + { + if ($licenses = $package->getLicense()) { + $spdxLicenses = new SpdxLicenses(); + + $json['licenses'] = array_map(static function ($licenseId) use ($spdxLicenses) { + $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url + + if (!$license) { + return $licenseId; + } + + return [ + 'name' => $license[0], + 'osi' => $licenseId, + 'url' => $license[2], + ]; + }, $licenses); + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendAutoload(array $json, CompletePackageInterface $package): array + { + if (\count($package->getAutoload()) > 0) { + $autoload = []; + + foreach ($package->getAutoload() as $type => $autoloads) { + if ($type === 'psr-0' || $type === 'psr-4') { + $psr = []; + + foreach ($autoloads as $name => $path) { + if (!$path) { + $path = '.'; + } + + $psr[$name ?: '*'] = $path; + } + + $autoload[$type] = $psr; + } elseif ($type === 'classmap') { + $autoload['classmap'] = $autoloads; + } + } + + $json['autoload'] = $autoload; + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLinks(array $json, CompletePackageInterface $package): array + { + foreach (Link::$TYPES as $linkType) { + $json = $this->appendLink($json, $package, $linkType); + } + + return $json; + } + + /** + * @param JsonStructure $json + * @return JsonStructure + */ + private function appendLink(array $json, CompletePackageInterface $package, string $linkType): array + { + $links = $package->{'get' . ucfirst($linkType)}(); + + if ($links) { + $json[$linkType] = []; + + foreach ($links as $link) { + $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint(); + } + } + + return $json; + } + + /** + * Init styles for tree + */ + protected function initStyles(OutputInterface $output): void + { + $this->colors = [ + 'green', + 'yellow', + 'cyan', + 'magenta', + 'blue', + ]; + + foreach ($this->colors as $color) { + $style = new OutputFormatterStyle($color); + $output->getFormatter()->setStyle($color, $style); + } + } + + /** + * Display the tree + * + * @param array> $arrayTree + */ + protected function displayPackageTree(array $arrayTree): void + { + $io = $this->getIO(); + foreach ($arrayTree as $package) { + $io->write(sprintf('%s', $package['name']), false); + $io->write(' ' . $package['version'], false); + if (isset($package['description'])) { + $io->write(' ' . strtok($package['description'], "\r\n")); + } else { + // output newline + $io->write(''); + } + + if (isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = '├'; + $j = 0; + $total = count($requires); + foreach ($requires as $require) { + $requireName = $require['name']; + $j++; + if ($j === $total) { + $treeBar = '└'; + } + $level = 1; + $color = $this->colors[$level]; + $info = sprintf( + '%s──<%s>%s %s', + $treeBar, + $color, + $requireName, + $color, + $require['version'] + ); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + $packagesInTree = [$package['name'], $requireName]; + + $this->displayTree($require, $packagesInTree, $treeBar, $level + 1); + } + } + } + } + + /** + * Generate the package tree + * + * @return array>|string|null> + */ + protected function generatePackageTree( + PackageInterface $package, + InstalledRepository $installedRepo, + RepositoryInterface $remoteRepos + ): array { + $requires = $package->getRequires(); + ksort($requires); + $children = []; + foreach ($requires as $requireName => $require) { + $packagesInTree = [$package->getName(), $requireName]; + + $treeChildDesc = [ + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ]; + + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree); + + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; + } + + $children[] = $treeChildDesc; + } + $tree = [ + 'name' => $package->getPrettyName(), + 'version' => $package->getPrettyVersion(), + 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '', + ]; + + if ($children) { + $tree['requires'] = $children; + } + + return $tree; + } + + /** + * Display a package tree + * + * @param array>|string|null>|string $package + * @param array $packagesInTree + */ + protected function displayTree( + $package, + array $packagesInTree, + string $previousTreeBar = '├', + int $level = 1 + ): void { + $previousTreeBar = str_replace('├', '│', $previousTreeBar); + if (is_array($package) && isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = $previousTreeBar . ' ├'; + $i = 0; + $total = count($requires); + foreach ($requires as $require) { + $currentTree = $packagesInTree; + $i++; + if ($i === $total) { + $treeBar = $previousTreeBar . ' └'; + } + $colorIdent = $level % count($this->colors); + $color = $this->colors[$colorIdent]; + + assert(is_string($require['name'])); + assert(is_string($require['version'])); + + $circularWarn = in_array( + $require['name'], + $currentTree, + true + ) ? '(circular dependency aborted here)' : ''; + $info = rtrim(sprintf( + '%s──<%s>%s %s %s', + $treeBar, + $color, + $require['name'], + $color, + $require['version'], + $circularWarn + )); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + + $currentTree[] = $require['name']; + $this->displayTree($require, $currentTree, $treeBar, $level + 1); + } + } + } + + /** + * Display a package tree + * + * @param string[] $packagesInTree + * @return array>|string>> + */ + protected function addTree( + string $name, + Link $link, + InstalledRepository $installedRepo, + RepositoryInterface $remoteRepos, + array $packagesInTree + ): array { + $children = []; + [$package] = $this->getPackage( + $installedRepo, + $remoteRepos, + $name, + $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint() + ); + if (is_object($package)) { + $requires = $package->getRequires(); + ksort($requires); + foreach ($requires as $requireName => $require) { + $currentTree = $packagesInTree; + + $treeChildDesc = [ + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ]; + + if (!in_array($requireName, $currentTree, true)) { + $currentTree[] = $requireName; + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree); + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; + } + } + + $children[] = $treeChildDesc; + } + } + + return $children; + } + + private function updateStatusToVersionStyle(string $updateStatus): string + { + // 'up-to-date' is printed green + // 'semver-safe-update' is printed red + // 'update-possible' is printed yellow + return str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus); + } + + private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package): string + { + if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) { + return 'up-to-date'; + } + + $constraint = $package->getVersion(); + if (0 !== strpos($constraint, 'dev-')) { + $constraint = '^'.$constraint; + } + if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) { + // it needs an immediate semver-compliant upgrade + return 'semver-safe-update'; + } + + // it needs an upgrade but has potential BC breaks so is not urgent + return 'update-possible'; + } + + private function writeTreeLine(string $line): void + { + $io = $this->getIO(); + if (!$io->isDecorated()) { + $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); + } + + $io->write($line); + } + + /** + * Given a package, this finds the latest package matching it + */ + private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface + { + // find the latest version allowed in this repo set + $name = $package->getName(); + $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo); + $stability = $composer->getPackage()->getMinimumStability(); + $flags = $composer->getPackage()->getStabilityFlags(); + if (isset($flags[$name])) { + $stability = array_search($flags[$name], BasePackage::STABILITIES, true); + } + + $bestStability = $stability; + if ($composer->getPackage()->getPreferStable()) { + $bestStability = $package->getStability(); + } + + $targetVersion = null; + if (0 === strpos($package->getVersion(), 'dev-')) { + $targetVersion = $package->getVersion(); + + // dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky) + if ($majorOnly) { + return null; + } + } + + if ($targetVersion === null) { + if ($majorOnly && Preg::isMatch('{^(?P(?:0\.)+)?(?P\d+)\.}', $package->getVersion(), $match)) { + $targetVersion = '>='.$match['zero_major'].(((int) $match['first_meaningful']) + 1).',<9999999-dev'; + } + + if ($minorOnly) { + $targetVersion = '^'.$package->getVersion(); + } + + if ($patchOnly) { + $trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion()); + $partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3; + while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) { + $trimmedVersion .= '.0'; + } + $targetVersion = '~'.$trimmedVersion; + } + } + + if ($this->getIO()->isVerbose()) { + $showWarnings = true; + } else { + $showWarnings = static function (PackageInterface $candidate) use ($package): bool { + if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) { + return false; + } + return version_compare($candidate->getVersion(), $package->getVersion(), '<='); + }; + } + $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings); + while ($candidate instanceof AliasPackage) { + $candidate = $candidate->getAliasOf(); + } + + return $candidate !== false ? $candidate : null; + } + + private function getRepositorySet(Composer $composer): RepositorySet + { + if (!$this->repositorySet) { + $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); + $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); + } + + return $this->repositorySet; + } + + private function getRelativeTime(\DateTimeInterface $releaseDate): string + { + if ($releaseDate->format('Ymd') === date('Ymd')) { + return 'today'; + } + + $diff = $releaseDate->diff(new \DateTimeImmutable()); + if ($diff->days < 7) { + return 'this week'; + } + + if ($diff->days < 14) { + return 'last week'; + } + + if ($diff->m < 1 && $diff->days < 31) { + return floor($diff->days / 7) . ' weeks ago'; + } + + if ($diff->y < 1) { + return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; + } + + return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/StatusCommand.php b/vendor/composer/composer/src/Composer/Command/StatusCommand.php new file mode 100644 index 0000000..5d90b31 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/StatusCommand.php @@ -0,0 +1,221 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\Downloader\ChangeReportInterface; +use Composer\Downloader\DvcsDownloaderInterface; +use Composer\Downloader\VcsCapableDownloaderInterface; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Script\ScriptEvents; +use Composer\Util\ProcessExecutor; + +/** + * @author Tiago Ribeiro + * @author Rui Marinho + */ +class StatusCommand extends BaseCommand +{ + private const EXIT_CODE_ERRORS = 1; + private const EXIT_CODE_UNPUSHED_CHANGES = 2; + private const EXIT_CODE_VERSION_CHANGES = 4; + + /** + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException + */ + protected function configure(): void + { + $this + ->setName('status') + ->setDescription('Shows a list of locally modified packages') + ->setDefinition([ + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), + ]) + ->setHelp( + <<requireComposer(); + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + // Dispatch pre-status-command + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); + + $exitCode = $this->doExecute($input); + + // Dispatch post-status-command + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); + + return $exitCode; + } + + private function doExecute(InputInterface $input): int + { + // init repos + $composer = $this->requireComposer(); + + $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); + + $dm = $composer->getDownloadManager(); + $im = $composer->getInstallationManager(); + + $errors = []; + $io = $this->getIO(); + $unpushedChanges = []; + $vcsVersionChanges = []; + + $parser = new VersionParser; + $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io); + $dumper = new ArrayDumper; + + // list packages + foreach ($installedRepo->getCanonicalPackages() as $package) { + $downloader = $dm->getDownloaderForPackage($package); + $targetDir = $im->getInstallPath($package); + if ($targetDir === null) { + continue; + } + + if ($downloader instanceof ChangeReportInterface) { + if (is_link($targetDir)) { + $errors[$targetDir] = $targetDir . ' is a symbolic link.'; + } + + if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) { + $errors[$targetDir] = $changes; + } + } + + if ($downloader instanceof VcsCapableDownloaderInterface) { + if ($downloader->getVcsReference($package, $targetDir)) { + switch ($package->getInstallationSource()) { + case 'source': + $previousRef = $package->getSourceReference(); + break; + case 'dist': + $previousRef = $package->getDistReference(); + break; + default: + $previousRef = null; + } + + $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); + + if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) { + $vcsVersionChanges[$targetDir] = [ + 'previous' => [ + 'version' => $package->getPrettyVersion(), + 'ref' => $previousRef, + ], + 'current' => [ + 'version' => $currentVersion['pretty_version'], + 'ref' => $currentVersion['commit'], + ], + ]; + } + } + } + + if ($downloader instanceof DvcsDownloaderInterface) { + if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { + $unpushedChanges[$targetDir] = $unpushed; + } + } + } + + // output errors/warnings + if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { + $io->writeError('No local changes'); + + return 0; + } + + if ($errors) { + $io->writeError('You have changes in the following dependencies:'); + + foreach ($errors as $path => $changes) { + if ($input->getOption('verbose')) { + $indentedChanges = implode("\n", array_map(static function ($line): string { + return ' ' . ltrim($line); + }, explode("\n", $changes))); + $io->write(''.$path.':'); + $io->write($indentedChanges); + } else { + $io->write($path); + } + } + } + + if ($unpushedChanges) { + $io->writeError('You have unpushed changes on the current branch in the following dependencies:'); + + foreach ($unpushedChanges as $path => $changes) { + if ($input->getOption('verbose')) { + $indentedChanges = implode("\n", array_map(static function ($line): string { + return ' ' . ltrim($line); + }, explode("\n", $changes))); + $io->write(''.$path.':'); + $io->write($indentedChanges); + } else { + $io->write($path); + } + } + } + + if ($vcsVersionChanges) { + $io->writeError('You have version variations in the following dependencies:'); + + foreach ($vcsVersionChanges as $path => $changes) { + if ($input->getOption('verbose')) { + // If we don't can't find a version, use the ref instead. + $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; + $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; + + if ($io->isVeryVerbose()) { + // Output the ref regardless of whether or not it's being used as the version + $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); + $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); + } + + $io->write(''.$path.':'); + $io->write(sprintf(' From %s to %s', $previousVersion, $currentVersion)); + } else { + $io->write($path); + } + } + } + + if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { + $io->writeError('Use --verbose (-v) to see a list of files'); + } + + return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php b/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php new file mode 100644 index 0000000..df63b3e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/SuggestsCommand.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\InstalledRepository; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SuggestsCommand extends BaseCommand +{ + use CompletionTrait; + + protected function configure(): void + { + $this + ->setName('suggests') + ->setDescription('Shows package suggestions') + ->setDefinition([ + new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'), + new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'), + new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()), + ]) + ->setHelp( + <<%command.name% command shows a sorted list of suggested packages. + +Read more at https://getcomposer.org/doc/03-cli.md#suggests +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $composer = $this->requireComposer(); + + $installedRepos = [ + new RootPackageRepository(clone $composer->getPackage()), + ]; + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides()); + $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev')); + } else { + $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform')); + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + $reporter = new SuggestedPackagesReporter($this->getIO()); + + $filter = $input->getArgument('packages'); + $packages = $installedRepo->getPackages(); + $packages[] = $composer->getPackage(); + foreach ($packages as $package) { + if (!empty($filter) && !in_array($package->getName(), $filter)) { + continue; + } + + $reporter->addSuggestionsFromPackage($package); + } + + // Determine output mode, default is by-package + $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE; + + // if by-suggestion is given we override the default + if ($input->getOption('by-suggestion')) { + $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION; + } + // unless by-package is also present then we enable both + if ($input->getOption('by-package')) { + $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE; + } + // list is exclusive and overrides everything else + if ($input->getOption('list')) { + $mode = SuggestedPackagesReporter::MODE_LIST; + } + + $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null); + + return 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Command/UpdateCommand.php b/vendor/composer/composer/src/Composer/Command/UpdateCommand.php new file mode 100644 index 0000000..21d6c3f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/UpdateCommand.php @@ -0,0 +1,388 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\DependencyResolver\Request; +use Composer\Installer; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\Loader\RootPackageLoader; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionSelector; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Package\Version\VersionParser; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; +use Composer\Util\HttpDownloader; +use Composer\Advisory\Auditor; +use Composer\Util\Platform; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jordi Boggiano + * @author Nils Adermann + */ +class UpdateCommand extends BaseCommand +{ + use CompletionTrait; + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('update') + ->setAliases(['u', 'upgrade']) + ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file') + ->setDefinition([ + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(false)), + new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'), + new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), + new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), + new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), + new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'), + new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), + new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), + new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), + new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), + new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), + new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), + new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), + new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), + new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), + new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), + new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), + new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), + new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), + new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), + new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'), + new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), + new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), + new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']), + ]) + ->setHelp( + <<update command reads the composer.json file from the +current directory, processes it, and updates, removes or installs all the +dependencies. + +php composer.phar update + +To limit the update operation to a few packages, you can list the package(s) +you want to update as such: + +php composer.phar update vendor/package1 foo/mypackage [...] + +You may also use an asterisk (*) pattern to limit the update operation to package(s) +from a specific vendor: + +php composer.phar update vendor/package1 foo/* [...] + +To run an update with more restrictive constraints you can use: + +php composer.phar update --with vendor/package:1.0.* + +To run a partial update with more restrictive constraints you can use the shorthand: + +php composer.phar update vendor/package:1.0.* + +To select packages names interactively with auto-completion use -i. + +Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->getIO(); + if ($input->getOption('dev')) { + $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); + } + if ($input->getOption('no-suggest')) { + $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); + } + + $composer = $this->requireComposer(); + + if (!HttpDownloader::isCurlEnabled()) { + $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); + } + + $packages = $input->getArgument('packages'); + $reqs = $this->formatRequirements($input->getOption('with')); + + // extract --with shorthands from the allowlist + if (count($packages) > 0) { + $allowlistPackagesWithRequirements = array_filter($packages, static function ($pkg): bool { + return Preg::isMatch('{\S+[ =:]\S+}', $pkg); + }); + foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) { + $reqs[$package] = $constraint; + } + + // replace the foo/bar:req by foo/bar in the allowlist + foreach ($allowlistPackagesWithRequirements as $package) { + $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package); + $index = array_search($package, $packages); + $packages[$index] = $packageName; + } + } + + $rootPackage = $composer->getPackage(); + $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences())); + $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags())); + + $parser = new VersionParser; + $temporaryConstraints = []; + $rootRequirements = array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()); + foreach ($reqs as $package => $constraint) { + $package = strtolower($package); + $parsedConstraint = $parser->parseConstraints($constraint); + $temporaryConstraints[$package] = $parsedConstraint; + if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) { + $io->writeError('The temporary constraint "'.$constraint.'" for "'.$package.'" must be a subset of the constraint in your composer.json ('.$rootRequirements[$package]->getPrettyConstraint().')'); + $io->write('Run `composer require '.$package.'` or `composer require '.$package.':'.$constraint.'` instead to replace the constraint'); + return self::FAILURE; + } + } + + if ($input->getOption('patch-only')) { + if (!$composer->getLocker()->isLocked()) { + throw new \InvalidArgumentException('patch-only can only be used with a lock file present'); + } + foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) { + if ($package->isDev()) { + continue; + } + if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) { + continue; + } + $constraint = $parser->parseConstraints('~'.$match[1]); + if (isset($temporaryConstraints[$package->getName()])) { + $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true); + } else { + $temporaryConstraints[$package->getName()] = $constraint; + } + } + } + + if ($input->getOption('interactive')) { + $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); + } + + if ($input->getOption('root-reqs')) { + $requires = array_keys($rootPackage->getRequires()); + if (!$input->getOption('no-dev')) { + $requires = array_merge($requires, array_keys($rootPackage->getDevRequires())); + } + + if (!empty($packages)) { + $packages = array_intersect($packages, $requires); + } else { + $packages = $requires; + } + } + + // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead + // they are further mutually exclusive with listing actual package names + $filteredPackages = array_filter($packages, static function ($package): bool { + return !in_array($package, ['lock', 'nothing', 'mirrors'], true); + }); + $updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages); + $packages = $filteredPackages; + + if ($updateMirrors && !empty($packages)) { + $io->writeError('You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.'); + + return -1; + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); + $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); + + $install = Installer::create($io, $composer); + + $config = $composer->getConfig(); + [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); + + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); + $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); + $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); + + $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + if ($input->getOption('with-all-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + } elseif ($input->getOption('with-dependencies')) { + $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + } + + $install + ->setDryRun($input->getOption('dry-run')) + ->setVerbose($input->getOption('verbose')) + ->setPreferSource($preferSource) + ->setPreferDist($preferDist) + ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) + ->setApcuAutoloader($apcu, $apcuPrefix) + ->setUpdate(true) + ->setInstall(!$input->getOption('no-install')) + ->setUpdateMirrors($updateMirrors) + ->setUpdateAllowList($packages) + ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) + ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) + ->setPreferStable($input->getOption('prefer-stable')) + ->setPreferLowest($input->getOption('prefer-lowest')) + ->setTemporaryConstraints($temporaryConstraints) + ->setAudit(!$input->getOption('no-audit')) + ->setAuditFormat($this->getAuditFormat($input)) + ->setMinimalUpdate($input->getOption('minimal-changes')) + ; + + if ($input->getOption('no-plugins')) { + $install->disablePlugins(); + } + + $result = $install->run(); + + if ($result === 0 && !$input->getOption('lock')) { + $bumpAfterUpdate = $input->getOption('bump-after-update'); + if (false === $bumpAfterUpdate) { + $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update'); + } + + if (false !== $bumpAfterUpdate) { + $io->writeError('Bumping dependencies'); + $bumpCommand = new BumpCommand(); + $bumpCommand->setComposer($composer); + $result = $bumpCommand->doBump( + $io, + $bumpAfterUpdate === 'dev', + $bumpAfterUpdate === 'no-dev', + $input->getOption('dry-run'), + $input->getArgument('packages') + ); + } + } + return $result; + } + + /** + * @param array $packages + * @return array + */ + private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages): array + { + if (!$input->isInteractive()) { + throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); + } + + $platformReqFilter = $this->getPlatformRequirementFilter($input); + $stabilityFlags = $composer->getPackage()->getStabilityFlags(); + $requires = array_merge( + $composer->getPackage()->getRequires(), + $composer->getPackage()->getDevRequires() + ); + + $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null; + + $io->writeError('Loading packages that can be updated...'); + $autocompleterValues = []; + $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + $versionSelector = $this->createVersionSelector($composer); + foreach ($installedPackages as $package) { + if ($filter !== null && !Preg::isMatch($filter, $package->getName())) { + continue; + } + $currentVersion = $package->getPrettyVersion(); + $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null; + $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability(); + $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter); + if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) { + $autocompleterValues[$package->getName()] = '' . $currentVersion . ' => ' . $latestVersion->getPrettyVersion() . ''; + } + } + if (0 === \count($installedPackages)) { + foreach ($requires as $req => $constraint) { + if (PlatformRepository::isPlatformPackage($req)) { + continue; + } + $autocompleterValues[$req] = ''; + } + } + + if (0 === \count($autocompleterValues)) { + throw new \RuntimeException('Could not find any package with new versions available'); + } + + $packages = $io->select( + 'Select packages: (Select more than one value separated by comma) ', + $autocompleterValues, + false, + 1, + 'No package named "%s" is installed.', + true + ); + + $table = new Table($output); + $table->setHeaders(['Selected packages']); + foreach ($packages as $package) { + $table->addRow([$package]); + } + $table->render(); + + if ($io->askConfirmation(sprintf( + 'Would you like to continue and update the above package%s [yes]? ', + 1 === count($packages) ? '' : 's' + ))) { + return $packages; + } + + throw new \RuntimeException('Installation aborted.'); + } + + private function createVersionSelector(Composer $composer): VersionSelector + { + $repositorySet = new RepositorySet(); + $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), function (RepositoryInterface $repository) { + return !$repository instanceof PlatformRepository; + }))); + + return new VersionSelector($repositorySet); + } +} diff --git a/vendor/composer/composer/src/Composer/Command/ValidateCommand.php b/vendor/composer/composer/src/Composer/Command/ValidateCommand.php new file mode 100644 index 0000000..fb71081 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Command/ValidateCommand.php @@ -0,0 +1,218 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PluginEvents; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Util\ConfigValidator; +use Composer\Util\Filesystem; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ValidateCommand + * + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ValidateCommand extends BaseCommand +{ + /** + * configure + */ + protected function configure(): void + { + $this + ->setName('validate') + ->setDescription('Validates a composer.json and composer.lock') + ->setDefinition([ + new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), + new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'), + new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), + new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), + new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'), + new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), + new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'), + ]) + ->setHelp( + <<getArgument('file') ?? Factory::getComposerFile(); + $io = $this->getIO(); + + if (!file_exists($file)) { + $io->writeError('' . $file . ' not found.'); + + return 3; + } + if (!Filesystem::isReadable($file)) { + $io->writeError('' . $file . ' is not readable.'); + + return 3; + } + + $validator = new ConfigValidator($io); + $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; + $checkPublish = !$input->getOption('no-check-publish'); + $checkLock = !$input->getOption('no-check-lock'); + $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION; + $isStrict = $input->getOption('strict'); + [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); + + $lockErrors = []; + $composer = $this->createComposerInstance($input, $io, $file); + // config.lock = false ~= implicit --no-check-lock; --check-lock overrides + $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock'); + $locker = $composer->getLocker(); + if ($locker->isLocked() && !$locker->isFresh()) { + $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update `.'; + } + + if ($locker->isLocked()) { + $lockErrors = array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), true)); + } + + $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); + + // $errors include publish and lock errors when exists + $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); + + if ($input->getOption('with-dependencies')) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + foreach ($localRepo->getPackages() as $package) { + $path = $composer->getInstallationManager()->getInstallPath($package); + if (null === $path) { + continue; + } + $file = $path . '/composer.json'; + if (is_dir($path) && file_exists($file)) { + [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); + + $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); + + // $errors include publish errors when exists + $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); + $exitCode = max($depCode, $exitCode); + } + } + } + + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output); + $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); + + return max($eventCode, $exitCode); + } + + /** + * @param string[] $errors + * @param string[] $warnings + * @param string[] $publishErrors + * @param string[] $lockErrors + */ + private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = false, array $publishErrors = [], bool $checkLock = false, array $lockErrors = [], bool $printSchemaUrl = false): void + { + $doPrintSchemaUrl = false; + + if (\count($errors) > 0) { + $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); + } elseif (\count($publishErrors) > 0 && $checkPublish) { + $io->writeError('' . $name . ' is valid for simple usage with Composer but has'); + $io->writeError('strict errors that make it unable to be published as a package'); + $doPrintSchemaUrl = $printSchemaUrl; + } elseif (\count($warnings) > 0) { + $io->writeError('' . $name . ' is valid, but with a few warnings'); + $doPrintSchemaUrl = $printSchemaUrl; + } elseif (\count($lockErrors) > 0) { + $io->write('' . $name . ' is valid but your composer.lock has some '.($checkLock ? 'errors' : 'warnings').''); + } else { + $io->write('' . $name . ' is valid'); + } + + if ($doPrintSchemaUrl) { + $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); + } + + if (\count($errors) > 0) { + $errors = array_map(static function ($err): string { + return '- ' . $err; + }, $errors); + array_unshift($errors, '# General errors'); + } + if (\count($warnings) > 0) { + $warnings = array_map(static function ($err): string { + return '- ' . $err; + }, $warnings); + array_unshift($warnings, '# General warnings'); + } + + // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined + $extraWarnings = []; + + // If checking publish errors, display them as errors, otherwise just show them as warnings + if (\count($publishErrors) > 0 && $checkPublish) { + $publishErrors = array_map(static function ($err): string { + return '- ' . $err; + }, $publishErrors); + + array_unshift($publishErrors, '# Publish errors'); + $errors = array_merge($errors, $publishErrors); + } + + // If checking lock errors, display them as errors, otherwise just show them as warnings + if (\count($lockErrors) > 0) { + if ($checkLock) { + array_unshift($lockErrors, '# Lock file errors'); + $errors = array_merge($errors, $lockErrors); + } else { + array_unshift($lockErrors, '# Lock file warnings'); + $extraWarnings = array_merge($extraWarnings, $lockErrors); + } + } + + $messages = [ + 'error' => $errors, + 'warning' => array_merge($warnings, $extraWarnings), + ]; + + foreach ($messages as $style => $msgs) { + foreach ($msgs as $msg) { + if (strpos($msg, '#') === 0) { + $io->writeError('<' . $style . '>' . $msg . ''); + } else { + $io->writeError($msg); + } + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Compiler.php b/vendor/composer/composer/src/Composer/Compiler.php new file mode 100644 index 0000000..ed5d3ad --- /dev/null +++ b/vendor/composer/composer/src/Composer/Compiler.php @@ -0,0 +1,326 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Json\JsonFile; +use Composer\CaBundle\CaBundle; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Process; +use Seld\PharUtils\Timestamps; +use Seld\PharUtils\Linter; + +/** + * The Compiler class compiles composer into a phar + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class Compiler +{ + /** @var string */ + private $version; + /** @var string */ + private $branchAliasVersion = ''; + /** @var \DateTime */ + private $versionDate; + + /** + * Compiles composer into a single phar file + * + * @param string $pharFile The full path to the file to create + * + * @throws \RuntimeException + */ + public function compile(string $pharFile = 'composer.phar'): void + { + if (file_exists($pharFile)) { + unlink($pharFile); + } + + $process = new ProcessExecutor(); + + if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, dirname(dirname(__DIR__)))) { + throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); + } + $this->version = trim($output); + + if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, dirname(dirname(__DIR__)))) { + throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.'); + } + + $this->versionDate = new \DateTime(trim($output)); + $this->versionDate->setTimezone(new \DateTimeZone('UTC')); + + if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, dirname(dirname(__DIR__)))) { + $this->version = trim($output); + } else { + // get branch-alias defined in composer.json for dev-main (if any) + $localConfig = __DIR__.'/../../composer.json'; + $file = new JsonFile($localConfig); + $localConfig = $file->read(); + if (isset($localConfig['extra']['branch-alias']['dev-main'])) { + $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-main']; + } + } + + if ('' === $this->version) { + throw new \UnexpectedValueException('Version detection failed'); + } + + $phar = new \Phar($pharFile, 0, 'composer.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA512); + + $phar->startBuffering(); + + $finderSort = static function ($a, $b): int { + return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/')); + }; + + // Add Composer sources + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notName('Compiler.php') + ->notName('ClassLoader.php') + ->notName('InstalledVersions.php') + ->in(__DIR__.'/..') + ->sort($finderSort) + ; + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + // Add runtime utilities separately to make sure they retains the docblocks as these will get copied into projects + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/InstalledVersions.php'), false); + + // Add Composer resources + $finder = new Finder(); + $finder->files() + ->in(__DIR__.'/../../res') + ->sort($finderSort) + ; + foreach ($finder as $file) { + $this->addFile($phar, $file, false); + } + + // Add vendor files + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->notPath('/\/(composer\.(?:json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon|UPGRADE.*\.(?:md|txt))$/') + ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/') + ->notPath('justinrainbow/json-schema/demo/') + ->notPath('justinrainbow/json-schema/dist/') + ->notPath('composer/pcre/extension.neon') + ->notPath('composer/LICENSE') + ->exclude('Tests') + ->exclude('tests') + ->exclude('docs') + ->in(__DIR__.'/../../vendor/') + ->sort($finderSort) + ; + + $extraFiles = []; + foreach ([ + __DIR__ . '/../../vendor/composer/installed.json', + __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json', + __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json', + CaBundle::getBundledCaBundlePath(), + __DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe', + __DIR__ . '/../../vendor/symfony/console/Resources/completion.bash', + ] as $file) { + $extraFiles[$file] = realpath($file); + if (!file_exists($file)) { + throw new \RuntimeException('Extra file listed is missing from the filesystem: '.$file); + } + } + $unexpectedFiles = []; + + foreach ($finder as $file) { + if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) { + unset($extraFiles[$index]); + } elseif (!Preg::isMatch('{(^LICENSE(?:\.txt)?$|\.php$)}', $file->getFilename())) { + $unexpectedFiles[] = (string) $file; + } + + if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) { + $this->addFile($phar, $file); + } else { + $this->addFile($phar, $file, false); + } + } + + if (count($extraFiles) > 0) { + throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true)); + } + if (count($unexpectedFiles) > 0) { + throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true)); + } + + // Add bin/composer + $this->addComposerBin($phar); + + // Stubs + $phar->setStub($this->getStub()); + + $phar->stopBuffering(); + + // disabled for interoperability with systems without gzip ext + // $phar->compressFiles(\Phar::GZ); + + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false); + + unset($phar); + + // re-sign the phar with reproducible timestamp / signature + $util = new Timestamps($pharFile); + $util->updateTimestamps($this->versionDate); + $util->save($pharFile, \Phar::SHA512); + + Linter::lint($pharFile, [ + 'vendor/symfony/console/Attribute/AsCommand.php', + 'vendor/symfony/polyfill-intl-grapheme/bootstrap80.php', + 'vendor/symfony/polyfill-intl-normalizer/bootstrap80.php', + 'vendor/symfony/polyfill-mbstring/bootstrap80.php', + 'vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'vendor/symfony/service-contracts/Attribute/SubscribedService.php', + ]); + } + + private function getRelativeFilePath(\SplFileInfo $file): string + { + $realPath = $file->getRealPath(); + $pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR; + + $pos = strpos($realPath, $pathPrefix); + $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; + + return strtr($relativePath, '\\', '/'); + } + + private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void + { + $path = $this->getRelativeFilePath($file); + $content = file_get_contents((string) $file); + if ($strip) { + $content = $this->stripWhitespace($content); + } elseif ('LICENSE' === $file->getFilename()) { + $content = "\n".$content."\n"; + } + + if ($path === 'src/Composer/Composer.php') { + $content = strtr( + $content, + [ + '@package_version@' => $this->version, + '@package_branch_alias_version@' => $this->branchAliasVersion, + '@release_date@' => $this->versionDate->format('Y-m-d H:i:s'), + ] + ); + $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content); + } + + $phar->addFromString($path, $content); + } + + private function addComposerBin(\Phar $phar): void + { + $content = file_get_contents(__DIR__.'/../../bin/composer'); + $content = Preg::replace('{^#!/usr/bin/env php\s*}', '', $content); + $phar->addFromString('bin/composer', $content); + } + + /** + * Removes whitespace from a PHP source string while preserving line numbers. + * + * @param string $source A PHP string + * @return string The PHP string with the whitespace removed + */ + private function stripWhitespace(string $source): string + { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = Preg::replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + + private function getStub(): string + { + $stub = <<<'EOF' +#!/usr/bin/env php + + * Jordi Boggiano + * + * For the full copyright and license information, please view + * the license that is located at the bottom of this file. + */ + +// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 +if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { + if (version_compare(phpversion('apc'), '3.0.12', '>=')) { + ini_set('apc.cache_by_default', 0); + } else { + fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); + fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); + } +} + +if (!class_exists('Phar')) { + echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; + exit(1); +} + +Phar::mapPhar('composer.phar'); + +EOF; + + // add warning once the phar is older than 60 days + if (Preg::isMatch('{^[a-f0-9]+$}', $this->version)) { + $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400; + $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n"; + } + + return $stub . <<<'EOF' +require 'phar://composer.phar/bin/composer'; + +__HALT_COMPILER(); +EOF; + } +} diff --git a/vendor/composer/composer/src/Composer/Composer.php b/vendor/composer/composer/src/Composer/Composer.php new file mode 100644 index 0000000..118321d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Composer.php @@ -0,0 +1,159 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Package\Locker; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginManager; +use Composer\Downloader\DownloadManager; +use Composer\Autoload\AutoloadGenerator; +use Composer\Package\Archiver\ArchiveManager; + +/** + * @author Jordi Boggiano + * @author Konstantin Kudryashiv + * @author Nils Adermann + */ +class Composer extends PartialComposer +{ + /* + * Examples of the following constants in the various configurations they can be in + * + * You are probably better off using Composer::getVersion() though as that will always return something usable + * + * releases (phar): + * const VERSION = '1.8.2'; + * const BRANCH_ALIAS_VERSION = ''; + * const RELEASE_DATE = '2019-01-29 15:00:53'; + * const SOURCE_VERSION = ''; + * + * snapshot builds (phar): + * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7'; + * const BRANCH_ALIAS_VERSION = '1.9-dev'; + * const RELEASE_DATE = '2019-02-20 07:43:56'; + * const SOURCE_VERSION = ''; + * + * source (git clone): + * const VERSION = '@package_version@'; + * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + * const RELEASE_DATE = '@release_date@'; + * const SOURCE_VERSION = '1.8-dev+source'; + * + * @see getVersion() + */ + public const VERSION = '2.8.9'; + public const BRANCH_ALIAS_VERSION = ''; + public const RELEASE_DATE = '2025-05-13 14:01:37'; + public const SOURCE_VERSION = ''; + + /** + * Version number of the internal composer-runtime-api package + * + * This is used to version features available to projects at runtime + * like the platform-check file, the Composer\InstalledVersions class + * and possibly others in the future. + * + * @var string + */ + public const RUNTIME_API_VERSION = '2.2.2'; + + public static function getVersion(): string + { + // no replacement done, this must be a source checkout + if (self::VERSION === '@package_version'.'@') { + return self::SOURCE_VERSION; + } + + // we have a branch alias and version is a commit id, this must be a snapshot build + if (self::BRANCH_ALIAS_VERSION !== '' && Preg::isMatch('{^[a-f0-9]{40}$}', self::VERSION)) { + return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION; + } + + return self::VERSION; + } + + /** + * @var Locker + */ + private $locker; + + /** + * @var Downloader\DownloadManager + */ + private $downloadManager; + + /** + * @var Plugin\PluginManager + */ + private $pluginManager; + + /** + * @var Autoload\AutoloadGenerator + */ + private $autoloadGenerator; + + /** + * @var ArchiveManager + */ + private $archiveManager; + + public function setLocker(Locker $locker): void + { + $this->locker = $locker; + } + + public function getLocker(): Locker + { + return $this->locker; + } + + public function setDownloadManager(DownloadManager $manager): void + { + $this->downloadManager = $manager; + } + + public function getDownloadManager(): DownloadManager + { + return $this->downloadManager; + } + + public function setArchiveManager(ArchiveManager $manager): void + { + $this->archiveManager = $manager; + } + + public function getArchiveManager(): ArchiveManager + { + return $this->archiveManager; + } + + public function setPluginManager(PluginManager $manager): void + { + $this->pluginManager = $manager; + } + + public function getPluginManager(): PluginManager + { + return $this->pluginManager; + } + + public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void + { + $this->autoloadGenerator = $autoloadGenerator; + } + + public function getAutoloadGenerator(): AutoloadGenerator + { + return $this->autoloadGenerator; + } +} diff --git a/vendor/composer/composer/src/Composer/Config.php b/vendor/composer/composer/src/Composer/Config.php new file mode 100644 index 0000000..f39579e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config.php @@ -0,0 +1,662 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Advisory\Auditor; +use Composer\Config\ConfigSourceInterface; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; + +/** + * @author Jordi Boggiano + */ +class Config +{ + public const SOURCE_DEFAULT = 'default'; + public const SOURCE_COMMAND = 'command'; + public const SOURCE_UNKNOWN = 'unknown'; + + public const RELATIVE_PATHS = 1; + + /** @var array */ + public static $defaultConfig = [ + 'process-timeout' => 300, + 'use-include-path' => false, + 'allow-plugins' => [], + 'use-parent-dir' => 'prompt', + 'preferred-install' => 'dist', + 'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL], + 'notify-on-install' => true, + 'github-protocols' => ['https', 'ssh', 'git'], + 'gitlab-protocol' => null, + 'vendor-dir' => 'vendor', + 'bin-dir' => '{$vendor-dir}/bin', + 'cache-dir' => '{$home}/cache', + 'data-dir' => '{$home}', + 'cache-files-dir' => '{$cache-dir}/files', + 'cache-repo-dir' => '{$cache-dir}/repo', + 'cache-vcs-dir' => '{$cache-dir}/vcs', + 'cache-ttl' => 15552000, // 6 months + 'cache-files-ttl' => null, // fallback to cache-ttl + 'cache-files-maxsize' => '300MiB', + 'cache-read-only' => false, + 'bin-compat' => 'auto', + 'discard-changes' => false, + 'autoloader-suffix' => null, + 'sort-packages' => false, + 'optimize-autoloader' => false, + 'classmap-authoritative' => false, + 'apcu-autoloader' => false, + 'prepend-autoloader' => true, + 'github-domains' => ['github.com'], + 'bitbucket-expose-hostname' => true, + 'disable-tls' => false, + 'secure-http' => true, + 'secure-svn-domains' => [], + 'cafile' => null, + 'capath' => null, + 'github-expose-hostname' => true, + 'gitlab-domains' => ['gitlab.com'], + 'store-auths' => 'prompt', + 'platform' => [], + 'archive-format' => 'tar', + 'archive-dir' => '.', + 'htaccess-protect' => true, + 'use-github-api' => true, + 'lock' => true, + 'platform-check' => 'php-only', + 'bitbucket-oauth' => [], + 'github-oauth' => [], + 'gitlab-oauth' => [], + 'gitlab-token' => [], + 'http-basic' => [], + 'bearer' => [], + 'bump-after-update' => false, + 'allow-missing-requirements' => false, + ]; + + /** @var array */ + public static $defaultRepositories = [ + 'packagist.org' => [ + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + ], + ]; + + /** @var array */ + private $config; + /** @var ?non-empty-string */ + private $baseDir; + /** @var array */ + private $repositories; + /** @var ConfigSourceInterface */ + private $configSource; + /** @var ConfigSourceInterface */ + private $authConfigSource; + /** @var ConfigSourceInterface|null */ + private $localAuthConfigSource = null; + /** @var bool */ + private $useEnvironment; + /** @var array */ + private $warnedHosts = []; + /** @var array */ + private $sslVerifyWarnedHosts = []; + /** @var array */ + private $sourceOfConfigValue = []; + + /** + * @param bool $useEnvironment Use COMPOSER_ environment variables to replace config settings + * @param ?string $baseDir Optional base directory of the config + */ + public function __construct(bool $useEnvironment = true, ?string $baseDir = null) + { + // load defaults + $this->config = static::$defaultConfig; + + $this->repositories = static::$defaultRepositories; + $this->useEnvironment = $useEnvironment; + $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null; + + foreach ($this->config as $configKey => $configValue) { + $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT); + } + + foreach ($this->repositories as $configKey => $configValue) { + $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT); + } + } + + /** + * Changing this can break path resolution for relative config paths so do not call this without knowing what you are doing + * + * The $baseDir should be an absolute path and without trailing slash + * + * @param non-empty-string|null $baseDir + */ + public function setBaseDir(?string $baseDir): void + { + $this->baseDir = $baseDir; + } + + public function setConfigSource(ConfigSourceInterface $source): void + { + $this->configSource = $source; + } + + public function getConfigSource(): ConfigSourceInterface + { + return $this->configSource; + } + + public function setAuthConfigSource(ConfigSourceInterface $source): void + { + $this->authConfigSource = $source; + } + + public function getAuthConfigSource(): ConfigSourceInterface + { + return $this->authConfigSource; + } + + public function setLocalAuthConfigSource(ConfigSourceInterface $source): void + { + $this->localAuthConfigSource = $source; + } + + public function getLocalAuthConfigSource(): ?ConfigSourceInterface + { + return $this->localAuthConfigSource; + } + + /** + * Merges new config values with the existing ones (overriding) + * + * @param array{config?: array, repositories?: array} $config + */ + public function merge(array $config, string $source = self::SOURCE_UNKNOWN): void + { + // override defaults with given config + if (!empty($config['config']) && is_array($config['config'])) { + foreach ($config['config'] as $key => $val) { + if (in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer'], true) && isset($this->config[$key])) { + $this->config[$key] = array_merge($this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif (in_array($key, ['allow-plugins'], true) && isset($this->config[$key]) && is_array($this->config[$key]) && is_array($val)) { + // merging $val first to get the local config on top of the global one, then appending the global config, + // then merging local one again to make sure the values from local win over global ones for keys present in both + $this->config[$key] = array_merge($val, $this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif (in_array($key, ['gitlab-domains', 'github-domains'], true) && isset($this->config[$key])) { + $this->config[$key] = array_unique(array_merge($this->config[$key], $val)); + $this->setSourceOfConfigValue($val, $key, $source); + } elseif ('preferred-install' === $key && isset($this->config[$key])) { + if (is_array($val) || is_array($this->config[$key])) { + if (is_string($val)) { + $val = ['*' => $val]; + } + if (is_string($this->config[$key])) { + $this->config[$key] = ['*' => $this->config[$key]]; + $this->sourceOfConfigValue[$key . '*'] = $source; + } + $this->config[$key] = array_merge($this->config[$key], $val); + $this->setSourceOfConfigValue($val, $key, $source); + // the full match pattern needs to be last + if (isset($this->config[$key]['*'])) { + $wildcard = $this->config[$key]['*']; + unset($this->config[$key]['*']); + $this->config[$key]['*'] = $wildcard; + } + } else { + $this->config[$key] = $val; + $this->setSourceOfConfigValue($val, $key, $source); + } + } elseif ('audit' === $key) { + $currentIgnores = $this->config['audit']['ignore']; + $this->config[$key] = array_merge($this->config['audit'], $val); + $this->setSourceOfConfigValue($val, $key, $source); + $this->config['audit']['ignore'] = array_merge($currentIgnores, $val['ignore'] ?? []); + } else { + $this->config[$key] = $val; + $this->setSourceOfConfigValue($val, $key, $source); + } + } + } + + if (!empty($config['repositories']) && is_array($config['repositories'])) { + $this->repositories = array_reverse($this->repositories, true); + $newRepos = array_reverse($config['repositories'], true); + foreach ($newRepos as $name => $repository) { + // disable a repository by name + if (false === $repository) { + $this->disableRepoByName((string) $name); + continue; + } + + // disable a repository with an anonymous {"name": false} repo + if (is_array($repository) && 1 === count($repository) && false === current($repository)) { + $this->disableRepoByName((string) key($repository)); + continue; + } + + // auto-deactivate the default packagist.org repo if it gets redefined + if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) { + $this->disableRepoByName('packagist.org'); + } + + // store repo + if (is_int($name)) { + $this->repositories[] = $repository; + $this->setSourceOfConfigValue($repository, 'repositories.' . array_search($repository, $this->repositories, true), $source); + } else { + if ($name === 'packagist') { // BC support for default "packagist" named repo + $this->repositories[$name . '.org'] = $repository; + $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source); + } else { + $this->repositories[$name] = $repository; + $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source); + } + } + } + $this->repositories = array_reverse($this->repositories, true); + } + } + + /** + * @return array + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * Returns a setting + * + * @param int $flags Options (see class constants) + * @throws \RuntimeException + * + * @return mixed + */ + public function get(string $key, int $flags = 0) + { + switch ($key) { + // strings/paths with env var and {$refs} support + case 'vendor-dir': + case 'bin-dir': + case 'process-timeout': + case 'data-dir': + case 'cache-dir': + case 'cache-files-dir': + case 'cache-repo-dir': + case 'cache-vcs-dir': + case 'cafile': + case 'capath': + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + + $val = $this->getComposerEnv($env); + if ($val !== false) { + $this->setSourceOfConfigValue($val, $key, $env); + } + + if ($key === 'process-timeout') { + return max(0, false !== $val ? (int) $val : $this->config[$key]); + } + + $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\'); + $val = Platform::expandPath($val); + + if (substr($key, -4) !== '-dir') { + return $val; + } + + return (($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS) ? $val : $this->realpath($val); + + // booleans with env var support + case 'cache-read-only': + case 'htaccess-protect': + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + + $val = $this->getComposerEnv($env); + if (false === $val) { + $val = $this->config[$key]; + } else { + $this->setSourceOfConfigValue($val, $key, $env); + } + + return $val !== 'false' && (bool) $val; + + // booleans without env var support + case 'disable-tls': + case 'secure-http': + case 'use-github-api': + case 'lock': + // special case for secure-http + if ($key === 'secure-http' && $this->get('disable-tls') === true) { + return false; + } + + return $this->config[$key] !== 'false' && (bool) $this->config[$key]; + + // ints without env var support + case 'cache-ttl': + return max(0, (int) $this->config[$key]); + + // numbers with kb/mb/gb support, without env var support + case 'cache-files-maxsize': + if (!Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', (string) $this->config[$key], $matches)) { + throw new \RuntimeException( + "Could not parse the value of '$key': {$this->config[$key]}" + ); + } + $size = (float) $matches[1]; + if (isset($matches[2])) { + switch (strtolower($matches[2])) { + case 'g': + $size *= 1024; + // intentional fallthrough + // no break + case 'm': + $size *= 1024; + // intentional fallthrough + // no break + case 'k': + $size *= 1024; + break; + } + } + + return max(0, (int) $size); + + // special cases below + case 'cache-files-ttl': + if (isset($this->config[$key])) { + return max(0, (int) $this->config[$key]); + } + + return $this->get('cache-ttl'); + + case 'home': + return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\'); + + case 'bin-compat': + $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; + + if (!in_array($value, ['auto', 'full', 'proxy', 'symlink'])) { + throw new \RuntimeException( + "Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy" + ); + } + + if ($value === 'symlink') { + trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', E_USER_DEPRECATED); + } + + return $value; + + case 'discard-changes': + $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES'); + if ($env !== false) { + if (!in_array($env, ['stash', 'true', 'false', '1', '0'], true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash" + ); + } + if ('stash' === $env) { + return 'stash'; + } + + // convert string value to bool + return $env !== 'false' && (bool) $env; + } + + if (!in_array($this->config[$key], [true, false, 'stash'], true)) { + throw new \RuntimeException( + "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash" + ); + } + + return $this->config[$key]; + + case 'github-protocols': + $protos = $this->config['github-protocols']; + if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) { + unset($protos[$index]); + } + if (reset($protos) === 'http') { + throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); + } + + return $protos; + + case 'autoloader-suffix': + if ($this->config[$key] === '') { // we need to guarantee null or non-empty-string + return null; + } + + return $this->process($this->config[$key], $flags); + + case 'audit': + $result = $this->config[$key]; + $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); + if (false !== $abandonedEnv) { + if (!in_array($abandonedEnv, $validChoices = Auditor::ABANDONEDS, true)) { + throw new \RuntimeException( + "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', Auditor::ABANDONEDS)."." + ); + } + $result['abandoned'] = $abandonedEnv; + } + + return $result; + + default: + if (!isset($this->config[$key])) { + return null; + } + + return $this->process($this->config[$key], $flags); + } + } + + /** + * @return array + */ + public function all(int $flags = 0): array + { + $all = [ + 'repositories' => $this->getRepositories(), + ]; + foreach (array_keys($this->config) as $key) { + $all['config'][$key] = $this->get($key, $flags); + } + + return $all; + } + + public function getSourceOfValue(string $key): string + { + $this->get($key); + + return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN; + } + + /** + * @param mixed $configValue + */ + private function setSourceOfConfigValue($configValue, string $path, string $source): void + { + $this->sourceOfConfigValue[$path] = $source; + + if (is_array($configValue)) { + foreach ($configValue as $key => $value) { + $this->setSourceOfConfigValue($value, $path . '.' . $key, $source); + } + } + } + + /** + * @return array + */ + public function raw(): array + { + return [ + 'repositories' => $this->getRepositories(), + 'config' => $this->config, + ]; + } + + /** + * Checks whether a setting exists + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->config); + } + + /** + * Replaces {$refs} inside a config string + * + * @param string|mixed $value a config string that can contain {$refs-to-other-config} + * @param int $flags Options (see class constants) + * + * @return string|mixed + */ + private function process($value, int $flags) + { + if (!is_string($value)) { + return $value; + } + + return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) { + return $this->get($match[1], $flags); + }, $value); + } + + /** + * Turns relative paths in absolute paths without realpath() + * + * Since the dirs might not exist yet we can not call realpath or it will fail. + */ + private function realpath(string $path): string + { + if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) { + return $path; + } + + return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path; + } + + /** + * Reads the value of a Composer environment variable + * + * This should be used to read COMPOSER_ environment variables + * that overload config values. + * + * @param non-empty-string $var + * + * @return string|false + */ + private function getComposerEnv(string $var) + { + if ($this->useEnvironment) { + return Platform::getEnv($var); + } + + return false; + } + + private function disableRepoByName(string $name): void + { + if (isset($this->repositories[$name])) { + unset($this->repositories[$name]); + } elseif ($name === 'packagist') { // BC support for default "packagist" named repo + unset($this->repositories['packagist.org']); + } + } + + /** + * Validates that the passed URL is allowed to be used by current config, or throws an exception. + * + * @param IOInterface $io + * @param mixed[] $repoOptions + */ + public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void + { + // Return right away if the URL is malformed or custom (see issue #5173), but only for non-HTTP(S) URLs + if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}', $url)) { + return; + } + + // Extract scheme and throw exception on known insecure protocols + $scheme = parse_url($url, PHP_URL_SCHEME); + $hostname = parse_url($url, PHP_URL_HOST); + if (in_array($scheme, ['http', 'git', 'ftp', 'svn'])) { + if ($this->get('secure-http')) { + if ($scheme === 'svn') { + if (in_array($hostname, $this->get('secure-svn-domains'), true)) { + return; + } + + throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details."); + } + + throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details."); + } + if ($io !== null) { + if (is_string($hostname)) { + if (!isset($this->warnedHosts[$hostname])) { + $io->writeError("Warning: Accessing $hostname over $scheme which is an insecure protocol."); + } + $this->warnedHosts[$hostname] = true; + } + } + } + + if ($io !== null && is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) { + $warning = null; + if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) { + $warning = 'verify_peer'; + } + + if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) { + $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name'; + } + + if ($warning !== null) { + $io->writeError("Warning: Accessing $hostname with $warning disabled."); + $this->sslVerifyWarnedHosts[$hostname] = true; + } + } + } + + /** + * Used by long-running custom scripts in composer.json + * + * "scripts": { + * "watch": [ + * "Composer\\Config::disableProcessTimeout", + * "vendor/bin/long-running-script --watch" + * ] + * } + */ + public static function disableProcessTimeout(): void + { + // Override global timeout set earlier by environment or config + ProcessExecutor::setTimeout(0); + } +} diff --git a/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php b/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php new file mode 100644 index 0000000..31f21f0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php @@ -0,0 +1,84 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Config; + +/** + * Configuration Source Interface + * + * @author Jordi Boggiano + * @author Beau Simensen + */ +interface ConfigSourceInterface +{ + /** + * Add a repository + * + * @param string $name Name + * @param mixed[]|false $config Configuration + * @param bool $append Whether the repo should be appended (true) or prepended (false) + */ + public function addRepository(string $name, $config, bool $append = true): void; + + /** + * Remove a repository + */ + public function removeRepository(string $name): void; + + /** + * Add a config setting + * + * @param string $name Name + * @param mixed $value Value + */ + public function addConfigSetting(string $name, $value): void; + + /** + * Remove a config setting + */ + public function removeConfigSetting(string $name): void; + + /** + * Add a property + * + * @param string $name Name + * @param string|string[] $value Value + */ + public function addProperty(string $name, $value): void; + + /** + * Remove a property + */ + public function removeProperty(string $name): void; + + /** + * Add a package link + * + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + * @param string $value Value + */ + public function addLink(string $type, string $name, string $value): void; + + /** + * Remove a package link + * + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + */ + public function removeLink(string $type, string $name): void; + + /** + * Gives a user-friendly name to this source (file path or so) + */ + public function getName(): string; +} diff --git a/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php b/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php new file mode 100644 index 0000000..596d14f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Config/JsonConfigSource.php @@ -0,0 +1,299 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Config; + +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Json\JsonValidationException; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; + +/** + * JSON Configuration Source + * + * @author Jordi Boggiano + * @author Beau Simensen + */ +class JsonConfigSource implements ConfigSourceInterface +{ + /** + * @var JsonFile + */ + private $file; + + /** + * @var bool + */ + private $authConfig; + + /** + * Constructor + */ + public function __construct(JsonFile $file, bool $authConfig = false) + { + $this->file = $file; + $this->authConfig = $authConfig; + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return $this->file->getPath(); + } + + /** + * @inheritDoc + */ + public function addRepository(string $name, $config, bool $append = true): void + { + $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use ($append): void { + // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have + // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation + if (isset($config['repositories'])) { + foreach ($config['repositories'] as $index => $val) { + if ($index === $repo) { + continue; + } + if (is_numeric($index) && ($val === ['packagist' => false] || $val === ['packagist.org' => false])) { + unset($config['repositories'][$index]); + $config['repositories']['packagist.org'] = false; + break; + } + } + } + + if ($append) { + $config['repositories'][$repo] = $repoConfig; + } else { + $config['repositories'] = [$repo => $repoConfig] + $config['repositories']; + } + }, $name, $config, $append); + } + + /** + * @inheritDoc + */ + public function removeRepository(string $name): void + { + $this->manipulateJson('removeRepository', static function (&$config, $repo): void { + unset($config['repositories'][$repo]); + }, $name); + } + + /** + * @inheritDoc + */ + public function addConfigSetting(string $name, $value): void + { + $authConfig = $this->authConfig; + $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use ($authConfig): void { + if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) { + [$key, $host] = explode('.', $key, 2); + if ($authConfig) { + $config[$key][$host] = $val; + } else { + $config['config'][$key][$host] = $val; + } + } else { + $config['config'][$key] = $val; + } + }, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeConfigSetting(string $name): void + { + $authConfig = $this->authConfig; + $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use ($authConfig): void { + if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) { + [$key, $host] = explode('.', $key, 2); + if ($authConfig) { + unset($config[$key][$host]); + } else { + unset($config['config'][$key][$host]); + } + } else { + unset($config['config'][$key]); + } + }, $name); + } + + /** + * @inheritDoc + */ + public function addProperty(string $name, $value): void + { + $this->manipulateJson('addProperty', static function (&$config, $key, $val): void { + if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { + $bits = explode('.', $key); + $last = array_pop($bits); + $arr = &$config[reset($bits)]; + foreach ($bits as $bit) { + if (!isset($arr[$bit])) { + $arr[$bit] = []; + } + $arr = &$arr[$bit]; + } + $arr[$last] = $val; + } else { + $config[$key] = $val; + } + }, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeProperty(string $name): void + { + $this->manipulateJson('removeProperty', static function (&$config, $key): void { + if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) { + $bits = explode('.', $key); + $last = array_pop($bits); + $arr = &$config[reset($bits)]; + foreach ($bits as $bit) { + if (!isset($arr[$bit])) { + return; + } + $arr = &$arr[$bit]; + } + unset($arr[$last]); + } else { + unset($config[$key]); + } + }, $name); + } + + /** + * @inheritDoc + */ + public function addLink(string $type, string $name, string $value): void + { + $this->manipulateJson('addLink', static function (&$config, $type, $name, $value): void { + $config[$type][$name] = $value; + }, $type, $name, $value); + } + + /** + * @inheritDoc + */ + public function removeLink(string $type, string $name): void + { + $this->manipulateJson('removeSubNode', static function (&$config, $type, $name): void { + unset($config[$type][$name]); + }, $type, $name); + $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type): void { + if (0 === count($config[$type])) { + unset($config[$type]); + } + }, $type); + } + + /** + * @param mixed ...$args + */ + private function manipulateJson(string $method, callable $fallback, ...$args): void + { + if ($this->file->exists()) { + if (!is_writable($this->file->getPath())) { + throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath())); + } + + if (!Filesystem::isReadable($this->file->getPath())) { + throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath())); + } + + $contents = file_get_contents($this->file->getPath()); + } elseif ($this->authConfig) { + $contents = "{\n}\n"; + } else { + $contents = "{\n \"config\": {\n }\n}\n"; + } + + $manipulator = new JsonManipulator($contents); + + $newFile = !$this->file->exists(); + + // override manipulator method for auth config files + if ($this->authConfig && $method === 'addConfigSetting') { + $method = 'addSubNode'; + [$mainNode, $name] = explode('.', $args[0], 2); + $args = [$mainNode, $name, $args[1]]; + } elseif ($this->authConfig && $method === 'removeConfigSetting') { + $method = 'removeSubNode'; + [$mainNode, $name] = explode('.', $args[0], 2); + $args = [$mainNode, $name]; + } + + // try to update cleanly + if (call_user_func_array([$manipulator, $method], $args)) { + file_put_contents($this->file->getPath(), $manipulator->getContents()); + } else { + // on failed clean update, call the fallback and rewrite the whole file + $config = $this->file->read(); + $this->arrayUnshiftRef($args, $config); + $fallback(...$args); + // avoid ending up with arrays for keys that should be objects + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) { + if (isset($config[$prop]) && $config[$prop] === []) { + $config[$prop] = new \stdClass; + } + } + foreach (['psr-0', 'psr-4'] as $prop) { + if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) { + $config['autoload'][$prop] = new \stdClass; + } + if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) { + $config['autoload-dev'][$prop] = new \stdClass; + } + } + foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'preferred-install'] as $prop) { + if (isset($config['config'][$prop]) && $config['config'][$prop] === []) { + $config['config'][$prop] = new \stdClass; + } + } + $this->file->write($config); + } + + try { + $this->file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + // restore contents to the original state + file_put_contents($this->file->getPath(), $contents); + throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). '.PHP_EOL.implode(PHP_EOL, $e->getErrors()), 0, $e); + } + + if ($newFile) { + Silencer::call('chmod', $this->file->getPath(), 0600); + } + } + + /** + * Prepend a reference to an element to the beginning of an array. + * + * @param mixed[] $array + * @param mixed $value + */ + private function arrayUnshiftRef(array &$array, &$value): int + { + $return = array_unshift($array, ''); + $array[0] = &$value; + + return $return; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Application.php b/vendor/composer/composer/src/Composer/Console/Application.php new file mode 100644 index 0000000..7fc75ff --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Application.php @@ -0,0 +1,757 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Composer\Installer; +use Composer\IO\NullIO; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use LogicException; +use RuntimeException; +use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Seld\JsonLint\ParsingException; +use Composer\Command; +use Composer\Composer; +use Composer\Factory; +use Composer\Downloader\TransportException; +use Composer\IO\IOInterface; +use Composer\IO\ConsoleIO; +use Composer\Json\JsonValidationException; +use Composer\Util\ErrorHandler; +use Composer\Util\HttpDownloader; +use Composer\EventDispatcher\ScriptExecutionException; +use Composer\Exception\NoSslException; +use Composer\XdebugHandler\XdebugHandler; +use Symfony\Component\Process\Exception\ProcessTimedOutException; + +/** + * The console application that handles the commands + * + * @author Ryan Weaver + * @author Jordi Boggiano + * @author François Pluchino + */ +class Application extends BaseApplication +{ + /** + * @var ?Composer + */ + protected $composer; + + /** + * @var IOInterface + */ + protected $io; + + /** @var string */ + private static $logo = ' ______ + / ____/___ ____ ___ ____ ____ ________ _____ + / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ +/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / +\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ + /_/ +'; + + /** @var bool */ + private $hasPluginCommands = false; + /** @var bool */ + private $disablePluginsByDefault = false; + /** @var bool */ + private $disableScriptsByDefault = false; + + /** + * @var string|false Store the initial working directory at startup time + */ + private $initialWorkingDirectory; + + public function __construct(string $name = 'Composer', string $version = '') + { + if (method_exists($this, 'setCatchErrors')) { + $this->setCatchErrors(true); + } + + static $shutdownRegistered = false; + if ($version === '') { + $version = Composer::getVersion(); + } + if (function_exists('ini_set') && extension_loaded('xdebug')) { + ini_set('xdebug.show_exception_trace', '0'); + ini_set('xdebug.scream', '0'); + } + + if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { + date_default_timezone_set(Silencer::call('date_default_timezone_get')); + } + + $this->io = new NullIO(); + + if (!$shutdownRegistered) { + $shutdownRegistered = true; + + register_shutdown_function(static function (): void { + $lastError = error_get_last(); + + if ($lastError && $lastError['message'] && + (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ || + strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) { + echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.'; + } + }); + } + + $this->initialWorkingDirectory = getcwd(); + + parent::__construct($name, $version); + } + + public function __destruct() + { + } + + public function run(?InputInterface $input = null, ?OutputInterface $output = null): int + { + if (null === $output) { + $output = Factory::createOutput(); + } + + return parent::run($input, $output); + } + + public function doRun(InputInterface $input, OutputInterface $output): int + { + $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); + $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); + + static $stdin = null; + if (null === $stdin) { + $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + } + if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) { + $input->setInteractive(false); + } + + $io = $this->io = new ConsoleIO($input, $output, new HelperSet([ + new QuestionHelper(), + ])); + + // Register error handler again to pass it the IO instance + ErrorHandler::register($io); + + if ($input->hasParameterOption('--no-cache')) { + $io->writeError('Disabling cache usage', true, IOInterface::DEBUG); + Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null'); + } + + // switch working dir + $newWorkDir = $this->getNewWorkingDir($input); + if (null !== $newWorkDir) { + $oldWorkingDir = Platform::getCwd(true); + chdir($newWorkDir); + $this->initialWorkingDirectory = $newWorkDir; + $cwd = Platform::getCwd(true); + $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), true, IOInterface::DEBUG); + } + + // determine command name to be executed without including plugin commands + $commandName = ''; + if ($name = $this->getCommandNameBeforeBinding($input)) { + try { + $commandName = $this->find($name)->getName(); + } catch (CommandNotFoundException $e) { + // we'll check command validity again later after plugins are loaded + $commandName = false; + } catch (\InvalidArgumentException $e) { + } + } + + // prompt user for dir change if no composer.json is present in current dir + if ( + null === $newWorkDir + // do not prompt for commands that can function without composer.json + && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true) + && !file_exists(Factory::getComposerFile()) + // if use-parent-dir is disabled we should not prompt + && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false + // config --file ... should not prompt + && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false)) + // calling a command's help should not prompt + && $input->hasParameterOption('--help', true) === false + && $input->hasParameterOption('-h', true) === false + ) { + $dir = dirname(Platform::getCwd(true)); + $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); + + // abort when we reach the home dir or top of the filesystem + while (dirname($dir) !== $dir && $dir !== $home) { + if (file_exists($dir.'/'.Factory::getComposerFile())) { + if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) { + $io->writeError('No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true'); + break; + } + if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('No composer.json in current directory, do you want to use the one at '.$dir.'? [Y,n]? ')) { + if ($useParentDirIfNoJsonAvailable === true) { + $io->writeError('No composer.json in current directory, changing working directory to '.$dir.''); + } else { + $io->writeError('Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.'); + } + $oldWorkingDir = Platform::getCwd(true); + chdir($dir); + } + break; + } + $dir = dirname($dir); + } + unset($dir, $home); + } + + $needsSudoCheck = !Platform::isWindows() + && function_exists('exec') + && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') + && !Platform::isDocker(); + $isNonAllowedRoot = false; + + // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins + if ($needsSudoCheck) { + $isNonAllowedRoot = $this->isRunningAsRoot(); + + if ($isNonAllowedRoot) { + if ($uid = (int) Platform::getEnv('SUDO_UID')) { + // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on + // ref. https://github.com/composer/composer/issues/5119 + Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1"); + } + } + + // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations + Silencer::call('exec', 'sudo -K > /dev/null 2>&1'); + } + + // avoid loading plugins/initializing the Composer instance earlier than necessary if no plugin command is needed + // if showing the version, we never need plugin commands + $mayNeedPluginCommand = false === $input->hasParameterOption(['--version', '-V']) + && ( + // not a composer command, so try loading plugin ones + false === $commandName + // list command requires plugin commands to show them + || in_array($commandName, ['', 'list', 'help'], true) + // autocompletion requires plugin commands but if we are running as root without COMPOSER_ALLOW_SUPERUSER + // we'd rather not autocomplete plugins than abort autocompletion entirely, so we avoid loading plugins in this case + || ($commandName === '_complete' && !$isNonAllowedRoot) + ); + + if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) { + // at this point plugins are needed, so if we are running as root and it is not allowed we need to prompt + // if interactive, and abort otherwise + if ($isNonAllowedRoot) { + $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); + + if ($io->isInteractive() && $io->askConfirmation('Continue as root/super user [yes]? ')) { + // avoid a second prompt later + $isNonAllowedRoot = false; + } else { + $io->writeError('Aborting as no plugin should be loaded if running as super user is not explicitly allowed'); + + return 1; + } + } + + try { + foreach ($this->getPluginCommands() as $command) { + if ($this->has($command->getName())) { + $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); + } else { + $this->add($command); + } + } + } catch (NoSslException $e) { + // suppress these as they are not relevant at this point + } catch (ParsingException $e) { + $details = $e->getDetails(); + + $file = realpath(Factory::getComposerFile()); + + $line = null; + if ($details && isset($details['line'])) { + $line = $details['line']; + } + + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage(), $file, $line); + + throw $e; + } + + $this->hasPluginCommands = true; + } + + if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { + $io->writeError('Composer plugins have been disabled for safety in this non-interactive session.'); + $io->writeError('Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); + $this->disablePluginsByDefault = true; + } + + // determine command name to be executed incl plugin commands, and check if it's a proxy command + $isProxyCommand = false; + if ($name = $this->getCommandNameBeforeBinding($input)) { + try { + $command = $this->find($name); + $commandName = $command->getName(); + $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand()); + } catch (\InvalidArgumentException $e) { + } + } + + if (!$isProxyCommand) { + $io->writeError(sprintf( + 'Running %s (%s) with %s on %s', + Composer::getVersion(), + Composer::RELEASE_DATE, + defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, + function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' + ), true, IOInterface::DEBUG); + + if (\PHP_VERSION_ID < 70205) { + $io->writeError('Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.'); + } + + if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) { + $io->writeError('Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug'); + } + + if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) { + $io->writeError(sprintf('Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); + } + + if ($isNonAllowedRoot) { + if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') { + $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); + + if ($io->isInteractive()) { + if (!$io->askConfirmation('Continue as root/super user [yes]? ')) { + return 1; + } + } + } + } + + // Check system temp folder for usability as it can cause weird runtime issues otherwise + Silencer::call(static function () use ($io): void { + $pid = function_exists('getmypid') ? getmypid() . '-' : ''; + $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5)); + if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) { + $io->writeError(sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', sys_get_temp_dir())); + } + }); + + // add non-standard scripts as own commands + $file = Factory::getComposerFile(); + if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode(file_get_contents($file), true))) { + if (isset($composer['scripts']) && is_array($composer['scripts'])) { + foreach ($composer['scripts'] as $script => $dummy) { + if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { + if ($this->has($script)) { + $io->writeError('A script named '.$script.' would override a Composer command and has been skipped'); + } else { + $description = null; + + if (isset($composer['scripts-descriptions'][$script])) { + $description = $composer['scripts-descriptions'][$script]; + } + + $aliases = $composer['scripts-aliases'][$script] ?? []; + + $this->add(new Command\ScriptAliasCommand($script, $description, $aliases)); + } + } + } + } + } + } + + try { + if ($input->hasParameterOption('--profile')) { + $startTime = microtime(true); + $this->io->enableDebugging($startTime); + } + + $result = parent::doRun($input, $output); + + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $io->writeError(sprintf('PHP version %s (%s)', \PHP_VERSION, \PHP_BINARY)); + $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.'); + } + + // chdir back to $oldWorkingDir if set + if (isset($oldWorkingDir) && '' !== $oldWorkingDir) { + Silencer::call('chdir', $oldWorkingDir); + } + + if (isset($startTime)) { + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); + } + + return $result; + } catch (ScriptExecutionException $e) { + if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { + $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.', true, IOInterface::QUIET); + $io->writeError('See also https://getcomposer.org/root', true, IOInterface::QUIET); + } + + return $e->getCode(); + } catch (\Throwable $e) { + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage()); + + $this->hintCommonErrors($e, $output); + + // symfony/console <6.4 does not handle \Error subtypes so we have to renderThrowable ourselves + // instead of rethrowing those for consumption by the parent class + // can be removed when Composer supports PHP 8.1+ + if (!method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + + return max(1, $e->getCode()); + } + + // override TransportException's code for the purpose of parent::run() using it as process exit code + // as http error codes are all beyond the 255 range of permitted exit codes + if ($e instanceof TransportException) { + $reflProp = new \ReflectionProperty($e, 'code'); + $reflProp->setAccessible(true); + $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION); + } + + throw $e; + } finally { + restore_error_handler(); + } + } + + /** + * @throws \RuntimeException + * @return ?string + */ + private function getNewWorkingDir(InputInterface $input): ?string + { + /** @var string|null $workingDir */ + $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, true); + if (null !== $workingDir && !is_dir($workingDir)) { + throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.'); + } + + return $workingDir; + } + + private function hintCommonErrors(\Throwable $exception, OutputInterface $output): void + { + $io = $this->getIO(); + + if ((get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + + Silencer::suppress(); + try { + $composer = $this->getComposer(false, true); + if (null !== $composer && function_exists('disk_free_space')) { + $config = $composer->getConfig(); + + $minSpaceFree = 100 * 1024 * 1024; + if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) + || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) + || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) + ) { + $io->writeError('The disk hosting '.$dir.' has less than 100MiB of free space, this may be the cause of the following exception', true, IOInterface::QUIET); + } + } + } catch (\Exception $e) { + } + Silencer::restore(); + + if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) { + $io->writeError('The following exception indicates your proxy is misconfigured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details', true, IOInterface::QUIET); + } + + if (Platform::isWindows() && $exception instanceof TransportException && str_contains($exception->getMessage(), 'unable to get local issuer certificate')) { + $avastDetect = glob('C:\Program Files\Avast*'); + if (is_array($avastDetect) && count($avastDetect) !== 0) { + $io->writeError('The following exception indicates a possible issue with the Avast Firewall', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); + } else { + $io->writeError('The following exception indicates a possible issue with a Firewall/Antivirus', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); + } + } + + if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { + $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); + } + + if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { + $io->writeError('The following exception is caused by a lack of memory or swap, or not having swap configured', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', true, IOInterface::QUIET); + } + + if ($exception instanceof ProcessTimedOutException) { + $io->writeError('The following exception is caused by a process timeout', true, IOInterface::QUIET); + $io->writeError('Check https://getcomposer.org/doc/06-config.md#process-timeout for details', true, IOInterface::QUIET); + } + + if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { + $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root', true, IOInterface::QUIET); + } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) { + $io->writeError('Plugins have been disabled, which may be why some commands are missing, unless you made a typo', true, IOInterface::QUIET); + } + + $hints = HttpDownloader::getExceptionHints($exception); + if (null !== $hints && count($hints) > 0) { + foreach ($hints as $hint) { + $io->writeError($hint, true, IOInterface::QUIET); + } + } + } + + /** + * @throws JsonValidationException + * @throws \InvalidArgumentException + * @return ?Composer If $required is true then the return value is guaranteed + */ + public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer + { + if (null === $disablePlugins) { + $disablePlugins = $this->disablePluginsByDefault; + } + if (null === $disableScripts) { + $disableScripts = $this->disableScriptsByDefault; + } + + if (null === $this->composer) { + try { + $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts); + } catch (\InvalidArgumentException $e) { + if ($required) { + $this->io->writeError($e->getMessage()); + if ($this->areExceptionsCaught()) { + exit(1); + } + throw $e; + } + } catch (JsonValidationException $e) { + if ($required) { + throw $e; + } + } catch (RuntimeException $e) { + if ($required) { + throw $e; + } + } + } + + return $this->composer; + } + + /** + * Removes the cached composer instance + */ + public function resetComposer(): void + { + $this->composer = null; + if (method_exists($this->getIO(), 'resetAuthentications')) { + $this->getIO()->resetAuthentications(); + } + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function getHelp(): string + { + return self::$logo . parent::getHelp(); + } + + /** + * Initializes all the composer commands. + * @return \Symfony\Component\Console\Command\Command[] + */ + protected function getDefaultCommands(): array + { + $commands = array_merge(parent::getDefaultCommands(), [ + new Command\AboutCommand(), + new Command\ConfigCommand(), + new Command\DependsCommand(), + new Command\ProhibitsCommand(), + new Command\InitCommand(), + new Command\InstallCommand(), + new Command\CreateProjectCommand(), + new Command\UpdateCommand(), + new Command\SearchCommand(), + new Command\ValidateCommand(), + new Command\AuditCommand(), + new Command\ShowCommand(), + new Command\SuggestsCommand(), + new Command\RequireCommand(), + new Command\DumpAutoloadCommand(), + new Command\StatusCommand(), + new Command\ArchiveCommand(), + new Command\DiagnoseCommand(), + new Command\RunScriptCommand(), + new Command\LicensesCommand(), + new Command\GlobalCommand(), + new Command\ClearCacheCommand(), + new Command\RemoveCommand(), + new Command\HomeCommand(), + new Command\ExecCommand(), + new Command\OutdatedCommand(), + new Command\CheckPlatformReqsCommand(), + new Command\FundCommand(), + new Command\ReinstallCommand(), + new Command\BumpCommand(), + ]); + + if (strpos(__FILE__, 'phar:') === 0 || '1' === Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { + $commands[] = new Command\SelfUpdateCommand(); + } + + return $commands; + } + + /** + * This ensures we can find the correct command name even if a global input option is present before it + * + * e.g. "composer -d foo bar" should detect bar as the command name, and not foo + */ + private function getCommandNameBeforeBinding(InputInterface $input): ?string + { + $input = clone $input; + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + return $input->getFirstArgument(); + } + + public function getLongVersion(): string + { + $branchAliasString = ''; + if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') { + $branchAliasString = sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION); + } + + return sprintf( + '%s version %s%s %s', + $this->getName(), + $this->getVersion(), + $branchAliasString, + Composer::RELEASE_DATE + ); + } + + protected function getDefaultInputDefinition(): InputDefinition + { + $definition = parent::getDefaultInputDefinition(); + $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); + $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); + $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.')); + $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); + $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); + + return $definition; + } + + /** + * @return Command\BaseCommand[] + */ + private function getPluginCommands(): array + { + $commands = []; + + $composer = $this->getComposer(false, false); + if (null === $composer) { + $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault); + } + + if (null !== $composer) { + $pm = $composer->getPluginManager(); + foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) { + $newCommands = $capability->getCommands(); + if (!is_array($newCommands)) { + throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands'); + } + foreach ($newCommands as $command) { + if (!$command instanceof Command\BaseCommand) { + throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects'); + } + } + $commands = array_merge($commands, $newCommands); + } + } + + return $commands; + } + + /** + * Get the working directory at startup time + * + * @return string|false + */ + public function getInitialWorkingDirectory() + { + return $this->initialWorkingDirectory; + } + + public function getDisablePluginsByDefault(): bool + { + return $this->disablePluginsByDefault; + } + + public function getDisableScriptsByDefault(): bool + { + return $this->disableScriptsByDefault; + } + + /** + * @return 'prompt'|bool + */ + private function getUseParentDirConfigValue() + { + $config = Factory::createConfig($this->io); + + return $config->get('use-parent-dir'); + } + + private function isRunningAsRoot(): bool + { + return function_exists('posix_getuid') && posix_getuid() === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/GithubActionError.php b/vendor/composer/composer/src/Composer/Console/GithubActionError.php new file mode 100644 index 0000000..8a19a19 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/GithubActionError.php @@ -0,0 +1,68 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Composer\IO\IOInterface; +use Composer\Util\Platform; + +final class GithubActionError +{ + /** + * @var IOInterface + */ + protected $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + public function emit(string $message, ?string $file = null, ?int $line = null): void + { + if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { + $message = $this->escapeData($message); + + if ($file && $line) { + $file = $this->escapeProperty($file); + $this->io->write("::error file=". $file .",line=". $line ."::". $message); + } elseif ($file) { + $file = $this->escapeProperty($file); + $this->io->write("::error file=". $file ."::". $message); + } else { + $this->io->write("::error ::". $message); + } + } + } + + private function escapeData(string $data): string + { + // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L80-L85 + $data = str_replace("%", '%25', $data); + $data = str_replace("\r", '%0D', $data); + $data = str_replace("\n", '%0A', $data); + + return $data; + } + + private function escapeProperty(string $property): string + { + // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L87-L94 + $property = str_replace("%", '%25', $property); + $property = str_replace("\r", '%0D', $property); + $property = str_replace("\n", '%0A', $property); + $property = str_replace(":", '%3A', $property); + $property = str_replace(",", '%2C', $property); + + return $property; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php b/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php new file mode 100644 index 0000000..af638d6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php @@ -0,0 +1,104 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console; + +use Closure; +use Composer\Pcre\Preg; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +/** + * @author Jordi Boggiano + */ +class HtmlOutputFormatter extends OutputFormatter +{ + /** @var array */ + private static $availableForegroundColors = [ + 30 => 'black', + 31 => 'red', + 32 => 'green', + 33 => 'yellow', + 34 => 'blue', + 35 => 'magenta', + 36 => 'cyan', + 37 => 'white', + ]; + /** @var array */ + private static $availableBackgroundColors = [ + 40 => 'black', + 41 => 'red', + 42 => 'green', + 43 => 'yellow', + 44 => 'blue', + 45 => 'magenta', + 46 => 'cyan', + 47 => 'white', + ]; + /** @var array */ + private static $availableOptions = [ + 1 => 'bold', + 4 => 'underscore', + //5 => 'blink', + //7 => 'reverse', + //8 => 'conceal' + ]; + + /** + * @param array $styles Array of "name => FormatterStyle" instances + */ + public function __construct(array $styles = []) + { + parent::__construct(true, $styles); + } + + public function format(?string $message): ?string + { + $formatted = parent::format($message); + + if ($formatted === null) { + return null; + } + + $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; + + return Preg::replaceCallback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted); + } + + /** + * @param array $matches + */ + private function formatHtml(array $matches): string + { + assert(is_string($matches[1])); + $out = ''.$matches[2].''; + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php b/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php new file mode 100644 index 0000000..19aff8c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Input/InputArgument.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument as BaseInputArgument; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + * + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required + */ +class InputArgument extends BaseInputArgument +{ + /** + * @var list|\Closure(CompletionInput,CompletionSuggestions):list + */ + private $suggestedValues; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|bool|int|float|string[]|null $default The default value (for self::OPTIONAL mode only) + * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct(string $name, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $mode, $description, $default); + + $this->suggestedValues = $suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType + throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); + } + if ([] !== $values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Console/Input/InputOption.php b/vendor/composer/composer/src/Composer/Console/Input/InputOption.php new file mode 100644 index 0000000..b5ff333 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Console/Input/InputOption.php @@ -0,0 +1,72 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputOption as BaseInputOption; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + * + * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required + */ +class InputOption extends BaseInputOption +{ + /** + * @var list|\Closure(CompletionInput,CompletionSuggestions):list + */ + private $suggestedValues; + + /** + * @param string|string[]|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string|bool|int|float|string[]|null $default The default value (must be null for self::VALUE_NONE) + * @param list|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completionnull for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $shortcut, $mode, $description, $default); + + $this->suggestedValues = $suggestedValues; + + if ([] !== $suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType + throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); + } + if ([] !== $values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php b/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php new file mode 100644 index 0000000..5991107 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php @@ -0,0 +1,233 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * Stores decisions on installing, removing or keeping packages + * + * @author Nils Adermann + * @implements \Iterator + */ +class Decisions implements \Iterator, \Countable +{ + public const DECISION_LITERAL = 0; + public const DECISION_REASON = 1; + + /** @var Pool */ + protected $pool; + /** @var array */ + protected $decisionMap; + /** + * @var array + */ + protected $decisionQueue = []; + + public function __construct(Pool $pool) + { + $this->pool = $pool; + $this->decisionMap = []; + } + + public function decide(int $literal, int $level, Rule $why): void + { + $this->addDecision($literal, $level); + $this->decisionQueue[] = [ + self::DECISION_LITERAL => $literal, + self::DECISION_REASON => $why, + ]; + } + + public function satisfy(int $literal): bool + { + $packageId = abs($literal); + + return ( + $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || + $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 + ); + } + + public function conflict(int $literal): bool + { + $packageId = abs($literal); + + return ( + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) || + (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0) + ); + } + + public function decided(int $literalOrPackageId): bool + { + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0; + } + + public function undecided(int $literalOrPackageId): bool + { + return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0; + } + + public function decidedInstall(int $literalOrPackageId): bool + { + $packageId = abs($literalOrPackageId); + + return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; + } + + public function decisionLevel(int $literalOrPackageId): int + { + $packageId = abs($literalOrPackageId); + if (isset($this->decisionMap[$packageId])) { + return abs($this->decisionMap[$packageId]); + } + + return 0; + } + + public function decisionRule(int $literalOrPackageId): Rule + { + $packageId = abs($literalOrPackageId); + + foreach ($this->decisionQueue as $decision) { + if ($packageId === abs($decision[self::DECISION_LITERAL])) { + return $decision[self::DECISION_REASON]; + } + } + + throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId); + } + + /** + * @return array{0: int, 1: Rule} a literal and decision reason + */ + public function atOffset(int $queueOffset): array + { + return $this->decisionQueue[$queueOffset]; + } + + public function validOffset(int $queueOffset): bool + { + return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue); + } + + public function lastReason(): Rule + { + return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON]; + } + + public function lastLiteral(): int + { + return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL]; + } + + public function reset(): void + { + while ($decision = array_pop($this->decisionQueue)) { + $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; + } + } + + /** + * @param int<-1, max> $offset + */ + public function resetToOffset(int $offset): void + { + while (\count($this->decisionQueue) > $offset + 1) { + $decision = array_pop($this->decisionQueue); + $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; + } + } + + public function revertLast(): void + { + $this->decisionMap[abs($this->lastLiteral())] = 0; + array_pop($this->decisionQueue); + } + + public function count(): int + { + return \count($this->decisionQueue); + } + + public function rewind(): void + { + end($this->decisionQueue); + } + + /** + * @return array{0: int, 1: Rule}|false + */ + #[\ReturnTypeWillChange] + public function current() + { + return current($this->decisionQueue); + } + + public function key(): ?int + { + return key($this->decisionQueue); + } + + public function next(): void + { + prev($this->decisionQueue); + } + + public function valid(): bool + { + return false !== current($this->decisionQueue); + } + + public function isEmpty(): bool + { + return \count($this->decisionQueue) === 0; + } + + protected function addDecision(int $literal, int $level): void + { + $packageId = abs($literal); + + $previousDecision = $this->decisionMap[$packageId] ?? 0; + if ($previousDecision !== 0) { + $literalString = $this->pool->literalToPrettyString($literal, []); + $package = $this->pool->literalToPackage($literal); + throw new SolverBugException( + "Trying to decide $literalString on level $level, even though $package was previously decided as ".$previousDecision."." + ); + } + + if ($literal > 0) { + $this->decisionMap[$packageId] = $level; + } else { + $this->decisionMap[$packageId] = -$level; + } + } + + public function toString(?Pool $pool = null): string + { + $decisionMap = $this->decisionMap; + ksort($decisionMap); + $str = '['; + foreach ($decisionMap as $packageId => $level) { + $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.','; + } + $str .= ']'; + + return $str; + } + + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php b/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php new file mode 100644 index 0000000..5901870 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php @@ -0,0 +1,285 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\Constraint; + +/** + * @author Nils Adermann + * @author Jordi Boggiano + */ +class DefaultPolicy implements PolicyInterface +{ + /** @var bool */ + private $preferStable; + /** @var bool */ + private $preferLowest; + /** @var array|null */ + private $preferredVersions; + /** @var array>> */ + private $preferredPackageResultCachePerPool; + /** @var array> */ + private $sortingCachePerPool; + + /** + * @param array|null $preferredVersions Must be an array of package name => normalized version + */ + public function __construct(bool $preferStable = false, bool $preferLowest = false, ?array $preferredVersions = null) + { + $this->preferStable = $preferStable; + $this->preferLowest = $preferLowest; + $this->preferredVersions = $preferredVersions; + } + + /** + * @param string $operator One of Constraint::STR_OP_* + * + * @phpstan-param Constraint::STR_OP_* $operator + */ + public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool + { + if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { + return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB]; + } + + // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher + if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) { + $constraint = new Constraint($operator, $b->getVersion()); + $version = new Constraint('==', $a->getVersion()); + + return $constraint->matchSpecific($version, true); + } + + return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion()); + } + + /** + * @param non-empty-list $literals + * @return non-empty-list + */ + public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array + { + sort($literals); + $resultCacheKey = implode(',', $literals).$requiredPackage; + $poolId = spl_object_id($pool); + + if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) { + return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey]; + } + + $packages = $this->groupLiteralsByName($pool, $literals); + + foreach ($packages as &$nameLiterals) { + usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { + $cacheKey = 'i'.$a.'.'.$b.$requiredPackage; // i prefix -> ignoreReplace = true + + if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { + return $this->sortingCachePerPool[$poolId][$cacheKey]; + } + + return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true); + }); + } + + foreach ($packages as &$sortedLiterals) { + $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals); + $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals); + } + + $selected = array_merge(...array_values($packages)); + + // now sort the result across all packages to respect replaces across packages + usort($selected, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { + $cacheKey = $a.'.'.$b.$requiredPackage; // no i prefix -> ignoreReplace = false + + if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { + return $this->sortingCachePerPool[$poolId][$cacheKey]; + } + + return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); + }); + + return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected; + } + + /** + * @param non-empty-list $literals + * @return non-empty-array> + */ + protected function groupLiteralsByName(Pool $pool, array $literals): array + { + $packages = []; + foreach ($literals as $literal) { + $packageName = $pool->literalToPackage($literal)->getName(); + + if (!isset($packages[$packageName])) { + $packages[$packageName] = []; + } + $packages[$packageName][] = $literal; + } + + return $packages; + } + + /** + * @protected + */ + public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = false): int + { + // prefer aliases to the original package + if ($a->getName() === $b->getName()) { + $aAliased = $a instanceof AliasPackage; + $bAliased = $b instanceof AliasPackage; + if ($aAliased && !$bAliased) { + return -1; // use a + } + if (!$aAliased && $bAliased) { + return 1; // use b + } + } + + if (!$ignoreReplace) { + // return original, not replaced + if ($this->replaces($a, $b)) { + return 1; // use b + } + if ($this->replaces($b, $a)) { + return -1; // use a + } + + // for replacers not replacing each other, put a higher prio on replacing + // packages with the same vendor as the required package + if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) { + $requiredVendor = substr($requiredPackage, 0, $pos); + + $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0; + $bIsSameVendor = strpos($b->getName(), $requiredVendor) === 0; + + if ($bIsSameVendor !== $aIsSameVendor) { + return $aIsSameVendor ? -1 : 1; + } + } + } + + // priority equal, sort by package id to make reproducible + if ($a->id === $b->id) { + return 0; + } + + return ($a->id < $b->id) ? -1 : 1; + } + + /** + * Checks if source replaces a package with the same name as target. + * + * Replace constraints are ignored. This method should only be used for + * prioritisation, not for actual constraint verification. + */ + protected function replaces(BasePackage $source, BasePackage $target): bool + { + foreach ($source->getReplaces() as $link) { + if ($link->getTarget() === $target->getName() +// && (null === $link->getConstraint() || +// $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) { + ) { + return true; + } + } + + return false; + } + + /** + * @param list $literals + * @return list + */ + protected function pruneToBestVersion(Pool $pool, array $literals): array + { + if ($this->preferredVersions !== null) { + $name = $pool->literalToPackage($literals[0])->getName(); + if (isset($this->preferredVersions[$name])) { + $preferredVersion = $this->preferredVersions[$name]; + $bestLiterals = []; + foreach ($literals as $literal) { + if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) { + $bestLiterals[] = $literal; + } + } + if (\count($bestLiterals) > 0) { + return $bestLiterals; + } + } + } + + $operator = $this->preferLowest ? '<' : '>'; + $bestLiterals = [$literals[0]]; + $bestPackage = $pool->literalToPackage($literals[0]); + foreach ($literals as $i => $literal) { + if (0 === $i) { + continue; + } + + $package = $pool->literalToPackage($literal); + + if ($this->versionCompare($package, $bestPackage, $operator)) { + $bestPackage = $package; + $bestLiterals = [$literal]; + } elseif ($this->versionCompare($package, $bestPackage, '==')) { + $bestLiterals[] = $literal; + } + } + + return $bestLiterals; + } + + /** + * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones + * + * If no package is a local alias, nothing happens + * + * @param list $literals + * @return list + */ + protected function pruneRemoteAliases(Pool $pool, array $literals): array + { + $hasLocalAlias = false; + + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $hasLocalAlias = true; + break; + } + } + + if (!$hasLocalAlias) { + return $literals; + } + + $selected = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $selected[] = $literal; + } + } + + return $selected; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php b/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php new file mode 100644 index 0000000..64dd7a2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php @@ -0,0 +1,93 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + */ +class GenericRule extends Rule +{ + /** @var list */ + protected $literals; + + /** + * @param list $literals + */ + public function __construct(array $literals, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + // sort all packages ascending by id + sort($literals); + + $this->literals = $literals; + } + + /** + * @return list + */ + public function getLiterals(): array + { + return $this->literals; + } + + /** + * @inheritDoc + */ + public function getHash() + { + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } + + return $data['hash']; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + return $this->literals === $rule->getLiterals(); + } + + public function isAssertion(): bool + { + return 1 === \count($this->literals); + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + $result = $this->isDisabled() ? 'disabled(' : '('; + + foreach ($this->literals as $i => $literal) { + if ($i !== 0) { + $result .= '|'; + } + $result .= $literal; + } + + $result .= ')'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php new file mode 100644 index 0000000..06d9877 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php @@ -0,0 +1,31 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\RepositoryInterface; + +/** + * @author Nils Adermann + * @internal + */ +class LocalRepoTransaction extends Transaction +{ + public function __construct(RepositoryInterface $lockedRepository, InstalledRepositoryInterface $localRepository) + { + parent::__construct( + $localRepository->getPackages(), + $lockedRepository->getPackages() + ); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php new file mode 100644 index 0000000..d77a211 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php @@ -0,0 +1,198 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Package; +use Composer\Pcre\Preg; + +/** + * @author Nils Adermann + * @internal + */ +class LockTransaction extends Transaction +{ + /** + * packages in current lock file, platform repo or otherwise present + * + * Indexed by spl_object_hash + * + * @var array + */ + protected $presentMap; + + /** + * Packages which cannot be mapped, platform repo, root package, other fixed repos + * + * Indexed by package id + * + * @var array + */ + protected $unlockableMap; + + /** + * @var array{dev: BasePackage[], non-dev: BasePackage[], all: BasePackage[]} + */ + protected $resultPackages; + + /** + * @param array $presentMap + * @param array $unlockableMap + */ + public function __construct(Pool $pool, array $presentMap, array $unlockableMap, Decisions $decisions) + { + $this->presentMap = $presentMap; + $this->unlockableMap = $unlockableMap; + + $this->setResultPackages($pool, $decisions); + parent::__construct($this->presentMap, $this->resultPackages['all']); + } + + // TODO make this a bit prettier instead of the two text indexes? + + public function setResultPackages(Pool $pool, Decisions $decisions): void + { + $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []]; + foreach ($decisions as $i => $decision) { + $literal = $decision[Decisions::DECISION_LITERAL]; + + if ($literal > 0) { + $package = $pool->literalToPackage($literal); + + $this->resultPackages['all'][] = $package; + if (!isset($this->unlockableMap[$package->id])) { + $this->resultPackages['non-dev'][] = $package; + } + } + } + } + + public function setNonDevPackages(LockTransaction $extractionResult): void + { + $packages = $extractionResult->getNewLockPackages(false); + + $this->resultPackages['dev'] = $this->resultPackages['non-dev']; + $this->resultPackages['non-dev'] = []; + + foreach ($packages as $package) { + foreach ($this->resultPackages['dev'] as $i => $resultPackage) { + // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible? + if ($package->getName() === $resultPackage->getName()) { + $this->resultPackages['non-dev'][] = $resultPackage; + unset($this->resultPackages['dev'][$i]); + } + } + } + } + + // TODO additionalFixedRepository needs to be looked at here as well? + /** + * @return BasePackage[] + */ + public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): array + { + $packages = []; + foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + // if we're just updating mirrors we need to reset everything to the same as currently "present" packages' references to keep the lock file as-is + if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) { + $package = $this->updateMirrorAndUrls($package); + } + + $packages[] = $package; + } + + return $packages; + } + + /** + * Try to return the original package from presentMap with updated URLs/mirrors + * + * If the type of source/dist changed, then we do not update those and keep them as they were + */ + private function updateMirrorAndUrls(BasePackage $package): BasePackage + { + foreach ($this->presentMap as $presentPackage) { + if ($package->getName() !== $presentPackage->getName()) { + continue; + } + + if ($package->getVersion() !== $presentPackage->getVersion()) { + continue; + } + + if ($presentPackage->getSourceReference() === null) { + continue; + } + + if ($presentPackage->getSourceType() !== $package->getSourceType()) { + continue; + } + + if ($presentPackage instanceof Package) { + $presentPackage->setSourceUrl($package->getSourceUrl()); + $presentPackage->setSourceMirrors($package->getSourceMirrors()); + } + + // if the dist type changed, we only update the source url/mirrors + if ($presentPackage->getDistType() !== $package->getDistType()) { + return $presentPackage; + } + + // update dist url if it is in a known format + if ( + $package->getDistUrl() !== null + && $presentPackage->getDistReference() !== null + && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl()) + ) { + $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl())); + } + $presentPackage->setDistMirrors($package->getDistMirrors()); + + return $presentPackage; + } + + return $package; + } + + /** + * Checks which of the given aliases from composer.json are actually in use for the lock file + * @param list $aliases + * @return list + */ + public function getAliases(array $aliases): array + { + $usedAliases = []; + + foreach ($this->resultPackages['all'] as $package) { + if ($package instanceof AliasPackage) { + foreach ($aliases as $index => $alias) { + if ($alias['package'] === $package->getName()) { + $usedAliases[] = $alias; + unset($aliases[$index]); + } + } + } + } + + usort($usedAliases, static function ($a, $b): int { + return strcmp($a['package'], $b['package']); + }); + + return $usedAliases; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php b/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php new file mode 100644 index 0000000..a610947 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php @@ -0,0 +1,113 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * + * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C]) + */ +class MultiConflictRule extends Rule +{ + /** @var non-empty-list */ + protected $literals; + + /** + * @param non-empty-list $literals + */ + public function __construct(array $literals, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + if (\count($literals) < 3) { + throw new \RuntimeException("multi conflict rule requires at least 3 literals"); + } + + // sort all packages ascending by id + sort($literals); + + $this->literals = $literals; + } + + /** + * @return non-empty-list + */ + public function getLiterals(): array + { + return $this->literals; + } + + /** + * @inheritDoc + */ + public function getHash() + { + $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true)); + if (false === $data) { + throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); + } + + return $data['hash']; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + if ($rule instanceof MultiConflictRule) { + return $this->literals === $rule->getLiterals(); + } + + return false; + } + + public function isAssertion(): bool + { + return false; + } + + /** + * @return never + * @throws \RuntimeException + */ + public function disable(): void + { + throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation."); + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + // TODO multi conflict? + $result = $this->isDisabled() ? 'disabled(multi(' : '(multi('; + + foreach ($this->literals as $i => $literal) { + if ($i !== 0) { + $result .= '|'; + } + $result .= $literal; + } + + $result .= '))'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php new file mode 100644 index 0000000..6aa24f4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; + +/** + * Solver install operation. + * + * @author Konstantin Kudryashov + */ +class InstallOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'install'; + + /** + * @var PackageInterface + */ + protected $package; + + public function __construct(PackageInterface $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): PackageInterface + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->package, $lock); + } + + public static function format(PackageInterface $package, bool $lock = false): string + { + return ($lock ? 'Locking ' : 'Installing ').''.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php new file mode 100644 index 0000000..5deac96 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Nils Adermann + */ +class MarkAliasInstalledOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'markAliasInstalled'; + + /** + * @var AliasPackage + */ + protected $package; + + public function __construct(AliasPackage $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): AliasPackage + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php new file mode 100644 index 0000000..9988f6c --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Nils Adermann + */ +class MarkAliasUninstalledOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'markAliasUninstalled'; + + /** + * @var AliasPackage + */ + protected $package; + + public function __construct(AliasPackage $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): AliasPackage + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php new file mode 100644 index 0000000..45e6acd --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +/** + * Solver operation interface. + * + * @author Konstantin Kudryashov + */ +interface OperationInterface +{ + /** + * Returns operation type. + * + * @return string + */ + public function getOperationType(); + + /** + * Serializes the operation in a human readable format + * + * @param bool $lock Whether this is an operation on the lock file + * @return string + */ + public function show(bool $lock); + + /** + * Serializes the operation in a human readable format + * + * @return string + */ + public function __toString(); +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php new file mode 100644 index 0000000..66f0da5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php @@ -0,0 +1,42 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +/** + * Abstract operation class. + * + * @author Aleksandr Bezpiatov + */ +abstract class SolverOperation implements OperationInterface +{ + /** + * @abstract must be redefined by extending classes + */ + protected const TYPE = ''; + + /** + * Returns operation type. + */ + public function getOperationType(): string + { + return static::TYPE; + } + + /** + * @inheritDoc + */ + public function __toString() + { + return $this->show(false); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php new file mode 100644 index 0000000..f6f5a47 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; + +/** + * Solver uninstall operation. + * + * @author Konstantin Kudryashov + */ +class UninstallOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'uninstall'; + + /** + * @var PackageInterface + */ + protected $package; + + public function __construct(PackageInterface $package) + { + $this->package = $package; + } + + /** + * Returns package instance. + */ + public function getPackage(): PackageInterface + { + return $this->package; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->package, $lock); + } + + public static function format(PackageInterface $package, bool $lock = false): string + { + return 'Removing '.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php new file mode 100644 index 0000000..48010fb --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php @@ -0,0 +1,88 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; + +/** + * Solver update operation. + * + * @author Konstantin Kudryashov + */ +class UpdateOperation extends SolverOperation implements OperationInterface +{ + protected const TYPE = 'update'; + + /** + * @var PackageInterface + */ + protected $initialPackage; + + /** + * @var PackageInterface + */ + protected $targetPackage; + + /** + * @param PackageInterface $initial initial package + * @param PackageInterface $target target package (updated) + */ + public function __construct(PackageInterface $initial, PackageInterface $target) + { + $this->initialPackage = $initial; + $this->targetPackage = $target; + } + + /** + * Returns initial package. + */ + public function getInitialPackage(): PackageInterface + { + return $this->initialPackage; + } + + /** + * Returns target package. + */ + public function getTargetPackage(): PackageInterface + { + return $this->targetPackage; + } + + /** + * @inheritDoc + */ + public function show($lock): string + { + return self::format($this->initialPackage, $this->targetPackage, $lock); + } + + public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = false): string + { + $fromVersion = $initialPackage->getFullPrettyVersion(); + $toVersion = $targetPackage->getFullPrettyVersion(); + + if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) { + $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); + $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); + } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) { + $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); + $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); + } + + $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading'; + + return $actionName.' '.$initialPackage->getPrettyName().' ('.$fromVersion.' => '.$toVersion.')'; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php b/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php new file mode 100644 index 0000000..b4511d0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\PackageInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * @author Nils Adermann + */ +interface PolicyInterface +{ + /** + * @phpstan-param Constraint::STR_OP_* $operator + */ + public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool; + + /** + * @param non-empty-list $literals + * @return non-empty-list + */ + public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array; +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php b/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php new file mode 100644 index 0000000..48f47cb --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Pool.php @@ -0,0 +1,265 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * A package pool contains all packages for dependency resolution + * + * @author Nils Adermann + * @author Jordi Boggiano + */ +class Pool implements \Countable +{ + /** @var BasePackage[] */ + protected $packages = []; + /** @var array */ + protected $packageByName = []; + /** @var VersionParser */ + protected $versionParser; + /** @var array> */ + protected $providerCache = []; + /** @var BasePackage[] */ + protected $unacceptableFixedOrLockedPackages; + /** @var array> Map of package name => normalized version => pretty version */ + protected $removedVersions = []; + /** @var array> Map of package object hash => removed normalized versions => removed pretty version */ + protected $removedVersionsByPackage = []; + + /** + * @param BasePackage[] $packages + * @param BasePackage[] $unacceptableFixedOrLockedPackages + * @param array> $removedVersions + * @param array> $removedVersionsByPackage + */ + public function __construct(array $packages = [], array $unacceptableFixedOrLockedPackages = [], array $removedVersions = [], array $removedVersionsByPackage = []) + { + $this->versionParser = new VersionParser; + $this->setPackages($packages); + $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; + $this->removedVersions = $removedVersions; + $this->removedVersionsByPackage = $removedVersionsByPackage; + } + + /** + * @return array + */ + public function getRemovedVersions(string $name, ConstraintInterface $constraint): array + { + if (!isset($this->removedVersions[$name])) { + return []; + } + + $result = []; + foreach ($this->removedVersions[$name] as $version => $prettyVersion) { + if ($constraint->matches(new Constraint('==', $version))) { + $result[$version] = $prettyVersion; + } + } + + return $result; + } + + /** + * @return array + */ + public function getRemovedVersionsByPackage(string $objectHash): array + { + if (!isset($this->removedVersionsByPackage[$objectHash])) { + return []; + } + + return $this->removedVersionsByPackage[$objectHash]; + } + + /** + * @param BasePackage[] $packages + */ + private function setPackages(array $packages): void + { + $id = 1; + + foreach ($packages as $package) { + $this->packages[] = $package; + + $package->id = $id++; + + foreach ($package->getNames() as $provided) { + $this->packageByName[$provided][] = $package; + } + } + } + + /** + * @return BasePackage[] + */ + public function getPackages(): array + { + return $this->packages; + } + + /** + * Retrieves the package object for a given package id. + */ + public function packageById(int $id): BasePackage + { + return $this->packages[$id - 1]; + } + + /** + * Returns how many packages have been loaded into the pool + */ + public function count(): int + { + return \count($this->packages); + } + + /** + * Searches all packages providing the given package name and match the constraint + * + * @param string $name The package name to be searched for + * @param ?ConstraintInterface $constraint A constraint that all returned + * packages must match or null to return all + * @return BasePackage[] A set of packages + */ + public function whatProvides(string $name, ?ConstraintInterface $constraint = null): array + { + $key = (string) $constraint; + if (isset($this->providerCache[$name][$key])) { + return $this->providerCache[$name][$key]; + } + + return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint); + } + + /** + * @param string $name The package name to be searched for + * @param ?ConstraintInterface $constraint A constraint that all returned + * packages must match or null to return all + * @return BasePackage[] + */ + private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null): array + { + if (!isset($this->packageByName[$name])) { + return []; + } + + $matches = []; + + foreach ($this->packageByName[$name] as $candidate) { + if ($this->match($candidate, $name, $constraint)) { + $matches[] = $candidate; + } + } + + return $matches; + } + + public function literalToPackage(int $literal): BasePackage + { + $packageId = abs($literal); + + return $this->packageById($packageId); + } + + /** + * @param array $installedMap + */ + public function literalToPrettyString(int $literal, array $installedMap): string + { + $package = $this->literalToPackage($literal); + + if (isset($installedMap[$package->id])) { + $prefix = ($literal > 0 ? 'keep' : 'remove'); + } else { + $prefix = ($literal > 0 ? 'install' : 'don\'t install'); + } + + return $prefix.' '.$package->getPrettyString(); + } + + /** + * Checks if the package matches the given constraint directly or through + * provided or replaced packages + * + * @param string $name Name of the package to be matched + */ + public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null): bool + { + $candidateName = $candidate->getName(); + $candidateVersion = $candidate->getVersion(); + + if ($candidateName === $name) { + return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion); + } + + $provides = $candidate->getProvides(); + $replaces = $candidate->getReplaces(); + + // aliases create multiple replaces/provides for one target so they can not use the shortcut below + if (isset($replaces[0]) || isset($provides[0])) { + foreach ($provides as $link) { + if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { + return true; + } + } + + foreach ($replaces as $link) { + if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { + return true; + } + } + + return false; + } + + if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) { + return true; + } + + if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) { + return true; + } + + return false; + } + + public function isUnacceptableFixedOrLockedPackage(BasePackage $package): bool + { + return \in_array($package, $this->unacceptableFixedOrLockedPackages, true); + } + + /** + * @return BasePackage[] + */ + public function getUnacceptableFixedOrLockedPackages(): array + { + return $this->unacceptableFixedOrLockedPackages; + } + + public function __toString(): string + { + $str = "Pool:\n"; + + foreach ($this->packages as $package) { + $str .= '- '.str_pad((string) $package->id, 6, ' ', STR_PAD_LEFT).': '.$package->getName()."\n"; + } + + return $str; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php b/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php new file mode 100644 index 0000000..68689ed --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php @@ -0,0 +1,801 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\EventDispatcher\EventDispatcher; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\StabilityFilter; +use Composer\Pcre\Preg; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PrePoolCreateEvent; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; + +/** + * @author Nils Adermann + */ +class PoolBuilder +{ + /** + * @var int[] + * @phpstan-var array, BasePackage::STABILITY_*> + */ + private $acceptableStabilities; + /** + * @var int[] + * @phpstan-var array + */ + private $stabilityFlags; + /** + * @var array[] + * @phpstan-var array> + */ + private $rootAliases; + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + /** + * @var array + */ + private $temporaryConstraints; + /** + * @var ?EventDispatcher + */ + private $eventDispatcher; + /** + * @var PoolOptimizer|null + */ + private $poolOptimizer; + /** + * @var IOInterface + */ + private $io; + /** + * @var array[] + * @phpstan-var array + */ + private $aliasMap = []; + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $packagesToLoad = []; + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $loadedPackages = []; + /** + * @var array[] + * @phpstan-var array>> + */ + private $loadedPerRepo = []; + /** + * @var array + */ + private $packages = []; + /** + * @var BasePackage[] + */ + private $unacceptableFixedOrLockedPackages = []; + /** @var array */ + private $updateAllowList = []; + /** @var array> */ + private $skippedLoad = []; + /** @var list */ + private $ignoredTypes = []; + /** @var list|null */ + private $allowedTypes = null; + + /** + * If provided, only these package names are loaded + * + * This is a special-use functionality of the Request class to optimize the pool creation process + * when only a minimal subset of packages is needed and we do not need their dependencies. + * + * @var array|null + */ + private $restrictedPackagesList = null; + + /** + * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories + * + * This half-unlocked state means the package itself will update but the UPDATE_LISTED_WITH_TRANSITIVE_DEPS* + * flags will not apply until the package really gets unlocked in some other way than being a path repo + * + * @var array + */ + private $pathRepoUnlocked = []; + + /** + * Keeps a list of dependencies which are root requirements, and as such + * have already their maximum required range loaded and can not be + * extended by markPackageNameForLoading + * + * Packages get cleared from this list if they get unlocked as in that case + * we need to actually load them + * + * @var array + */ + private $maxExtendedReqs = []; + /** + * @var array + * @phpstan-var array + */ + private $updateAllowWarned = []; + + /** @var int */ + private $indexCounter = 0; + + /** + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param array[] $rootAliases + * @phpstan-param array> $rootAliases + * @param string[] $rootReferences an array of package name => source reference + * @phpstan-param array $rootReferences + * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages + */ + public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = []) + { + $this->acceptableStabilities = $acceptableStabilities; + $this->stabilityFlags = $stabilityFlags; + $this->rootAliases = $rootAliases; + $this->rootReferences = $rootReferences; + $this->eventDispatcher = $eventDispatcher; + $this->poolOptimizer = $poolOptimizer; + $this->io = $io; + $this->temporaryConstraints = $temporaryConstraints; + } + + /** + * Packages of those types are ignored + * + * @param list $types + */ + public function setIgnoredTypes(array $types): void + { + $this->ignoredTypes = $types; + } + + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + */ + public function setAllowedTypes(?array $types): void + { + $this->allowedTypes = $types; + } + + /** + * @param RepositoryInterface[] $repositories + */ + public function buildPool(array $repositories, Request $request): Pool + { + $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null; + + if (\count($request->getUpdateAllowList()) > 0) { + $this->updateAllowList = $request->getUpdateAllowList(); + $this->warnAboutNonMatchingUpdateAllowList($request); + + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + + foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { + if (!$this->isUpdateAllowed($lockedPackage)) { + // remember which packages we skipped loading remote content for in this partial update + $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage; + foreach ($lockedPackage->getReplaces() as $link) { + $this->skippedLoad[$link->getTarget()][] = $lockedPackage; + } + + // Path repo packages are never loaded from lock, to force them to always remain in sync + // unless symlinking is disabled in which case we probably should rather treat them like + // regular packages. We mark them specially so they can be reloaded fully including update propagation + // if they do get unlocked, but by default they are unlocked without update propagation. + if ($lockedPackage->getDistType() === 'path') { + $transportOptions = $lockedPackage->getTransportOptions(); + if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { + $this->pathRepoUnlocked[$lockedPackage->getName()] = true; + continue; + } + } + + $request->lockPackage($lockedPackage); + } + } + } + + foreach ($request->getFixedOrLockedPackages() as $package) { + // using MatchAllConstraint here because fixed packages do not need to retrigger + // loading any packages + $this->loadedPackages[$package->getName()] = new MatchAllConstraint(); + + // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways + foreach ($package->getReplaces() as $link) { + $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint(); + } + + // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to + // specific versions while replace is a conflict with all versions of the name + + if ( + $package->getRepository() instanceof RootPackageRepository + || $package->getRepository() instanceof PlatformRepository + || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability()) + ) { + $this->loadPackage($request, $repositories, $package, false); + } else { + $this->unacceptableFixedOrLockedPackages[] = $package; + } + } + + foreach ($request->getRequires() as $packageName => $constraint) { + // fixed and locked packages have already been added, so if a root require needs one of them, no need to do anything + if (isset($this->loadedPackages[$packageName])) { + continue; + } + + $this->packagesToLoad[$packageName] = $constraint; + $this->maxExtendedReqs[$packageName] = true; + } + + // clean up packagesToLoad for anything we manually marked loaded above + foreach ($this->packagesToLoad as $name => $constraint) { + if (isset($this->loadedPackages[$name])) { + unset($this->packagesToLoad[$name]); + } + } + + while (\count($this->packagesToLoad) > 0) { + $this->loadPackagesMarkedForLoading($request, $repositories); + } + + if (\count($this->temporaryConstraints) > 0) { + foreach ($this->packages as $i => $package) { + // we check all alias related packages at once, so no need to check individual aliases + if ($package instanceof AliasPackage) { + continue; + } + + foreach ($package->getNames() as $packageName) { + if (!isset($this->temporaryConstraints[$packageName])) { + continue; + } + + $constraint = $this->temporaryConstraints[$packageName]; + $packageAndAliases = [$i => $package]; + if (isset($this->aliasMap[spl_object_hash($package)])) { + $packageAndAliases += $this->aliasMap[spl_object_hash($package)]; + } + + $found = false; + foreach ($packageAndAliases as $packageOrAlias) { + if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) { + $found = true; + } + } + + if (!$found) { + foreach ($packageAndAliases as $index => $packageOrAlias) { + unset($this->packages[$index]); + } + } + } + } + } + + if ($this->eventDispatcher !== null) { + $prePoolCreateEvent = new PrePoolCreateEvent( + PluginEvents::PRE_POOL_CREATE, + $repositories, + $request, + $this->acceptableStabilities, + $this->stabilityFlags, + $this->rootAliases, + $this->rootReferences, + $this->packages, + $this->unacceptableFixedOrLockedPackages + ); + $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent); + $this->packages = $prePoolCreateEvent->getPackages(); + $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages(); + } + + $pool = new Pool($this->packages, $this->unacceptableFixedOrLockedPackages); + + $this->aliasMap = []; + $this->packagesToLoad = []; + $this->loadedPackages = []; + $this->loadedPerRepo = []; + $this->packages = []; + $this->unacceptableFixedOrLockedPackages = []; + $this->maxExtendedReqs = []; + $this->skippedLoad = []; + $this->indexCounter = 0; + + $this->io->debug('Built pool.'); + + $pool = $this->runOptimizer($request, $pool); + + Intervals::clear(); + + return $pool; + } + + private function markPackageNameForLoading(Request $request, string $name, ConstraintInterface $constraint): void + { + // Skip platform requires at this stage + if (PlatformRepository::isPlatformPackage($name)) { + return; + } + + // Root require (which was not unlocked) already loaded the maximum range so no + // need to check anything here + if (isset($this->maxExtendedReqs[$name])) { + return; + } + + // Root requires can not be overruled by dependencies so there is no point in + // extending the loaded constraint for those. + // This is triggered when loading a root require which was locked but got unlocked, then + // we make sure that we load at most the intervals covered by the root constraint. + $rootRequires = $request->getRequires(); + if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) { + $constraint = $rootRequires[$name]; + } + + // Not yet loaded or already marked for a reload, set the constraint to be loaded + if (!isset($this->loadedPackages[$name])) { + // Maybe it was already marked before but not loaded yet. In that case + // we have to extend the constraint (we don't check if they are identical because + // MultiConstraint::create() will optimize anyway) + if (isset($this->packagesToLoad[$name])) { + // Already marked for loading and this does not expand the constraint to be loaded, nothing to do + if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) { + return; + } + + // extend the constraint to be loaded + $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], false)); + } + + $this->packagesToLoad[$name] = $constraint; + + return; + } + + // No need to load this package with this constraint because it is + // a subset of the constraint with which we have already loaded packages + if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) { + return; + } + + // We have already loaded that package but not in the constraint that's + // required. We extend the constraint and mark that package as not being loaded + // yet so we get the required package versions + $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], false)); + unset($this->loadedPackages[$name]); + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function loadPackagesMarkedForLoading(Request $request, array $repositories): void + { + foreach ($this->packagesToLoad as $name => $constraint) { + if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) { + unset($this->packagesToLoad[$name]); + continue; + } + $this->loadedPackages[$name] = $constraint; + } + + $packageBatch = $this->packagesToLoad; + $this->packagesToLoad = []; + + foreach ($repositories as $repoIndex => $repository) { + if (0 === \count($packageBatch)) { + break; + } + + // these repos have their packages fixed or locked if they need to be loaded so we + // never need to load anything else from them + if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { + continue; + } + $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []); + + foreach ($result['namesFound'] as $name) { + // avoid loading the same package again from other repositories once it has been found + unset($packageBatch[$name]); + } + foreach ($result['packages'] as $package) { + $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; + + if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) { + continue; + } + $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()])); + } + } + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void + { + $index = $this->indexCounter++; + $this->packages[$index] = $package; + + if ($package instanceof AliasPackage) { + $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package; + } + + $name = $package->getName(); + + // we're simply setting the root references on all versions for a name here and rely on the solver to pick the + // right version. It'd be more work to figure out which versions and which aliases of those versions this may + // apply to + if (isset($this->rootReferences[$name])) { + // do not modify the references on already locked or fixed packages + if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) { + $package->setSourceDistReferences($this->rootReferences[$name]); + } + } + + // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are + // manually loaded as separate packages in this case + // + // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to + // false because their deps should not be unlocked, but that is irrelevant for root aliases + if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) { + $alias = $this->rootAliases[$name][$package->getVersion()]; + if ($package instanceof AliasPackage) { + $basePackage = $package->getAliasOf(); + } else { + $basePackage = $package; + } + if ($basePackage instanceof CompletePackage) { + $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); + } else { + $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); + } + $aliasPackage->setRootPackageAlias(true); + + $newIndex = $this->indexCounter++; + $this->packages[$newIndex] = $aliasPackage; + $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage; + } + + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + $linkConstraint = $link->getConstraint(); + + // if the required package is loaded as a locked package only and hasn't had its deps analyzed + if (isset($this->skippedLoad[$require])) { + // if we're doing a full update or this is a partial update with transitive deps and we're currently + // looking at a package which needs to be updated we need to unlock the package we now know is a + // dependency of another package which we are trying to update, and then attempt to load it again + if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { + $skippedRootRequires = $this->getSkippedRootRequires($request, $require); + + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { + $this->unlockPackage($request, $repositories, $require); + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } else { + foreach ($skippedRootRequires as $rootRequire) { + if (!isset($this->updateAllowWarned[$rootRequire])) { + $this->updateAllowWarned[$rootRequire] = true; + $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); + } + } + } + } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) { + // if doing a partial update and a package depends on a path-repo-unlocked package which is not referenced by the root, we need to ensure it gets loaded as it was not loaded by the request's root requirements + // and would not be loaded above if update propagation is not allowed (which happens if the requirer is itself a path-repo-unlocked package) or if transitive deps are not allowed to be unlocked + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } + } else { + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } + } + + // if we're doing a partial update with deps we also need to unlock packages which are being replaced in case + // they are currently locked and thus prevent this updateable package from being installable/updateable + if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { + foreach ($package->getReplaces() as $link) { + $replace = $link->getTarget(); + if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) { + $skippedRootRequires = $this->getSkippedRootRequires($request, $replace); + + if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { + $this->unlockPackage($request, $repositories, $replace); + // the replaced package only needs to be loaded if something else requires it + $this->markPackageNameForLoadingIfRequired($request, $replace); + } else { + foreach ($skippedRootRequires as $rootRequire) { + if (!isset($this->updateAllowWarned[$rootRequire])) { + $this->updateAllowWarned[$rootRequire] = true; + $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); + } + } + } + } + } + } + } + + /** + * Checks if a particular name is required directly in the request + * + * @param string $name packageName + */ + private function isRootRequire(Request $request, string $name): bool + { + $rootRequires = $request->getRequires(); + + return isset($rootRequires[$name]); + } + + /** + * @return string[] + */ + private function getSkippedRootRequires(Request $request, string $name): array + { + if (!isset($this->skippedLoad[$name])) { + return []; + } + + $rootRequires = $request->getRequires(); + $matches = []; + + if (isset($rootRequires[$name])) { + return array_map(static function (PackageInterface $package) use ($name): string { + if ($name !== $package->getName()) { + return $package->getName() .' (via replace of '.$name.')'; + } + + return $package->getName(); + }, $this->skippedLoad[$name]); + } + + foreach ($this->skippedLoad[$name] as $packageOrReplacer) { + if (isset($rootRequires[$packageOrReplacer->getName()])) { + $matches[] = $packageOrReplacer->getName(); + } + foreach ($packageOrReplacer->getReplaces() as $link) { + if (isset($rootRequires[$link->getTarget()])) { + if ($name !== $packageOrReplacer->getName()) { + $matches[] = $packageOrReplacer->getName() .' (via replace of '.$name.')'; + } else { + $matches[] = $packageOrReplacer->getName(); + } + break; + } + } + } + + return $matches; + } + + /** + * Checks whether the update allow list allows this package in the lock file to be updated + */ + private function isUpdateAllowed(BasePackage $package): bool + { + foreach ($this->updateAllowList as $pattern) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + if (Preg::isMatch($patternRegexp, $package->getName())) { + return true; + } + } + + return false; + } + + private function warnAboutNonMatchingUpdateAllowList(Request $request): void + { + if (null === $request->getLockedRepository()) { + throw new \LogicException('No lock repo present and yet a partial update was requested.'); + } + + foreach ($this->updateAllowList as $pattern) { + $matchedPlatformPackage = false; + + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + // update pattern matches a locked package? => all good + foreach ($request->getLockedRepository()->getPackages() as $package) { + if (Preg::isMatch($patternRegexp, $package->getName())) { + continue 2; + } + } + // update pattern matches a root require? => all good, probably a new package + foreach ($request->getRequires() as $packageName => $constraint) { + if (Preg::isMatch($patternRegexp, $packageName)) { + if (PlatformRepository::isPlatformPackage($packageName)) { + $matchedPlatformPackage = true; + continue; + } + continue 2; + } + } + if ($matchedPlatformPackage) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.'); + } elseif (strpos($pattern, '*') !== false) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); + } else { + $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); + } + } + } + + /** + * Reverts the decision to use a locked package if a partial update with transitive dependencies + * found that this package actually needs to be updated + * + * @param RepositoryInterface[] $repositories + */ + private function unlockPackage(Request $request, array $repositories, string $name): void + { + foreach ($this->skippedLoad[$name] as $packageOrReplacer) { + // if we unfixed a replaced package name, we also need to unfix the replacer itself + // as long as it was not unfixed yet + if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) { + $replacerName = $packageOrReplacer->getName(); + if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName))) { + $this->unlockPackage($request, $repositories, $replacerName); + + if ($this->isRootRequire($request, $replacerName)) { + $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint); + } else { + foreach ($this->packages as $loadedPackage) { + $requires = $loadedPackage->getRequires(); + if (isset($requires[$replacerName])) { + $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint()); + } + } + } + } + } + } + + if (isset($this->pathRepoUnlocked[$name])) { + foreach ($this->packages as $index => $package) { + if ($package->getName() === $name) { + $this->removeLoadedPackage($request, $repositories, $package, $index); + } + } + } + + unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]); + + // remove locked package by this name which was already initialized + foreach ($request->getLockedPackages() as $lockedPackage) { + if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) { + if (false !== $index = array_search($lockedPackage, $this->packages, true)) { + $request->unlockPackage($lockedPackage); + $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index); + + // make sure that any requirements for this package by other locked or fixed packages are now + // also loaded, as they were previously ignored because the locked (now unlocked) package already + // satisfied their requirements + // and if this package is replacing another that is required by a locked or fixed package, ensure + // that we load that replaced package in case an update to this package removes the replacement + foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) { + if ($fixedOrLockedPackage === $lockedPackage) { + continue; + } + + if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) { + $requires = $fixedOrLockedPackage->getRequires(); + if (isset($requires[$lockedPackage->getName()])) { + $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); + } + + foreach ($lockedPackage->getReplaces() as $replace) { + if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { + $this->unlockPackage($request, $repositories, $replace->getTarget()); + // this package is in $requires so no need to call markPackageNameForLoadingIfRequired + $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint()); + } + } + } + } + } + } + } + } + + private function markPackageNameForLoadingIfRequired(Request $request, string $name): void + { + if ($this->isRootRequire($request, $name)) { + $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]); + } + + foreach ($this->packages as $package) { + foreach ($package->getRequires() as $link) { + if ($name === $link->getTarget()) { + $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint()); + } + } + } + } + + /** + * @param RepositoryInterface[] $repositories + */ + private function removeLoadedPackage(Request $request, array $repositories, BasePackage $package, int $index): void + { + $repoIndex = array_search($package->getRepository(), $repositories, true); + + unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]); + unset($this->packages[$index]); + if (isset($this->aliasMap[spl_object_hash($package)])) { + foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) { + unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]); + unset($this->packages[$aliasIndex]); + } + unset($this->aliasMap[spl_object_hash($package)]); + } + } + + private function runOptimizer(Request $request, Pool $pool): Pool + { + if (null === $this->poolOptimizer) { + return $pool; + } + + $this->io->debug('Running pool optimizer.'); + + $before = microtime(true); + $total = \count($pool->getPackages()); + + $pool = $this->poolOptimizer->optimize($request, $pool); + + $filtered = $total - \count($pool->getPackages()); + + if (0 === $filtered) { + return $pool; + } + + $this->io->write(sprintf('Pool optimizer completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); + $this->io->write(sprintf( + 'Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.', + number_format($total), + number_format($filtered), + round(100 / $total * $filtered) + ), true, IOInterface::VERY_VERBOSE); + + return $pool; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php b/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php new file mode 100644 index 0000000..3de9e03 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php @@ -0,0 +1,475 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; +use Composer\Semver\CompilingMatcher; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; + +/** + * Optimizes a given pool + * + * @author Yanick Witschi + */ +class PoolOptimizer +{ + /** + * @var PolicyInterface + */ + private $policy; + + /** + * @var array + */ + private $irremovablePackages = []; + + /** + * @var array> + */ + private $requireConstraintsPerPackage = []; + + /** + * @var array> + */ + private $conflictConstraintsPerPackage = []; + + /** + * @var array + */ + private $packagesToRemove = []; + + /** + * @var array + */ + private $aliasesPerPackage = []; + + /** + * @var array> + */ + private $removedVersionsByPackage = []; + + public function __construct(PolicyInterface $policy) + { + $this->policy = $policy; + } + + public function optimize(Request $request, Pool $pool): Pool + { + $this->prepare($request, $pool); + + $this->optimizeByIdenticalDependencies($request, $pool); + + $this->optimizeImpossiblePackagesAway($request, $pool); + + $optimizedPool = $this->applyRemovalsToPool($pool); + + // No need to run this recursively at the moment + // because the current optimizations cannot provide + // even more gains when ran again. Might change + // in the future with additional optimizations. + + $this->irremovablePackages = []; + $this->requireConstraintsPerPackage = []; + $this->conflictConstraintsPerPackage = []; + $this->packagesToRemove = []; + $this->aliasesPerPackage = []; + $this->removedVersionsByPackage = []; + + return $optimizedPool; + } + + private function prepare(Request $request, Pool $pool): void + { + $irremovablePackageConstraintGroups = []; + + // Mark fixed or locked packages as irremovable + foreach ($request->getFixedOrLockedPackages() as $package) { + $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion()); + } + + // Extract requested package requirements + foreach ($request->getRequires() as $require => $constraint) { + $this->extractRequireConstraintsPerPackage($require, $constraint); + } + + // First pass over all packages to extract information and mark package constraints irremovable + foreach ($pool->getPackages() as $package) { + // Extract package requirements + foreach ($package->getRequires() as $link) { + $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint()); + } + // Extract package conflicts + foreach ($package->getConflicts() as $link) { + $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint()); + } + + // Keep track of alias packages for every package so if either the alias or aliased is kept + // we keep the others as they are a unit of packages really + if ($package instanceof AliasPackage) { + $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package; + } + } + + $irremovablePackageConstraints = []; + foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) { + $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false); + } + unset($irremovablePackageConstraintGroups); + + // Mark the packages as irremovable based on the constraints + foreach ($pool->getPackages() as $package) { + if (!isset($irremovablePackageConstraints[$package->getName()])) { + continue; + } + + if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) { + $this->markPackageIrremovable($package); + } + } + } + + private function markPackageIrremovable(BasePackage $package): void + { + $this->irremovablePackages[$package->id] = true; + if ($package instanceof AliasPackage) { + // recursing here so aliasesPerPackage for the aliasOf can be checked + // and all its aliases marked as irremovable as well + $this->markPackageIrremovable($package->getAliasOf()); + } + if (isset($this->aliasesPerPackage[$package->id])) { + foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { + $this->irremovablePackages[$aliasPackage->id] = true; + } + } + } + + /** + * @return Pool Optimized pool + */ + private function applyRemovalsToPool(Pool $pool): Pool + { + $packages = []; + $removedVersions = []; + foreach ($pool->getPackages() as $package) { + if (!isset($this->packagesToRemove[$package->id])) { + $packages[] = $package; + } else { + $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion(); + } + } + + $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage); + + return $optimizedPool; + } + + private function optimizeByIdenticalDependencies(Request $request, Pool $pool): void + { + $identicalDefinitionsPerPackage = []; + $packageIdenticalDefinitionLookup = []; + + foreach ($pool->getPackages() as $package) { + + // If that package was already marked irremovable, we can skip + // the entire process for it + if (isset($this->irremovablePackages[$package->id])) { + continue; + } + + $this->markPackageForRemoval($package->id); + + $dependencyHash = $this->calculateDependencyHash($package); + + foreach ($package->getNames(false) as $packageName) { + if (!isset($this->requireConstraintsPerPackage[$packageName])) { + continue; + } + + foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { + $groupHashParts = []; + + if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { + $groupHashParts[] = 'require:' . (string) $requireConstraint; + } + + if (\count($package->getReplaces()) > 0) { + foreach ($package->getReplaces() as $link) { + if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) { + // Use the same hash part as the regular require hash because that's what the replacement does + $groupHashParts[] = 'require:' . (string) $link->getConstraint(); + } + } + } + + if (isset($this->conflictConstraintsPerPackage[$packageName])) { + foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) { + if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) { + $groupHashParts[] = 'conflict:' . (string) $conflictConstraint; + } + } + } + + if (0 === \count($groupHashParts)) { + continue; + } + + $groupHash = implode('', $groupHashParts); + $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package; + $packageIdenticalDefinitionLookup[$package->id][$packageName] = ['groupHash' => $groupHash, 'dependencyHash' => $dependencyHash]; + } + } + } + + foreach ($identicalDefinitionsPerPackage as $constraintGroups) { + foreach ($constraintGroups as $constraintGroup) { + foreach ($constraintGroup as $packages) { + // Only one package in this constraint group has the same requirements, we're not allowed to remove that package + if (1 === \count($packages)) { + $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + continue; + } + + // Otherwise we find out which one is the preferred package in this constraint group which is + // then not allowed to be removed either + $literals = []; + + foreach ($packages as $package) { + $literals[] = $package->id; + } + + foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) { + $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + } + } + } + } + } + + private function calculateDependencyHash(BasePackage $package): string + { + $hash = ''; + + $hashRelevantLinks = [ + 'requires' => $package->getRequires(), + 'conflicts' => $package->getConflicts(), + 'replaces' => $package->getReplaces(), + 'provides' => $package->getProvides(), + ]; + + foreach ($hashRelevantLinks as $key => $links) { + if (0 === \count($links)) { + continue; + } + + // start new hash section + $hash .= $key . ':'; + + $subhash = []; + + foreach ($links as $link) { + // To get the best dependency hash matches we should use Intervals::compactConstraint() here. + // However, the majority of projects are going to specify their constraints already pretty + // much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt + // performance more than the additional few packages that could be filtered out would benefit the process. + $subhash[$link->getTarget()] = (string) $link->getConstraint(); + } + + // Sort for best result + ksort($subhash); + + foreach ($subhash as $target => $constraint) { + $hash .= $target . '@' . $constraint; + } + } + + return $hash; + } + + private function markPackageForRemoval(int $id): void + { + // We are not allowed to remove packages if they have been marked as irremovable + if (isset($this->irremovablePackages[$id])) { + throw new \LogicException('Attempted removing a package which was previously marked irremovable'); + } + + $this->packagesToRemove[$id] = true; + } + + /** + * @param array>>> $identicalDefinitionsPerPackage + * @param array> $packageIdenticalDefinitionLookup + */ + private function keepPackage(BasePackage $package, array $identicalDefinitionsPerPackage, array $packageIdenticalDefinitionLookup): void + { + // Already marked to keep + if (!isset($this->packagesToRemove[$package->id])) { + return; + } + + unset($this->packagesToRemove[$package->id]); + + if ($package instanceof AliasPackage) { + // recursing here so aliasesPerPackage for the aliasOf can be checked + // and all its aliases marked to be kept as well + $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + } + + // record all the versions of the package group so we can list them later in Problem output + foreach ($package->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + + if (isset($this->aliasesPerPackage[$package->id])) { + foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { + unset($this->packagesToRemove[$aliasPackage->id]); + + // record all the versions of the package group so we can list them later in Problem output + foreach ($aliasPackage->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + } + } + } + + /** + * Use the list of locked packages to constrain the loaded packages + * This will reduce packages with significant numbers of historical versions to a smaller number + * and reduce the resulting rule set that is generated + */ + private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void + { + if (\count($request->getLockedPackages()) === 0) { + return; + } + + $packageIndex = []; + + foreach ($pool->getPackages() as $package) { + $id = $package->id; + + // Do not remove irremovable packages + if (isset($this->irremovablePackages[$id])) { + continue; + } + // Do not remove a package aliased by another package, nor aliases + if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) { + continue; + } + // Do not remove locked packages + if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) { + continue; + } + + $packageIndex[$package->getName()][$package->id] = $package; + } + + foreach ($request->getLockedPackages() as $package) { + // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements + // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still + $isUnusedPackage = true; + foreach ($package->getNames(false) as $packageName) { + if (isset($this->requireConstraintsPerPackage[$packageName])) { + $isUnusedPackage = false; + break; + } + } + + if ($isUnusedPackage) { + continue; + } + + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + if (!isset($packageIndex[$require])) { + continue; + } + + $linkConstraint = $link->getConstraint(); + foreach ($packageIndex[$require] as $id => $requiredPkg) { + if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) { + $this->markPackageForRemoval($id); + unset($packageIndex[$require][$id]); + } + } + } + } + } + + /** + * Disjunctive require constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate + * two require constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd + * only keep either one which can cause trouble (e.g. when using --prefer-lowest). + * + * @return void + */ + private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint) + { + foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { + $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded; + } + } + + /** + * Disjunctive conflict constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate + * two conflict constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd + * only keep either one which can cause trouble (e.g. when using --prefer-lowest). + * + * @return void + */ + private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint) + { + foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { + $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded; + } + } + + /** + * @return ConstraintInterface[] + */ + private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint) + { + $constraint = Intervals::compactConstraint($constraint); + + if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) { + // No need to call ourselves recursively here because Intervals::compactConstraint() ensures that there + // are no nested disjunctive MultiConstraint instances possible + return $constraint->getConstraints(); + } + + // Regular constraints and conjunctive MultiConstraints + return [$constraint]; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php b/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php new file mode 100644 index 0000000..b107dcb --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Problem.php @@ -0,0 +1,671 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\CompletePackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Repository\LockArrayRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Package\Version\VersionParser; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * Represents a problem detected while solving dependencies + * + * @author Nils Adermann + */ +class Problem +{ + /** + * A map containing the id of each rule part of this problem as a key + * @var array + */ + protected $reasonSeen; + + /** + * A set of reasons for the problem, each is a rule or a root require and a rule + * @var array> + */ + protected $reasons = []; + + /** @var int */ + protected $section = 0; + + /** + * Add a rule as a reason + * + * @param Rule $rule A rule which is a reason for this problem + */ + public function addRule(Rule $rule): void + { + $this->addReason(spl_object_hash($rule), $rule); + } + + /** + * Retrieve all reasons for this problem + * + * @return array> The problem's reasons + */ + public function getReasons(): array + { + return $this->reasons; + } + + /** + * A human readable textual representation of the problem's reasons + * + * @param array $installedMap A map of all present packages + * @param array $learnedPool + */ + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? + $reasons = array_merge(...array_reverse($this->reasons)); + + if (\count($reasons) === 1) { + reset($reasons); + $rule = current($reasons); + + if ($rule->getReason() !== Rule::RULE_ROOT_REQUIRE) { + throw new \LogicException("Single reason problems must contain a root require rule."); + } + + $reasonData = $rule->getReasonData(); + $packageName = $reasonData['packageName']; + $constraint = $reasonData['constraint']; + + $packages = $pool->whatProvides($packageName, $constraint); + if (\count($packages) === 0) { + return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint)); + } + } + + usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) { + $rule1Prio = $this->getRulePriority($rule1); + $rule2Prio = $this->getRulePriority($rule2); + if ($rule1Prio !== $rule2Prio) { + return $rule2Prio - $rule1Prio; + } + + return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2); + }); + + return self::formatDeduplicatedRules($reasons, ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + } + + private function getSortableString(Pool $pool, Rule $rule): string + { + switch ($rule->getReason()) { + case Rule::RULE_ROOT_REQUIRE: + return $rule->getReasonData()['packageName']; + case Rule::RULE_FIXED: + return (string) $rule->getReasonData()['package']; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool)); + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return (string) $rule->getReasonData(); + case Rule::RULE_LEARNED: + return implode('-', $rule->getLiterals()); + } + + // @phpstan-ignore deadCode.unreachable + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + + private function getRulePriority(Rule $rule): int + { + switch ($rule->getReason()) { + case Rule::RULE_FIXED: + return 3; + case Rule::RULE_ROOT_REQUIRE: + return 2; + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_REQUIRES: + return 1; + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_LEARNED: + case Rule::RULE_PACKAGE_ALIAS: + case Rule::RULE_PACKAGE_INVERSE_ALIAS: + return 0; + } + + // @phpstan-ignore deadCode.unreachable + throw new \LogicException('Unknown rule type: '.$rule->getReason()); + } + + /** + * @param Rule[] $rules + * @param array $installedMap A map of all present packages + * @param array $learnedPool + * @internal + */ + public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + $messages = []; + $templates = []; + $parser = new VersionParser; + $deduplicatableRuleTypes = [Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT]; + foreach ($rules as $rule) { + $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && Preg::isMatchStrictGroups('{^(?P\S+) (?P\S+) (?Prequires|conflicts)}', $message, $m)) { + $message = str_replace('%', '%%', $message); + $template = Preg::replace('{^\S+ \S+ }', '%s%s ', $message); + $messages[] = $template; + $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; + $sourcePackage = $rule->getSourcePackage($pool); + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) { + $templates[$template][$m[1]][$version] = $prettyVersion; + } + } elseif ($message !== '') { + $messages[] = $message; + } + } + + $result = []; + foreach (array_unique($messages) as $message) { + if (isset($templates[$message])) { + foreach ($templates[$message] as $package => $versions) { + uksort($versions, 'version_compare'); + if (!$isVerbose) { + $versions = self::condenseVersionList($versions, 1); + } + if (\count($versions) > 1) { + // remove the s from requires/conflicts to correct grammar + $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message); + $result[] = sprintf($message, $package, '['.implode(', ', $versions).']'); + } else { + $result[] = sprintf($message, $package, ' '.reset($versions)); + } + } + } else { + $result[] = $message; + } + } + + return "\n$indent- ".implode("\n$indent- ", $result); + } + + public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool + { + foreach ($this->reasons as $sectionRules) { + foreach ($sectionRules as $rule) { + if ($rule->isCausedByLock($repositorySet, $request, $pool)) { + return true; + } + } + } + + return false; + } + + /** + * Store a reason descriptor but ignore duplicates + * + * @param string $id A canonical identifier for the reason + * @param Rule $reason The reason descriptor + */ + protected function addReason(string $id, Rule $reason): void + { + // TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message + // that is important to understand the issue? + + if (!isset($this->reasonSeen[$id])) { + $this->reasonSeen[$id] = true; + $this->reasons[$this->section][] = $reason; + } + } + + public function nextSection(): void + { + $this->section++; + } + + /** + * @internal + * @return array{0: string, 1: string} + */ + public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null): array + { + if (PlatformRepository::isPlatformPackage($packageName)) { + // handle php/php-*/hhvm + if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') { + $version = self::getPlatformPackageVersion($pool, $packageName, phpversion()); + + $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but '; + + if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) { + return [$msg, 'your HHVM version does not satisfy that requirement.']; + } + + if ($packageName === 'hhvm') { + return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.']; + } + + if (null === $version) { + return [$msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".']; + } + + return [$msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.']; + } + + // handle php extensions + if (0 === stripos($packageName, 'ext-')) { + if (false !== strpos($packageName, ' ')) { + return ['- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.']; + } + + $ext = substr($packageName, 4); + $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but '; + + $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext)); + if (null === $version) { + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the extension (or parts of it):\n". + " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; + } + + if (extension_loaded($ext)) { + return [ + $msg, + 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr, + ]; + } + + return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr]; + } + + return [$msg, 'it has the wrong version installed ('.$version.').']; + } + + // handle linked libs + if (0 === stripos($packageName, 'lib-')) { + if (strtolower($packageName) === 'lib-icu') { + $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.'; + + return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error]; + } + + $providersStr = self::getProvidersList($repositorySet, $packageName, 5); + if ($providersStr !== null) { + $providersStr = "\n\n Alternatively you can require one of these packages that provide the library (or parts of it):\n". + " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; + } + + return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr]; + } + } + + $lockedPackage = null; + foreach ($request->getLockedPackages() as $package) { + if ($package->getName() === $packageName) { + $lockedPackage = $package; + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return ["- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.']; + } + break; + } + } + + if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) { + $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString()); + $packages = $repositorySet->findPackages($packageName, new MultiConstraint([ + new Constraint(Constraint::STR_OP_EQ, $newConstraint), + new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint)) + ], false)); + if (\count($packages) > 0) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".']; + } + } + + // first check if the actual requested package is found in normal conditions + // if so it must mean it is rejected by another constraint than the one given here + $packages = $repositorySet->findPackages($packageName, $constraint); + if (\count($packages) > 0) { + $rootReqs = $repositorySet->getRootRequires(); + if (isset($rootReqs[$packageName])) { + $filtered = array_filter($packages, static function ($p) use ($rootReqs, $packageName): bool { + return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').']; + } + } + + $tempReqs = $repositorySet->getTemporaryConstraints(); + foreach (reset($packages)->getNames() as $name) { + if (isset($tempReqs[$name])) { + $filtered = array_filter($packages, static function ($p) use ($tempReqs, $name): bool { + return $tempReqs[$name]->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $name".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$name.':'.$tempReqs[$name]->getPrettyString().').']; + } + } + } + + if ($lockedPackage !== null) { + $fixedConstraint = new Constraint('==', $lockedPackage->getVersion()); + $filtered = array_filter($packages, static function ($p) use ($fixedConstraint): bool { + return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); + }); + if (0 === count($filtered)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.']; + } + } + + $nonLockedPackages = array_filter($packages, static function ($p): bool { + return !$p->getRepository() instanceof LockArrayRepository; + }); + + if (0 === \count($nonLockedPackages)) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.']; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.']; + } + + // check if the package is found when bypassing stability checks + $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { + // we must first verify if a valid package would be found in a lower priority repository + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.']; + } + + // check if the package is found when bypassing the constraint and stability checks + $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); + if (\count($packages) > 0) { + // we must first verify if a valid package would be found in a lower priority repository + $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); + if (\count($allReposPackages) > 0) { + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); + } + + $suffix = ''; + if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') { + foreach ($packages as $candidate) { + if (in_array($candidate->getVersion(), ['dev-default', 'dev-main'], true)) { + $suffix = ' Perhaps dev-master was renamed to '.$candidate->getPrettyVersion().'?'; + break; + } + } + } + + // check if the root package is a name match and hint the dependencies on root troubleshooting article + $allReposPackages = $packages; + $topPackage = reset($allReposPackages); + if ($topPackage instanceof RootPackageInterface) { + $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix]; + } + + if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) { + $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName); + + return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.']; + } + + $providersStr = self::getProvidersList($repositorySet, $packageName, 15); + if ($providersStr !== null) { + return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement."]; + } + + return ["- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."]; + } + + /** + * @internal + * @param PackageInterface[] $packages + */ + public static function getPackageList(array $packages, bool $isVerbose, ?Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string + { + $prepared = []; + $hasDefaultBranch = []; + foreach ($packages as $package) { + $prepared[$package->getName()]['name'] = $package->getPrettyName(); + $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); + if ($pool !== null && $constraint !== null) { + foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } + if ($pool !== null && $useRemovedVersionGroup) { + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } + if ($package->isDefaultBranch()) { + $hasDefaultBranch[$package->getName()] = true; + } + } + + $preparedStrings = []; + foreach ($prepared as $name => $package) { + // remove the implicit default branch alias to avoid cruft in the display + if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) { + unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]); + } + + uksort($package['versions'], 'version_compare'); + + if (!$isVerbose) { + $package['versions'] = self::condenseVersionList($package['versions'], 4); + } + $preparedStrings[] = $package['name'].'['.implode(', ', $package['versions']).']'; + } + + return implode(', ', $preparedStrings); + } + + /** + * @param string $version the effective runtime version of the platform package + * @return ?string a version string or null if it appears the package was artificially disabled + */ + private static function getPlatformPackageVersion(Pool $pool, string $packageName, string $version): ?string + { + $available = $pool->whatProvides($packageName); + + if (\count($available) > 0) { + $selected = null; + foreach ($available as $pkg) { + if ($pkg->getRepository() instanceof PlatformRepository) { + $selected = $pkg; + break; + } + } + if ($selected === null) { + $selected = reset($available); + } + + // must be a package providing/replacing and not a real platform package + if ($selected->getName() !== $packageName) { + /** @var Link $link */ + foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) { + if ($link->getTarget() === $packageName) { + return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString(); + } + } + } + + $version = $selected->getPrettyVersion(); + $extra = $selected->getExtra(); + if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) { + $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription()); + } + } else { + return null; + } + + return $version; + } + + /** + * @param array $versions an array of pretty versions, with normalized versions as keys + * @return list a list of pretty versions and '...' where versions were removed + */ + private static function condenseVersionList(array $versions, int $max, int $maxDev = 16): array + { + if (count($versions) <= $max) { + return array_values($versions); + } + + $filtered = []; + $byMajor = []; + foreach ($versions as $version => $pretty) { + if (0 === stripos((string) $version, 'dev-')) { + $byMajor['dev'][] = $pretty; + } else { + $byMajor[Preg::replace('{^(\d+)\..*}', '$1', (string) $version)][] = $pretty; + } + } + foreach ($byMajor as $majorVersion => $versionsForMajor) { + $maxVersions = $majorVersion === 'dev' ? $maxDev : $max; + if (count($versionsForMajor) > $maxVersions) { + // output only 1st and last versions + $filtered[] = $versionsForMajor[0]; + $filtered[] = '...'; + $filtered[] = $versionsForMajor[count($versionsForMajor) - 1]; + } else { + $filtered = array_merge($filtered, $versionsForMajor); + } + } + + return $filtered; + } + + /** + * @param PackageInterface[] $packages + */ + private static function hasMultipleNames(array $packages): bool + { + $name = null; + foreach ($packages as $package) { + if ($name === null || $name === $package->getName()) { + $name = $package->getName(); + } else { + return true; + } + } + + return false; + } + + /** + * @param non-empty-array $higherRepoPackages + * @param non-empty-array $allReposPackages + * @return array{0: string, 1: string} + */ + private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array + { + $nextRepoPackages = []; + $nextRepo = null; + + foreach ($allReposPackages as $package) { + if ($nextRepo === null || $nextRepo === $package->getRepository()) { + $nextRepoPackages[] = $package; + $nextRepo = $package->getRepository(); + } else { + break; + } + } + + assert(null !== $nextRepo); + + if (\count($higherRepoPackages) > 0) { + $topPackage = reset($higherRepoPackages); + if ($topPackage instanceof RootPackageInterface) { + return [ + "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', + 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', + ]; + } + } + + if ($nextRepo instanceof LockArrayRepository) { + $singular = count($higherRepoPackages) === 1; + + $suggestion = 'Make sure you either fix the '.$reason.' or avoid updating this package to keep the one present in the lock file ('.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).').'; + // symlinked path repos cannot be locked so do not suggest keeping it locked + if ($nextRepoPackages[0]->getDistType() === 'path') { + $transportOptions = $nextRepoPackages[0]->getTransportOptions(); + if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { + $suggestion = 'Make sure you fix the '.$reason.' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.'; + } + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', + 'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. '.$suggestion, + ]; + } + + return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages from the higher priority repository do not match your '.$reason.' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.']; + } + + /** + * Turns a constraint into text usable in a sentence describing a request + */ + protected static function constraintToText(?ConstraintInterface $constraint = null): string + { + if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) { + if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) { + return ' '.$constraint->getPrettyString() .' (exact version match)'; + } + + $versions = [$constraint->getPrettyString()]; + for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) { + $versions[] = end($versions) . '.0'; + } + + return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')'; + } + + return $constraint !== null ? ' '.$constraint->getPrettyString() : ''; + } + + private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string + { + $providers = $repositorySet->getProviders($packageName); + if (\count($providers) > 0) { + $providersStr = implode(array_map(static function ($p): string { + $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : ''; + + return ' - '.$p['name'].$description."\n"; + }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); + if (count($providers) > $maxProviders + 1) { + $providersStr .= ' ... and '.(count($providers) - $maxProviders).' more.'."\n"; + } + + return $providersStr; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Request.php b/vendor/composer/composer/src/Composer/DependencyResolver/Request.php new file mode 100644 index 0000000..b11f4e1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Request.php @@ -0,0 +1,256 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Repository\LockArrayRepository; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; + +/** + * @author Nils Adermann + */ +class Request +{ + /** + * Identifies a partial update for listed packages only, all dependencies will remain at locked versions + */ + public const UPDATE_ONLY_LISTED = 0; + + /** + * Identifies a partial update for listed packages and recursively all their dependencies, however dependencies + * also directly required by the root composer.json and their dependencies will remain at the locked version. + */ + public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1; + + /** + * Identifies a partial update for listed packages and recursively all their dependencies, even dependencies + * also directly required by the root composer.json will be updated. + */ + public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2; + + /** @var ?LockArrayRepository */ + protected $lockedRepository; + /** @var array */ + protected $requires = []; + /** @var array */ + protected $fixedPackages = []; + /** @var array */ + protected $lockedPackages = []; + /** @var array */ + protected $fixedLockedPackages = []; + /** @var array */ + protected $updateAllowList = []; + /** @var false|self::UPDATE_* */ + protected $updateAllowTransitiveDependencies = false; + /** @var non-empty-list|null */ + private $restrictedPackages = null; + + public function __construct(?LockArrayRepository $lockedRepository = null) + { + $this->lockedRepository = $lockedRepository; + } + + public function requireName(string $packageName, ?ConstraintInterface $constraint = null): void + { + $packageName = strtolower($packageName); + + if ($constraint === null) { + $constraint = new MatchAllConstraint(); + } + if (isset($this->requires[$packageName])) { + throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyString().' => '.$constraint->getPrettyString().', check why it is happening, might be a root alias'); + } + $this->requires[$packageName] = $constraint; + } + + /** + * Mark a package as currently present and having to remain installed + * + * This is used for platform packages which cannot be modified by Composer. A rule enforcing their installation is + * generated for dependency resolution. Partial updates with dependencies cannot in any way modify these packages. + */ + public function fixPackage(BasePackage $package): void + { + $this->fixedPackages[spl_object_hash($package)] = $package; + } + + /** + * Mark a package as locked to a specific version but removable + * + * This is used for lock file packages which need to be treated similar to fixed packages by the pool builder in + * that by default they should really only have the currently present version loaded and no remote alternatives. + * + * However unlike fixed packages there will not be a special rule enforcing their installation for the solver, so + * if nothing requires these packages they will be removed. Additionally in a partial update these packages can be + * unlocked, meaning other versions can be installed if explicitly requested as part of the update. + */ + public function lockPackage(BasePackage $package): void + { + $this->lockedPackages[spl_object_hash($package)] = $package; + } + + /** + * Marks a locked package fixed. So it's treated irremovable like a platform package. + * + * This is necessary for the composer install step which verifies the lock file integrity and should not allow + * removal of any packages. At the same time lock packages there cannot simply be marked fixed, as error reporting + * would then report them as platform packages, so this still marks them as locked packages at the same time. + */ + public function fixLockedPackage(BasePackage $package): void + { + $this->fixedPackages[spl_object_hash($package)] = $package; + $this->fixedLockedPackages[spl_object_hash($package)] = $package; + } + + public function unlockPackage(BasePackage $package): void + { + unset($this->lockedPackages[spl_object_hash($package)]); + } + + /** + * @param array $updateAllowList + * @param false|self::UPDATE_* $updateAllowTransitiveDependencies + */ + public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void + { + $this->updateAllowList = $updateAllowList; + $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; + } + + /** + * @return array + */ + public function getUpdateAllowList(): array + { + return $this->updateAllowList; + } + + public function getUpdateAllowTransitiveDependencies(): bool + { + return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED; + } + + public function getUpdateAllowTransitiveRootDependencies(): bool + { + return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + } + + /** + * @return array + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * @return array + */ + public function getFixedPackages(): array + { + return $this->fixedPackages; + } + + public function isFixedPackage(BasePackage $package): bool + { + return isset($this->fixedPackages[spl_object_hash($package)]); + } + + /** + * @return array + */ + public function getLockedPackages(): array + { + return $this->lockedPackages; + } + + public function isLockedPackage(PackageInterface $package): bool + { + return isset($this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]); + } + + /** + * @return array + */ + public function getFixedOrLockedPackages(): array + { + return array_merge($this->fixedPackages, $this->lockedPackages); + } + + /** + * @return ($packageIds is true ? array : array) + * + * @TODO look into removing the packageIds option, the only place true is used + * is for the installed map in the solver problems. + * Some locked packages may not be in the pool, + * so they have a package->id of -1 + */ + public function getPresentMap(bool $packageIds = false): array + { + $presentMap = []; + + if ($this->lockedRepository !== null) { + foreach ($this->lockedRepository->getPackages() as $package) { + $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; + } + } + + foreach ($this->fixedPackages as $package) { + $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; + } + + return $presentMap; + } + + /** + * @return array + */ + public function getFixedPackagesMap(): array + { + $fixedPackagesMap = []; + + foreach ($this->fixedPackages as $package) { + $fixedPackagesMap[$package->getId()] = $package; + } + + return $fixedPackagesMap; + } + + /** + * @return ?LockArrayRepository + */ + public function getLockedRepository(): ?LockArrayRepository + { + return $this->lockedRepository; + } + + /** + * Restricts the pool builder from loading other packages than those listed here + * + * @param non-empty-list $names + */ + public function restrictPackages(array $names): void + { + $this->restrictedPackages = $names; + } + + /** + * @return list + */ + public function getRestrictedPackages(): ?array + { + return $this->restrictedPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php b/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php new file mode 100644 index 0000000..8dde02b --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Rule.php @@ -0,0 +1,462 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositorySet; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * @author Nils Adermann + * @author Ruben Gonzalez + * @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage} + */ +abstract class Rule +{ + // reason constants and // their reason data contents + public const RULE_ROOT_REQUIRE = 2; // array{packageName: string, constraint: ConstraintInterface} + public const RULE_FIXED = 3; // array{package: BasePackage} + public const RULE_PACKAGE_CONFLICT = 6; // Link + public const RULE_PACKAGE_REQUIRES = 7; // Link + public const RULE_PACKAGE_SAME_NAME = 10; // string (package name) + public const RULE_LEARNED = 12; // int (rule id) + public const RULE_PACKAGE_ALIAS = 13; // BasePackage + public const RULE_PACKAGE_INVERSE_ALIAS = 14; // BasePackage + + // bitfield defs + private const BITFIELD_TYPE = 0; + private const BITFIELD_REASON = 8; + private const BITFIELD_DISABLED = 16; + + /** @var int */ + protected $bitfield; + /** @var Request */ + protected $request; + /** + * @var Link|BasePackage|ConstraintInterface|string + * @phpstan-var ReasonData + */ + protected $reasonData; + + /** + * @param self::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + public function __construct($reason, $reasonData) + { + $this->reasonData = $reasonData; + + $this->bitfield = (0 << self::BITFIELD_DISABLED) | + ($reason << self::BITFIELD_REASON) | + (255 << self::BITFIELD_TYPE); + } + + /** + * @return list + */ + abstract public function getLiterals(): array; + + /** + * @return int|string + */ + abstract public function getHash(); + + abstract public function __toString(): string; + + abstract public function equals(Rule $rule): bool; + + /** + * @return self::RULE_* + */ + public function getReason(): int + { + return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON; + } + + /** + * @phpstan-return ReasonData + */ + public function getReasonData() + { + return $this->reasonData; + } + + public function getRequiredPackage(): ?string + { + switch ($this->getReason()) { + case self::RULE_ROOT_REQUIRE: + return $this->getReasonData()['packageName']; + case self::RULE_FIXED: + return $this->getReasonData()['package']->getName(); + case self::RULE_PACKAGE_REQUIRES: + return $this->getReasonData()->getTarget(); + } + + return null; + } + + /** + * @param RuleSet::TYPE_* $type + */ + public function setType($type): void + { + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE); + } + + public function getType(): int + { + return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE; + } + + public function disable(): void + { + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED); + } + + public function enable(): void + { + $this->bitfield &= ~(255 << self::BITFIELD_DISABLED); + } + + public function isDisabled(): bool + { + return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + } + + public function isEnabled(): bool + { + return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); + } + + abstract public function isAssertion(): bool; + + public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool + { + if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { + if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) { + return false; + } + if ($request->getLockedRepository() !== null) { + foreach ($request->getLockedRepository()->getPackages() as $package) { + if ($package->getName() === $this->getReasonData()->getTarget()) { + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return true; + } + if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) { + return true; + } + // required package was locked but has been unlocked and still matches + if (!$request->isLockedPackage($package)) { + return true; + } + break; + } + } + } + } + + if ($this->getReason() === self::RULE_ROOT_REQUIRE) { + if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) { + return false; + } + if ($request->getLockedRepository() !== null) { + foreach ($request->getLockedRepository()->getPackages() as $package) { + if ($package->getName() === $this->getReasonData()['packageName']) { + if ($pool->isUnacceptableFixedOrLockedPackage($package)) { + return true; + } + if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) { + return true; + } + break; + } + } + } + } + + return false; + } + + /** + * @internal + */ + public function getSourcePackage(Pool $pool): BasePackage + { + $literals = $this->getLiterals(); + + switch ($this->getReason()) { + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + $reasonData = $this->getReasonData(); + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + [$package2, $package1] = [$package1, $package2]; + } + + return $package2; + + case self::RULE_PACKAGE_REQUIRES: + $sourceLiteral = $literals[0]; + $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); + + return $sourcePackage; + + default: + throw new \LogicException('Not implemented'); + } + } + + /** + * @param BasePackage[] $installedMap + * @param array $learnedPool + */ + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string + { + $literals = $this->getLiterals(); + + switch ($this->getReason()) { + case self::RULE_ROOT_REQUIRE: + $reasonData = $this->getReasonData(); + $packageName = $reasonData['packageName']; + $constraint = $reasonData['constraint']; + + $packages = $pool->whatProvides($packageName, $constraint); + if (0 === \count($packages)) { + return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString(); + } + + $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool { + return !($p instanceof AliasPackage); + })); + if (\count($packagesNonAlias) === 1) { + $package = $packagesNonAlias[0]; + if ($request->isLockedPackage($package)) { + return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested."; + } + } + + return 'Root composer.json requires '.$packageName.' '.$constraint->getPrettyString().' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.'; + + case self::RULE_FIXED: + $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']); + + if ($request->isLockedPackage($package)) { + return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.'; + } + + return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer'; + + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + $conflictTarget = $package1->getPrettyString(); + $reasonData = $this->getReasonData(); + + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + [$package2, $package1] = [$package1, $package2]; + $conflictTarget = $package1->getPrettyName().' '.$reasonData->getPrettyConstraint(); + } + + // if the conflict is not directly against the package but something it provides/replaces, + // we try to find that link to display a better message + if ($reasonData->getTarget() !== $package1->getName()) { + $provideType = null; + $provided = null; + foreach ($package1->getProvides() as $provide) { + if ($provide->getTarget() === $reasonData->getTarget()) { + $provideType = 'provides'; + $provided = $provide->getPrettyConstraint(); + break; + } + } + foreach ($package1->getReplaces() as $replace) { + if ($replace->getTarget() === $reasonData->getTarget()) { + $provideType = 'replaces'; + $provided = $replace->getPrettyConstraint(); + break; + } + } + if (null !== $provideType) { + $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')'; + } + } + + return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.'; + + case self::RULE_PACKAGE_REQUIRES: + assert(\count($literals) > 0); + $sourceLiteral = array_shift($literals); + $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); + $reasonData = $this->getReasonData(); + + $requires = []; + foreach ($literals as $literal) { + $requires[] = $pool->literalToPackage($literal); + } + + $text = $reasonData->getPrettyString($sourcePackage); + if (\count($requires) > 0) { + $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.'; + } else { + $targetName = $reasonData->getTarget(); + + $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint()); + + return $text . ' -> ' . $reason[1]; + } + + return $text; + + case self::RULE_PACKAGE_SAME_NAME: + $packageNames = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + $packageNames[$package->getName()] = true; + } + unset($literal); + $replacedName = $this->getReasonData(); + + if (\count($packageNames) > 1) { + if (!isset($packageNames[$replacedName])) { + $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.'; + } else { + $replacerNames = $packageNames; + unset($replacerNames[$replacedName]); + $replacerNames = array_keys($replacerNames); + + if (\count($replacerNames) === 1) { + $reason = $replacerNames[0] . ' replaces '; + } else { + $reason = '['.implode(', ', $replacerNames).'] replace '; + } + $reason .= $replacedName.' and thus cannot coexist with it.'; + } + + $installedPackages = []; + $removablePackages = []; + foreach ($literals as $literal) { + if (isset($installedMap[abs($literal)])) { + $installedPackages[] = $pool->literalToPackage($literal); + } else { + $removablePackages[] = $pool->literalToPackage($literal); + } + } + + if (\count($installedPackages) > 0 && \count($removablePackages) > 0) { + return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason; + } + + return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason; + } + + return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.'; + case self::RULE_LEARNED: + /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */ + // if (isset($learnedPool[$this->getReasonData()])) { + // echo $this->getReasonData()."\n"; + // $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->getReasonData()], ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); + // } else { + // $learnedString = ' (reasoning unavailable)'; + // } + $learnedString = ' (conflict analysis result)'; + + if (\count($literals) === 1) { + $ruleText = $pool->literalToPrettyString($literals[0], $installedMap); + } else { + $groups = []; + foreach ($literals as $literal) { + $package = $pool->literalToPackage($literal); + if (isset($installedMap[$package->id])) { + $group = $literal > 0 ? 'keep' : 'remove'; + } else { + $group = $literal > 0 ? 'install' : 'don\'t install'; + } + + $groups[$group][] = $this->deduplicateDefaultBranchAlias($package); + } + $ruleTexts = []; + foreach ($groups as $group => $packages) { + $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); + } + + $ruleText = implode(' | ', $ruleTexts); + } + + return 'Conclusion: '.$ruleText.$learnedString; + case self::RULE_PACKAGE_ALIAS: + $aliasPackage = $pool->literalToPackage($literals[0]); + + // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless + if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + return ''; + } + $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.'; + case self::RULE_PACKAGE_INVERSE_ALIAS: + // inverse alias rules work the other way around than above + $aliasPackage = $pool->literalToPackage($literals[1]); + + // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless + if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + return ''; + } + $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + + return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.'; + default: + $ruleText = ''; + foreach ($literals as $i => $literal) { + if ($i !== 0) { + $ruleText .= '|'; + } + $ruleText .= $pool->literalToPrettyString($literal, $installedMap); + } + + return '('.$ruleText.')'; + } + } + + /** + * @param array $literalsOrPackages An array containing packages or literals + */ + protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string + { + $packages = []; + foreach ($literalsOrPackages as $package) { + $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package); + } + + return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); + } + + private function deduplicateDefaultBranchAlias(BasePackage $package): BasePackage + { + if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php b/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php new file mode 100644 index 0000000..33d0ed0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php @@ -0,0 +1,117 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * @phpstan-import-type ReasonData from Rule + */ +class Rule2Literals extends Rule +{ + /** @var int */ + protected $literal1; + /** @var int */ + protected $literal2; + + /** + * @param Rule::RULE_* $reason A RULE_* constant + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + public function __construct(int $literal1, int $literal2, $reason, $reasonData) + { + parent::__construct($reason, $reasonData); + + if ($literal1 < $literal2) { + $this->literal1 = $literal1; + $this->literal2 = $literal2; + } else { + $this->literal1 = $literal2; + $this->literal2 = $literal1; + } + } + + /** + * @return non-empty-list + */ + public function getLiterals(): array + { + return [$this->literal1, $this->literal2]; + } + + /** + * @inheritDoc + */ + public function getHash() + { + return $this->literal1.','.$this->literal2; + } + + /** + * Checks if this rule is equal to another one + * + * Ignores whether either of the rules is disabled. + * + * @param Rule $rule The rule to check against + * @return bool Whether the rules are equal + */ + public function equals(Rule $rule): bool + { + // specialized fast-case + if ($rule instanceof self) { + if ($this->literal1 !== $rule->literal1) { + return false; + } + + if ($this->literal2 !== $rule->literal2) { + return false; + } + + return true; + } + + $literals = $rule->getLiterals(); + if (2 !== \count($literals)) { + return false; + } + + if ($this->literal1 !== $literals[0]) { + return false; + } + + if ($this->literal2 !== $literals[1]) { + return false; + } + + return true; + } + + /** @return false */ + public function isAssertion(): bool + { + return false; + } + + /** + * Formats a rule as a string of the format (Literal1|Literal2|...) + */ + public function __toString(): string + { + $result = $this->isDisabled() ? 'disabled(' : '('; + + $result .= $this->literal1 . '|' . $this->literal2 . ')'; + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php new file mode 100644 index 0000000..cca9eb1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php @@ -0,0 +1,194 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Repository\RepositorySet; + +/** + * @author Nils Adermann + * @implements \IteratorAggregate + * @internal + * @final + */ +class RuleSet implements \IteratorAggregate, \Countable +{ + // highest priority => lowest number + public const TYPE_PACKAGE = 0; + public const TYPE_REQUEST = 1; + public const TYPE_LEARNED = 4; + + /** + * READ-ONLY: Lookup table for rule id to rule object + * + * @var array + */ + public $ruleById = []; + + const TYPES = [ + self::TYPE_PACKAGE => 'PACKAGE', + self::TYPE_REQUEST => 'REQUEST', + self::TYPE_LEARNED => 'LEARNED', + ]; + + /** @var array */ + protected $rules; + + /** @var 0|positive-int */ + protected $nextRuleId = 0; + + /** @var array */ + protected $rulesByHash = []; + + public function __construct() + { + foreach ($this->getTypes() as $type) { + $this->rules[$type] = []; + } + } + + /** + * @param self::TYPE_* $type + */ + public function add(Rule $rule, $type): void + { + if (!isset(self::TYPES[$type])) { + throw new \OutOfBoundsException('Unknown rule type: ' . $type); + } + + $hash = $rule->getHash(); + + // Do not add if rule already exists + if (isset($this->rulesByHash[$hash])) { + $potentialDuplicates = $this->rulesByHash[$hash]; + if (\is_array($potentialDuplicates)) { + foreach ($potentialDuplicates as $potentialDuplicate) { + if ($rule->equals($potentialDuplicate)) { + return; + } + } + } else { + if ($rule->equals($potentialDuplicates)) { + return; + } + } + } + + if (!isset($this->rules[$type])) { + $this->rules[$type] = []; + } + + $this->rules[$type][] = $rule; + $this->ruleById[$this->nextRuleId] = $rule; + $rule->setType($type); + + $this->nextRuleId++; + + if (!isset($this->rulesByHash[$hash])) { + $this->rulesByHash[$hash] = $rule; + } elseif (\is_array($this->rulesByHash[$hash])) { + $this->rulesByHash[$hash][] = $rule; + } else { + $originalRule = $this->rulesByHash[$hash]; + $this->rulesByHash[$hash] = [$originalRule, $rule]; + } + } + + public function count(): int + { + return $this->nextRuleId; + } + + public function ruleById(int $id): Rule + { + return $this->ruleById[$id]; + } + + /** @return array */ + public function getRules(): array + { + return $this->rules; + } + + public function getIterator(): RuleSetIterator + { + return new RuleSetIterator($this->getRules()); + } + + /** + * @param self::TYPE_*|array $types + */ + public function getIteratorFor($types): RuleSetIterator + { + if (!\is_array($types)) { + $types = [$types]; + } + + $allRules = $this->getRules(); + + /** @var array $rules */ + $rules = []; + + foreach ($types as $type) { + $rules[$type] = $allRules[$type]; + } + + return new RuleSetIterator($rules); + } + + /** + * @param array|self::TYPE_* $types + */ + public function getIteratorWithout($types): RuleSetIterator + { + if (!\is_array($types)) { + $types = [$types]; + } + + $rules = $this->getRules(); + + foreach ($types as $type) { + unset($rules[$type]); + } + + return new RuleSetIterator($rules); + } + + /** + * @return array{self::TYPE_PACKAGE, self::TYPE_REQUEST, self::TYPE_LEARNED} + */ + public function getTypes(): array + { + $types = self::TYPES; + + return array_keys($types); + } + + public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string + { + $string = "\n"; + foreach ($this->rules as $type => $rules) { + $string .= str_pad(self::TYPES[$type], 8, ' ') . ": "; + foreach ($rules as $rule) { + $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n"; + } + $string .= "\n\n"; + } + + return $string; + } + + public function __toString(): string + { + return $this->getPrettyString(); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php new file mode 100644 index 0000000..08c874f --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -0,0 +1,327 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; + +/** + * @author Nils Adermann + * @phpstan-import-type ReasonData from Rule + */ +class RuleSetGenerator +{ + /** @var PolicyInterface */ + protected $policy; + /** @var Pool */ + protected $pool; + /** @var RuleSet */ + protected $rules; + /** @var array */ + protected $addedMap = []; + /** @var array */ + protected $addedPackagesByNames = []; + + public function __construct(PolicyInterface $policy, Pool $pool) + { + $this->policy = $policy; + $this->pool = $pool; + $this->rules = new RuleSet; + } + + /** + * Creates a new rule for the requirements of a package + * + * This rule is of the form (-A|B|C), where B and C are the providers of + * one requirement of the package A. + * + * @param BasePackage $package The package with a requirement + * @param BasePackage[] $providers The providers of the requirement + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData Any data, e.g. the requirement name, that goes with the reason + * @return Rule|null The generated rule or null if tautological + * + * @phpstan-param ReasonData $reasonData + */ + protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule + { + $literals = [-$package->id]; + + foreach ($providers as $provider) { + // self fulfilling rule? + if ($provider === $package) { + return null; + } + $literals[] = $provider->id; + } + + return new GenericRule($literals, $reason, $reasonData); + } + + /** + * Creates a rule to install at least one of a set of packages + * + * The rule is (A|B|C) with A, B and C different packages. If the given + * set of packages is empty an impossible rule is generated. + * + * @param non-empty-array $packages The set of packages to choose from + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for + * generating this rule + * @param mixed $reasonData Additional data like the root require or fix request info + * @return Rule The generated rule + * + * @phpstan-param ReasonData $reasonData + */ + protected function createInstallOneOfRule(array $packages, $reason, $reasonData): Rule + { + $literals = []; + foreach ($packages as $package) { + $literals[] = $package->id; + } + + return new GenericRule($literals, $reason, $reasonData); + } + + /** + * Creates a rule for two conflicting packages + * + * The rule for conflicting packages A and B is (-A|-B). A is called the issuer + * and B the provider. + * + * @param BasePackage $issuer The package declaring the conflict + * @param BasePackage $provider The package causing the conflict + * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule + * @param mixed $reasonData Any data, e.g. the package name, that goes with the reason + * @return ?Rule The generated rule + * + * @phpstan-param ReasonData $reasonData + */ + protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule + { + // ignore self conflict + if ($issuer === $provider) { + return null; + } + + return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData); + } + + /** + * @param non-empty-array $packages + * @param Rule::RULE_* $reason A RULE_* constant + * @param mixed $reasonData + * + * @phpstan-param ReasonData $reasonData + */ + protected function createMultiConflictRule(array $packages, $reason, $reasonData): Rule + { + $literals = []; + foreach ($packages as $package) { + $literals[] = -$package->id; + } + + if (\count($literals) === 2) { + return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData); + } + + return new MultiConflictRule($literals, $reason, $reasonData); + } + + /** + * Adds a rule unless it duplicates an existing one of any type + * + * To be able to directly pass in the result of one of the rule creation + * methods null is allowed which will not insert a rule. + * + * @param RuleSet::TYPE_* $type A TYPE_* constant defining the rule type + * @param Rule $newRule The rule about to be added + */ + private function addRule($type, ?Rule $newRule = null): void + { + if (null === $newRule) { + return; + } + + $this->rules->add($newRule, $type); + } + + protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + /** @var \SplQueue */ + $workQueue = new \SplQueue; + $workQueue->enqueue($package); + + while (!$workQueue->isEmpty()) { + $package = $workQueue->dequeue(); + if (isset($this->addedMap[$package->id])) { + continue; + } + + $this->addedMap[$package->id] = $package; + + if (!$package instanceof AliasPackage) { + foreach ($package->getNames(false) as $name) { + $this->addedPackagesByNames[$name][] = $package; + } + } else { + $workQueue->enqueue($package->getAliasOf()); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], Rule::RULE_PACKAGE_ALIAS, $package)); + + // aliases must be installed with their main package, so create a rule the other way around as well + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf())); + + // if alias package has no self.version requires, its requirements do not + // need to be added as the aliased package processing will take care of it + if (!$package->hasSelfVersionRequires()) { + continue; + } + } + + foreach ($package->getRequires() as $link) { + $constraint = $link->getConstraint(); + if ($platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint); + } + + $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint); + + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); + + foreach ($possibleRequires as $require) { + $workQueue->enqueue($require); + } + } + } + } + + protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter): void + { + /** @var BasePackage $package */ + foreach ($this->addedMap as $package) { + foreach ($package->getConflicts() as $link) { + // even if conflict ends up being with an alias, there would be at least one actual package by this name + if (!isset($this->addedPackagesByNames[$link->getTarget()])) { + continue; + } + + $constraint = $link->getConstraint(); + if ($platformRequirementFilter->isIgnored($link->getTarget())) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, false); + } + + $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint); + + foreach ($conflicts as $conflict) { + // define the conflict rule for regular packages, for alias packages it's only needed if the name + // matches the conflict exactly, otherwise the name match is by provide/replace which means the + // package which this is an alias of will conflict anyway, so no need to create additional rules + if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) { + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link)); + } + } + } + } + + foreach ($this->addedPackagesByNames as $name => $packages) { + if (\count($packages) > 1) { + $reason = Rule::RULE_PACKAGE_SAME_NAME; + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name)); + } + } + } + + protected function addRulesForRequest(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($request->getFixedPackages() as $package) { + if ($package->id === -1) { + // fixed package was not added to the pool as it did not pass the stability requirements, this is fine + if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) { + continue; + } + + // otherwise, looks like a bug + throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool."); + } + + $this->addRulesForPackage($package, $platformRequirementFilter); + + $rule = $this->createInstallOneOfRule([$package], Rule::RULE_FIXED, [ + 'package' => $package, + ]); + $this->addRule(RuleSet::TYPE_REQUEST, $rule); + } + + foreach ($request->getRequires() as $packageName => $constraint) { + if ($platformRequirementFilter->isIgnored($packageName)) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); + } + + $packages = $this->pool->whatProvides($packageName, $constraint); + if (\count($packages) > 0) { + foreach ($packages as $package) { + $this->addRulesForPackage($package, $platformRequirementFilter); + } + + $rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, [ + 'packageName' => $packageName, + 'constraint' => $constraint, + ]); + $this->addRule(RuleSet::TYPE_REQUEST, $rule); + } + } + } + + protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($this->pool->getPackages() as $package) { + // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded + // even if the alias itself isn't required, otherwise a package could be installed without its alias which + // leads to unexpected behavior + if (!isset($this->addedMap[$package->id]) && + $package instanceof AliasPackage && + ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id])) + ) { + $this->addRulesForPackage($package, $platformRequirementFilter); + } + } + } + + public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet + { + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + $this->addRulesForRequest($request, $platformRequirementFilter); + + $this->addRulesForRootAliases($platformRequirementFilter); + + $this->addConflictRules($platformRequirementFilter); + + // Remove references to packages + $this->addedMap = $this->addedPackagesByNames = []; + + $rules = $this->rules; + + $this->rules = new RuleSet; + + return $rules; + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php new file mode 100644 index 0000000..3b8383d --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php @@ -0,0 +1,105 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + * @implements \Iterator + */ +class RuleSetIterator implements \Iterator +{ + /** @var array */ + protected $rules; + /** @var array */ + protected $types; + + /** @var int */ + protected $currentOffset; + /** @var RuleSet::TYPE_*|-1 */ + protected $currentType; + /** @var int */ + protected $currentTypeOffset; + + /** + * @param array $rules + */ + public function __construct(array $rules) + { + $this->rules = $rules; + $this->types = array_keys($rules); + sort($this->types); + + $this->rewind(); + } + + public function current(): Rule + { + return $this->rules[$this->currentType][$this->currentOffset]; + } + + /** + * @return RuleSet::TYPE_*|-1 + */ + public function key(): int + { + return $this->currentType; + } + + public function next(): void + { + $this->currentOffset++; + + if (!isset($this->rules[$this->currentType])) { + return; + } + + if ($this->currentOffset >= \count($this->rules[$this->currentType])) { + $this->currentOffset = 0; + + do { + $this->currentTypeOffset++; + + if (!isset($this->types[$this->currentTypeOffset])) { + $this->currentType = -1; + break; + } + + $this->currentType = $this->types[$this->currentTypeOffset]; + } while (0 === \count($this->rules[$this->currentType])); + } + } + + public function rewind(): void + { + $this->currentOffset = 0; + + $this->currentTypeOffset = -1; + $this->currentType = -1; + + do { + $this->currentTypeOffset++; + + if (!isset($this->types[$this->currentTypeOffset])) { + $this->currentType = -1; + break; + } + + $this->currentType = $this->types[$this->currentTypeOffset]; + } while (0 === \count($this->rules[$this->currentType])); + } + + public function valid(): bool + { + return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php new file mode 100644 index 0000000..ddd5960 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * An extension of SplDoublyLinkedList with seek and removal of current element + * + * SplDoublyLinkedList only allows deleting a particular offset and has no + * method to set the internal iterator to a particular offset. + * + * @author Nils Adermann + * @extends \SplDoublyLinkedList + */ +class RuleWatchChain extends \SplDoublyLinkedList +{ + /** + * Moves the internal iterator to the specified offset + * + * @param int $offset The offset to seek to. + */ + public function seek(int $offset): void + { + $this->rewind(); + for ($i = 0; $i < $offset; $i++, $this->next()); + } + + /** + * Removes the current element from the list + * + * As SplDoublyLinkedList only allows deleting a particular offset and + * incorrectly sets the internal iterator if you delete the current value + * this method sets the internal iterator back to the following element + * using the seek method. + */ + public function remove(): void + { + $offset = $this->key(); + $this->offsetUnset($offset); + $this->seek($offset); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php new file mode 100644 index 0000000..6a13b40 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php @@ -0,0 +1,167 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * The RuleWatchGraph efficiently propagates decisions to other rules + * + * All rules generated for solving a SAT problem should be inserted into the + * graph. When a decision on a literal is made, the graph can be used to + * propagate the decision to all other rules involving the literal, leading to + * other trivial decisions resulting from unit clauses. + * + * @author Nils Adermann + */ +class RuleWatchGraph +{ + /** @var array */ + protected $watchChains = []; + + /** + * Inserts a rule node into the appropriate chains within the graph + * + * The node is prepended to the watch chains for each of the two literals it + * watches. + * + * Assertions are skipped because they only depend on a single package and + * have no alternative literal that could be true, so there is no need to + * watch changes in any literals. + * + * @param RuleWatchNode $node The rule node to be inserted into the graph + */ + public function insert(RuleWatchNode $node): void + { + if ($node->getRule()->isAssertion()) { + return; + } + + if (!$node->getRule() instanceof MultiConflictRule) { + foreach ([$node->watch1, $node->watch2] as $literal) { + if (!isset($this->watchChains[$literal])) { + $this->watchChains[$literal] = new RuleWatchChain; + } + + $this->watchChains[$literal]->unshift($node); + } + } else { + foreach ($node->getRule()->getLiterals() as $literal) { + if (!isset($this->watchChains[$literal])) { + $this->watchChains[$literal] = new RuleWatchChain; + } + + $this->watchChains[$literal]->unshift($node); + } + } + } + + /** + * Propagates a decision on a literal to all rules watching the literal + * + * If a decision, e.g. +A has been made, then all rules containing -A, e.g. + * (-A|+B|+C) now need to satisfy at least one of the other literals, so + * that the rule as a whole becomes true, since with +A applied the rule + * is now (false|+B|+C) so essentially (+B|+C). + * + * This means that all rules watching the literal -A need to be updated to + * watch 2 other literals which can still be satisfied instead. So literals + * that conflict with previously made decisions are not an option. + * + * Alternatively it can occur that a unit clause results: e.g. if in the + * above example the rule was (-A|+B), then A turning true means that + * B must now be decided true as well. + * + * @param int $decidedLiteral The literal which was decided (A in our example) + * @param int $level The level at which the decision took place and at which + * all resulting decisions should be made. + * @param Decisions $decisions Used to check previous decisions and to + * register decisions resulting from propagation + * @return Rule|null If a conflict is found the conflicting rule is returned + */ + public function propagateLiteral(int $decidedLiteral, int $level, Decisions $decisions): ?Rule + { + // we invert the decided literal here, example: + // A was decided => (-A|B) now requires B to be true, so we look for + // rules which are fulfilled by -A, rather than A. + $literal = -$decidedLiteral; + + if (!isset($this->watchChains[$literal])) { + return null; + } + + $chain = $this->watchChains[$literal]; + + $chain->rewind(); + while ($chain->valid()) { + $node = $chain->current(); + if (!$node->getRule() instanceof MultiConflictRule) { + $otherWatch = $node->getOtherWatch($literal); + + if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { + $ruleLiterals = $node->getRule()->getLiterals(); + + $alternativeLiterals = array_filter($ruleLiterals, static function ($ruleLiteral) use ($literal, $otherWatch, $decisions): bool { + return $literal !== $ruleLiteral && + $otherWatch !== $ruleLiteral && + !$decisions->conflict($ruleLiteral); + }); + + if (\count($alternativeLiterals) > 0) { + reset($alternativeLiterals); + $this->moveWatch($literal, current($alternativeLiterals), $node); + continue; + } + + if ($decisions->conflict($otherWatch)) { + return $node->getRule(); + } + + $decisions->decide($otherWatch, $level, $node->getRule()); + } + } else { + foreach ($node->getRule()->getLiterals() as $otherLiteral) { + if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) { + if ($decisions->conflict($otherLiteral)) { + return $node->getRule(); + } + + $decisions->decide($otherLiteral, $level, $node->getRule()); + } + } + } + + $chain->next(); + } + + return null; + } + + /** + * Moves a rule node from one watch chain to another + * + * The rule node's watched literals are updated accordingly. + * + * @param int $fromLiteral A literal the node used to watch + * @param int $toLiteral A literal the node should watch now + * @param RuleWatchNode $node The rule node to be moved + */ + protected function moveWatch(int $fromLiteral, int $toLiteral, RuleWatchNode $node): void + { + if (!isset($this->watchChains[$toLiteral])) { + $this->watchChains[$toLiteral] = new RuleWatchChain; + } + + $node->moveWatch($fromLiteral, $toLiteral); + $this->watchChains[$fromLiteral]->remove(); + $this->watchChains[$toLiteral]->unshift($node); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php new file mode 100644 index 0000000..79c1fcb --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php @@ -0,0 +1,114 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * Wrapper around a Rule which keeps track of the two literals it watches + * + * Used by RuleWatchGraph to store rules in two RuleWatchChains. + * + * @author Nils Adermann + */ +class RuleWatchNode +{ + /** @var int */ + public $watch1; + /** @var int */ + public $watch2; + + /** @var Rule */ + protected $rule; + + /** + * Creates a new node watching the first and second literals of the rule. + * + * @param Rule $rule The rule to wrap + */ + public function __construct(Rule $rule) + { + $this->rule = $rule; + + $literals = $rule->getLiterals(); + + $literalCount = \count($literals); + $this->watch1 = $literalCount > 0 ? $literals[0] : 0; + $this->watch2 = $literalCount > 1 ? $literals[1] : 0; + } + + /** + * Places the second watch on the rule's literal, decided at the highest level + * + * Useful for learned rules where the literal for the highest rule is most + * likely to quickly lead to further decisions. + * + * @param Decisions $decisions The decisions made so far by the solver + */ + public function watch2OnHighest(Decisions $decisions): void + { + $literals = $this->rule->getLiterals(); + + // if there are only 2 elements, both are being watched anyway + if (\count($literals) < 3 || $this->rule instanceof MultiConflictRule) { + return; + } + + $watchLevel = 0; + + foreach ($literals as $literal) { + $level = $decisions->decisionLevel($literal); + + if ($level > $watchLevel) { + $this->watch2 = $literal; + $watchLevel = $level; + } + } + } + + /** + * Returns the rule this node wraps + */ + public function getRule(): Rule + { + return $this->rule; + } + + /** + * Given one watched literal, this method returns the other watched literal + * + * @param int $literal The watched literal that should not be returned + * @return int A literal + */ + public function getOtherWatch(int $literal): int + { + if ($this->watch1 === $literal) { + return $this->watch2; + } + + return $this->watch1; + } + + /** + * Moves a watch from one literal to another + * + * @param int $from The previously watched literal + * @param int $to The literal to be watched now + */ + public function moveWatch(int $from, int $to): void + { + if ($this->watch1 === $from) { + $this->watch1 = $to; + } else { + $this->watch2 = $to; + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php b/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php new file mode 100644 index 0000000..b8aa847 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Solver.php @@ -0,0 +1,758 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; + +/** + * @author Nils Adermann + */ +class Solver +{ + private const BRANCH_LITERALS = 0; + private const BRANCH_LEVEL = 1; + + /** @var PolicyInterface */ + protected $policy; + /** @var Pool */ + protected $pool; + + /** @var RuleSet */ + protected $rules; + + /** @var RuleWatchGraph */ + protected $watchGraph; + /** @var Decisions */ + protected $decisions; + /** @var BasePackage[] */ + protected $fixedMap; + + /** @var int */ + protected $propagateIndex; + /** @var array, int}> */ + protected $branches = []; + /** @var Problem[] */ + protected $problems = []; + /** @var array */ + protected $learnedPool = []; + /** @var array */ + protected $learnedWhy = []; + + /** @var bool */ + public $testFlagLearnedPositiveLiteral = false; + + /** @var IOInterface */ + protected $io; + + public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io) + { + $this->io = $io; + $this->policy = $policy; + $this->pool = $pool; + } + + public function getRuleSetSize(): int + { + return \count($this->rules); + } + + public function getPool(): Pool + { + return $this->pool; + } + + // aka solver_makeruledecisions + + private function makeAssertionRuleDecisions(): void + { + $decisionStart = \count($this->decisions) - 1; + + $rulesCount = \count($this->rules); + for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { + $rule = $this->rules->ruleById[$ruleIndex]; + + if (!$rule->isAssertion() || $rule->isDisabled()) { + continue; + } + + $literals = $rule->getLiterals(); + $literal = $literals[0]; + + if (!$this->decisions->decided($literal)) { + $this->decisions->decide($literal, 1, $rule); + continue; + } + + if ($this->decisions->satisfy($literal)) { + continue; + } + + // found a conflict + if (RuleSet::TYPE_LEARNED === $rule->getType()) { + $rule->disable(); + continue; + } + + $conflict = $this->decisions->decisionRule($literal); + + if (RuleSet::TYPE_PACKAGE === $conflict->getType()) { + $problem = new Problem(); + + $problem->addRule($rule); + $problem->addRule($conflict); + $rule->disable(); + $this->problems[] = $problem; + continue; + } + + // conflict with another root require/fixed package + $problem = new Problem(); + $problem->addRule($rule); + $problem->addRule($conflict); + + // push all of our rules (can only be root require/fixed package rules) + // asserting this literal on the problem stack + foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) { + if ($assertRule->isDisabled() || !$assertRule->isAssertion()) { + continue; + } + + $assertRuleLiterals = $assertRule->getLiterals(); + $assertRuleLiteral = $assertRuleLiterals[0]; + + if (abs($literal) !== abs($assertRuleLiteral)) { + continue; + } + $problem->addRule($assertRule); + $assertRule->disable(); + } + $this->problems[] = $problem; + + $this->decisions->resetToOffset($decisionStart); + $ruleIndex = -1; + } + } + + protected function setupFixedMap(Request $request): void + { + $this->fixedMap = []; + foreach ($request->getFixedPackages() as $package) { + $this->fixedMap[$package->id] = $package; + } + } + + protected function checkForRootRequireProblems(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void + { + foreach ($request->getRequires() as $packageName => $constraint) { + if ($platformRequirementFilter->isIgnored($packageName)) { + continue; + } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); + } + + if (0 === \count($this->pool->whatProvides($packageName, $constraint))) { + $problem = new Problem(); + $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint])); + $this->problems[] = $problem; + } + } + } + + public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction + { + $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); + + $this->setupFixedMap($request); + + $this->io->writeError('Generating rules', true, IOInterface::DEBUG); + $ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); + $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter); + unset($ruleSetGenerator); + $this->checkForRootRequireProblems($request, $platformRequirementFilter); + $this->decisions = new Decisions($this->pool); + $this->watchGraph = new RuleWatchGraph; + + foreach ($this->rules as $rule) { + $this->watchGraph->insert(new RuleWatchNode($rule)); + } + + /* make decisions based on root require/fix assertions */ + $this->makeAssertionRuleDecisions(); + + $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); + $before = microtime(true); + $this->runSat(); + $this->io->writeError('', true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); + + if (\count($this->problems) > 0) { + throw new SolverProblemsException($this->problems, $this->learnedPool); + } + + return new LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions); + } + + /** + * Makes a decision and propagates it to all rules. + * + * Evaluates each term affected by the decision (linked through watches) + * If we find unit rules we make new decisions based on them + * + * @return Rule|null A rule on conflict, otherwise null. + */ + protected function propagate(int $level): ?Rule + { + while ($this->decisions->validOffset($this->propagateIndex)) { + $decision = $this->decisions->atOffset($this->propagateIndex); + + $conflict = $this->watchGraph->propagateLiteral( + $decision[Decisions::DECISION_LITERAL], + $level, + $this->decisions + ); + + $this->propagateIndex++; + + if ($conflict !== null) { + return $conflict; + } + } + + return null; + } + + /** + * Reverts a decision at the given level. + */ + private function revert(int $level): void + { + while (!$this->decisions->isEmpty()) { + $literal = $this->decisions->lastLiteral(); + + if ($this->decisions->undecided($literal)) { + break; + } + + $decisionLevel = $this->decisions->decisionLevel($literal); + + if ($decisionLevel <= $level) { + break; + } + + $this->decisions->revertLast(); + $this->propagateIndex = \count($this->decisions); + } + + while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { + array_pop($this->branches); + } + } + + /** + * setpropagatelearn + * + * add free decision (a positive literal) to decision queue + * increase level and propagate decision + * return if no conflict. + * + * in conflict case, analyze conflict rule, add resulting + * rule to learnt rule set, make decision from learnt + * rule (always unit) and re-propagate. + * + * returns the new solver level or 0 if unsolvable + */ + private function setPropagateLearn(int $level, int $literal, Rule $rule): int + { + $level++; + + $this->decisions->decide($literal, $level, $rule); + + while (true) { + $rule = $this->propagate($level); + + if (null === $rule) { + break; + } + + if ($level === 1) { + $this->analyzeUnsolvable($rule); + + return 0; + } + + // conflict + [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule); + + if ($newLevel <= 0 || $newLevel >= $level) { + throw new SolverBugException( + "Trying to revert to invalid level ".$newLevel." from level ".$level."." + ); + } + + $level = $newLevel; + + $this->revert($level); + + $this->rules->add($newRule, RuleSet::TYPE_LEARNED); + + $this->learnedWhy[spl_object_hash($newRule)] = $why; + + $ruleNode = new RuleWatchNode($newRule); + $ruleNode->watch2OnHighest($this->decisions); + $this->watchGraph->insert($ruleNode); + + $this->decisions->decide($learnLiteral, $level, $newRule); + } + + return $level; + } + + /** + * @param non-empty-list $decisionQueue + */ + private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int + { + // choose best package to install from decisionQueue + $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage()); + + $selectedLiteral = array_shift($literals); + + // if there are multiple candidates, then branch + if (\count($literals) > 0) { + $this->branches[] = [$literals, $level]; + } + + return $this->setPropagateLearn($level, $selectedLiteral, $rule); + } + + /** + * @return array{int, int, GenericRule, int} + */ + protected function analyze(int $level, Rule $rule): array + { + $analyzedRule = $rule; + $ruleLevel = 1; + $num = 0; + $l1num = 0; + $seen = []; + $learnedLiteral = null; + $otherLearnedLiterals = []; + + $decisionId = \count($this->decisions); + + $this->learnedPool[] = []; + + while (true) { + $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; + + foreach ($rule->getLiterals() as $literal) { + // multiconflictrule is really a bunch of rules in one, so some may not have finished propagating yet + if ($rule instanceof MultiConflictRule && !$this->decisions->decided($literal)) { + continue; + } + + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + + if (isset($seen[abs($literal)])) { + continue; + } + $seen[abs($literal)] = true; + + $l = $this->decisions->decisionLevel($literal); + + if (1 === $l) { + $l1num++; + } elseif ($level === $l) { + $num++; + } else { + // not level1 or conflict level, add to new rule + $otherLearnedLiterals[] = $literal; + + if ($l > $ruleLevel) { + $ruleLevel = $l; + } + } + } + unset($literal); + + $l1retry = true; + while ($l1retry) { + $l1retry = false; + + if (0 === $num && 0 === --$l1num) { + // all level 1 literals done + break 2; + } + + while (true) { + if ($decisionId <= 0) { + throw new SolverBugException( + "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule." + ); + } + + $decisionId--; + + $decision = $this->decisions->atOffset($decisionId); + $literal = $decision[Decisions::DECISION_LITERAL]; + + if (isset($seen[abs($literal)])) { + break; + } + } + + unset($seen[abs($literal)]); + + if (0 !== $num && 0 === --$num) { + if ($literal < 0) { + $this->testFlagLearnedPositiveLiteral = true; + } + $learnedLiteral = -$literal; + + if (0 === $l1num) { + break 2; + } + + foreach ($otherLearnedLiterals as $otherLiteral) { + unset($seen[abs($otherLiteral)]); + } + // only level 1 marks left + $l1num++; + $l1retry = true; + } else { + $decision = $this->decisions->atOffset($decisionId); + $rule = $decision[Decisions::DECISION_REASON]; + + if ($rule instanceof MultiConflictRule) { + // there is only ever exactly one positive decision in a MultiConflictRule + foreach ($rule->getLiterals() as $ruleLiteral) { + if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) { + $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; + $l = $this->decisions->decisionLevel($ruleLiteral); + if (1 === $l) { + $l1num++; + } elseif ($level === $l) { + $num++; + } else { + // not level1 or conflict level, add to new rule + $otherLearnedLiterals[] = $ruleLiteral; + + if ($l > $ruleLevel) { + $ruleLevel = $l; + } + } + $seen[abs($ruleLiteral)] = true; + break; + } + } + + $l1retry = true; + } + } + } + + $decision = $this->decisions->atOffset($decisionId); + $rule = $decision[Decisions::DECISION_REASON]; + } + + $why = \count($this->learnedPool) - 1; + + if (null === $learnedLiteral) { + throw new SolverBugException( + "Did not find a learnable literal in analyzed rule $analyzedRule." + ); + } + + array_unshift($otherLearnedLiterals, $learnedLiteral); + $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why); + + return [$learnedLiteral, $ruleLevel, $newRule, $why]; + } + + /** + * @param array $ruleSeen + */ + private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, array &$ruleSeen): void + { + $why = spl_object_hash($conflictRule); + $ruleSeen[$why] = true; + + if ($conflictRule->getType() === RuleSet::TYPE_LEARNED) { + $learnedWhy = $this->learnedWhy[$why]; + $problemRules = $this->learnedPool[$learnedWhy]; + + foreach ($problemRules as $problemRule) { + if (!isset($ruleSeen[spl_object_hash($problemRule)])) { + $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen); + } + } + + return; + } + + if ($conflictRule->getType() === RuleSet::TYPE_PACKAGE) { + // package rules cannot be part of a problem + return; + } + + $problem->nextSection(); + $problem->addRule($conflictRule); + } + + private function analyzeUnsolvable(Rule $conflictRule): void + { + $problem = new Problem(); + $problem->addRule($conflictRule); + + $ruleSeen = []; + + $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen); + + $this->problems[] = $problem; + + $seen = []; + $literals = $conflictRule->getLiterals(); + + foreach ($literals as $literal) { + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + $seen[abs($literal)] = true; + } + + foreach ($this->decisions as $decision) { + $decisionLiteral = $decision[Decisions::DECISION_LITERAL]; + + // skip literals that are not in this rule + if (!isset($seen[abs($decisionLiteral)])) { + continue; + } + + $why = $decision[Decisions::DECISION_REASON]; + + $problem->addRule($why); + $this->analyzeUnsolvableRule($problem, $why, $ruleSeen); + + $literals = $why->getLiterals(); + foreach ($literals as $literal) { + // skip the one true literal + if ($this->decisions->satisfy($literal)) { + continue; + } + $seen[abs($literal)] = true; + } + } + } + + private function runSat(): void + { + $this->propagateIndex = 0; + + /* + * here's the main loop: + * 1) propagate new decisions (only needed once) + * 2) fulfill root requires/fixed packages + * 3) fulfill all unresolved rules + * 4) minimalize solution if we had choices + * if we encounter a problem, we rewind to a safe level and restart + * with step 1 + */ + + $level = 1; + $systemLevel = $level + 1; + + while (true) { + if (1 === $level) { + $conflictRule = $this->propagate($level); + if (null !== $conflictRule) { + $this->analyzeUnsolvable($conflictRule); + + return; + } + } + + // handle root require/fixed package rules + if ($level < $systemLevel) { + $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST); + foreach ($iterator as $rule) { + if ($rule->isEnabled()) { + $decisionQueue = []; + $noneSatisfied = true; + + foreach ($rule->getLiterals() as $literal) { + if ($this->decisions->satisfy($literal)) { + $noneSatisfied = false; + break; + } + if ($literal > 0 && $this->decisions->undecided($literal)) { + $decisionQueue[] = $literal; + } + } + + if ($noneSatisfied && \count($decisionQueue) > 0) { + // if any of the options in the decision queue are fixed, only use those + $prunedQueue = []; + foreach ($decisionQueue as $literal) { + if (isset($this->fixedMap[abs($literal)])) { + $prunedQueue[] = $literal; + } + } + if (\count($prunedQueue) > 0) { + $decisionQueue = $prunedQueue; + } + } + + if ($noneSatisfied && \count($decisionQueue) > 0) { + $oLevel = $level; + $level = $this->selectAndInstall($level, $decisionQueue, $rule); + + if (0 === $level) { + return; + } + if ($level <= $oLevel) { + break; + } + } + } + } + + $systemLevel = $level + 1; + + // root requires/fixed packages left + $iterator->next(); + if ($iterator->valid()) { + continue; + } + } + + if ($level < $systemLevel) { + $systemLevel = $level; + } + + $rulesCount = \count($this->rules); + $pass = 1; + + $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG); + for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { + if ($i === $rulesCount) { + if (1 === $pass) { + $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG); + } else { + $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG); + } + + $i = 0; + $pass++; + } + + $rule = $this->rules->ruleById[$i]; + $literals = $rule->getLiterals(); + + if ($rule->isDisabled()) { + continue; + } + + $decisionQueue = []; + + // make sure that + // * all negative literals are installed + // * no positive literal is installed + // i.e. the rule is not fulfilled and we + // just need to decide on the positive literals + // + foreach ($literals as $literal) { + if ($literal <= 0) { + if (!$this->decisions->decidedInstall($literal)) { + continue 2; // next rule + } + } else { + if ($this->decisions->decidedInstall($literal)) { + continue 2; // next rule + } + if ($this->decisions->undecided($literal)) { + $decisionQueue[] = $literal; + } + } + } + + // need to have at least 2 item to pick from + if (\count($decisionQueue) < 2) { + continue; + } + + $level = $this->selectAndInstall($level, $decisionQueue, $rule); + + if (0 === $level) { + return; + } + + // something changed, so look at all rules again + $rulesCount = \count($this->rules); + $n = -1; + } + + if ($level < $systemLevel) { + continue; + } + + // minimization step + if (\count($this->branches) > 0) { + $lastLiteral = null; + $lastLevel = null; + $lastBranchIndex = 0; + $lastBranchOffset = 0; + + for ($i = \count($this->branches) - 1; $i >= 0; $i--) { + [$literals, $l] = $this->branches[$i]; + + foreach ($literals as $offset => $literal) { + if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { + $lastLiteral = $literal; + $lastBranchIndex = $i; + $lastBranchOffset = $offset; + $lastLevel = $l; + } + } + } + + if ($lastLiteral !== null) { + assert($lastLevel !== null); + unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); + + $level = $lastLevel; + $this->revert($level); + + $why = $this->decisions->lastReason(); + + $level = $this->setPropagateLearn($level, $lastLiteral, $why); + + if ($level === 0) { + return; + } + + continue; + } + } + + break; + } + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php b/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php new file mode 100644 index 0000000..7ac7267 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php @@ -0,0 +1,27 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +/** + * @author Nils Adermann + */ +class SolverBugException extends \RuntimeException +{ + public function __construct(string $message) + { + parent::__construct( + $message."\nThis exception was most likely caused by a bug in Composer.\n". + "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n" + ); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php b/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php new file mode 100644 index 0000000..bd76e4f --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php @@ -0,0 +1,148 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Util\IniHelper; +use Composer\Repository\RepositorySet; + +/** + * @author Nils Adermann + * + * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode() + */ +class SolverProblemsException extends \RuntimeException +{ + public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; + + /** @var Problem[] */ + protected $problems; + /** @var array */ + protected $learnedPool; + + /** + * @param Problem[] $problems + * @param array $learnedPool + */ + public function __construct(array $problems, array $learnedPool) + { + $this->problems = $problems; + $this->learnedPool = $learnedPool; + + parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); + } + + public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string + { + $installedMap = $request->getPresentMap(true); + $missingExtensions = []; + $isCausedByLock = false; + + $problems = []; + foreach ($this->problems as $problem) { + $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool)."\n"; + + $missingExtensions = array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons())); + + $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool); + } + + $i = 1; + $text = "\n"; + foreach (array_unique($problems) as $problem) { + $text .= " Problem ".($i++).$problem; + } + + $hints = []; + if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) { + $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; + } + + if (\count($missingExtensions) > 0) { + $hints[] = $this->createExtensionHint($missingExtensions); + } + + if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) { + $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; + } + + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) { + $hints[] = "ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+."; + } + + if (!class_exists('PHPUnit\Framework\TestCase', false)) { + if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) { + $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2."; + } + } + + if (\count($hints) > 0) { + $text .= "\n" . implode("\n\n", $hints); + } + + return $text; + } + + /** + * @return Problem[] + */ + public function getProblems(): array + { + return $this->problems; + } + + /** + * @param string[] $missingExtensions + */ + private function createExtensionHint(array $missingExtensions): string + { + $paths = IniHelper::getAll(); + + if ('' === $paths[0]) { + if (count($paths) === 1) { + return ''; + } + + array_shift($paths); + } + + $ignoreExtensionsArguments = implode(" ", array_map(static function ($extension) { + return "--ignore-platform-req=$extension"; + }, array_unique($missingExtensions))); + + $text = "To enable extensions, verify that they are enabled in your .ini files:\n - "; + $text .= implode("\n - ", $paths); + $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode."; + $text .= "\nAlternatively, you can run Composer with `$ignoreExtensionsArguments` to temporarily ignore these required extensions."; + + return $text; + } + + /** + * @param Rule[][] $reasonSets + * @return string[] + */ + private function getExtensionProblems(array $reasonSets): array + { + $missingExtensions = []; + foreach ($reasonSets as $reasonSet) { + foreach ($reasonSet as $rule) { + $required = $rule->getRequiredPackage(); + if (null !== $required && 0 === strpos($required, 'ext-')) { + $missingExtensions[$required] = 1; + } + } + } + + return array_keys($missingExtensions); + } +} diff --git a/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php b/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php new file mode 100644 index 0000000..3443dd7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php @@ -0,0 +1,358 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Repository\PlatformRepository; +use Composer\DependencyResolver\Operation\OperationInterface; + +/** + * @author Nils Adermann + * @internal + */ +class Transaction +{ + /** + * @var OperationInterface[] + */ + protected $operations; + + /** + * Packages present at the beginning of the transaction + * @var PackageInterface[] + */ + protected $presentPackages; + + /** + * Package set resulting from this transaction + * @var array + */ + protected $resultPackageMap; + + /** + * @var array + */ + protected $resultPackagesByName = []; + + /** + * @param PackageInterface[] $presentPackages + * @param PackageInterface[] $resultPackages + */ + public function __construct(array $presentPackages, array $resultPackages) + { + $this->presentPackages = $presentPackages; + $this->setResultPackageMaps($resultPackages); + $this->operations = $this->calculateOperations(); + } + + /** + * @return OperationInterface[] + */ + public function getOperations(): array + { + return $this->operations; + } + + /** + * @param PackageInterface[] $resultPackages + */ + private function setResultPackageMaps(array $resultPackages): void + { + $packageSort = static function (PackageInterface $a, PackageInterface $b): int { + // sort alias packages by the same name behind their non alias version + if ($a->getName() === $b->getName()) { + if ($a instanceof AliasPackage !== $b instanceof AliasPackage) { + return $a instanceof AliasPackage ? -1 : 1; + } + // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter + return strcmp($b->getVersion(), $a->getVersion()); + } + + return strcmp($b->getName(), $a->getName()); + }; + + $this->resultPackageMap = []; + foreach ($resultPackages as $package) { + $this->resultPackageMap[spl_object_hash($package)] = $package; + foreach ($package->getNames() as $name) { + $this->resultPackagesByName[$name][] = $package; + } + } + + uasort($this->resultPackageMap, $packageSort); + foreach ($this->resultPackagesByName as $name => $packages) { + uasort($this->resultPackagesByName[$name], $packageSort); + } + } + + /** + * @return OperationInterface[] + */ + protected function calculateOperations(): array + { + $operations = []; + + $presentPackageMap = []; + $removeMap = []; + $presentAliasMap = []; + $removeAliasMap = []; + foreach ($this->presentPackages as $package) { + if ($package instanceof AliasPackage) { + $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + } else { + $presentPackageMap[$package->getName()] = $package; + $removeMap[$package->getName()] = $package; + } + } + + $stack = $this->getRootPackages(); + + $visited = []; + $processed = []; + + while (\count($stack) > 0) { + $package = array_pop($stack); + + if (isset($processed[spl_object_hash($package)])) { + continue; + } + + if (!isset($visited[spl_object_hash($package)])) { + $visited[spl_object_hash($package)] = true; + + $stack[] = $package; + if ($package instanceof AliasPackage) { + $stack[] = $package->getAliasOf(); + } else { + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + $stack[] = $require; + } + } + } + } elseif (!isset($processed[spl_object_hash($package)])) { + $processed[spl_object_hash($package)] = true; + + if ($package instanceof AliasPackage) { + $aliasKey = $package->getName().'::'.$package->getVersion(); + if (isset($presentAliasMap[$aliasKey])) { + unset($removeAliasMap[$aliasKey]); + } else { + $operations[] = new Operation\MarkAliasInstalledOperation($package); + } + } else { + if (isset($presentPackageMap[$package->getName()])) { + $source = $presentPackageMap[$package->getName()]; + + // do we need to update? + // TODO different for lock? + if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() || + $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() || + $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference() + ) { + $operations[] = new Operation\UpdateOperation($source, $package); + } + unset($removeMap[$package->getName()]); + } else { + $operations[] = new Operation\InstallOperation($package); + unset($removeMap[$package->getName()]); + } + } + } + } + + foreach ($removeMap as $name => $package) { + array_unshift($operations, new Operation\UninstallOperation($package)); + } + foreach ($removeAliasMap as $nameVersion => $package) { + $operations[] = new Operation\MarkAliasUninstalledOperation($package); + } + + $operations = $this->movePluginsToFront($operations); + // TODO fix this: + // we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls + $operations = $this->moveUninstallsToFront($operations); + + // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place? + /* + if ('update' === $opType) { + $targetPackage = $operation->getTargetPackage(); + if ($targetPackage->isDev()) { + $initialPackage = $operation->getInitialPackage(); + if ($targetPackage->getVersion() === $initialPackage->getVersion() + && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) + && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) + ) { + $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); + $this->io->writeError('', true, IOInterface::DEBUG); + + continue; + } + } + }*/ + + return $this->operations = $operations; + } + + /** + * Determine which packages in the result are not required by any other packages in it. + * + * These serve as a starting point to enumerate packages in a topological order despite potential cycles. + * If there are packages with a cycle on the top level the package with the lowest name gets picked + * + * @return array + */ + protected function getRootPackages(): array + { + $roots = $this->resultPackageMap; + + foreach ($this->resultPackageMap as $packageHash => $package) { + if (!isset($roots[$packageHash])) { + continue; + } + + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + if ($require !== $package) { + unset($roots[spl_object_hash($require)]); + } + } + } + } + + return $roots; + } + + /** + * @return PackageInterface[] + */ + protected function getProvidersInResult(Link $link): array + { + if (!isset($this->resultPackagesByName[$link->getTarget()])) { + return []; + } + + return $this->resultPackagesByName[$link->getTarget()]; + } + + /** + * Workaround: if your packages depend on plugins, we must be sure + * that those are installed / updated first; else it would lead to packages + * being installed multiple times in different folders, when running Composer + * twice. + * + * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, + * it at least fixes the symptoms and makes usage of composer possible (again) + * in such scenarios. + * + * @param OperationInterface[] $operations + * @return OperationInterface[] reordered operation list + */ + private function movePluginsToFront(array $operations): array + { + $dlModifyingPluginsNoDeps = []; + $dlModifyingPluginsWithDeps = []; + $dlModifyingPluginRequires = []; + $pluginsNoDeps = []; + $pluginsWithDeps = []; + $pluginRequires = []; + + foreach (array_reverse($operations, true) as $idx => $op) { + if ($op instanceof Operation\InstallOperation) { + $package = $op->getPackage(); + } elseif ($op instanceof Operation\UpdateOperation) { + $package = $op->getTargetPackage(); + } else { + continue; + } + + $extra = $package->getExtra(); + $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; + + // is this a downloads modifying plugin or a dependency of one? + if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) { + // get the package's requires, but filter out any platform requirements + $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { + return !PlatformRepository::isPlatformPackage($req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isDownloadsModifyingPlugin && 0 === \count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($dlModifyingPluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $dlModifyingPluginRequires = array_merge($dlModifyingPluginRequires, $requires); + // move the operation to the front + array_unshift($dlModifyingPluginsWithDeps, $op); + } + + unset($operations[$idx]); + continue; + } + + // is this package a plugin? + $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; + + // is this a plugin or a dependency of a plugin? + if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) { + // get the package's requires, but filter out any platform requirements + $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { + return !PlatformRepository::isPlatformPackage($req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isPlugin && 0 === \count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($pluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $pluginRequires = array_merge($pluginRequires, $requires); + // move the operation to the front + array_unshift($pluginsWithDeps, $op); + } + + unset($operations[$idx]); + } + } + + return array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations); + } + + /** + * Removals of packages should be executed before installations in + * case two packages resolve to the same path (due to custom installers) + * + * @param OperationInterface[] $operations + * @return OperationInterface[] reordered operation list + */ + private function moveUninstallsToFront(array $operations): array + { + $uninstOps = []; + foreach ($operations as $idx => $op) { + if ($op instanceof Operation\UninstallOperation || $op instanceof Operation\MarkAliasUninstalledOperation) { + $uninstOps[] = $op; + unset($operations[$idx]); + } + } + + return array_merge($uninstOps, $operations); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php b/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php new file mode 100644 index 0000000..ff132e2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php @@ -0,0 +1,224 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Util\Platform; +use Symfony\Component\Finder\Finder; +use React\Promise\PromiseInterface; +use Composer\DependencyResolver\Operation\InstallOperation; + +/** + * Base downloader for archives + * + * @author Kirill chEbba Chebunin + * @author Jordi Boggiano + * @author François Pluchino + */ +abstract class ArchiveDownloader extends FileDownloader +{ + /** + * @var array + */ + protected $cleanupExecuted = []; + + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + unset($this->cleanupExecuted[$package->getName()]); + + return parent::prepare($type, $package, $path, $prevPackage); + } + + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + $this->cleanupExecuted[$package->getName()] = true; + + return parent::cleanup($type, $package, $path, $prevPackage); + } + + /** + * @inheritDoc + * + * @throws \RuntimeException + * @throws \UnexpectedValueException + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); + } + + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the archive to be extracted. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { + $this->filesystem->emptyDirectory($path); + } + + do { + $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4)); + } while (is_dir($temporaryDir)); + + $this->addCleanupPath($package, $temporaryDir); + // avoid cleaning up $path if installing in "." for eg create-project as we can not + // delete the directory we are currently in on windows + if (!is_dir($path) || realpath($path) !== Platform::getCwd()) { + $this->addCleanupPath($package, $path); + } + + $this->filesystem->ensureDirectoryExists($temporaryDir); + $fileName = $this->getFileName($package, $path); + + $filesystem = $this->filesystem; + + $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) { + // remove cache if the file was corrupted + $this->clearLastCacheWrite($package); + + // clean up + $filesystem->removeDirectory($temporaryDir); + if (is_dir($path) && realpath($path) !== Platform::getCwd()) { + $filesystem->removeDirectory($path); + } + $this->removeCleanupPath($package, $temporaryDir); + $realpath = realpath($path); + if ($realpath !== false) { + $this->removeCleanupPath($package, $realpath); + } + }; + + try { + $promise = $this->extract($package, $fileName, $temporaryDir); + } catch (\Exception $e) { + $cleanup(); + throw $e; + } + + return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path): \React\Promise\PromiseInterface { + if (file_exists($fileName)) { + $filesystem->unlink($fileName); + } + + /** + * Returns the folder content, excluding .DS_Store + * + * @param string $dir Directory + * @return \SplFileInfo[] + */ + $getFolderContent = static function ($dir): array { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->notName('.DS_Store') + ->depth(0) + ->in($dir); + + return iterator_to_array($finder); + }; + $renameRecursively = null; + /** + * Renames (and recursively merges if needed) a folder into another one + * + * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure + * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would + * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/ + * + * @param string $from Directory + * @param string $to Directory + * @return void + */ + $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) { + $contentDir = $getFolderContent($from); + + // move files back out of the temp dir + foreach ($contentDir as $file) { + $file = (string) $file; + if (is_dir($to . '/' . basename($file))) { + if (!is_dir($file)) { + throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.'); + } + $renameRecursively($file, $to . '/' . basename($file)); + } else { + $filesystem->rename($file, $to . '/' . basename($file)); + } + } + }; + + $renameAsOne = false; + if (!file_exists($path)) { + $renameAsOne = true; + } elseif ($filesystem->isDirEmpty($path)) { + try { + if ($filesystem->removeDirectoryPhp($path)) { + $renameAsOne = true; + } + } catch (\RuntimeException $e) { + // ignore error, and simply do not renameAsOne + } + } + + $contentDir = $getFolderContent($temporaryDir); + $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir)); + + if ($renameAsOne) { + // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents + if ($singleDirAtTopLevel) { + $extractedDir = (string) reset($contentDir); + } else { + $extractedDir = $temporaryDir; + } + $filesystem->rename($extractedDir, $path); + } else { + // only one dir in the archive, extract its contents out of it + $from = $temporaryDir; + if ($singleDirAtTopLevel) { + $from = (string) reset($contentDir); + } + + $renameRecursively($from, $path); + } + + $promise = $filesystem->removeDirectoryAsync($temporaryDir); + + return $promise->then(function () use ($package, $path, $temporaryDir) { + $this->removeCleanupPath($package, $temporaryDir); + $this->removeCleanupPath($package, $path); + }); + }, static function ($e) use ($cleanup) { + $cleanup(); + + throw $e; + }); + } + + /** + * @inheritDoc + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + return ': Extracting archive'; + } + + /** + * Extract file to directory + * + * @param string $file Extracted file + * @param string $path Directory + * @phpstan-return PromiseInterface + * + * @throws \UnexpectedValueException If can not extract downloaded file to path + */ + abstract protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php b/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php new file mode 100644 index 0000000..857a7f0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * ChangeReport interface. + * + * @author Sascha Egerer + */ +interface ChangeReportInterface +{ + /** + * Checks for changes to the local copy + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null changes or null + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php b/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php new file mode 100644 index 0000000..e4adfdf --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DownloadManager.php @@ -0,0 +1,447 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Exception\IrrecoverableDownloadException; +use React\Promise\PromiseInterface; + +/** + * Downloaders manager. + * + * @author Konstantin Kudryashov + */ +class DownloadManager +{ + /** @var IOInterface */ + private $io; + /** @var bool */ + private $preferDist = false; + /** @var bool */ + private $preferSource; + /** @var array */ + private $packagePreferences = []; + /** @var Filesystem */ + private $filesystem; + /** @var array */ + private $downloaders = []; + + /** + * Initializes download manager. + * + * @param IOInterface $io The Input Output Interface + * @param bool $preferSource prefer downloading from source + * @param Filesystem|null $filesystem custom Filesystem object + */ + public function __construct(IOInterface $io, bool $preferSource = false, ?Filesystem $filesystem = null) + { + $this->io = $io; + $this->preferSource = $preferSource; + $this->filesystem = $filesystem ?: new Filesystem(); + } + + /** + * Makes downloader prefer source installation over the dist. + * + * @param bool $preferSource prefer downloading from source + * @return DownloadManager + */ + public function setPreferSource(bool $preferSource): self + { + $this->preferSource = $preferSource; + + return $this; + } + + /** + * Makes downloader prefer dist installation over the source. + * + * @param bool $preferDist prefer downloading from dist + * @return DownloadManager + */ + public function setPreferDist(bool $preferDist): self + { + $this->preferDist = $preferDist; + + return $this; + } + + /** + * Sets fine tuned preference settings for package level source/dist selection. + * + * @param array $preferences array of preferences by package patterns + * + * @return DownloadManager + */ + public function setPreferences(array $preferences): self + { + $this->packagePreferences = $preferences; + + return $this; + } + + /** + * Sets installer downloader for a specific installation type. + * + * @param string $type installation type + * @param DownloaderInterface $downloader downloader instance + * @return DownloadManager + */ + public function setDownloader(string $type, DownloaderInterface $downloader): self + { + $type = strtolower($type); + $this->downloaders[$type] = $downloader; + + return $this; + } + + /** + * Returns downloader for a specific installation type. + * + * @param string $type installation type + * @throws \InvalidArgumentException if downloader for provided type is not registered + */ + public function getDownloader(string $type): DownloaderInterface + { + $type = strtolower($type); + if (!isset($this->downloaders[$type])) { + throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders)))); + } + + return $this->downloaders[$type]; + } + + /** + * Returns downloader for already installed package. + * + * @param PackageInterface $package package instance + * @throws \InvalidArgumentException if package has no installation source specified + * @throws \LogicException if specific downloader used to load package with + * wrong type + */ + public function getDownloaderForPackage(PackageInterface $package): ?DownloaderInterface + { + $installationSource = $package->getInstallationSource(); + + if ('metapackage' === $package->getType()) { + return null; + } + + if ('dist' === $installationSource) { + $downloader = $this->getDownloader($package->getDistType()); + } elseif ('source' === $installationSource) { + $downloader = $this->getDownloader($package->getSourceType()); + } else { + throw new \InvalidArgumentException( + 'Package '.$package.' does not have an installation source set' + ); + } + + if ($installationSource !== $downloader->getInstallationSource()) { + throw new \LogicException(sprintf( + 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', + get_class($downloader), + $downloader->getInstallationSource(), + $installationSource, + $package + )); + } + + return $downloader; + } + + public function getDownloaderType(DownloaderInterface $downloader): string + { + return array_search($downloader, $this->downloaders); + } + + /** + * Downloads package into target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if package have no urls to download from + * @throws \RuntimeException + */ + public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $this->filesystem->ensureDirectoryExists(dirname($targetDir)); + + $sources = $this->getAvailableSources($package, $prevPackage); + + $io = $this->io; + + $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) { + $source = array_shift($sources); + if ($retry) { + $io->writeError(' Now trying to download from ' . $source . ''); + } + $package->setInstallationSource($source); + + $downloader = $this->getDownloaderForPackage($package); + if (!$downloader) { + return \React\Promise\resolve(null); + } + + $handleError = static function ($e) use ($sources, $source, $package, $io, $download) { + if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) { + if (!$sources) { + throw $e; + } + + $io->writeError( + ' Failed to download '. + $package->getPrettyName(). + ' from ' . $source . ': '. + $e->getMessage().'' + ); + + return $download(true); + } + + throw $e; + }; + + try { + $result = $downloader->download($package, $targetDir, $prevPackage); + } catch (\Exception $e) { + return $handleError($e); + } + + $res = $result->then(static function ($res) { + return $res; + }, $handleError); + + return $res; + }; + + return $download(); + } + + /** + * Prepares an operation execution + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + */ + public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->prepare($type, $package, $targetDir, $prevPackage); + } + + return \React\Promise\resolve(null); + } + + /** + * Installs package into target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if package have no urls to download from + * @throws \RuntimeException + */ + public function install(PackageInterface $package, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->install($package, $targetDir); + } + + return \React\Promise\resolve(null); + } + + /** + * Updates package from initial to target version. + * + * @param PackageInterface $initial initial package version + * @param PackageInterface $target target package version + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + * + * @throws \InvalidArgumentException if initial package is not installed + */ + public function update(PackageInterface $initial, PackageInterface $target, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($target); + $initialDownloader = $this->getDownloaderForPackage($initial); + + // no downloaders present means update from metapackage to metapackage, nothing to do + if (!$initialDownloader && !$downloader) { + return \React\Promise\resolve(null); + } + + // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed + if (!$downloader) { + return $initialDownloader->remove($initial, $targetDir); + } + + $initialType = $this->getDownloaderType($initialDownloader); + $targetType = $this->getDownloaderType($downloader); + if ($initialType === $targetType) { + try { + return $downloader->update($initial, $target, $targetDir); + } catch (\RuntimeException $e) { + if (!$this->io->isInteractive()) { + throw $e; + } + $this->io->writeError(' Update failed ('.$e->getMessage().')'); + if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [yes]? ')) { + throw $e; + } + } + } + + // if downloader type changed, or update failed and user asks for reinstall, + // we wipe the dir and do a new install instead of updating it + $promise = $initialDownloader->remove($initial, $targetDir); + + return $promise->then(function ($res) use ($target, $targetDir): PromiseInterface { + return $this->install($target, $targetDir); + }); + } + + /** + * Removes package from target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @phpstan-return PromiseInterface + */ + public function remove(PackageInterface $package, string $targetDir): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->remove($package, $targetDir); + } + + return \React\Promise\resolve(null); + } + + /** + * Cleans up a failed operation + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * @phpstan-return PromiseInterface + */ + public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface + { + $targetDir = $this->normalizeTargetDir($targetDir); + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->cleanup($type, $package, $targetDir, $prevPackage); + } + + return \React\Promise\resolve(null); + } + + /** + * Determines the install preference of a package + * + * @param PackageInterface $package package instance + */ + protected function resolvePackageInstallPreference(PackageInterface $package): string + { + foreach ($this->packagePreferences as $pattern => $preference) { + $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i'; + if (Preg::isMatch($pattern, $package->getName())) { + if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) { + return 'dist'; + } + + return 'source'; + } + } + + return $package->isDev() ? 'source' : 'dist'; + } + + /** + * @return string[] + * @phpstan-return array<'dist'|'source'>&non-empty-array + */ + private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array + { + $sourceType = $package->getSourceType(); + $distType = $package->getDistType(); + + // add source before dist by default + $sources = []; + if ($sourceType) { + $sources[] = 'source'; + } + if ($distType) { + $sources[] = 'dist'; + } + + if (empty($sources)) { + throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); + } + + if ( + $prevPackage + // if we are updating, we want to keep the same source as the previously installed package (if available in the new one) + && in_array($prevPackage->getInstallationSource(), $sources, true) + // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over + && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev()) + ) { + $prevSource = $prevPackage->getInstallationSource(); + usort($sources, static function ($a, $b) use ($prevSource): int { + return $a === $prevSource ? -1 : 1; + }); + + return $sources; + } + + // reverse sources in case dist is the preferred source for this package + if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { + $sources = array_reverse($sources); + } + + return $sources; + } + + /** + * Downloaders expect a /path/to/dir without trailing slash + * + * If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them + */ + private function normalizeTargetDir(string $dir): string + { + if ($dir === '\\' || $dir === '/') { + return $dir; + } + + return rtrim($dir, '\\/'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php new file mode 100644 index 0000000..8cb86cd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php @@ -0,0 +1,99 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +/** + * Downloader interface. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface DownloaderInterface +{ + /** + * Returns installation source (either source or dist). + * + * @return string "source" or "dist" + */ + public function getInstallationSource(): string; + + /** + * This should do any network-related tasks to prepare for an upcoming install/update + * + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @phpstan-return PromiseInterface + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Installs specific package into specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function install(PackageInterface $package, string $path): PromiseInterface; + + /** + * Updates specific package in specific folder from initial to target version. + * + * @param PackageInterface $initial initial package + * @param PackageInterface $target updated package + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface; + + /** + * Removes specific package from specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @phpstan-return PromiseInterface + */ + public function remove(PackageInterface $package, string $path): PromiseInterface; + + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages, either after install/update/uninstall is complete, + * or if any package failed any operation. This is to give all installers a change to cleanup things + * they did previously, so you need to keep track of changes applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @phpstan-return PromiseInterface + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php new file mode 100644 index 0000000..6e5b67c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * DVCS Downloader interface. + * + * @author James Titcumb + */ +interface DvcsDownloaderInterface +{ + /** + * Checks for unpushed changes to a current branch + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null changes or null + */ + public function getUnpushedChanges(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php b/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php new file mode 100644 index 0000000..5f3b242 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FileDownloader.php @@ -0,0 +1,545 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Exception\IrrecoverableDownloadException; +use Composer\Package\Comparer\Comparer; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\Package\PackageInterface; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PostFileDownloadEvent; +use Composer\Plugin\PreFileDownloadEvent; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Filesystem; +use Composer\Util\Http\Response; +use Composer\Util\Platform; +use Composer\Util\Silencer; +use Composer\Util\HttpDownloader; +use Composer\Util\Url as UrlUtil; +use Composer\Util\ProcessExecutor; +use React\Promise\PromiseInterface; + +/** + * Base downloader for files + * + * @author Kirill chEbba Chebunin + * @author Jordi Boggiano + * @author François Pluchino + * @author Nils Adermann + */ +class FileDownloader implements DownloaderInterface, ChangeReportInterface +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var Filesystem */ + protected $filesystem; + /** @var ?Cache */ + protected $cache; + /** @var ?EventDispatcher */ + protected $eventDispatcher; + /** @var ProcessExecutor */ + protected $process; + /** + * @var array + * @private + * @internal + */ + public static $downloadMetadata = []; + /** + * Collects response headers when running on GH Actions + * + * @see https://github.com/composer/composer/issues/11148 + * @var array> + * @private + * @internal + */ + public static $responseHeaders = []; + + /** + * @var array Map of package name to cache key + */ + private $lastCacheWrites = []; + /** @var array Map of package name to list of paths */ + private $additionalCleanupPaths = []; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param HttpDownloader $httpDownloader The remote filesystem + * @param EventDispatcher $eventDispatcher The event dispatcher + * @param Cache $cache Cache instance + * @param Filesystem $filesystem The filesystem + */ + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?Cache $cache = null, ?Filesystem $filesystem = null, ?ProcessExecutor $process = null) + { + $this->io = $io; + $this->config = $config; + $this->eventDispatcher = $eventDispatcher; + $this->httpDownloader = $httpDownloader; + $this->cache = $cache; + $this->process = $process ?? new ProcessExecutor($io); + $this->filesystem = $filesystem ?? new Filesystem($this->process); + + if ($this->cache !== null && $this->cache->gcIsNecessary()) { + $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE); + $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); + } + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): string + { + return 'dist'; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + if (null === $package->getDistUrl()) { + throw new \InvalidArgumentException('The given package is missing url information'); + } + + $cacheKeyGenerator = static function (PackageInterface $package, $key): string { + $cacheKey = hash('sha1', $key); + + return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); + }; + + $retries = 3; + $distUrls = $package->getDistUrls(); + /** @var array $urls */ + $urls = []; + foreach ($distUrls as $index => $url) { + $processedUrl = $this->processUrl($package, $url); + $urls[$index] = [ + 'base' => $url, + 'processed' => $processedUrl, + // we use the complete download url here to avoid conflicting entries + // from different packages, which would potentially allow a given package + // in a third party repo to pre-populate the cache for the same package in + // packagist for example. + 'cacheKey' => $cacheKeyGenerator($package, $processedUrl), + ]; + } + assert(count($urls) > 0); + + $fileName = $this->getFileName($package, $path); + $this->filesystem->ensureDirectoryExists($path); + $this->filesystem->ensureDirectoryExists(dirname($fileName)); + + $accept = null; + $reject = null; + $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) { + $url = reset($urls); + $index = key($urls); + + if ($this->eventDispatcher !== null) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + if ($preFileDownloadEvent->getCustomCacheKey() !== null) { + $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey()); + } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) { + $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl()); + } + $url['processed'] = $preFileDownloadEvent->getProcessedUrl(); + } + + $urls[$index] = $url; + + $checksum = $package->getDistSha1Checksum(); + $cacheKey = $url['cacheKey']; + + // use from cache if it is present and has a valid checksum or we have no checksum to check against + if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { + if ($output) { + $this->io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", true, IOInterface::VERY_VERBOSE); + } + // mark the file as having been written in cache even though it is only read from cache, so that if + // the cache is corrupt the archive will be deleted and the next attempt will re-download it + // see https://github.com/composer/composer/issues/10028 + if (!$this->cache->isReadOnly()) { + $this->lastCacheWrites[$package->getName()] = $cacheKey; + } + $result = \React\Promise\resolve($fileName); + } else { + if ($output) { + $this->io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + } + + $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) + ->then($accept, $reject); + } + + return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string { + // in case of retry, the first call's Promise chain finally calls this twice at the end, + // once with $result being the returned $fileName from $accept, and then once for every + // failed request with a null result, which can be skipped. + if (null === $result) { + return $fileName; + } + + if (!file_exists($fileName)) { + throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the' + .' directory is writable and you have internet connectivity'); + } + + if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) { + throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')'); + } + + if ($this->eventDispatcher !== null) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + return $fileName; + }); + }; + + $accept = function (Response $response) use ($package, $fileName, &$urls): string { + $url = reset($urls); + $cacheKey = $url['cacheKey']; + $fileSize = @filesize($fileName); + if (false === $fileSize) { + $fileSize = $response->getHeader('Content-Length') ?? '?'; + } + FileDownloader::$downloadMetadata[$package->getName()] = $fileSize; + + if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { + FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders(); + } + + if ($this->cache !== null && !$this->cache->isReadOnly()) { + $this->lastCacheWrites[$package->getName()] = $cacheKey; + $this->cache->copyFrom($cacheKey, $fileName); + } + + $response->collect(); + + return $fileName; + }; + + $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) { + // clean up + if (file_exists($fileName)) { + $this->filesystem->unlink($fileName); + } + $this->clearLastCacheWrite($package); + + if ($e instanceof IrrecoverableDownloadException) { + throw $e; + } + + if ($e instanceof MaxFileSizeExceededException) { + throw $e; + } + + if ($e instanceof TransportException) { + // if we got an http response with a proper code, then requesting again will probably not help, abort + if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) { + $retries = 0; + } + } + + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + $retries = 0; + $urls = []; + } + + if ($retries > 0) { + usleep(500000); + $retries--; + + return $download(); + } + + array_shift($urls); + if (\count($urls) > 0) { + if ($this->io->isDebug()) { + $this->io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); + $this->io->writeError(' Trying the next URL for '.$package->getName()); + } else { + $this->io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); + } + + $retries = 3; + usleep(100000); + + return $download(); + } + + throw $e; + }; + + return $download(); + } + + /** + * @inheritDoc + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + $fileName = $this->getFileName($package, $path); + if (file_exists($fileName)) { + $this->filesystem->unlink($fileName); + } + + $dirsToCleanUp = [ + $path, + $this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0], + $this->config->get('vendor-dir').'/composer/', + $this->config->get('vendor-dir'), + ]; + + if (isset($this->additionalCleanupPaths[$package->getName()])) { + foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) { + $this->filesystem->remove($pathToClean); + } + } + + foreach ($dirsToCleanUp as $dir) { + if (is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== Platform::getCwd()) { + $this->filesystem->removeDirectoryPhp($dir); + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package)); + } + + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the file to be installed. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { + $this->filesystem->emptyDirectory($path); + } + $this->filesystem->ensureDirectoryExists($path); + $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME)); + + // Single files can not have a mode set like files in archives + // so we make sure if the file is a binary that it is executable + foreach ($package->getBinaries() as $bin) { + if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { + Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); + } + } + + return \React\Promise\resolve(null); + } + + /** + * @param PATHINFO_EXTENSION|PATHINFO_BASENAME $component + */ + protected function getDistPath(PackageInterface $package, int $component): string + { + return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component); + } + + protected function clearLastCacheWrite(PackageInterface $package): void + { + if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) { + $this->cache->remove($this->lastCacheWrites[$package->getName()]); + unset($this->lastCacheWrites[$package->getName()]); + } + } + + protected function addCleanupPath(PackageInterface $package, string $path): void + { + $this->additionalCleanupPaths[$package->getName()][] = $path; + } + + protected function removeCleanupPath(PackageInterface $package, string $path): void + { + if (isset($this->additionalCleanupPaths[$package->getName()])) { + $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true); + if (false !== $idx) { + unset($this->additionalCleanupPaths[$package->getName()][$idx]); + } + } + } + + /** + * @inheritDoc + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface + { + $this->io->writeError(" - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path)); + + $promise = $this->remove($initial, $path, false); + + return $promise->then(function () use ($target, $path): PromiseInterface { + return $this->install($target, $path, false); + }); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package)); + } + $promise = $this->filesystem->removeDirectoryAsync($path); + + return $promise->then(static function ($result) use ($path): void { + if (!$result) { + throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); + } + }); + } + + /** + * Gets file name for specific package + * + * @param PackageInterface $package package instance + * @param string $path download path + * @return string file name + */ + protected function getFileName(PackageInterface $package, string $path): string + { + $extension = $this->getDistPath($package, PATHINFO_EXTENSION); + if ($extension === '') { + $extension = $package->getDistType(); + } + + return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.'); + } + + /** + * Gets appendix message to add to the "- Upgrading x" string being output on update + * + * @param PackageInterface $package package instance + * @param string $path download path + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + return ''; + } + + /** + * Process the download url + * + * @param PackageInterface $package package instance + * @param non-empty-string $url download url + * @throws \RuntimeException If any problem with the url + * @return non-empty-string url + */ + protected function processUrl(PackageInterface $package, string $url): string + { + if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) { + throw new \RuntimeException('You must enable the openssl extension to download files via https'); + } + + if ($package->getDistReference() !== null) { + $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); + } + + return $url; + } + + /** + * @inheritDoc + * @throws \RuntimeException + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + $prevIO = $this->io; + + $this->io = new NullIO; + $this->io->loadConfiguration($this->config); + $e = null; + $output = ''; + + $targetDir = Filesystem::trimTrailingSlash($path); + try { + if (is_dir($targetDir.'_compare')) { + $this->filesystem->removeDirectory($targetDir.'_compare'); + } + + $promise = $this->download($package, $targetDir.'_compare', null, false); + $promise->then(null, function ($ex) use (&$e) { + $e = $ex; + }); + $this->httpDownloader->wait(); + if ($e !== null) { + throw $e; + } + $promise = $this->install($package, $targetDir.'_compare', false); + $promise->then(null, function ($ex) use (&$e) { + $e = $ex; + }); + $this->process->wait(); + if ($e !== null) { + throw $e; + } + + $comparer = new Comparer(); + $comparer->setSource($targetDir.'_compare'); + $comparer->setUpdate($targetDir); + $comparer->doCompare(); + $output = $comparer->getChangedAsString(true); + $this->filesystem->removeDirectory($targetDir.'_compare'); + } catch (\Exception $e) { + } + + $this->io = $prevIO; + + if ($e !== null) { + if ($this->io->isDebug()) { + throw $e; + } + + return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage(); + } + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php b/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php new file mode 100644 index 0000000..8dbc831 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FilesystemException.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +/** + * Exception thrown when issues exist on local filesystem + * + * @author Javier Spagnoletti + */ +class FilesystemException extends \Exception +{ + public function __construct(string $message = '', int $code = 0, ?\Exception $previous = null) + { + parent::__construct("Filesystem exception: \n".$message, $code, $previous); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php b/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php new file mode 100644 index 0000000..60c6ed4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php @@ -0,0 +1,129 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use RuntimeException; + +/** + * @author BohwaZ + */ +class FossilDownloader extends VcsDownloader +{ + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + $repoFile = $path . '.fossil'; + $realPath = Platform::realpath($path); + + $this->io->writeError("Cloning ".$package->getSourceReference()); + $this->execute(['fossil', 'clone', '--', $url, $repoFile]); + $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath); + $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + $this->io->writeError(" Updating to ".$target->getSourceReference()); + + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $realPath = Platform::realpath($path); + $this->execute(['fossil', 'pull'], $realPath); + $this->execute(['fossil', 'up', (string) $target->getSourceReference()], $realPath); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path)); + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output); + + $log = ''; + $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/'; + + foreach ($this->process->splitLines($output) as $line) { + if (Preg::isMatch($match, $line)) { + break; + } + $log .= $line; + } + + return $log; + } + + /** + * @param non-empty-list $command + * @throws \RuntimeException + */ + private function execute(array $command, ?string $cwd = null, ?string &$output = null): void + { + if (0 !== $this->process->execute($command, $output, $cwd)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php b/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php new file mode 100644 index 0000000..e54d954 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php @@ -0,0 +1,634 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Git as GitUtil; +use Composer\Util\Url; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Cache; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface +{ + /** + * @var bool[] + * @phpstan-var array + */ + private $hasStashedChanges = []; + /** + * @var bool[] + * @phpstan-var array + */ + private $hasDiscardedChanges = []; + /** + * @var GitUtil + */ + private $gitUtil; + /** + * @var array + * @phpstan-var array> + */ + private $cachedPackages = []; + + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) + { + parent::__construct($io, $config, $process, $fs); + $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem); + } + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + // Do not create an extra local cache when repository is already local + if (Filesystem::isLocalPath($url)) { + return \React\Promise\resolve(null); + } + + GitUtil::cleanEnv(); + + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $gitVersion = GitUtil::getVersion($this->process); + + // --dissociate option is only available since git 2.3.0-rc0 + if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { + $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); + $this->io->writeError(sprintf(' Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG); + $ref = $package->getSourceReference(); + if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) { + $this->cachedPackages[$package->getId()][$ref] = true; + } + } elseif (null === $gitVersion) { + throw new \RuntimeException('git was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + GitUtil::cleanEnv(); + $path = $this->normalizePath($path); + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $ref = $package->getSourceReference(); + + if (!empty($this->cachedPackages[$package->getId()][$ref])) { + $msg = "Cloning ".$this->getShortHash($ref).' from cache'; + + $cloneFlags = ['--dissociate', '--reference', $cachePath]; + $transportOptions = $package->getTransportOptions(); + if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) { + $cloneFlags = []; + } + + $commands = [ + array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags), + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'], + ]; + } else { + $msg = "Cloning ".$this->getShortHash($ref); + $commands = [ + array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]), + ['git', 'remote', 'add', 'composer', '--', '%url%'], + ['git', 'fetch', 'composer'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'], + ]; + if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); + } + } + + $this->io->writeError($msg); + + $this->gitUtil->runCommands($commands, $url, $path, true); + + $sourceUrl = $package->getSourceUrl(); + if ($url !== $sourceUrl && $sourceUrl !== null) { + $this->updateOriginUrl($path, $sourceUrl); + } else { + $this->setPushUrl($path, $url); + } + + if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) { + if ($package->getDistReference() === $package->getSourceReference()) { + $package->setDistReference($newRef); + } + $package->setSourceReference($newRef); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + GitUtil::cleanEnv(); + $path = $this->normalizePath($path); + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; + $ref = $target->getSourceReference(); + + if (!empty($this->cachedPackages[$target->getId()][$ref])) { + $msg = "Checking out ".$this->getShortHash($ref).' from cache'; + $remoteUrl = $cachePath; + } else { + $msg = "Checking out ".$this->getShortHash($ref); + $remoteUrl = '%url%'; + if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); + } + } + + $this->io->writeError($msg); + + if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) { + $commands = [ + ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl], + ['git', 'fetch', 'composer'], + ['git', 'fetch', '--tags', 'composer'], + ]; + + $this->gitUtil->runCommands($commands, $url, $path); + } + + $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%']; + $this->gitUtil->runCommands([$command], $url, $path); + + if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) { + if ($target->getDistReference() === $target->getSourceReference()) { + $target->setDistReference($newRef); + } + $target->setSourceReference($newRef); + } + + $updateOriginUrl = false; + if ( + 0 === $this->process->execute(['git', 'remote', '-v'], $output, $path) + && Preg::isMatch('{^origin\s+(?P\S+)}m', $output, $originMatch) + && Preg::isMatch('{^composer\s+(?P\S+)}m', $output, $composerMatch) + ) { + if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) { + $updateOriginUrl = true; + } + } + if ($updateOriginUrl && $target->getSourceUrl() !== null) { + $this->updateOriginUrl($path, $target->getSourceUrl()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + GitUtil::cleanEnv(); + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $command = ['git', 'status', '--porcelain', '--untracked-files=no']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + public function getUnpushedChanges(PackageInterface $package, string $path): ?string + { + GitUtil::cleanEnv(); + $path = $this->normalizePath($path); + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $command = ['git', 'show-ref', '--head', '-d']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $refs = trim($output); + if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) { + // could not match the HEAD for some reason + return null; + } + + $headRef = $match[1]; + if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) { + // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this + return null; + } + + $candidateBranches = $matches[1]; + // use the first match as branch name for now + $branch = $candidateBranches[0]; + $unpushedChanges = null; + $branchNotFoundError = false; + + // do two passes, as if we find anything we want to fetch and then re-try + for ($i = 0; $i <= 1; $i++) { + $remoteBranches = []; + + // try to find matching branch names in remote repos + foreach ($candidateBranches as $candidate) { + if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/'.preg_quote($candidate).')$}mi', $refs, $matches)) { + foreach ($matches[1] as $match) { + $branch = $candidate; + $remoteBranches[] = $match; + } + break; + } + } + + // if it doesn't exist, then we assume it is an unpushed branch + // this is bad as we have no reference point to do a diff so we just bail listing + // the branch as being unpushed + if (count($remoteBranches) === 0) { + $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed'; + $branchNotFoundError = true; + } else { + // if first iteration found no remote branch but it has now found some, reset $unpushedChanges + // so we get the real diff output no matter its length + if ($branchNotFoundError) { + $unpushedChanges = null; + } + foreach ($remoteBranches as $remoteBranch) { + $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + $output = trim($output); + // keep the shortest diff from all remote branches we compare against + if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) { + $unpushedChanges = $output; + } + } + } + + // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date + // remotes and then try again as outdated remotes can sometimes cause false-positives + if ($unpushedChanges && $i === 0) { + $this->process->execute(['git', 'fetch', '--all'], $output, $path); + + // update list of refs after fetching + $command = ['git', 'show-ref', '--head', '-d']; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + $refs = trim($output); + } + + // abort after first pass if we didn't find anything + if (!$unpushedChanges) { + break; + } + } + + return $unpushedChanges; + } + + /** + * @inheritDoc + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + GitUtil::cleanEnv(); + $path = $this->normalizePath($path); + + $unpushed = $this->getUnpushedChanges($package, $path); + if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) { + throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed); + } + + if (null === ($changes = $this->getLocalChanges($package, $path))) { + return \React\Promise\resolve(null); + } + + if (!$this->io->isInteractive()) { + $discardChanges = $this->config->get('discard-changes'); + if (true === $discardChanges) { + return $this->discardChanges($path); + } + if ('stash' === $discardChanges) { + if (!$update) { + return parent::cleanChanges($package, $path, $update); + } + + return $this->stashChanges($path); + } + + return parent::cleanChanges($package, $path, $update); + } + + $changes = array_map(static function ($elem): string { + return ' '.$elem; + }, Preg::split('{\s*\r?\n\s*}', $changes)); + $this->io->writeError(' '.$package->getPrettyName().' has modified files:'); + $this->io->writeError(array_slice($changes, 0, 10)); + if (count($changes) > 10) { + $this->io->writeError(' ' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list'); + } + + while (true) { + switch ($this->io->ask(' Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]? ', '?')) { + case 'y': + $this->discardChanges($path); + break 2; + + case 's': + if (!$update) { + goto help; + } + + $this->stashChanges($path); + break 2; + + case 'n': + throw new \RuntimeException('Update aborted'); + + case 'v': + $this->io->writeError($changes); + break; + + case 'd': + $this->viewDiff($path); + break; + + case '?': + default: + help : + $this->io->writeError([ + ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), + ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', + ' v - view modified files', + ' d - view local modifications (diff)', + ]); + if ($update) { + $this->io->writeError(' s - stash changes and try to reapply them after the update'); + } + $this->io->writeError(' ? - print help'); + break; + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function reapplyChanges(string $path): void + { + $path = $this->normalizePath($path); + if (!empty($this->hasStashedChanges[$path])) { + unset($this->hasStashedChanges[$path]); + $this->io->writeError(' Re-applying stashed changes'); + if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) { + throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); + } + } + + unset($this->hasDiscardedChanges[$path]); + } + + /** + * Updates the given path to the given commit ref + * + * @throws \RuntimeException + * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found + */ + protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string + { + $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : []; + + // This uses the "--" sequence to separate branch from file parameters. + // + // Otherwise git tries the branch name as well as file name. + // If the non-existent branch is actually the name of a file, the file + // is checked out. + + $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); + + /** + * @var \Closure(non-empty-list): bool $execute + * @phpstan-ignore varTag.nativeType + */ + $execute = function (array $command) use (&$output, $path) { + /** @var non-empty-list $command */ + $output = ''; + + return 0 === $this->process->execute($command, $output, $path); + }; + + $branches = null; + if ($execute(['git', 'branch', '-r'])) { + $branches = $output; + } + + // check whether non-commitish are branches or tags, and fetch branches with the remote name + $gitRef = $reference; + if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference) + && null !== $branches + && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches) + ) { + $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']); + $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--']; + + if ($execute($command1) && $execute($command2)) { + return null; + } + } + + // try to checkout branch by name and then reset it so it's on the proper branch name + if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) { + // add 'v' in front of the branch if it was stripped when generating the pretty name + if (null !== $branches && !Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) { + $branch = 'v' . $branch; + } + + $command = ['git', 'checkout', $branch, '--']; + $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']); + $resetCommand = ['git', 'reset', '--hard', $reference, '--']; + + if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) { + return null; + } + } + + $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']); + $command2 = ['git', 'reset', '--hard', $gitRef, '--']; + if ($execute($command1) && $execute($command2)) { + return null; + } + + $exceptionExtra = ''; + + // reference was not found (prints "fatal: reference is not a tree: $ref") + if (false !== strpos($this->process->getErrorOutput(), $reference)) { + $this->io->writeError(' '.$reference.' is gone (history was rewritten?)'); + $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.'; + } + + $command = implode(' ', $command1). ' && '.implode(' ', $command2); + + throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra)); + } + + protected function updateOriginUrl(string $path, string $url): void + { + $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path); + $this->setPushUrl($path, $url); + } + + protected function setPushUrl(string $path, string $url): void + { + // set push url for github projects + if (Preg::isMatch('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) { + $protocols = $this->config->get('github-protocols'); + $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git'; + if (!in_array('ssh', $protocols, true)) { + $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git'; + } + $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl]; + $this->process->execute($cmd, $ignoredOutput, $path); + } + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $path = $this->normalizePath($path); + $command = array_merge(['git', 'log', $fromReference.'..'.$toReference, '--pretty=format:%h - %an: %s'], GitUtil::getNoShowSignatureFlags($this->process)); + + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return $output; + } + + /** + * @phpstan-return PromiseInterface + * @throws \RuntimeException + */ + protected function discardChanges(string $path): PromiseInterface + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$output); + } + if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$output); + } + + $this->hasDiscardedChanges[$path] = true; + + return \React\Promise\resolve(null); + } + + /** + * @phpstan-return PromiseInterface + * @throws \RuntimeException + */ + protected function stashChanges(string $path): PromiseInterface + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) { + throw new \RuntimeException("Could not stash changes\n\n:".$output); + } + + $this->hasStashedChanges[$path] = true; + + return \React\Promise\resolve(null); + } + + /** + * @throws \RuntimeException + */ + protected function viewDiff(string $path): void + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) { + throw new \RuntimeException("Could not view diff\n\n:".$output); + } + + $this->io->writeError($output); + } + + protected function normalizePath(string $path): string + { + if (Platform::isWindows() && strlen($path) > 0) { + $basePath = $path; + $removed = []; + + while (!is_dir($basePath) && $basePath !== '\\') { + array_unshift($removed, basename($basePath)); + $basePath = dirname($basePath); + } + + if ($basePath === '\\') { + return $path; + } + + $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/'); + } + + return $path; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + $path = $this->normalizePath($path); + + return is_dir($path.'/.git'); + } + + protected function getShortHash(string $reference): string + { + if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) { + return substr($reference, 0, 10); + } + + return $reference; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php b/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php new file mode 100644 index 0000000..0102198 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php @@ -0,0 +1,67 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; + +/** + * GZip archive downloader. + * + * @author Pavel Puchkin + */ +class GzipDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $filename = pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME); + $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename; + + // Try to use gunzip on *nix + if (!Platform::isWindows()) { + $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + if (extension_loaded('zlib')) { + // Fallback to using the PHP extension. + $this->extractUsingExt($file, $targetFilepath); + + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + throw new \RuntimeException($processError); + } + + // Windows version of PHP has built-in support of gzip functions + $this->extractUsingExt($file, $targetFilepath); + + return \React\Promise\resolve(null); + } + + private function extractUsingExt(string $file, string $targetFilepath): void + { + $archiveFile = gzopen($file, 'rb'); + $targetFile = fopen($targetFilepath, 'wb'); + while ($string = gzread($archiveFile, 4096)) { + fwrite($targetFile, $string, Platform::strlen($string)); + } + gzclose($archiveFile); + fclose($targetFile); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php b/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php new file mode 100644 index 0000000..4709ae3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/HgDownloader.php @@ -0,0 +1,122 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; +use Composer\Util\Hg as HgUtils; + +/** + * @author Per Bernhardt + */ +class HgDownloader extends VcsDownloader +{ + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + if (null === HgUtils::getVersion($this->process)) { + throw new \RuntimeException('hg was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + $cloneCommand = static function (string $url) use ($path): array { + return ['hg', 'clone', '--', $url, $path]; + }; + + $hgUtils->runCommand($cloneCommand, $url, $path); + + $command = ['hg', 'up', '--', (string) $package->getSourceReference()]; + if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + $ref = $target->getSourceReference(); + $this->io->writeError(" Updating to ".$target->getSourceReference()); + + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $command = static function ($url): array { + return ['hg', 'pull', '--', $url]; + }; + $hgUtils->runCommand($command, $url, $path); + + $command = static function () use ($ref): array { + return ['hg', 'up', '--', $ref]; + }; + $hgUtils->runCommand($command, $url, $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!is_dir($path.'/.hg')) { + return null; + } + + $this->process->execute(['hg', 'st'], $output, realpath($path)); + + $output = trim($output); + + return strlen($output) > 0 ? $output : null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact']; + + if (0 !== $this->process->execute($command, $output, realpath($path))) { + throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); + } + + return $output; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_dir($path . '/.hg'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php b/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php new file mode 100644 index 0000000..e57e7af --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php @@ -0,0 +1,17 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +class MaxFileSizeExceededException extends TransportException +{ +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php new file mode 100644 index 0000000..f71ea25 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PathDownloader.php @@ -0,0 +1,335 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\Archiver\ArchivableFilesFinder; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Util\Platform; +use Composer\Util\Filesystem; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * Download a package from a local path. + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface +{ + private const STRATEGY_SYMLINK = 10; + private const STRATEGY_MIRROR = 20; + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.'); + } + $realUrl = realpath($url); + if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { + throw new \RuntimeException(sprintf( + 'Source path "%s" is not found for package %s', + $url, + $package->getName() + )); + } + + if (realpath($path) === $realUrl) { + return \React\Promise\resolve(null); + } + + if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { + // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. + // + // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174 + // for previous attempts that were shut down because they did not work well enough or introduced too many risks. + throw new \RuntimeException(sprintf( + 'Package %s cannot install to "%s" inside its source at "%s"', + $package->getName(), + realpath($path), + $realUrl + )); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } + $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } + + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); + } + + return \React\Promise\resolve(null); + } + + // Get the transport options with default values + $transportOptions = $package->getTransportOptions() + ['relative' => true]; + + [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions); + + $symfonyFilesystem = new SymfonyFilesystem(); + $this->filesystem->removeDirectory($path); + + if ($output) { + $this->io->writeError(" - " . InstallOperation::format($package).': ', false); + } + + $isFallback = false; + if (self::STRATEGY_SYMLINK === $currentStrategy) { + try { + if (Platform::isWindows()) { + // Implement symlinks as NTFS junctions on Windows + if ($output) { + $this->io->writeError(sprintf('Junctioning from %s', $url), false); + } + $this->filesystem->junction($realUrl, $path); + } else { + $path = rtrim($path, "/"); + if ($output) { + $this->io->writeError(sprintf('Symlinking from %s', $url), false); + } + if ($transportOptions['relative'] === true) { + $absolutePath = $path; + if (!$this->filesystem->isAbsolutePath($absolutePath)) { + $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path; + } + $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true); + $symfonyFilesystem->symlink($shortestPath.'/', $path); + } else { + $symfonyFilesystem->symlink($realUrl.'/', $path); + } + } + } catch (IOException $e) { + if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + if ($output) { + $this->io->writeError(''); + $this->io->writeError(' Symlink failed, fallback to use mirroring!'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $isFallback = true; + } else { + throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path)); + } + } + } + + // Fallback if symlink failed or if symlink is not allowed for the package + if (self::STRATEGY_MIRROR === $currentStrategy) { + $realUrl = $this->filesystem->normalizePath($realUrl); + + if ($output) { + $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), false); + } + $iterator = new ArchivableFilesFinder($realUrl, []); + $symfonyFilesystem->mirror($realUrl, $path, $iterator); + } + + if ($output) { + $this->io->writeError(''); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface + { + $path = Filesystem::trimTrailingSlash($path); + /** + * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first + * to prevent a false positive when checking if the dist and install paths are the same. + * See https://bugs.php.net/bug.php?id=77639 + * + * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process + * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which + * is disastrous within a junction. So in that case we have no other real choice but to fail hard. + */ + if (Platform::isWindows() && $this->filesystem->isJunction($path)) { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); + } + if (!$this->filesystem->removeJunction($path)) { + $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); + throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); + } + + return \React\Promise\resolve(null); + } + + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.'); + } + + // ensure that the source path (dist url) is not the same as the install path, which + // can happen when using custom installers, see https://github.com/composer/composer/pull/9116 + // not using realpath here as we do not want to resolve the symlink to the original dist url + // it points to + $fs = new Filesystem; + $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path; + $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url; + if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) { + if ($output) { + $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); + } + + return \React\Promise\resolve(null); + } + + return parent::remove($package, $path, $output); + } + + /** + * @inheritDoc + */ + public function getVcsReference(PackageInterface $package, string $path): ?string + { + $path = Filesystem::trimTrailingSlash($path); + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + $packageVersion = $guesser->guessVersion($packageConfig, $path); + if ($packageVersion !== null) { + return $packageVersion['commit']; + } + + return null; + } + + /** + * @inheritDoc + */ + protected function getInstallOperationAppendix(PackageInterface $package, string $path): string + { + $url = $package->getDistUrl(); + if (null === $url) { + throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); + } + $realUrl = realpath($url); + if (false === $realUrl) { + throw new \RuntimeException('Failed to realpath '.$url); + } + + if (realpath($path) === $realUrl) { + return ': Source already present'; + } + + [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions()); + + if ($currentStrategy === self::STRATEGY_SYMLINK) { + if (Platform::isWindows()) { + return ': Junctioning from '.$package->getDistUrl(); + } + + return ': Symlinking from '.$package->getDistUrl(); + } + + return ': Mirroring from '.$package->getDistUrl(); + } + + /** + * @param mixed[] $transportOptions + * + * @phpstan-return array{self::STRATEGY_*, non-empty-list} + */ + private function computeAllowedStrategies(array $transportOptions): array + { + // When symlink transport option is null, both symlink and mirror are allowed + $currentStrategy = self::STRATEGY_SYMLINK; + $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR]; + + $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS'); + if ((bool) $mirrorPathRepos) { + $currentStrategy = self::STRATEGY_MIRROR; + } + + $symlinkOption = $transportOptions['symlink'] ?? null; + + if (true === $symlinkOption) { + $currentStrategy = self::STRATEGY_SYMLINK; + $allowedStrategies = [self::STRATEGY_SYMLINK]; + } elseif (false === $symlinkOption) { + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + // Check we can use junctions safely if we are on Windows + if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) { + if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + // Check we can use symlink() otherwise + if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) { + if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = [self::STRATEGY_MIRROR]; + } + + return [$currentStrategy, $allowedStrategies]; + } + + /** + * Returns true if junctions can be created and safely used on Windows + * + * A PHP bug makes junction detection fragile, leading to possible data loss + * when removing a package. See https://bugs.php.net/bug.php?id=77552 + * + * For safety we require a minimum version of Windows 7, so we can call the + * system rmdir which will preserve target content if given a junction. + * + * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). + */ + private function safeJunctions(): bool + { + // We need to call mklink, and rmdir on Windows 7 (version 6.1) + return function_exists('proc_open') && + (PHP_WINDOWS_VERSION_MAJOR > 6 || + (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1)); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php new file mode 100644 index 0000000..faf159e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Repository\VcsRepository; +use Composer\Util\Perforce; + +/** + * @author Matt Whittom + */ +class PerforceDownloader extends VcsDownloader +{ + /** @var Perforce|null */ + protected $perforce; + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + $ref = $package->getSourceReference(); + $label = $this->getLabelFromSourceReference((string) $ref); + + $this->io->writeError('Cloning ' . $ref); + $this->initPerforce($package, $path, $url); + $this->perforce->setStream($ref); + $this->perforce->p4Login(); + $this->perforce->writeP4ClientSpec(); + $this->perforce->connectClient(); + $this->perforce->syncCodeBase($label); + $this->perforce->cleanupClientSpec(); + + return \React\Promise\resolve(null); + } + + private function getLabelFromSourceReference(string $ref): ?string + { + $pos = strpos($ref, '@'); + if (false !== $pos) { + return substr($ref, $pos + 1); + } + + return null; + } + + public function initPerforce(PackageInterface $package, string $path, string $url): void + { + if (!empty($this->perforce)) { + $this->perforce->initializePath($path); + + return; + } + + $repository = $package->getRepository(); + $repoConfig = null; + if ($repository instanceof VcsRepository) { + $repoConfig = $this->getRepoConfig($repository); + } + $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io); + } + + /** + * @return array + */ + private function getRepoConfig(VcsRepository $repository): array + { + return $repository->getRepoConfig(); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + return $this->doInstall($target, $path, $url); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + $this->io->writeError('Perforce driver does not check for local changes before overriding'); + + return null; + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + return $this->perforce->getCommitLogs($fromReference, $toReference); + } + + public function setPerforce(Perforce $perforce): void + { + $this->perforce = $perforce; + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php b/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php new file mode 100644 index 0000000..e0ae4fa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/PharDownloader.php @@ -0,0 +1,41 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; + +/** + * Downloader for phar files + * + * @author Kirill chEbba Chebunin + */ +class PharDownloader extends ArchiveDownloader +{ + /** + * @inheritDoc + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + // Can throw an UnexpectedValueException + $archive = new \Phar($file); + $archive->extractTo($path, null, true); + /* TODO: handle openssl signed phars + * https://github.com/composer/composer/pull/33#issuecomment-2250768 + * https://github.com/koto/phar-util + * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html + */ + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php b/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php new file mode 100644 index 0000000..04e16ea --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/RarDownloader.php @@ -0,0 +1,82 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Util\IniHelper; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Package\PackageInterface; +use RarArchive; + +/** + * RAR archive downloader. + * + * Based on previous work by Jordi Boggiano ({@see ZipDownloader}). + * + * @author Derrick Nelson + */ +class RarDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $processError = null; + + // Try to use unrar on *nix + if (!Platform::isWindows()) { + $command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + } + + if (!class_exists('RarArchive')) { + // php.ini path is added to the error message to help users find the correct file + $iniMessage = IniHelper::getMessage(); + + $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" + . $iniMessage . "\n" . $processError; + + if (!Platform::isWindows()) { + $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; + } + + throw new \RuntimeException($error); + } + + $rarArchive = RarArchive::open($file); + + if (false === $rarArchive) { + throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); + } + + $entries = $rarArchive->getEntries(); + + if (false === $entries) { + throw new \RuntimeException('Could not retrieve RAR archive entries'); + } + + foreach ($entries as $entry) { + if (false === $entry->extract($path)) { + throw new \RuntimeException('Could not extract entry'); + } + } + + $rarArchive->close(); + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php b/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php new file mode 100644 index 0000000..ca88ea8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php @@ -0,0 +1,252 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Svn as SvnUtil; +use Composer\Repository\VcsRepository; +use Composer\Util\ProcessExecutor; +use React\Promise\PromiseInterface; + +/** + * @author Ben Bieker + * @author Till Klampaeckel + */ +class SvnDownloader extends VcsDownloader +{ + /** @var bool */ + protected $cacheCredentials = true; + + /** + * @inheritDoc + */ + protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface + { + SvnUtil::cleanEnv(); + $util = new SvnUtil($url, $this->io, $this->config, $this->process); + if (null === $util->binaryVersion()) { + throw new \RuntimeException('svn was not found in your PATH, skipping source download'); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface + { + SvnUtil::cleanEnv(); + $ref = $package->getSourceReference(); + + $repo = $package->getRepository(); + if ($repo instanceof VcsRepository) { + $repoConfig = $repo->getRepoConfig(); + if (array_key_exists('svn-cache-credentials', $repoConfig)) { + $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials']; + } + } + + $this->io->writeError(" Checking out ".$package->getSourceReference()); + $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface + { + SvnUtil::cleanEnv(); + $ref = $target->getSourceReference(); + + if (!$this->hasMetadataRepository($path)) { + throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); + } + + $util = new SvnUtil($url, $this->io, $this->config, $this->process); + $flags = []; + if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { + $flags[] = '--ignore-ancestry'; + } + + $this->io->writeError(" Checking out " . $ref); + $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getLocalChanges(PackageInterface $package, string $path): ?string + { + if (!$this->hasMetadataRepository($path)) { + return null; + } + + $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path); + + return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null; + } + + /** + * Execute an SVN command and try to fix up the process with credentials + * if necessary. + * + * @param string $baseUrl Base URL of the repository + * @param non-empty-list $command SVN command to run + * @param string $url SVN url + * @param string $cwd Working directory + * @param string $path Target for a checkout + * @throws \RuntimeException + */ + protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string + { + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); + $util->setCacheCredentials($this->cacheCredentials); + try { + return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); + } catch (\RuntimeException $e) { + throw new \RuntimeException( + $package->getPrettyName().' could not be downloaded, '.$e->getMessage() + ); + } + } + + /** + * @inheritDoc + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + if (null === ($changes = $this->getLocalChanges($package, $path))) { + return \React\Promise\resolve(null); + } + + if (!$this->io->isInteractive()) { + if (true === $this->config->get('discard-changes')) { + return $this->discardChanges($path); + } + + return parent::cleanChanges($package, $path, $update); + } + + $changes = array_map(static function ($elem): string { + return ' '.$elem; + }, Preg::split('{\s*\r?\n\s*}', $changes)); + $countChanges = count($changes); + $this->io->writeError(sprintf(' '.$package->getPrettyName().' has modified file%s:', $countChanges === 1 ? '' : 's')); + $this->io->writeError(array_slice($changes, 0, 10)); + if ($countChanges > 10) { + $remainingChanges = $countChanges - 10; + $this->io->writeError( + sprintf( + ' '.$remainingChanges.' more file%s modified, choose "v" to view the full list', + $remainingChanges === 1 ? '' : 's' + ) + ); + } + + while (true) { + switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { + case 'y': + $this->discardChanges($path); + break 2; + + case 'n': + throw new \RuntimeException('Update aborted'); + + case 'v': + $this->io->writeError($changes); + break; + + case '?': + default: + $this->io->writeError([ + ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), + ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', + ' v - view modified files', + ' ? - print help', + ]); + break; + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function getCommitLogs(string $fromReference, string $toReference, string $path): string + { + if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) { + // retrieve the svn base url from the checkout folder + $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path]; + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException( + 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput() + ); + } + + $urlPattern = '#(.*)#'; + if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) { + $baseUrl = $matches[1]; + } else { + throw new \RuntimeException( + 'Unable to determine svn url for path '. $path + ); + } + + // strip paths from references and only keep the actual revision + $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference); + $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference); + + $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental']; + + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); + $util->setCacheCredentials($this->cacheCredentials); + try { + return $util->executeLocal($command, $path, null, $this->io->isVerbose()); + } catch (\RuntimeException $e) { + throw new \RuntimeException( + 'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage() + ); + } + } + + return "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; + } + + /** + * @phpstan-return PromiseInterface + */ + protected function discardChanges(string $path): PromiseInterface + { + if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) { + throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + protected function hasMetadataRepository(string $path): bool + { + return is_dir($path.'/.svn'); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php b/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php new file mode 100644 index 0000000..0a16f3a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/TarDownloader.php @@ -0,0 +1,36 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +/** + * Downloader for tar files: tar, tar.gz or tar.bz2 + * + * @author Kirill chEbba Chebunin + */ +class TarDownloader extends ArchiveDownloader +{ + /** + * @inheritDoc + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + // Can throw an UnexpectedValueException + $archive = new \PharData($file); + $archive->extractTo($path, null, true); + + return \React\Promise\resolve(null); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/TransportException.php b/vendor/composer/composer/src/Composer/Downloader/TransportException.php new file mode 100644 index 0000000..a30842e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/TransportException.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +/** + * @author Jordi Boggiano + */ +class TransportException extends \RuntimeException +{ + /** @var ?array */ + protected $headers; + /** @var ?string */ + protected $response; + /** @var ?int */ + protected $statusCode; + /** @var array */ + protected $responseInfo = []; + + public function __construct(string $message = "", int $code = 400, ?\Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } + + /** + * @param array $headers + */ + public function setHeaders(array $headers): void + { + $this->headers = $headers; + } + + /** + * @return ?array + */ + public function getHeaders(): ?array + { + return $this->headers; + } + + public function setResponse(?string $response): void + { + $this->response = $response; + } + + /** + * @return ?string + */ + public function getResponse(): ?string + { + return $this->response; + } + + /** + * @param ?int $statusCode + */ + public function setStatusCode($statusCode): void + { + $this->statusCode = $statusCode; + } + + /** + * @return ?int + */ + public function getStatusCode(): ?int + { + return $this->statusCode; + } + + /** + * @return array + */ + public function getResponseInfo(): array + { + return $this->responseInfo; + } + + /** + * @param array $responseInfo + */ + public function setResponseInfo(array $responseInfo): void + { + $this->responseInfo = $responseInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php b/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php new file mode 100644 index 0000000..c99005a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * VCS Capable Downloader interface. + * + * @author Steve Buzonas + */ +interface VcsCapableDownloaderInterface +{ + /** + * Gets the VCS Reference for the package at path + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null reference or null + */ + public function getVcsReference(PackageInterface $package, string $path): ?string; +} diff --git a/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php b/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php new file mode 100644 index 0000000..626bcb5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/VcsDownloader.php @@ -0,0 +1,363 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Config; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Util\ProcessExecutor; +use Composer\IO\IOInterface; +use Composer\Util\Filesystem; +use React\Promise\PromiseInterface; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * @author Jordi Boggiano + */ +abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var Filesystem */ + protected $filesystem; + /** @var array */ + protected $hasCleanedChanges = []; + + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?? new ProcessExecutor($io); + $this->filesystem = $fs ?? new Filesystem($this->process); + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): string + { + return 'source'; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if (!$package->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); + } + + $urls = $this->prepareUrls($package->getSourceUrls()); + + while ($url = array_shift($urls)) { + try { + return $this->doDownload($package, $path, $url, $prevPackage); + } catch (\Exception $e) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($e instanceof \PHPUnit\Framework\Exception) { + throw $e; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + if (!count($urls)) { + throw $e; + } + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if ($type === 'update') { + $this->cleanChanges($prevPackage, $path, true); + $this->hasCleanedChanges[$prevPackage->getUniqueName()] = true; + } elseif ($type === 'install') { + $this->filesystem->emptyDirectory($path); + } elseif ($type === 'uninstall') { + $this->cleanChanges($package, $path, false); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface + { + if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) { + $this->reapplyChanges($path); + unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(PackageInterface $package, string $path): PromiseInterface + { + if (!$package->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); + } + + $this->io->writeError(" - " . InstallOperation::format($package).': ', false); + + $urls = $this->prepareUrls($package->getSourceUrls()); + while ($url = array_shift($urls)) { + try { + $this->doInstall($package, $path, $url); + break; + } catch (\Exception $e) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($e instanceof \PHPUnit\Framework\Exception) { + throw $e; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + if (!count($urls)) { + throw $e; + } + } + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface + { + if (!$target->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information'); + } + + $this->io->writeError(" - " . UpdateOperation::format($initial, $target).': ', false); + + $urls = $this->prepareUrls($target->getSourceUrls()); + + $exception = null; + while ($url = array_shift($urls)) { + try { + $this->doUpdate($initial, $target, $path, $url); + + $exception = null; + break; + } catch (\Exception $exception) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($exception instanceof \PHPUnit\Framework\Exception) { + throw $exception; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + } + } + + // print the commit logs if in verbose mode and VCS metadata is present + // because in case of missing metadata code would trigger another exception + if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { + $message = 'Pulling in changes:'; + $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); + + if ('' === trim($logs)) { + $message = 'Rolling back changes:'; + $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path); + } + + if ('' !== trim($logs)) { + $logs = implode("\n", array_map(static function ($line): string { + return ' ' . $line; + }, explode("\n", $logs))); + + // escape angle brackets for proper output in the console + $logs = str_replace('<', '\<', $logs); + + $this->io->writeError(' '.$message); + $this->io->writeError($logs); + } + } + + if (!$urls && $exception) { + throw $exception; + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function remove(PackageInterface $package, string $path): PromiseInterface + { + $this->io->writeError(" - " . UninstallOperation::format($package)); + + $promise = $this->filesystem->removeDirectoryAsync($path); + + return $promise->then(static function (bool $result) use ($path) { + if (!$result) { + throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); + } + }); + } + + /** + * @inheritDoc + */ + public function getVcsReference(PackageInterface $package, string $path): ?string + { + $parser = new VersionParser; + $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); + $dumper = new ArrayDumper; + + $packageConfig = $dumper->dump($package); + if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { + return $packageVersion['commit']; + } + + return null; + } + + /** + * Prompt the user to check if changes should be stashed/removed or the operation aborted + * + * @param bool $update if true (update) the changes can be stashed and reapplied after an update, + * if false (remove) the changes should be assumed to be lost if the operation is not aborted + * + * @throws \RuntimeException in case the operation must be aborted + * @phpstan-return PromiseInterface + */ + protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface + { + // the default implementation just fails if there are any changes, override in child classes to provide stash-ability + if (null !== $this->getLocalChanges($package, $path)) { + throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); + } + + return \React\Promise\resolve(null); + } + + /** + * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not) + * + * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly + */ + protected function reapplyChanges(string $path): void + { + } + + /** + * Downloads data needed to run an install/update later + * + * @param PackageInterface $package package instance + * @param string $path download path + * @param string $url package url + * @param PackageInterface|null $prevPackage previous package (in case of an update) + * @phpstan-return PromiseInterface + */ + abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface; + + /** + * Downloads specific package into specific folder. + * + * @param PackageInterface $package package instance + * @param string $path download path + * @param string $url package url + * @phpstan-return PromiseInterface + */ + abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface; + + /** + * Updates specific package in specific folder from initial to target version. + * + * @param PackageInterface $initial initial package + * @param PackageInterface $target updated package + * @param string $path download path + * @param string $url package url + * @phpstan-return PromiseInterface + */ + abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface; + + /** + * Fetches the commit logs between two commits + * + * @param string $fromReference the source reference + * @param string $toReference the target reference + * @param string $path the package path + */ + abstract protected function getCommitLogs(string $fromReference, string $toReference, string $path): string; + + /** + * Checks if VCS metadata repository has been initialized + * repository example: .git|.svn|.hg + */ + abstract protected function hasMetadataRepository(string $path): bool; + + /** + * @param string[] $urls + * + * @return string[] + */ + private function prepareUrls(array $urls): array + { + foreach ($urls as $index => $url) { + if (Filesystem::isLocalPath($url)) { + // realpath() below will not understand + // url that starts with "file://" + $fileProtocol = 'file://'; + $isFileProtocol = false; + if (0 === strpos($url, $fileProtocol)) { + $url = substr($url, strlen($fileProtocol)); + $isFileProtocol = true; + } + + // realpath() below will not understand %20 spaces etc. + if (false !== strpos($url, '%')) { + $url = rawurldecode($url); + } + + $urls[$index] = realpath($url); + + if ($isFileProtocol) { + $urls[$index] = $fileProtocol . $urls[$index]; + } + } + } + + return $urls; + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php b/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php new file mode 100644 index 0000000..286d32c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/XzDownloader.php @@ -0,0 +1,39 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; + +/** + * Xz archive downloader. + * + * @author Pavel Puchkin + * @author Pierre Rudloff + */ +class XzDownloader extends ArchiveDownloader +{ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + $command = ['tar', '-xJf', $file, '-C', $path]; + + if (0 === $this->process->execute($command, $ignoredOutput)) { + return \React\Promise\resolve(null); + } + + $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); + + throw new \RuntimeException($processError); + } +} diff --git a/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php b/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php new file mode 100644 index 0000000..5c86579 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php @@ -0,0 +1,307 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\IniHelper; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; +use React\Promise\PromiseInterface; +use ZipArchive; + +/** + * @author Jordi Boggiano + */ +class ZipDownloader extends ArchiveDownloader +{ + /** @var array> */ + private static $unzipCommands; + /** @var bool */ + private static $hasZipArchive; + /** @var bool */ + private static $isWindows; + + /** @var ZipArchive|null */ + private $zipArchiveObject; // @phpstan-ignore property.onlyRead (helper property that is set via reflection for testing purposes) + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface + { + if (null === self::$unzipCommands) { + self::$unzipCommands = []; + $finder = new ExecutableFinder; + if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) { + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } + if ($cmd = $finder->find('unzip')) { + self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%']; + } + if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present + self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } + if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present + self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; + } + } + + $procOpenMissing = false; + if (!function_exists('proc_open')) { + self::$unzipCommands = []; + $procOpenMissing = true; + } + + if (null === self::$hasZipArchive) { + self::$hasZipArchive = class_exists('ZipArchive'); + } + + if (!self::$hasZipArchive && !self::$unzipCommands) { + // php.ini path is added to the error message to help users find the correct file + $iniMessage = IniHelper::getMessage(); + if ($procOpenMissing) { + $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage; + } else { + $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage; + } + + throw new \RuntimeException($error); + } + + if (null === self::$isWindows) { + self::$isWindows = Platform::isWindows(); + + if (!self::$isWindows && !self::$unzipCommands) { + if ($procOpenMissing) { + $this->io->writeError("proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); + $this->io->writeError("Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them."); + } else { + $this->io->writeError("As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); + $this->io->writeError("Installing 'unzip' or '7z' (21.01+) may remediate them."); + } + } + } + + return parent::download($package, $path, $prevPackage, $output); + } + + /** + * extract $file to $path with "unzip" command + * + * @param string $file File to extract + * @param string $path Path where to extract file + * @phpstan-return PromiseInterface + */ + private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface + { + static $warned7ZipLinux = false; + + // Force Exception throwing if the other alternative extraction method is not available + $isLastChance = !self::$hasZipArchive; + + if (0 === \count(self::$unzipCommands)) { + // This was call as the favorite extract way, but is not available + // We switch to the alternative + return $this->extractWithZipArchive($package, $file, $path); + } + + $commandSpec = reset(self::$unzipCommands); + $executable = $commandSpec[0]; + $command = array_slice($commandSpec, 1); + $map = [ + // normalize separators to backslashes to avoid problems with 7-zip on windows + // see https://github.com/composer/composer/issues/10058 + '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR), + '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR), + ]; + $command = array_map(static function ($value) use ($map) { + return strtr($value, $map); + }, $command); + + if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz'], true)) { + $warned7ZipLinux = true; + if (0 === $this->process->execute([$commandSpec[1]], $output)) { + if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) { + $this->io->writeError(' Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.'); + } + } + } + + $io = $this->io; + $tryFallback = function (\Throwable $processError) use ($isLastChance, $io, $file, $path, $package, $executable): \React\Promise\PromiseInterface { + if ($isLastChance) { + throw $processError; + } + + if (str_contains($processError->getMessage(), 'zip bomb')) { + throw $processError; + } + + if (!is_file($file)) { + $io->writeError(' '.$processError->getMessage().''); + $io->writeError(' This most likely is due to a custom installer plugin not handling the returned Promise from the downloader'); + $io->writeError(' See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix'); + } else { + $io->writeError(' '.$processError->getMessage().''); + $io->writeError(' The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)'); + $io->writeError(' Unzip with '.$executable.' command failed, falling back to ZipArchive class'); + + // additional debug data to try to figure out GH actions issues https://github.com/composer/composer/issues/11148 + if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { + $io->writeError(' Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:'); + $io->writeError('File size: '.@filesize($file)); + $io->writeError('File SHA1: '.hash_file('sha1', $file)); + $io->writeError('First 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), 0, 100))); + $io->writeError('Last 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), -100))); + if (strlen((string) $package->getDistUrl()) > 0) { + $io->writeError('Origin URL: '.$this->processUrl($package, (string) $package->getDistUrl())); + $io->writeError('Response Headers: '.json_encode(FileDownloader::$responseHeaders[$package->getName()] ?? [])); + } + } + } + + return $this->extractWithZipArchive($package, $file, $path); + }; + + try { + $promise = $this->process->executeAsync($command); + + return $promise->then(function (Process $process) use ($tryFallback, $command, $package, $file) { + if (!$process->isSuccessful()) { + if (isset($this->cleanupExecuted[$package->getName()])) { + throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.'); + } + + $output = $process->getErrorOutput(); + $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output); + + return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output)); + } + }); + } catch (\Throwable $e) { + return $tryFallback($e); + } + } + + /** + * extract $file to $path with ZipArchive + * + * @param string $file File to extract + * @param string $path Path where to extract file + * @phpstan-return PromiseInterface + */ + private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface + { + $processError = null; + $zipArchive = $this->zipArchiveObject ?: new ZipArchive(); + + try { + if (!file_exists($file) || ($filesize = filesize($file)) === false || $filesize === 0) { + $retval = -1; + } else { + $retval = $zipArchive->open($file); + } + + if (true === $retval) { + $totalSize = 0; + $archiveSize = filesize($file); + $totalFiles = $zipArchive->count(); + if ($totalFiles > 0) { + $inspectAll = false; + $filesToInspect = min($totalFiles, 5); + for ($i = 0; $i < $filesToInspect; $i++) { + $stat = $zipArchive->statIndex($inspectAll ? $i : random_int(0, $totalFiles - 1)); + if ($stat === false) { + continue; + } + $totalSize += $stat['size']; + if (!$inspectAll && $stat['size'] > $stat['comp_size'] * 200) { + $totalSize = 0; + $inspectAll = true; + $i = -1; + $filesToInspect = $totalFiles; + } + } + if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50*1024*1024) { + throw new \RuntimeException('Invalid zip file for "'.$package->getName().'" with compression ratio >99% (possible zip bomb)'); + } + } + + $extractResult = $zipArchive->extractTo($path); + + if (true === $extractResult) { + $zipArchive->close(); + + return \React\Promise\resolve(null); + } + + $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file for \"{$package->getName()}\", it is either corrupted or using an invalid format.\n")); + } else { + $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval); + } + } catch (\ErrorException $e) { + $processError = new \RuntimeException('The archive for "'.$package->getName().'" may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e); + } catch (\Throwable $e) { + $processError = $e; + } + + throw $processError; + } + + /** + * extract $file to $path + * + * @param string $file File to extract + * @param string $path Path where to extract file + */ + protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface + { + return $this->extractWithSystemUnzip($package, $file, $path); + } + + /** + * Give a meaningful error message to the user. + */ + protected function getErrorMessage(int $retval, string $file): string + { + switch ($retval) { + case ZipArchive::ER_EXISTS: + return sprintf("File '%s' already exists.", $file); + case ZipArchive::ER_INCONS: + return sprintf("Zip archive '%s' is inconsistent.", $file); + case ZipArchive::ER_INVAL: + return sprintf("Invalid argument (%s)", $file); + case ZipArchive::ER_MEMORY: + return sprintf("Malloc failure (%s)", $file); + case ZipArchive::ER_NOENT: + return sprintf("No such zip file: '%s'", $file); + case ZipArchive::ER_NOZIP: + return sprintf("'%s' is not a zip archive.", $file); + case ZipArchive::ER_OPEN: + return sprintf("Can't open zip file: %s", $file); + case ZipArchive::ER_READ: + return sprintf("Zip read error (%s)", $file); + case ZipArchive::ER_SEEK: + return sprintf("Zip seek error (%s)", $file); + case -1: + return sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file); + default: + return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval); + } + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/Event.php b/vendor/composer/composer/src/Composer/EventDispatcher/Event.php new file mode 100644 index 0000000..4230df6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/Event.php @@ -0,0 +1,103 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * The base event class + * + * @author Nils Adermann + */ +class Event +{ + /** + * @var string This event's name + */ + protected $name; + + /** + * @var string[] Arguments passed by the user, these will be forwarded to CLI script handlers + */ + protected $args; + + /** + * @var mixed[] Flags usable in PHP script handlers + */ + protected $flags; + + /** + * @var bool Whether the event should not be passed to more listeners + */ + private $propagationStopped = false; + + /** + * Constructor. + * + * @param string $name The event name + * @param string[] $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, array $args = [], array $flags = []) + { + $this->name = $name; + $this->args = $args; + $this->flags = $flags; + } + + /** + * Returns the event's name. + * + * @return string The event name + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the event's arguments. + * + * @return string[] The event arguments + */ + public function getArguments(): array + { + return $this->args; + } + + /** + * Returns the event's flags. + * + * @return mixed[] The event flags + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * Checks if stopPropagation has been called + * + * @return bool Whether propagation has been stopped + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Prevents the event from being passed to further listeners + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php b/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php new file mode 100644 index 0000000..45cf3f1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php @@ -0,0 +1,712 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +use Composer\DependencyResolver\Transaction; +use Composer\Installer\InstallerEvent; +use Composer\IO\BufferIO; +use Composer\IO\ConsoleIO; +use Composer\IO\IOInterface; +use Composer\Composer; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Plugin\CommandEvent; +use Composer\Plugin\PreCommandRunEvent; +use Composer\Util\Platform; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Repository\RepositoryInterface; +use Composer\Script; +use Composer\Installer\PackageEvent; +use Composer\Installer\BinaryInstaller; +use Composer\Util\ProcessExecutor; +use Composer\Script\Event as ScriptEvent; +use Composer\Autoload\ClassLoader; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\ExecutableFinder; + +/** + * The Event Dispatcher. + * + * Example in command: + * $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO()); + * // ... + * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); + * // ... + * + * @author François Pluchino + * @author Jordi Boggiano + * @author Nils Adermann + */ +class EventDispatcher +{ + /** @var PartialComposer */ + protected $composer; + /** @var IOInterface */ + protected $io; + /** @var ?ClassLoader */ + protected $loader; + /** @var ProcessExecutor */ + protected $process; + /** @var array>> */ + protected $listeners = []; + /** @var bool */ + protected $runScripts = true; + /** @var list */ + private $eventStack; + /** @var list */ + private $skipScripts; + + /** + * Constructor. + * + * @param PartialComposer $composer The composer instance + * @param IOInterface $io The IOInterface instance + * @param ProcessExecutor $process + */ + public function __construct(PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null) + { + $this->composer = $composer; + $this->io = $io; + $this->process = $process ?? new ProcessExecutor($io); + $this->eventStack = []; + $this->skipScripts = array_values(array_filter( + array_map('trim', explode(',', (string) Platform::getEnv('COMPOSER_SKIP_SCRIPTS'))), + function ($val) { + return $val !== ''; + } + )); + } + + /** + * Set whether script handlers are active or not + * + * @return $this + */ + public function setRunScripts(bool $runScripts = true): self + { + $this->runScripts = $runScripts; + + return $this; + } + + /** + * Dispatch an event + * + * @param string|null $eventName The event name, required if no $event is provided + * @param Event $event An event instance, required if no $eventName is provided + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatch(?string $eventName, ?Event $event = null): int + { + if (null === $event) { + if (null === $eventName) { + throw new \InvalidArgumentException('If no $event is passed in to '.__METHOD__.' you have to pass in an $eventName, got null.'); + } + $event = new Event($eventName); + } + + return $this->doDispatch($event); + } + + /** + * Dispatch a script event. + * + * @param string $eventName The constant in ScriptEvents + * @param array $additionalArgs Arguments passed by the user + * @param array $flags Optional flags to pass data not as argument + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchScript(string $eventName, bool $devMode = false, array $additionalArgs = [], array $flags = []): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); + } + + /** + * Dispatch a package event. + * + * @param string $eventName The constant in PackageEvents + * @param bool $devMode Whether or not we are in dev mode + * @param RepositoryInterface $localRepo The installed repository + * @param OperationInterface[] $operations The list of operations + * @param OperationInterface $operation The package being installed/updated/removed + * + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation)); + } + + /** + * Dispatch a installer event. + * + * @param string $eventName The constant in InstallerEvents + * @param bool $devMode Whether or not we are in dev mode + * @param bool $executeOperations True if operations will be executed, false in --dry-run + * @param Transaction $transaction The transaction contains the list of operations + * + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction): int + { + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); + } + + /** + * Triggers the listeners of an event. + * + * @param Event $event The event object to pass to the event handlers/listeners. + * @throws \RuntimeException|\Exception + * @return int return code of the executed script if any, for php scripts a false return + * value is changed to 1, anything else to 0 + */ + protected function doDispatch(Event $event) + { + if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) { + $details = null; + if ($event instanceof PackageEvent) { + $details = (string) $event->getOperation(); + } elseif ($event instanceof CommandEvent) { + $details = $event->getCommandName(); + } elseif ($event instanceof PreCommandRunEvent) { + $details = $event->getCommand(); + } + $this->io->writeError('Dispatching '.$event->getName().''.($details ? ' ('.$details.')' : '').' event'); + } + + $listeners = $this->getListeners($event); + + $this->pushEvent($event); + + $autoloadersBefore = spl_autoload_functions(); + + try { + $returnMax = 0; + foreach ($listeners as $callable) { + $return = 0; + $this->ensureBinDirIsInPath(); + + $additionalArgs = $event->getArguments(); + if (is_string($callable) && str_contains($callable, '@no_additional_args')) { + $callable = Preg::replace('{ ?@no_additional_args}', '', $callable); + $additionalArgs = []; + } + $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : ''); + if (!is_string($callable)) { + if (!is_callable($callable)) { + $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; + + throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); + } + if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); + } + $return = false === $callable($event) ? 1 : 0; + } elseif ($this->isComposerScript($callable)) { + $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE); + + $script = explode(' ', substr($callable, 1)); + $scriptName = $script[0]; + unset($script[0]); + + $index = array_search('@additional_args', $script, true); + if ($index !== false) { + $args = array_splice($script, $index, 0, $additionalArgs); + } else { + $args = array_merge($script, $additionalArgs); + } + $flags = $event->getFlags(); + if (isset($flags['script-alias-input'])) { + $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script)); + $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input']; + unset($argsString); + } + if (strpos($callable, '@composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args); + if (0 !== ($exitCode = $this->executeTty($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); + + throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } else { + if (!$this->getListeners(new Event($scriptName))) { + $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); + } + + try { + /** @var InstallerEvent $event */ + $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); + } catch (ScriptExecutionException $e) { + $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); + throw $e; + } + } + } elseif ($this->isPhpScript($callable)) { + $className = substr($callable, 0, strpos($callable, '::')); + $methodName = substr($callable, strpos($callable, '::') + 2); + + if (!class_exists($className)) { + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (!is_callable($callable)) { + $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + + try { + $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); + throw $e; + } + } elseif ($this->isCommandClass($callable)) { + $className = $callable; + if (!class_exists($className)) { + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (!is_a($className, Command::class, true)) { + $this->io->writeError('Class '.$className.' does not extend '.Command::class.', can not call '.$event->getName().' script', true, IOInterface::QUIET); + continue; + } + if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($event->getName())))) { + $this->io->writeError('You cannot bind '.$event->getName().' to a Command class, use a non-reserved name', true, IOInterface::QUIET); + continue; + } + + $app = new Application(); + $app->setCatchExceptions(false); + if (method_exists($app, 'setCatchErrors')) { + $app->setCatchErrors(false); + } + $app->setAutoExit(false); + $cmd = new $className($event->getName()); + $app->add($cmd); + $app->setDefaultCommand((string) $cmd->getName(), true); + try { + $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs)); + // reusing the output from $this->io is mostly needed for tests, but generally speaking + // it does not hurt to keep the same stream as the current Application + if ($this->io instanceof ConsoleIO) { + $reflProp = new \ReflectionProperty($this->io, 'output'); + if (\PHP_VERSION_ID < 80100) { + $reflProp->setAccessible(true); + } + $output = $reflProp->getValue($this->io); + } else { + $output = new ConsoleOutput(); + } + $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output); + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); + throw $e; + } + } else { + $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs)); + + // @putenv does not receive arguments + if (strpos($callable, '@putenv ') === 0) { + $exec = $callable; + } else { + if (str_contains($callable, '@additional_args')) { + $exec = str_replace('@additional_args', $args, $callable); + } else { + $exec = $callable . ($args === '' ? '' : ' '.$args); + } + } + + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); + } elseif ( + // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it + $event->getName() !== '__exec_command' + // do not output the command being run when using `composer ` as it is also fairly obvious the user is running it + && ($event->getFlags()['script-alias-input'] ?? null) === null + ) { + $this->io->writeError(sprintf('> %s', $exec)); + } + + $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); + if (count($possibleLocalBinaries) > 0) { + foreach ($possibleLocalBinaries as $localExec) { + if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) { + $caller = BinaryInstaller::determineBinaryCaller($localExec); + $exec = Preg::replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); + break; + } + } + } + + if (strpos($exec, '@putenv ') === 0) { + if (false === strpos($exec, '=')) { + Platform::clearEnv(substr($exec, 8)); + } else { + [$var, $value] = explode('=', substr($exec, 8), 2); + Platform::putEnv($var, $value); + } + + continue; + } + if (strpos($exec, '@php ') === 0) { + $pathAndArgs = substr($exec, 5); + if (Platform::isWindows()) { + $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) { + return str_replace('/', '\\', $path[0]); + }, $pathAndArgs); + } + // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it + // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path + $matched = Preg::isMatchStrictGroups('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match); + if ($matched && !file_exists($match[0])) { + $finder = new ExecutableFinder; + if ($pathToExec = $finder->find($match[0])) { + if (Platform::isWindows()) { + $execWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToExec); + // prefer non-extension file if it exists when executing with PHP + if (file_exists($execWithoutExt)) { + $pathToExec = $execWithoutExt; + } + unset($execWithoutExt); + } + $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0])); + } + } + $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; + } else { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if ($phpPath) { + Platform::putEnv('PHP_BINARY', $phpPath); + } + + if (Platform::isWindows()) { + $exec = Preg::replaceCallback('{^\S+}', static function ($path) { + return str_replace('/', '\\', $path[0]); + }, $exec); + } + } + + // if composer is being executed, make sure it runs the expected composer from current path + // resolution, even if bin-dir contains composer too because the project requires composer/composer + // see https://github.com/composer/composer/issues/8748 + if (strpos($exec, 'composer ') === 0) { + $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8); + } + + if (0 !== ($exitCode = $this->executeTty($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); + + throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } + + $returnMax = max($returnMax, $return); + + if ($event->isPropagationStopped()) { + break; + } + } + } finally { + $this->popEvent(); + + $knownIdentifiers = []; + foreach ($autoloadersBefore as $key => $cb) { + $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb]; + } + foreach (spl_autoload_functions() as $cb) { + // once we get to the first known autoloader, we can leave any appended autoloader without problems + if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) { + break; + } + + // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first + if ($cb instanceof ClassLoader) { + $cb->unregister(); + $cb->register(false); + } else { + spl_autoload_unregister($cb); + spl_autoload_register($cb); + } + } + } + + return $returnMax; + } + + protected function executeTty(string $exec): int + { + if ($this->io->isInteractive()) { + return $this->process->executeTty($exec); + } + + return $this->process->execute($exec); + } + + protected function getPhpExecCommand(): string + { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if (!$phpPath) { + throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath); + } + $phpArgs = $finder->findArguments(); + $phpArgs = $phpArgs ? ' ' . implode(' ', $phpArgs) : ''; + $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); + $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')); + $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')); + + return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; + } + + /** + * @param Event $event Event invoking the PHP callable + * + * @return mixed + */ + protected function executeEventPhpScript(string $className, string $methodName, Event $event) + { + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); + } else { + $this->io->writeError(sprintf('> %s::%s', $className, $methodName)); + } + + return $className::$methodName($event); + } + + /** + * Add a listener for a particular event + * + * @param string $eventName The event name - typically a constant + * @param callable|string $listener A callable expecting an event argument, or a command string to be executed (same as a composer.json "scripts" entry) + * @param int $priority A higher value represents a higher priority + */ + public function addListener(string $eventName, $listener, int $priority = 0): void + { + $this->listeners[$eventName][$priority][] = $listener; + } + + /** + * @param callable|object $listener A callable or an object instance for which all listeners should be removed + */ + public function removeListener($listener): void + { + foreach ($this->listeners as $eventName => $priorities) { + foreach ($priorities as $priority => $listeners) { + foreach ($listeners as $index => $candidate) { + if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) { + unset($this->listeners[$eventName][$priority][$index]); + } + } + } + } + } + + /** + * Adds object methods as listeners for the events in getSubscribedEvents + * + * @see EventSubscriberInterface + */ + public function addSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + /** + * Retrieves all listeners for a given event + * + * @return array All listeners: callables and scripts + */ + protected function getListeners(Event $event): array + { + $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : []; + + if (!isset($this->listeners[$event->getName()][0])) { + $this->listeners[$event->getName()][0] = []; + } + krsort($this->listeners[$event->getName()]); + + $listeners = $this->listeners; + $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners); + + return array_merge(...$listeners[$event->getName()]); + } + + /** + * Checks if an event has listeners registered + */ + public function hasEventListeners(Event $event): bool + { + $listeners = $this->getListeners($event); + + return count($listeners) > 0; + } + + /** + * Finds all listeners defined as scripts in the package + * + * @param Event $event Event object + * @return string[] Listeners + */ + protected function getScriptListeners(Event $event): array + { + $package = $this->composer->getPackage(); + $scripts = $package->getScripts(); + + if (empty($scripts[$event->getName()])) { + return []; + } + + if (in_array($event->getName(), $this->skipScripts, true)) { + $this->io->writeError('Skipped script listeners for '.$event->getName().' because of COMPOSER_SKIP_SCRIPTS', true, IOInterface::VERBOSE); + + return []; + } + + assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); + + if ($this->loader) { + $this->loader->unregister(); + } + + $generator = $this->composer->getAutoloadGenerator(); + if ($event instanceof ScriptEvent) { + $generator->setDevMode($event->isDevMode()); + } + + $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); + $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages); + $map = $generator->parseAutoloads($packageMap, $package); + $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); + $this->loader->register(false); + + return $scripts[$event->getName()]; + } + + /** + * Checks if string given references a class path and method + */ + protected function isPhpScript(string $callable): bool + { + return false === strpos($callable, ' ') && false !== strpos($callable, '::'); + } + + /** + * Checks if string given references a command class + */ + protected function isCommandClass(string $callable): bool + { + return str_contains($callable, '\\') && !str_contains($callable, ' ') && str_ends_with($callable, 'Command'); + } + + /** + * Checks if string given references a composer run-script + */ + protected function isComposerScript(string $callable): bool + { + return strpos($callable, '@') === 0 && strpos($callable, '@php ') !== 0 && strpos($callable, '@putenv ') !== 0; + } + + /** + * Push an event to the stack of active event + * + * @throws \RuntimeException + */ + protected function pushEvent(Event $event): int + { + $eventName = $event->getName(); + if (in_array($eventName, $this->eventStack)) { + throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName)); + } + + return array_push($this->eventStack, $eventName); + } + + /** + * Pops the active event from the stack + */ + protected function popEvent(): ?string + { + return array_pop($this->eventStack); + } + + private function ensureBinDirIsInPath(): void + { + $pathEnv = 'PATH'; + + // checking if only Path and not PATH is set then we probably need to update the Path env + // on Windows getenv is case-insensitive so we cannot check it via Platform::getEnv and + // we need to check in $_SERVER directly + if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) { + $pathEnv = 'Path'; + } + + // add the bin dir to the PATH to make local binaries of deps usable in scripts + $binDir = $this->composer->getConfig()->get('bin-dir'); + if (is_dir($binDir)) { + $binDir = realpath($binDir); + $pathValue = (string) Platform::getEnv($pathEnv); + if (!Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $pathValue)) { + Platform::putEnv($pathEnv, $binDir.PATH_SEPARATOR.$pathValue); + } + } + } + + /** + * @param callable $cb DO NOT MOVE TO TYPE HINT as private autoload callbacks are not technically callable + */ + private function getCallbackIdentifier($cb): string + { + if (is_string($cb)) { + return 'fn:'.$cb; + } + if (is_object($cb)) { + return 'obj:'.spl_object_hash($cb); + } + if (is_array($cb)) { + return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1]; + } + + // not great but also do not want to break everything here + return 'unsupported'; + } +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php b/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..fe0c5bd --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php @@ -0,0 +1,48 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * An EventSubscriber knows which events it is interested in. + * + * If an EventSubscriber is added to an EventDispatcher, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array> The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php b/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php new file mode 100644 index 0000000..72a4aa2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\EventDispatcher; + +/** + * Thrown when a script running an external process exits with a non-0 status code + * + * @author Jordi Boggiano + */ +class ScriptExecutionException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php b/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php new file mode 100644 index 0000000..a442786 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php @@ -0,0 +1,20 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Exception; + +/** + * @author Jordi Boggiano + */ +class IrrecoverableDownloadException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Exception/NoSslException.php b/vendor/composer/composer/src/Composer/Exception/NoSslException.php new file mode 100644 index 0000000..4696dd3 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Exception/NoSslException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Exception; + +/** + * Specific exception for Composer\Util\HttpDownloader creation. + * + * @author Jordi Boggiano + */ +class NoSslException extends \RuntimeException +{ +} diff --git a/vendor/composer/composer/src/Composer/Factory.php b/vendor/composer/composer/src/Composer/Factory.php new file mode 100644 index 0000000..5399899 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Factory.php @@ -0,0 +1,764 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Config\JsonConfigSource; +use Composer\Json\JsonFile; +use Composer\IO\IOInterface; +use Composer\Package\Archiver; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\RootPackageInterface; +use Composer\Repository\FilesystemRepository; +use Composer\Repository\RepositoryManager; +use Composer\Repository\RepositoryFactory; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Loop; +use Composer\Util\Silencer; +use Composer\Plugin\PluginEvents; +use Composer\EventDispatcher\Event; +use Phar; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Output\ConsoleOutput; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Autoload\AutoloadGenerator; +use Composer\Package\Version\VersionParser; +use Composer\Downloader\TransportException; +use Composer\Json\JsonValidationException; +use Composer\Repository\InstalledRepositoryInterface; +use UnexpectedValueException; +use ZipArchive; + +/** + * Creates a configured instance of composer. + * + * @author Ryan Weaver + * @author Jordi Boggiano + * @author Igor Wiedler + * @author Nils Adermann + */ +class Factory +{ + /** + * @throws \RuntimeException + */ + protected static function getHomeDir(): string + { + $home = Platform::getEnv('COMPOSER_HOME'); + if ($home) { + return $home; + } + + if (Platform::isWindows()) { + if (!Platform::getEnv('APPDATA')) { + throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + + return rtrim(strtr(Platform::getEnv('APPDATA'), '\\', '/'), '/') . '/Composer'; + } + + $userDir = self::getUserDir(); + $dirs = []; + + if (self::useXdg()) { + // XDG Base Directory Specifications + $xdgConfig = Platform::getEnv('XDG_CONFIG_HOME'); + if (!$xdgConfig) { + $xdgConfig = $userDir . '/.config'; + } + + $dirs[] = $xdgConfig . '/composer'; + } + + $dirs[] = $userDir . '/.composer'; + + // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer + foreach ($dirs as $dir) { + if (Silencer::call('is_dir', $dir)) { + return $dir; + } + } + + // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise) + return $dirs[0]; + } + + protected static function getCacheDir(string $home): string + { + $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR'); + if ($cacheDir) { + return $cacheDir; + } + + $homeEnv = Platform::getEnv('COMPOSER_HOME'); + if ($homeEnv) { + return $homeEnv . '/cache'; + } + + if (Platform::isWindows()) { + if ($cacheDir = Platform::getEnv('LOCALAPPDATA')) { + $cacheDir .= '/Composer'; + } else { + $cacheDir = $home . '/cache'; + } + + return rtrim(strtr($cacheDir, '\\', '/'), '/'); + } + + $userDir = self::getUserDir(); + if (PHP_OS === 'Darwin') { + // Migrate existing cache dir in old location if present + if (is_dir($home . '/cache') && !is_dir($userDir . '/Library/Caches/composer')) { + Silencer::call('rename', $home . '/cache', $userDir . '/Library/Caches/composer'); + } + + return $userDir . '/Library/Caches/composer'; + } + + if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) { + return $home . '/cache'; + } + + if (self::useXdg()) { + $xdgCache = Platform::getEnv('XDG_CACHE_HOME') ?: $userDir . '/.cache'; + + return $xdgCache . '/composer'; + } + + return $home . '/cache'; + } + + protected static function getDataDir(string $home): string + { + $homeEnv = Platform::getEnv('COMPOSER_HOME'); + if ($homeEnv) { + return $homeEnv; + } + + if (Platform::isWindows()) { + return strtr($home, '\\', '/'); + } + + $userDir = self::getUserDir(); + if ($home !== $userDir . '/.composer' && self::useXdg()) { + $xdgData = Platform::getEnv('XDG_DATA_HOME') ?: $userDir . '/.local/share'; + + return $xdgData . '/composer'; + } + + return $home; + } + + public static function createConfig(?IOInterface $io = null, ?string $cwd = null): Config + { + $cwd = $cwd ?? Platform::getCwd(true); + + $config = new Config(true, $cwd); + + // determine and add main dirs to the config + $home = self::getHomeDir(); + $config->merge([ + 'config' => [ + 'home' => $home, + 'cache-dir' => self::getCacheDir($home), + 'data-dir' => self::getDataDir($home), + ], + ], Config::SOURCE_DEFAULT); + + // load global config + $file = new JsonFile($config->get('home').'/config.json'); + if ($file->exists()) { + if ($io instanceof IOInterface) { + $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $file); + $config->merge($file->read(), $file->getPath()); + } + $config->setConfigSource(new JsonConfigSource($file)); + + $htaccessProtect = $config->get('htaccess-protect'); + if ($htaccessProtect) { + // Protect directory against web access. Since HOME could be + // the www-data's user home and be web-accessible it is a + // potential security risk + $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')]; + foreach ($dirs as $dir) { + if (!file_exists($dir . '/.htaccess')) { + if (!is_dir($dir)) { + Silencer::call('mkdir', $dir, 0777, true); + } + Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all'); + } + } + } + + // load global auth file + $file = new JsonFile($config->get('home').'/auth.json'); + if ($file->exists()) { + if ($io instanceof IOInterface) { + $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA); + $config->merge(['config' => $file->read()], $file->getPath()); + } + $config->setAuthConfigSource(new JsonConfigSource($file, true)); + + self::loadComposerAuthEnv($config, $io); + + return $config; + } + + public static function getComposerFile(): string + { + $env = Platform::getEnv('COMPOSER'); + if (is_string($env)) { + $env = trim($env); + if ('' !== $env) { + if (is_dir($env)) { + throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.'); + } + + return $env; + } + } + + return './composer.json'; + } + + public static function getLockFile(string $composerFile): string + { + return "json" === pathinfo($composerFile, PATHINFO_EXTENSION) + ? substr($composerFile, 0, -4).'lock' + : $composerFile . '.lock'; + } + + /** + * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle} + */ + public static function createAdditionalStyles(): array + { + return [ + 'highlight' => new OutputFormatterStyle('red'), + 'warning' => new OutputFormatterStyle('black', 'yellow'), + ]; + } + + public static function createOutput(): ConsoleOutput + { + $styles = self::createAdditionalStyles(); + $formatter = new OutputFormatter(false, $styles); + + return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); + } + + /** + * Creates a Composer instance + * + * @param IOInterface $io IO instance + * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will + * read from the default filename + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @param bool $disableScripts Whether scripts should not be run + * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) + * @throws \InvalidArgumentException + * @throws \UnexpectedValueException + * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer + * @phpstan-return ($fullLoad is true ? Composer : PartialComposer) + */ + public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false) + { + // if a custom composer.json path is given, we change the default cwd to be that file's directory + if (is_string($localConfig) && is_file($localConfig) && null === $cwd) { + $cwd = dirname($localConfig); + } + + $cwd = $cwd ?? Platform::getCwd(true); + + // load Composer configuration + if (null === $localConfig) { + $localConfig = static::getComposerFile(); + } + + $localConfigSource = Config::SOURCE_UNKNOWN; + if (is_string($localConfig)) { + $composerFile = $localConfig; + + $file = new JsonFile($localConfig, null, $io); + + if (!$file->exists()) { + if ($localConfig === './composer.json' || $localConfig === 'composer.json') { + $message = 'Composer could not find a composer.json file in '.$cwd; + } else { + $message = 'Composer could not find the config file: '.$localConfig; + } + $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : ''; + throw new \InvalidArgumentException($message.PHP_EOL.$instructions); + } + + if (!Platform::isInputCompletionProcess()) { + try { + $file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); + $message = $e->getMessage() . ':' . PHP_EOL . $errors; + throw new JsonValidationException($message); + } + } + + $localConfig = $file->read(); + $localConfigSource = $file->getPath(); + } + + // Load config and override with local config/auth config + $config = static::createConfig($io, $cwd); + $isGlobal = $localConfigSource !== Config::SOURCE_UNKNOWN && realpath($config->get('home')) === realpath(dirname($localConfigSource)); + $config->merge($localConfig, $localConfigSource); + + if (isset($composerFile)) { + $io->writeError('Loading config file ' . $composerFile .' ('.realpath($composerFile).')', true, IOInterface::DEBUG); + $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io))); + + $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io); + if ($localAuthFile->exists()) { + $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG); + self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA); + $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath()); + $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, true)); + } + } + + // make sure we load the auth env again over the local auth.json + composer.json config + self::loadComposerAuthEnv($config, $io); + + $vendorDir = $config->get('vendor-dir'); + + // initialize composer + $composer = $fullLoad ? new Composer() : new PartialComposer(); + $composer->setConfig($config); + if ($isGlobal) { + $composer->setGlobal(); + } + + if ($fullLoad) { + // load auth configs into the IO instance + $io->loadConfiguration($config); + + // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it + // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once + if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) { + // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir + // as we cannot guarantee integrity of that file + if (class_exists('Composer\InstalledVersions')) { + FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath); + } + } + } + + $httpDownloader = self::createHttpDownloader($io, $config); + $process = new ProcessExecutor($io); + $loop = new Loop($httpDownloader, $process); + $composer->setLoop($loop); + + // initialize event dispatcher + $dispatcher = new EventDispatcher($composer, $io, $process); + $dispatcher->setRunScripts(!$disableScripts); + $composer->setEventDispatcher($dispatcher); + + // initialize repository manager + $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process); + $composer->setRepositoryManager($rm); + + // force-set the version of the global package if not defined as + // guessing it adds no value and only takes time + if (!$fullLoad && !isset($localConfig['version'])) { + $localConfig['version'] = '1.0.0'; + } + + // load package + $parser = new VersionParser; + $guesser = new VersionGuesser($config, $process, $parser, $io); + $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io); + $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); + $composer->setPackage($package); + + // load local repository + $this->addLocalRepository($io, $rm, $vendorDir, $package, $process); + + // initialize installation manager + $im = $this->createInstallationManager($loop, $io, $dispatcher); + $composer->setInstallationManager($im); + + if ($composer instanceof Composer) { + // initialize download manager + $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); + $composer->setDownloadManager($dm); + + // initialize autoload generator + $generator = new AutoloadGenerator($dispatcher, $io); + $composer->setAutoloadGenerator($generator); + + // initialize archive manager + $am = $this->createArchiveManager($config, $dm, $loop); + $composer->setArchiveManager($am); + } + + // add installers to the manager (must happen after download manager is created since they read it out of $composer) + $this->createDefaultInstallers($im, $composer, $io, $process); + + // init locker if possible + if ($composer instanceof Composer && isset($composerFile)) { + $lockFile = self::getLockFile($composerFile); + if (!$config->get('lock') && file_exists($lockFile)) { + $io->writeError(''.$lockFile.' is present but ignored as the "lock" config option is disabled.'); + } + + $locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process); + $composer->setLocker($locker); + } elseif ($composer instanceof Composer) { + $locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process); + $composer->setLocker($locker); + } + + if ($composer instanceof Composer) { + $globalComposer = null; + if (!$composer->isGlobal()) { + $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts); + } + + $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); + $composer->setPluginManager($pm); + + if ($composer->isGlobal()) { + $pm->setRunningInGlobalDir(true); + } + + $pm->loadInstalledPlugins(); + } + + if ($fullLoad) { + $initEvent = new Event(PluginEvents::INIT); + $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent); + + // once everything is initialized we can + // purge packages from local repos if they have been deleted on the filesystem + $this->purgePackages($rm->getLocalRepository(), $im); + } + + return $composer; + } + + /** + * @param bool $disablePlugins Whether plugins should not be loaded + * @param bool $disableScripts Whether scripts should not be executed + */ + public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer + { + $factory = new static(); + + return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true); + } + + /** + * @param Repository\RepositoryManager $rm + */ + protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null): void + { + $fs = null; + if ($process) { + $fs = new Filesystem($process); + } + + $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs)); + } + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true + * @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null) + */ + protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer + { + // make sure if disable plugins was 'local' it is now turned off + $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true; + + $composer = null; + try { + $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts); + } catch (\Exception $e) { + $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG); + } + + return $composer; + } + + /** + * @param IO\IOInterface $io + * @param EventDispatcher $eventDispatcher + */ + public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null): Downloader\DownloadManager + { + $cache = null; + if ($config->get('cache-files-ttl') > 0) { + $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); + $cache->setReadOnly($config->get('cache-read-only')); + } + + $fs = new Filesystem($process); + + $dm = new Downloader\DownloadManager($io, false, $fs); + switch ($preferred = $config->get('preferred-install')) { + case 'dist': + $dm->setPreferDist(true); + break; + case 'source': + $dm->setPreferSource(true); + break; + case 'auto': + default: + // noop + break; + } + + if (is_array($preferred)) { + $dm->setPreferences($preferred); + } + + $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs)); + $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs)); + $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs)); + $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs)); + $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + + return $dm; + } + + /** + * @param Config $config The configuration + * @param Downloader\DownloadManager $dm Manager use to download sources + * @return Archiver\ArchiveManager + */ + public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop) + { + $am = new Archiver\ArchiveManager($dm, $loop); + if (class_exists(ZipArchive::class)) { + $am->addArchiver(new Archiver\ZipArchiver); + } + if (class_exists(Phar::class)) { + $am->addArchiver(new Archiver\PharArchiver); + } + + return $am; + } + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + */ + protected function createPluginManager(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager + { + return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); + } + + public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null): Installer\InstallationManager + { + return new Installer\InstallationManager($loop, $io, $eventDispatcher); + } + + protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null): void + { + $fs = new Filesystem($process); + $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/')); + + $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller)); + $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller)); + $im->addInstaller(new Installer\MetapackageInstaller($io)); + } + + /** + * @param InstalledRepositoryInterface $repo repository to purge packages from + * @param Installer\InstallationManager $im manager to check whether packages are still installed + */ + protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void + { + foreach ($repo->getPackages() as $package) { + if (!$im->isPackageInstalled($repo, $package)) { + $repo->removePackage($package); + } + } + } + + protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader + { + return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); + } + + /** + * @param IOInterface $io IO instance + * @param mixed $config either a configuration array or a filename to read from, if null it will read from + * the default filename + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + * @param bool $disableScripts Whether scripts should not be run + */ + public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer + { + $factory = new static(); + + // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path + // we disable local plugins as they really should not be loaded from CWD + // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly + // to the path where the composer.json being loaded resides + if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) { + $disablePlugins = 'local'; + } + + return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts); + } + + /** + * If you are calling this in a plugin, you probably should instead use $composer->getLoop()->getHttpDownloader() + * + * @param IOInterface $io IO instance + * @param Config $config Config instance + * @param mixed[] $options Array of options passed directly to HttpDownloader constructor + */ + public static function createHttpDownloader(IOInterface $io, Config $config, array $options = []): HttpDownloader + { + static $warned = false; + $disableTls = false; + // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command + if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) { + $warned = true; + $disableTls = !extension_loaded('openssl'); + } elseif ($config->get('disable-tls') === true) { + if (!$warned) { + $io->writeError('You are running Composer with SSL/TLS protection disabled.'); + } + $warned = true; + $disableTls = true; + } elseif (!extension_loaded('openssl')) { + throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' + . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + $httpDownloaderOptions = []; + if ($disableTls === false) { + if ('' !== $config->get('cafile')) { + $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile'); + } + if ('' !== $config->get('capath')) { + $httpDownloaderOptions['ssl']['capath'] = $config->get('capath'); + } + $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options); + } + try { + $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls); + } catch (TransportException $e) { + if (false !== strpos($e->getMessage(), 'cafile')) { + $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); + $io->write('A valid CA certificate file is required for SSL/TLS protection.'); + $io->write('You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); + } + throw $e; + } + + return $httpDownloader; + } + + private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void + { + $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH'); + if (false === $composerAuthEnv || '' === $composerAuthEnv) { + return; + } + + $authData = json_decode($composerAuthEnv); + if (null === $authData) { + throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); + } + + if ($io instanceof IOInterface) { + $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG); + } + self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); + $authData = json_decode($composerAuthEnv, true); + if (null !== $authData) { + $config->merge(['config' => $authData], 'COMPOSER_AUTH'); + } + } + + private static function useXdg(): bool + { + foreach (array_keys($_SERVER) as $key) { + if (strpos((string) $key, 'XDG_') === 0) { + return true; + } + } + + if (Silencer::call('is_dir', '/etc/xdg')) { + return true; + } + + return false; + } + + /** + * @throws \RuntimeException + */ + private static function getUserDir(): string + { + $home = Platform::getEnv('HOME'); + if (!$home) { + throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + + return rtrim(strtr($home, '\\', '/'), '/'); + } + + /** + * @param mixed $fileOrData + * @param JsonFile::*_SCHEMA $schema + */ + private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null): void + { + if (Platform::isInputCompletionProcess()) { + return; + } + + try { + if ($fileOrData instanceof JsonFile) { + $fileOrData->validateSchema($schema); + } else { + if (null === $source) { + throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data'); + } + JsonFile::validateJsonSchema($source, $fileOrData, $schema); + } + } catch (JsonValidationException $e) { + $msg = $e->getMessage().', this may result in errors and should be resolved:'.PHP_EOL.' - '.implode(PHP_EOL.' - ', $e->getErrors()); + if ($io instanceof IOInterface) { + $io->writeError(''.$msg.''); + } else { + throw new UnexpectedValueException($msg); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php new file mode 100644 index 0000000..8167fdd --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php @@ -0,0 +1,28 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +use Composer\Repository\PlatformRepository; + +final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + public function isIgnored(string $req): bool + { + return PlatformRepository::isPlatformPackage($req); + } + + public function isUpperBoundIgnored(string $req): bool + { + return $this->isIgnored($req); + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php new file mode 100644 index 0000000..73d5363 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php @@ -0,0 +1,97 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Interval; +use Composer\Semver\Intervals; + +final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + /** + * @var non-empty-string + */ + private $ignoreRegex; + + /** + * @var non-empty-string + */ + private $ignoreUpperBoundRegex; + + /** + * @param string[] $reqList + */ + public function __construct(array $reqList) + { + $ignoreAll = $ignoreUpperBound = []; + foreach ($reqList as $req) { + if (substr($req, -1) === '+') { + $ignoreUpperBound[] = substr($req, 0, -1); + } else { + $ignoreAll[] = $req; + } + } + $this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll); + $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound); + } + + public function isIgnored(string $req): bool + { + if (!PlatformRepository::isPlatformPackage($req)) { + return false; + } + + return Preg::isMatch($this->ignoreRegex, $req); + } + + public function isUpperBoundIgnored(string $req): bool + { + if (!PlatformRepository::isPlatformPackage($req)) { + return false; + } + + return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req); + } + + /** + * @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped + */ + public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = true): ConstraintInterface + { + if (!PlatformRepository::isPlatformPackage($req)) { + return $constraint; + } + + if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) { + return $constraint; + } + + if (Preg::isMatch($this->ignoreRegex, $req)) { + return new MatchAllConstraint; + } + + $intervals = Intervals::get($constraint); + $last = end($intervals['numeric']); + if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) { + $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], false); + } + + return $constraint; + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php new file mode 100644 index 0000000..ab225d6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +final class IgnoreNothingPlatformRequirementFilter implements PlatformRequirementFilterInterface +{ + /** + * @return false + */ + public function isIgnored(string $req): bool + { + return false; + } + + /** + * @return false + */ + public function isUpperBoundIgnored(string $req): bool + { + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php new file mode 100644 index 0000000..6701562 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php @@ -0,0 +1,47 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +final class PlatformRequirementFilterFactory +{ + /** + * @param mixed $boolOrList + */ + public static function fromBoolOrList($boolOrList): PlatformRequirementFilterInterface + { + if (is_bool($boolOrList)) { + return $boolOrList ? self::ignoreAll() : self::ignoreNothing(); + } + + if (is_array($boolOrList)) { + return new IgnoreListPlatformRequirementFilter($boolOrList); + } + + throw new \InvalidArgumentException( + sprintf( + 'PlatformRequirementFilter: Unknown $boolOrList parameter %s. Please report at https://github.com/composer/composer/issues/new.', + gettype($boolOrList) + ) + ); + } + + public static function ignoreAll(): PlatformRequirementFilterInterface + { + return new IgnoreAllPlatformRequirementFilter(); + } + + public static function ignoreNothing(): PlatformRequirementFilterInterface + { + return new IgnoreNothingPlatformRequirementFilter(); + } +} diff --git a/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php new file mode 100644 index 0000000..59e8245 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php @@ -0,0 +1,20 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Filter\PlatformRequirementFilter; + +interface PlatformRequirementFilterInterface +{ + public function isIgnored(string $req): bool; + + public function isUpperBoundIgnored(string $req): bool; +} diff --git a/vendor/composer/composer/src/Composer/IO/BaseIO.php b/vendor/composer/composer/src/Composer/IO/BaseIO.php new file mode 100644 index 0000000..55ac166 --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/BaseIO.php @@ -0,0 +1,270 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Silencer; +use Psr\Log\LogLevel; + +abstract class BaseIO implements IOInterface +{ + /** @var array */ + protected $authentications = []; + + /** + * @inheritDoc + */ + public function getAuthentications() + { + return $this->authentications; + } + + /** + * @return void + */ + public function resetAuthentications() + { + $this->authentications = []; + } + + /** + * @inheritDoc + */ + public function hasAuthentication($repositoryName) + { + return isset($this->authentications[$repositoryName]); + } + + /** + * @inheritDoc + */ + public function getAuthentication($repositoryName) + { + if (isset($this->authentications[$repositoryName])) { + return $this->authentications[$repositoryName]; + } + + return ['username' => null, 'password' => null]; + } + + /** + * @inheritDoc + */ + public function setAuthentication($repositoryName, $username, $password = null) + { + $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password]; + } + + /** + * @inheritDoc + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->write($messages, $newline, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->writeError($messages, $newline, $verbosity); + } + + /** + * Check for overwrite and set the authentication information for the repository. + * + * @param string $repositoryName The unique name of repository + * @param string $username The username + * @param string $password The password + * + * @return void + */ + protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null) + { + if ($this->hasAuthentication($repositoryName)) { + $auth = $this->getAuthentication($repositoryName); + if ($auth['username'] === $username && $auth['password'] === $password) { + return; + } + + $this->writeError( + sprintf( + "Warning: You should avoid overwriting already defined auth settings for %s.", + $repositoryName + ) + ); + } + $this->setAuthentication($repositoryName, $username, $password); + } + + /** + * @inheritDoc + */ + public function loadConfiguration(Config $config) + { + $bitbucketOauth = $config->get('bitbucket-oauth'); + $githubOauth = $config->get('github-oauth'); + $gitlabOauth = $config->get('gitlab-oauth'); + $gitlabToken = $config->get('gitlab-token'); + $httpBasic = $config->get('http-basic'); + $bearerToken = $config->get('bearer'); + + // reload oauth tokens from config if available + + foreach ($bitbucketOauth as $domain => $cred) { + $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']); + } + + foreach ($githubOauth as $domain => $token) { + if ($domain !== 'github.com' && !in_array($domain, $config->get('github-domains'), true)) { + $this->debug($domain.' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['github-domains' => array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/ + // plus dots which were at some point used for GH app integration tokens + if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) { + throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); + } + $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); + } + + foreach ($gitlabOauth as $domain => $token) { + if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { + $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + $token = is_array($token) ? $token["token"] : $token; + $this->checkAndSetAuthentication($domain, $token, 'oauth2'); + } + + foreach ($gitlabToken as $domain => $token) { + if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { + $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); + $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); + } + + $username = is_array($token) ? $token["username"] : $token; + $password = is_array($token) ? $token["token"] : 'private-token'; + $this->checkAndSetAuthentication($domain, $username, $password); + } + + // reload http basic credentials from config if available + foreach ($httpBasic as $domain => $cred) { + $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']); + } + + foreach ($bearerToken as $domain => $token) { + $this->checkAndSetAuthentication($domain, $token, 'bearer'); + } + + // setup process timeout + ProcessExecutor::setTimeout($config->get('process-timeout')); + } + + /** + * @param string|\Stringable $message + */ + public function emergency($message, array $context = []): void + { + $this->log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function alert($message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function critical($message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function error($message, array $context = []): void + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function warning($message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function notice($message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function info($message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * @param string|\Stringable $message + */ + public function debug($message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * @param mixed|LogLevel::* $level + * @param string|\Stringable $message + */ + public function log($level, $message, array $context = []): void + { + $message = (string) $message; + + if ($context !== []) { + $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); + if ($json !== false) { + $message .= ' ' . $json; + } + } + + if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { + $this->writeError(''.$message.''); + } elseif ($level === LogLevel::WARNING) { + $this->writeError(''.$message.''); + } elseif ($level === LogLevel::NOTICE) { + $this->writeError(''.$message.'', true, self::VERBOSE); + } elseif ($level === LogLevel::INFO) { + $this->writeError(''.$message.'', true, self::VERY_VERBOSE); + } else { + $this->writeError($message, true, self::DEBUG); + } + } +} diff --git a/vendor/composer/composer/src/Composer/IO/BufferIO.php b/vendor/composer/composer/src/Composer/IO/BufferIO.php new file mode 100644 index 0000000..6cf962b --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/BufferIO.php @@ -0,0 +1,105 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Pcre\Preg; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Helper\HelperSet; + +/** + * @author Jordi Boggiano + */ +class BufferIO extends ConsoleIO +{ + public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null) + { + $input = new StringInput($input); + $input->setInteractive(false); + + $stream = fopen('php://memory', 'rw'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } + $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter); + + parent::__construct($input, $output, new HelperSet([ + new QuestionHelper(), + ])); + } + + /** + * @return string output + */ + public function getOutput(): string + { + assert($this->output instanceof StreamOutput); + fseek($this->output->getStream(), 0); + + $output = (string) stream_get_contents($this->output->getStream()); + + $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string { + $pre = strip_tags($matches[1]); + + if (strlen($pre) === strlen($matches[2])) { + return ''; + } + + // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs + return rtrim($matches[1])."\n"; + }, $output); + + return $output; + } + + /** + * @param string[] $inputs + * + * @see createStream + */ + public function setUserInputs(array $inputs): void + { + if (!$this->input instanceof StreamableInputInterface) { + throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $this->input->setStream($this->createStream($inputs)); + $this->input->setInteractive(true); + } + + /** + * @param string[] $inputs + * + * @return resource stream + */ + private function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+'); + if ($stream === false) { + throw new \RuntimeException('Unable to open memory output stream'); + } + + foreach ($inputs as $input) { + fwrite($stream, $input.PHP_EOL); + } + + rewind($stream); + + return $stream; + } +} diff --git a/vendor/composer/composer/src/Composer/IO/ConsoleIO.php b/vendor/composer/composer/src/Composer/IO/ConsoleIO.php new file mode 100644 index 0000000..8ecea42 --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/ConsoleIO.php @@ -0,0 +1,345 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Question\StrictConfirmationQuestion; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * The Input/Output helper. + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class ConsoleIO extends BaseIO +{ + /** @var InputInterface */ + protected $input; + /** @var OutputInterface */ + protected $output; + /** @var HelperSet */ + protected $helperSet; + /** @var string */ + protected $lastMessage = ''; + /** @var string */ + protected $lastMessageErr = ''; + + /** @var float */ + private $startTime; + /** @var array */ + private $verbosityMap; + + /** + * Constructor. + * + * @param InputInterface $input The input instance + * @param OutputInterface $output The output instance + * @param HelperSet $helperSet The helperSet instance + */ + public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) + { + $this->input = $input; + $this->output = $output; + $this->helperSet = $helperSet; + $this->verbosityMap = [ + self::QUIET => OutputInterface::VERBOSITY_QUIET, + self::NORMAL => OutputInterface::VERBOSITY_NORMAL, + self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, + self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, + self::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ]; + } + + /** + * @return void + */ + public function enableDebugging(float $startTime) + { + $this->startTime = $startTime; + } + + /** + * @inheritDoc + */ + public function isInteractive() + { + return $this->input->isInteractive(); + } + + /** + * @inheritDoc + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * @inheritDoc + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * @inheritDoc + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * @inheritDoc + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + /** + * @inheritDoc + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, false, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, true, $verbosity); + } + + /** + * @inheritDoc + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, false, $verbosity, true); + } + + /** + * @inheritDoc + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) + { + $this->doWrite($messages, $newline, true, $verbosity, true); + } + + /** + * @param string[]|string $messages + */ + private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = false): void + { + $sfVerbosity = $this->verbosityMap[$verbosity]; + if ($sfVerbosity > $this->output->getVerbosity()) { + return; + } + + if ($raw) { + $sfVerbosity |= OutputInterface::OUTPUT_RAW; + } + + if (null !== $this->startTime) { + $memoryUsage = memory_get_usage() / 1024 / 1024; + $timeSpent = microtime(true) - $this->startTime; + $messages = array_map(static function ($message) use ($memoryUsage, $timeSpent): string { + return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + }, (array) $messages); + } + + if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { + $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); + $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages); + + return; + } + + $this->output->write($messages, $newline, $sfVerbosity); + $this->lastMessage = implode($newline ? "\n" : '', (array) $messages); + } + + /** + * @inheritDoc + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) + { + $this->doOverwrite($messages, $newline, $size, false, $verbosity); + } + + /** + * @inheritDoc + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) + { + $this->doOverwrite($messages, $newline, $size, true, $verbosity); + } + + /** + * @param string[]|string $messages + */ + private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity): void + { + // messages can be an array, let's convert it to string anyway + $messages = implode($newline ? "\n" : '', (array) $messages); + + // since overwrite is supposed to overwrite last message... + if (!isset($size)) { + // removing possible formatting of lastMessage with strip_tags + $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); + } + // ...let's fill its length with backspaces + $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity); + + // write the new message + $this->doWrite($messages, false, $stderr, $verbosity); + + // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to + // track the length of previous output and fill it with spaces to make sure the line is cleared. + // See https://github.com/composer/composer/pull/5836 for more details + $fill = $size - strlen(strip_tags($messages)); + if ($fill > 0) { + // whitespace whatever has left + $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity); + // move the cursor back + $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity); + } + + if ($newline) { + $this->doWrite('', true, $stderr, $verbosity); + } + + if ($stderr) { + $this->lastMessageErr = $messages; + } else { + $this->lastMessage = $messages; + } + } + + /** + * @return ProgressBar + */ + public function getProgressBar(int $max = 0) + { + return new ProgressBar($this->getErrorOutput(), $max); + } + + /** + * @inheritDoc + */ + public function ask($question, $default = null) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question($question, $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askConfirmation($question, $default = true) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new StrictConfirmationQuestion($question, $default); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askAndValidate($question, $validator, $attempts = null, $default = null) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question($question, $default); + $question->setValidator($validator); + $question->setMaxAttempts($attempts); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function askAndHideAnswer($question) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question($question); + $question->setHidden(true); + + return $helper->ask($this->input, $this->getErrorOutput(), $question); + } + + /** + * @inheritDoc + */ + public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new ChoiceQuestion($question, $choices, $default); + $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int + $question->setErrorMessage($errorMessage); + $question->setMultiselect($multiselect); + + $result = $helper->ask($this->input, $this->getErrorOutput(), $question); + + $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string')); + if ($isAssoc) { + return $result; + } + + if (!is_array($result)) { + return (string) array_search($result, $choices, true); + } + + $results = []; + foreach ($choices as $index => $choice) { + if (in_array($choice, $result, true)) { + $results[] = (string) $index; + } + } + + return $results; + } + + public function getTable(): Table + { + return new Table($this->output); + } + + private function getErrorOutput(): OutputInterface + { + if ($this->output instanceof ConsoleOutputInterface) { + return $this->output->getErrorOutput(); + } + + return $this->output; + } +} diff --git a/vendor/composer/composer/src/Composer/IO/IOInterface.php b/vendor/composer/composer/src/Composer/IO/IOInterface.php new file mode 100644 index 0000000..88b1cd4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/IOInterface.php @@ -0,0 +1,242 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +use Composer\Config; +use Psr\Log\LoggerInterface; + +/** + * The Input/Output helper interface. + * + * @author François Pluchino + */ +interface IOInterface extends LoggerInterface +{ + public const QUIET = 1; + public const NORMAL = 2; + public const VERBOSE = 4; + public const VERY_VERBOSE = 8; + public const DEBUG = 16; + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Is this output verbose? + * + * @return bool + */ + public function isVerbose(); + + /** + * Is the output very verbose? + * + * @return bool + */ + public function isVeryVerbose(); + + /** + * Is the output in debug verbosity? + * + * @return bool + */ + public function isDebug(); + + /** + * Is this output decorated? + * + * @return bool + */ + public function isDecorated(); + + /** + * Writes a message to the output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the error output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the output, without formatting it. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Writes a message to the error output, without formatting it. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL); + + /** + * Overwrites a previous message to the output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL); + + /** + * Overwrites a previous message to the error output. + * + * @param string|string[] $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param int $size The size of line + * @param int $verbosity Verbosity level from the VERBOSITY_* constants + * + * @return void + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL); + + /** + * Asks a question to the user. + * + * @param string $question The question to ask + * @param string|bool|int|float|null $default The default answer if none is given by the user + * + * @throws \RuntimeException If there is no data to read in the input stream + * @return mixed The user answer + */ + public function ask(string $question, $default = null); + + /** + * Asks a confirmation to the user. + * + * The question will be asked until the user answers by nothing, yes, or no. + * + * @param string $question The question to ask + * @param bool $default The default answer if the user enters nothing + * + * @return bool true if the user has confirmed, false otherwise + */ + public function askConfirmation(string $question, bool $default = true); + + /** + * Asks for a value and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param string $question The question to ask + * @param callable $validator A PHP callback + * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) + * @param mixed $default The default answer if none is given by the user + * + * @throws \Exception When any of the validators return an error + * @return mixed + */ + public function askAndValidate(string $question, callable $validator, ?int $attempts = null, $default = null); + + /** + * Asks a question to the user and hide the answer. + * + * @param string $question The question to ask + * + * @return string|null The answer + */ + public function askAndHideAnswer(string $question); + + /** + * Asks the user to select a value. + * + * @param string $question The question to ask + * @param string[] $choices List of choices to pick from + * @param bool|string $default The default answer if the user enters nothing + * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param bool $multiselect Select more than one value separated by comma + * + * @throws \InvalidArgumentException + * + * @return int|string|list|bool The selected value or values (the key of the choices array) + * @phpstan-return ($multiselect is true ? list : string|int|bool) + */ + public function select(string $question, array $choices, $default, $attempts = false, string $errorMessage = 'Value "%s" is invalid', bool $multiselect = false); + + /** + * Get all authentication information entered. + * + * @return array The map of authentication data + */ + public function getAuthentications(); + + /** + * Verify if the repository has a authentication information. + * + * @param string $repositoryName The unique name of repository + * + * @return bool + */ + public function hasAuthentication(string $repositoryName); + + /** + * Get the username and password of repository. + * + * @param string $repositoryName The unique name of repository + * + * @return array{username: string|null, password: string|null} + */ + public function getAuthentication(string $repositoryName); + + /** + * Set the authentication information for the repository. + * + * @param string $repositoryName The unique name of repository + * @param string $username The username + * @param null|string $password The password + * + * @return void + */ + public function setAuthentication(string $repositoryName, string $username, ?string $password = null); + + /** + * Loads authentications from a config instance + * + * @return void + */ + public function loadConfiguration(Config $config); +} diff --git a/vendor/composer/composer/src/Composer/IO/NullIO.php b/vendor/composer/composer/src/Composer/IO/NullIO.php new file mode 100644 index 0000000..fd73fee --- /dev/null +++ b/vendor/composer/composer/src/Composer/IO/NullIO.php @@ -0,0 +1,129 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +/** + * IOInterface that is not interactive and never writes the output + * + * @author Christophe Coevoet + */ +class NullIO extends BaseIO +{ + /** + * @inheritDoc + */ + public function isInteractive(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isVerbose(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isVeryVerbose(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isDebug(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function isDecorated(): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function write($messages, bool $newline = true, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void + { + } + + /** + * @inheritDoc + */ + public function ask($question, $default = null) + { + return $default; + } + + /** + * @inheritDoc + */ + public function askConfirmation($question, $default = true): bool + { + return $default; + } + + /** + * @inheritDoc + */ + public function askAndValidate($question, $validator, $attempts = null, $default = null) + { + return $default; + } + + /** + * @inheritDoc + */ + public function askAndHideAnswer($question): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + return $default; + } +} diff --git a/vendor/composer/composer/src/Composer/InstalledVersions.php b/vendor/composer/composer/src/Composer/InstalledVersions.php new file mode 100644 index 0000000..2052022 --- /dev/null +++ b/vendor/composer/composer/src/Composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer.php b/vendor/composer/composer/src/Composer/Installer.php new file mode 100644 index 0000000..2502a7d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer.php @@ -0,0 +1,1581 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\AutoloadGenerator; +use Composer\Console\GithubActionError; +use Composer\DependencyResolver\DefaultPolicy; +use Composer\DependencyResolver\LocalRepoTransaction; +use Composer\DependencyResolver\LockTransaction; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\PoolOptimizer; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Request; +use Composer\DependencyResolver\Solver; +use Composer\DependencyResolver\SolverProblemsException; +use Composer\DependencyResolver\PolicyInterface; +use Composer\Downloader\DownloadManager; +use Composer\Downloader\TransportException; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\Installer\InstallationManager; +use Composer\Installer\InstallerEvents; +use Composer\Installer\SuggestedPackagesReporter; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\RootAliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Version\VersionParser; +use Composer\Package\Package; +use Composer\Repository\ArrayRepository; +use Composer\Repository\RepositorySet; +use Composer\Repository\CompositeRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Locker; +use Composer\Package\RootPackageInterface; +use Composer\Repository\InstalledArrayRepository; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositoryManager; +use Composer\Repository\LockArrayRepository; +use Composer\Script\ScriptEvents; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Advisory\Auditor; +use Composer\Util\Platform; + +/** + * @author Jordi Boggiano + * @author Beau Simensen + * @author Konstantin Kudryashov + * @author Nils Adermann + */ +class Installer +{ + public const ERROR_NONE = 0; // no error/success state + public const ERROR_GENERIC_FAILURE = 1; + public const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE = 3; + public const ERROR_LOCK_FILE_INVALID = 4; + // used/declared in SolverProblemsException, carried over here for completeness + public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2; + public const ERROR_AUDIT_FAILED = 5; + // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100 + public const ERROR_TRANSPORT_EXCEPTION = 100; + + /** + * @var IOInterface + */ + protected $io; + + /** + * @var Config + */ + protected $config; + + /** + * @var RootPackageInterface&BasePackage + */ + protected $package; + + // TODO can we get rid of the below and just use the package itself? + /** + * @var RootPackageInterface&BasePackage + */ + protected $fixedRootPackage; + + /** + * @var DownloadManager + */ + protected $downloadManager; + + /** + * @var RepositoryManager + */ + protected $repositoryManager; + + /** + * @var Locker + */ + protected $locker; + + /** + * @var InstallationManager + */ + protected $installationManager; + + /** + * @var EventDispatcher + */ + protected $eventDispatcher; + + /** + * @var AutoloadGenerator + */ + protected $autoloadGenerator; + + /** @var bool */ + protected $preferSource = false; + /** @var bool */ + protected $preferDist = false; + /** @var bool */ + protected $optimizeAutoloader = false; + /** @var bool */ + protected $classMapAuthoritative = false; + /** @var bool */ + protected $apcuAutoloader = false; + /** @var string|null */ + protected $apcuAutoloaderPrefix = null; + /** @var bool */ + protected $devMode = false; + /** @var bool */ + protected $dryRun = false; + /** @var bool */ + protected $downloadOnly = false; + /** @var bool */ + protected $verbose = false; + /** @var bool */ + protected $update = false; + /** @var bool */ + protected $install = true; + /** @var bool */ + protected $dumpAutoloader = true; + /** @var bool */ + protected $runScripts = true; + /** @var bool */ + protected $preferStable = false; + /** @var bool */ + protected $preferLowest = false; + /** @var bool */ + protected $minimalUpdate = false; + /** @var bool */ + protected $writeLock; + /** @var bool */ + protected $executeOperations = true; + /** @var bool */ + protected $audit = true; + /** @var bool */ + protected $errorOnAudit = false; + /** @var Auditor::FORMAT_* */ + protected $auditFormat = Auditor::FORMAT_SUMMARY; + /** @var list */ + private $ignoredTypes = ['php-ext', 'php-ext-zend']; + /** @var list|null */ + private $allowedTypes = null; + + /** @var bool */ + protected $updateMirrors = false; + /** + * Array of package names/globs flagged for update + * + * @var non-empty-list|null + */ + protected $updateAllowList = null; + /** @var Request::UPDATE_* */ + protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; + + /** + * @var SuggestedPackagesReporter + */ + protected $suggestedPackagesReporter; + + /** + * @var PlatformRequirementFilterInterface + */ + protected $platformRequirementFilter; + + /** + * @var ?RepositoryInterface + */ + protected $additionalFixedRepository; + + /** @var array */ + protected $temporaryConstraints = []; + + /** + * Constructor + * + * @param RootPackageInterface&BasePackage $package + */ + public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator) + { + $this->io = $io; + $this->config = $config; + $this->package = $package; + $this->downloadManager = $downloadManager; + $this->repositoryManager = $repositoryManager; + $this->locker = $locker; + $this->installationManager = $installationManager; + $this->eventDispatcher = $eventDispatcher; + $this->autoloadGenerator = $autoloadGenerator; + $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); + $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + + $this->writeLock = $config->get('lock'); + } + + /** + * Run installation (or update) + * + * @throws \Exception + * @return int 0 on success or a positive error code on failure + * @phpstan-return self::ERROR_* + */ + public function run(): int + { + // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands + // of PHP objects, the GC can spend quite some time walking the tree of references looking + // for stuff to collect while there is nothing to collect. This slows things down dramatically + // and turning it off results in much better performance. Do not try this at home however. + gc_collect_cycles(); + gc_disable(); + + if ($this->updateAllowList !== null && $this->updateMirrors) { + throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive."); + } + + $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh(); + + // Force update if there is no lock file present + if (!$this->update && !$this->locker->isLocked()) { + $this->io->writeError('No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.'); + $this->update = true; + } + + if ($this->dryRun) { + $this->verbose = true; + $this->runScripts = false; + $this->executeOperations = false; + $this->writeLock = false; + $this->dumpAutoloader = false; + $this->mockLocalRepositories($this->repositoryManager); + } + + if ($this->downloadOnly) { + $this->dumpAutoloader = false; + } + + if ($this->update && !$this->install) { + $this->dumpAutoloader = false; + } + + if ($this->runScripts) { + Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); + + // dispatch pre event + // should we treat this more strictly as running an update and then running an install, triggering events multiple times? + $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); + } + + $this->downloadManager->setPreferSource($this->preferSource); + $this->downloadManager->setPreferDist($this->preferDist); + + $localRepo = $this->repositoryManager->getLocalRepository(); + + try { + if ($this->update) { + $res = $this->doUpdate($localRepo, $this->install); + } else { + $res = $this->doInstall($localRepo); + } + if ($res !== 0) { + return $res; + } + } catch (\Exception $e) { + if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { + $this->installationManager->notifyInstalls($this->io); + } + + throw $e; + } + if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { + $this->installationManager->notifyInstalls($this->io); + } + + if ($this->update) { + $installedRepo = new InstalledRepository([ + $this->locker->getLockedRepository($this->devMode), + $this->createPlatformRepo(false), + new RootPackageRepository(clone $this->package), + ]); + if ($isFreshInstall) { + $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package); + } + $this->suggestedPackagesReporter->outputMinimalistic($installedRepo); + } + + // Find abandoned packages and warn user + $lockedRepository = $this->locker->getLockedRepository(true); + foreach ($lockedRepository->getPackages() as $package) { + if (!$package instanceof CompletePackage || !$package->isAbandoned()) { + continue; + } + + $replacement = is_string($package->getReplacementPackage()) + ? 'Use ' . $package->getReplacementPackage() . ' instead' + : 'No replacement was suggested'; + + $this->io->writeError( + sprintf( + "Package %s is abandoned, you should avoid using it. %s.", + $package->getPrettyName(), + $replacement + ) + ); + } + + if ($this->dumpAutoloader) { + // write autoloader + if ($this->optimizeAutoloader) { + $this->io->writeError('Generating optimized autoload files'); + } else { + $this->io->writeError('Generating autoload files'); + } + + $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); + $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix); + $this->autoloadGenerator->setRunScripts($this->runScripts); + $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter); + $this + ->autoloadGenerator + ->dump( + $this->config, + $localRepo, + $this->package, + $this->installationManager, + 'composer', + $this->optimizeAutoloader, + null, + $this->locker + ); + } + + if ($this->install && $this->executeOperations) { + // force binaries re-generation in case they are missing + foreach ($localRepo->getPackages() as $package) { + $this->installationManager->ensureBinariesPresence($package); + } + } + + $fundEnv = Platform::getEnv('COMPOSER_FUND'); + $showFunding = true; + if (is_numeric($fundEnv)) { + $showFunding = intval($fundEnv) !== 0; + } + + if ($showFunding) { + $fundingCount = 0; + foreach ($localRepo->getPackages() as $package) { + if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { + $fundingCount++; + } + } + if ($fundingCount > 0) { + $this->io->writeError([ + sprintf( + "%d package%s you are using %s looking for funding.", + $fundingCount, + 1 === $fundingCount ? '' : 's', + 1 === $fundingCount ? 'is' : 'are' + ), + 'Use the `composer fund` command to find out more!', + ]); + } + } + + if ($this->runScripts) { + // dispatch post event + $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); + } + + // re-enable GC except on HHVM which triggers a warning here + if (!defined('HHVM_VERSION')) { + gc_enable(); + } + + if ($this->audit) { + if ($this->update && !$this->install) { + $packages = $lockedRepository->getCanonicalPackages(); + $target = 'locked'; + } else { + $packages = $localRepo->getCanonicalPackages(); + $target = 'installed'; + } + if (count($packages) > 0) { + try { + $auditor = new Auditor(); + $repoSet = new RepositorySet(); + foreach ($this->repositoryManager->getRepositories() as $repo) { + $repoSet->addRepository($repo); + } + + $auditConfig = $this->config->get('audit'); + + return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, true, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; + } catch (TransportException $e) { + $this->io->error('Failed to audit '.$target.' packages.'); + if ($this->io->isVerbose()) { + $this->io->error('['.get_class($e).'] '.$e->getMessage()); + } + } + } else { + $this->io->writeError('No '.$target.' packages - skipping audit.'); + } + } + + return 0; + } + + /** + * @phpstan-return self::ERROR_* + */ + protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall): int + { + $platformRepo = $this->createPlatformRepo(true); + $aliases = $this->getRootAliases(true); + + $lockedRepository = null; + + try { + if ($this->locker->isLocked()) { + $lockedRepository = $this->locker->getLockedRepository(true); + } + } catch (\Seld\JsonLint\ParsingException $e) { + if ($this->updateAllowList !== null || $this->updateMirrors) { + // in case we are doing a partial update or updating mirrors, the lock file is needed so we error + throw $e; + } + // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when + // doing a full update + } + + if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) { + $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', true, IOInterface::QUIET); + + return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE; + } + + $this->io->writeError('Loading composer repositories with package information'); + + // creating repository set + $policy = $this->createPolicy(true, $lockedRepository); + $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); + $repositories = $this->repositoryManager->getRepositories(); + foreach ($repositories as $repository) { + $repositorySet->addRepository($repository); + } + if ($lockedRepository) { + $repositorySet->addRepository($lockedRepository); + } + + $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); + $this->requirePackagesForUpdate($request, $lockedRepository, true); + + // pass the allow list into the request, so the pool builder can apply it + if ($this->updateAllowList !== null) { + $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); + } + + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes); + + $this->io->writeError('Updating dependencies'); + + // solve dependencies + $solver = new Solver($policy, $pool, $this->io); + try { + $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $ruleSetSize = $solver->getRuleSetSize(); + $solver = null; + } catch (SolverProblemsException $e) { + $err = 'Your requirements could not be resolved to an installable set of packages.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); + if (!$this->devMode) { + $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', true, IOInterface::QUIET); + } + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); + } + + $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); + $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); + + $pool = null; + + if (!$lockTransaction->getOperations()) { + $this->io->writeError('Nothing to modify in lock file'); + } + + $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository); + if ($exitCode !== 0) { + return $exitCode; + } + + \Composer\Semver\CompilingMatcher::clear(); + + // write lock + $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); + $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires()); + + $installsUpdates = $uninstalls = []; + if ($lockTransaction->getOperations()) { + $installNames = $updateNames = $uninstallNames = []; + foreach ($lockTransaction->getOperations() as $operation) { + if ($operation instanceof InstallOperation) { + $installsUpdates[] = $operation; + $installNames[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UpdateOperation) { + // when mirrors/metadata from a package gets updated we do not want to list it as an + // update in the output as it is only an internal lock file metadata update + if ($this->updateMirrors + && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName() + && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion() + ) { + continue; + } + + $installsUpdates[] = $operation; + $updateNames[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UninstallOperation) { + $uninstalls[] = $operation; + $uninstallNames[] = $operation->getPackage()->getPrettyName(); + } + } + + if ($this->config->get('lock')) { + $this->io->writeError(sprintf( + "Lock file operations: %d install%s, %d update%s, %d removal%s", + count($installNames), + 1 === count($installNames) ? '' : 's', + count($updateNames), + 1 === count($updateNames) ? '' : 's', + count($uninstalls), + 1 === count($uninstalls) ? '' : 's' + )); + if ($installNames) { + $this->io->writeError("Installs: ".implode(', ', $installNames), true, IOInterface::VERBOSE); + } + if ($updateNames) { + $this->io->writeError("Updates: ".implode(', ', $updateNames), true, IOInterface::VERBOSE); + } + if ($uninstalls) { + $this->io->writeError("Removals: ".implode(', ', $uninstallNames), true, IOInterface::VERBOSE); + } + } + } + + $sortByName = static function ($a, $b): int { + if ($a instanceof UpdateOperation) { + $a = $a->getTargetPackage()->getName(); + } else { + $a = $a->getPackage()->getName(); + } + if ($b instanceof UpdateOperation) { + $b = $b->getTargetPackage()->getName(); + } else { + $b = $b->getPackage()->getName(); + } + + return strcmp($a, $b); + }; + usort($uninstalls, $sortByName); + usort($installsUpdates, $sortByName); + + foreach (array_merge($uninstalls, $installsUpdates) as $operation) { + // collect suggestions + if ($operation instanceof InstallOperation) { + $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); + } + + // output op if lock file is enabled, but alias op only in debug verbosity + if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) { + $sourceRepo = ''; + if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) { + $operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage()); + if ($operationPkg->getRepository() !== null) { + $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName(); + } + } + $this->io->writeError(' - ' . $operation->show(true) . $sourceRepo); + } + } + + $updatedLock = $this->locker->setLockData( + $lockTransaction->getNewLockPackages(false, $this->updateMirrors), + $lockTransaction->getNewLockPackages(true, $this->updateMirrors), + $platformReqs, + $platformDevReqs, + $lockTransaction->getAliases($aliases), + $this->package->getMinimumStability(), + $this->package->getStabilityFlags(), + $this->preferStable || $this->package->getPreferStable(), + $this->preferLowest, + $this->config->get('platform') ?: [], + $this->writeLock && $this->executeOperations + ); + if ($updatedLock && $this->writeLock && $this->executeOperations) { + $this->io->writeError('Writing lock file'); + } + + if ($doInstall) { + // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false + return $this->doInstall($localRepo, true); + } + + return 0; + } + + /** + * Run the solver a second time on top of the existing update result with only the current result set in the pool + * and see what packages would get removed if we only had the non-dev packages in the solver request + * + * @param array> $aliases + * + * @phpstan-param list $aliases + * @phpstan-return self::ERROR_* + */ + protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null): int + { + if (!$this->package->getDevRequires()) { + return 0; + } + + $resultRepo = new ArrayRepository([]); + $loader = new ArrayLoader(null, true); + $dumper = new ArrayDumper(); + foreach ($lockTransaction->getNewLockPackages(false) as $pkg) { + $resultRepo->addPackage($loader->load($dumper->dump($pkg))); + } + + $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); + $repositorySet->addRepository($resultRepo); + + $request = $this->createRequest($this->fixedRootPackage, $platformRepo); + $this->requirePackagesForUpdate($request, $lockedRepository, false); + + $pool = $repositorySet->createPoolWithAllPackages(); + + $solver = new Solver($policy, $pool, $this->io); + try { + $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $solver = null; + } catch (SolverProblemsException $e) { + $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.'); + $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.'); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return $e->getCode(); + } + + $lockTransaction->setNonDevPackages($nonDevLockTransaction); + + return 0; + } + + /** + * @param bool $alreadySolved Whether the function is called as part of an update command or independently + * @return int exit code + * @phpstan-return self::ERROR_* + */ + protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = false): int + { + if ($this->config->get('lock')) { + $this->io->writeError('Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').''); + } + + $lockedRepository = $this->locker->getLockedRepository($this->devMode); + + // verify that the lock file works with the current platform repository + // we can skip this part if we're doing this as the second step after an update + if (!$alreadySolved) { + $this->io->writeError('Verifying lock file contents can be installed on current platform.'); + + $platformRepo = $this->createPlatformRepo(false); + // creating repository set + $policy = $this->createPolicy(false); + // use aliases from lock file only, so empty root aliases here + $repositorySet = $this->createRepositorySet(false, $platformRepo, [], $lockedRepository); + $repositorySet->addRepository($lockedRepository); + + // creating requirements request + $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); + + if (!$this->locker->isFresh()) { + $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `.', true, IOInterface::QUIET); + } + + $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode); + if ($missingRequirementInfo !== []) { + $this->io->writeError($missingRequirementInfo); + + if (!$this->config->get('allow-missing-requirements')) { + return self::ERROR_LOCK_FILE_INVALID; + } + } + + foreach ($lockedRepository->getPackages() as $package) { + $request->fixLockedPackage($package); + } + + $rootRequires = $this->package->getRequires(); + if ($this->devMode) { + $rootRequires = array_merge($rootRequires, $this->package->getDevRequires()); + } + foreach ($rootRequires as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + + foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { + if (!isset($rootRequires[$link->getTarget()])) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + unset($rootRequires, $link); + + $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes); + + // solve dependencies + $solver = new Solver($policy, $pool, $this->io); + try { + $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); + $solver = null; + + // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system + if (0 !== count($lockTransaction->getOperations())) { + $this->io->writeError('Your lock file cannot be installed on this system without changes. Please run composer update.', true, IOInterface::QUIET); + + return self::ERROR_LOCK_FILE_INVALID; + } + } catch (SolverProblemsException $e) { + $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + + return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); + } + } + + // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly? + $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction); + + $installs = $updates = $uninstalls = []; + foreach ($localRepoTransaction->getOperations() as $operation) { + if ($operation instanceof InstallOperation) { + $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UpdateOperation) { + $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UninstallOperation) { + $uninstalls[] = $operation->getPackage()->getPrettyName(); + } + } + + if ($installs === [] && $updates === [] && $uninstalls === []) { + $this->io->writeError('Nothing to install, update or remove'); + } else { + $this->io->writeError(sprintf( + "Package operations: %d install%s, %d update%s, %d removal%s", + count($installs), + 1 === count($installs) ? '' : 's', + count($updates), + 1 === count($updates) ? '' : 's', + count($uninstalls), + 1 === count($uninstalls) ? '' : 's' + )); + if ($installs) { + $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); + } + if ($updates) { + $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE); + } + if ($uninstalls) { + $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE); + } + } + + if ($this->executeOperations) { + $localRepo->setDevPackageNames($this->locker->getDevPackageNames()); + $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly); + + // see https://github.com/composer/composer/issues/2764 + if (count($localRepoTransaction->getOperations()) > 0) { + $vendorDir = $this->config->get('vendor-dir'); + if (is_dir($vendorDir)) { + // suppress errors as this fails sometimes on OSX for no apparent reason + // see https://github.com/composer/composer/issues/4070#issuecomment-129792748 + @touch($vendorDir); + } + } + } else { + foreach ($localRepoTransaction->getOperations() as $operation) { + // output op, but alias op only in debug verbosity + if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) { + $this->io->writeError(' - ' . $operation->show(false)); + } + } + } + + return 0; + } + + protected function createPlatformRepo(bool $forUpdate): PlatformRepository + { + if ($forUpdate) { + $platformOverrides = $this->config->get('platform') ?: []; + } else { + $platformOverrides = $this->locker->getPlatformOverrides(); + } + + return new PlatformRepository([], $platformOverrides); + } + + /** + * @param array> $rootAliases + * + * @phpstan-param list $rootAliases + */ + private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null): RepositorySet + { + if ($forUpdate) { + $minimumStability = $this->package->getMinimumStability(); + $stabilityFlags = $this->package->getStabilityFlags(); + + $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires()); + } else { + $minimumStability = $this->locker->getMinimumStability(); + $stabilityFlags = $this->locker->getStabilityFlags(); + + $requires = []; + foreach ($lockedRepository->getPackages() as $package) { + $constraint = new Constraint('=', $package->getVersion()); + $constraint->setPrettyString($package->getPrettyVersion()); + $requires[$package->getName()] = $constraint; + } + } + + $rootRequires = []; + foreach ($requires as $req => $constraint) { + if ($constraint instanceof Link) { + $constraint = $constraint->getConstraint(); + } + // skip platform requirements from the root package to avoid filtering out existing platform packages + if ($this->platformRequirementFilter->isIgnored($req)) { + continue; + } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { + $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint); + } + $rootRequires[$req] = $constraint; + } + + $this->fixedRootPackage = clone $this->package; + $this->fixedRootPackage->setRequires([]); + $this->fixedRootPackage->setDevRequires([]); + + $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())]; + + $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints); + $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); + $repositorySet->addRepository($platformRepo); + if ($this->additionalFixedRepository) { + // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet + // see https://github.com/composer/composer/pull/9574 + $additionalFixedRepositories = $this->additionalFixedRepository; + if ($additionalFixedRepositories instanceof CompositeRepository) { + $additionalFixedRepositories = $additionalFixedRepositories->getRepositories(); + } else { + $additionalFixedRepositories = [$additionalFixedRepositories]; + } + foreach ($additionalFixedRepositories as $additionalFixedRepository) { + if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) { + $repositorySet->allowInstalledRepositories(); + break; + } + } + + $repositorySet->addRepository($this->additionalFixedRepository); + } + + return $repositorySet; + } + + private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy + { + $preferStable = null; + $preferLowest = null; + if (!$forUpdate) { + $preferStable = $this->locker->getPreferStable(); + $preferLowest = $this->locker->getPreferLowest(); + } + // old lock file without prefer stable/lowest will return null + // so in this case we use the composer.json info + if (null === $preferStable) { + $preferStable = $this->preferStable || $this->package->getPreferStable(); + } + if (null === $preferLowest) { + $preferLowest = $this->preferLowest; + } + + $preferredVersions = null; + if ($forUpdate && $this->minimalUpdate && $this->updateAllowList !== null && $lockedRepo !== null) { + $preferredVersions = []; + foreach ($lockedRepo->getPackages() as $pkg) { + if ($pkg instanceof AliasPackage || in_array($pkg->getName(), $this->updateAllowList, true)) { + continue; + } + $preferredVersions[$pkg->getName()] = $pkg->getVersion(); + } + } + + return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions); + } + + /** + * @param RootPackageInterface&BasePackage $rootPackage + */ + private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null): Request + { + $request = new Request($lockedRepository); + + $request->fixPackage($rootPackage); + if ($rootPackage instanceof RootAliasPackage) { + $request->fixPackage($rootPackage->getAliasOf()); + } + + $fixedPackages = $platformRepo->getPackages(); + if ($this->additionalFixedRepository) { + $fixedPackages = array_merge($fixedPackages, $this->additionalFixedRepository->getPackages()); + } + + // fix the version of all platform packages + additionally installed packages + // to prevent the solver trying to remove or update those + // TODO why not replaces? + $provided = $rootPackage->getProvides(); + foreach ($fixedPackages as $package) { + // skip platform packages that are provided by the root package + if ($package->getRepository() !== $platformRepo + || !isset($provided[$package->getName()]) + || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion())) + ) { + $request->fixPackage($package); + } + } + + return $request; + } + + private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = true): void + { + // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata + if ($this->updateMirrors) { + $excludedPackages = []; + if (!$includeDevRequires) { + $excludedPackages = array_flip($this->locker->getDevPackageNames()); + } + + foreach ($lockedRepository->getPackages() as $lockedPackage) { + // exclude alias packages here as for root aliases, both alias and aliased are + // present in the lock repo and we only want to require the aliased version + if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) { + $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + } + } + } else { + $links = $this->package->getRequires(); + if ($includeDevRequires) { + $links = array_merge($links, $this->package->getDevRequires()); + } + foreach ($links as $link) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + } + + /** + * @return array> + * + * @phpstan-return list + */ + private function getRootAliases(bool $forUpdate): array + { + if ($forUpdate) { + $aliases = $this->package->getAliases(); + } else { + $aliases = $this->locker->getAliases(); + } + + return $aliases; + } + + /** + * @param Link[] $links + * + * @return array + */ + private function extractPlatformRequirements(array $links): array + { + $platformReqs = []; + foreach ($links as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + $platformReqs[$link->getTarget()] = $link->getPrettyConstraint(); + } + } + + return $platformReqs; + } + + /** + * Replace local repositories with InstalledArrayRepository instances + * + * This is to prevent any accidental modification of the existing repos on disk + */ + private function mockLocalRepositories(RepositoryManager $rm): void + { + $packages = []; + foreach ($rm->getLocalRepository()->getPackages() as $package) { + $packages[(string) $package] = clone $package; + } + foreach ($packages as $key => $package) { + if ($package instanceof AliasPackage) { + $alias = (string) $package->getAliasOf(); + $className = get_class($package); + $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); + } + } + $rm->setLocalRepository( + new InstalledArrayRepository($packages) + ); + } + + private function createPoolOptimizer(PolicyInterface $policy): ?PoolOptimizer + { + // Not the best architectural decision here, would need to be able + // to configure from the outside of Installer but this is only + // a debugging tool and should never be required in any other use case + if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) { + $this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG); + + return null; + } + + return new PoolOptimizer($policy); + } + + /** + * Create Installer + * + * @return Installer + */ + public static function create(IOInterface $io, Composer $composer): self + { + return new static( + $io, + $composer->getConfig(), + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $composer->getEventDispatcher(), + $composer->getAutoloadGenerator() + ); + } + + /** + * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored + * + * @param list $types + * @return $this + */ + public function setIgnoredTypes(array $types): self + { + $this->ignoredTypes = $types; + + return $this; + } + + /** + * Only packages of those types are allowed if set to non-null + * + * @param list|null $types + * @return $this + */ + public function setAllowedTypes(?array $types): self + { + $this->allowedTypes = $types; + + return $this; + } + + /** + * @return $this + */ + public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository): self + { + $this->additionalFixedRepository = $additionalFixedRepository; + + return $this; + } + + /** + * @param array $constraints + * @return Installer + */ + public function setTemporaryConstraints(array $constraints): self + { + $this->temporaryConstraints = $constraints; + + return $this; + } + + /** + * Whether to run in drymode or not + * + * @return Installer + */ + public function setDryRun(bool $dryRun = true): self + { + $this->dryRun = $dryRun; + + return $this; + } + + /** + * Checks, if this is a dry run (simulation mode). + */ + public function isDryRun(): bool + { + return $this->dryRun; + } + + /** + * Whether to download only or not. + * + * @return Installer + */ + public function setDownloadOnly(bool $downloadOnly = true): self + { + $this->downloadOnly = $downloadOnly; + + return $this; + } + + /** + * prefer source installation + * + * @return Installer + */ + public function setPreferSource(bool $preferSource = true): self + { + $this->preferSource = $preferSource; + + return $this; + } + + /** + * prefer dist installation + * + * @return Installer + */ + public function setPreferDist(bool $preferDist = true): self + { + $this->preferDist = $preferDist; + + return $this; + } + + /** + * Whether or not generated autoloader are optimized + * + * @return Installer + */ + public function setOptimizeAutoloader(bool $optimizeAutoloader): self + { + $this->optimizeAutoloader = $optimizeAutoloader; + if (!$this->optimizeAutoloader) { + // Force classMapAuthoritative off when not optimizing the + // autoloader + $this->setClassMapAuthoritative(false); + } + + return $this; + } + + /** + * Whether or not generated autoloader considers the class map + * authoritative. + * + * @return Installer + */ + public function setClassMapAuthoritative(bool $classMapAuthoritative): self + { + $this->classMapAuthoritative = $classMapAuthoritative; + if ($this->classMapAuthoritative) { + // Force optimizeAutoloader when classmap is authoritative + $this->setOptimizeAutoloader(true); + } + + return $this; + } + + /** + * Whether or not generated autoloader considers APCu caching. + * + * @return Installer + */ + public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null): self + { + $this->apcuAutoloader = $apcuAutoloader; + $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix; + + return $this; + } + + /** + * update packages + * + * @return Installer + */ + public function setUpdate(bool $update): self + { + $this->update = $update; + + return $this; + } + + /** + * Allows disabling the install step after an update + * + * @return Installer + */ + public function setInstall(bool $install): self + { + $this->install = $install; + + return $this; + } + + /** + * enables dev packages + * + * @return Installer + */ + public function setDevMode(bool $devMode = true): self + { + $this->devMode = $devMode; + + return $this; + } + + /** + * set whether to run autoloader or not + * + * This is disabled implicitly when enabling dryRun + * + * @return Installer + */ + public function setDumpAutoloader(bool $dumpAutoloader = true): self + { + $this->dumpAutoloader = $dumpAutoloader; + + return $this; + } + + /** + * set whether to run scripts or not + * + * This is disabled implicitly when enabling dryRun + * + * @return Installer + * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead + */ + public function setRunScripts(bool $runScripts = true): self + { + $this->runScripts = $runScripts; + + return $this; + } + + /** + * set the config instance + * + * @return Installer + */ + public function setConfig(Config $config): self + { + $this->config = $config; + + return $this; + } + + /** + * run in verbose mode + * + * @return Installer + */ + public function setVerbose(bool $verbose = true): self + { + $this->verbose = $verbose; + + return $this; + } + + /** + * Checks, if running in verbose mode. + */ + public function isVerbose(): bool + { + return $this->verbose; + } + + /** + * set ignore Platform Package requirements + * + * If this is set to true, all platform requirements are ignored + * If this is set to false, no platform requirements are ignored + * If this is set to string[], those packages will be ignored + * + * @param bool|string[] $ignorePlatformReqs + * + * @return Installer + * + * @deprecated use setPlatformRequirementFilter instead + */ + public function setIgnorePlatformRequirements($ignorePlatformReqs): self + { + trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); + + return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); + } + + /** + * @return Installer + */ + public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter): self + { + $this->platformRequirementFilter = $platformRequirementFilter; + + return $this; + } + + /** + * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info + * + * @return Installer + */ + public function setUpdateMirrors(bool $updateMirrors): self + { + $this->updateMirrors = $updateMirrors; + + return $this; + } + + /** + * restrict the update operation to a few packages, all other packages + * that are already installed will be kept at their current version + * + * @param string[] $packages + * + * @return Installer + */ + public function setUpdateAllowList(array $packages): self + { + if (count($packages) === 0) { + $this->updateAllowList = null; + } else { + $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages))); + } + + return $this; + } + + /** + * Should dependencies of packages marked for update be updated? + * + * Depending on the chosen constant this will either only update the directly named packages, all transitive + * dependencies which are not root requirement or all transitive dependencies including root requirements + * + * @param int $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class + * @return Installer + */ + public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies): self + { + if (!in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], true)) { + throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied"); + } + + $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; + + return $this; + } + + /** + * Should packages be preferred in a stable version when updating? + * + * @return Installer + */ + public function setPreferStable(bool $preferStable = true): self + { + $this->preferStable = $preferStable; + + return $this; + } + + /** + * Should packages be preferred in a lowest version when updating? + * + * @return Installer + */ + public function setPreferLowest(bool $preferLowest = true): self + { + $this->preferLowest = $preferLowest; + + return $this; + } + + /** + * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist + * + * This reduces the update to + * + * @return Installer + */ + public function setMinimalUpdate(bool $minimalUpdate = true): self + { + $this->minimalUpdate = $minimalUpdate; + + return $this; + } + + /** + * Should the lock file be updated when updating? + * + * This is disabled implicitly when enabling dryRun + * + * @return Installer + */ + public function setWriteLock(bool $writeLock = true): self + { + $this->writeLock = $writeLock; + + return $this; + } + + /** + * Should the operations (package install, update and removal) be executed on disk? + * + * This is disabled implicitly when enabling dryRun + * + * @return Installer + */ + public function setExecuteOperations(bool $executeOperations = true): self + { + $this->executeOperations = $executeOperations; + + return $this; + } + + /** + * Should an audit be run after installation is complete? + * + * @return Installer + */ + public function setAudit(bool $audit): self + { + $this->audit = $audit; + + return $this; + } + + /** + * Should exit with status code 5 on audit error + * + * @param bool $errorOnAudit + * @return Installer + */ + public function setErrorOnAudit(bool $errorOnAudit): self + { + $this->errorOnAudit = $errorOnAudit; + + return $this; + } + + /** + * What format should be used for audit output? + * + * @param Auditor::FORMAT_* $auditFormat + * @return Installer + */ + public function setAuditFormat(string $auditFormat): self + { + $this->auditFormat = $auditFormat; + + return $this; + } + + /** + * Disables plugins. + * + * Call this if you want to ensure that third-party code never gets + * executed. The default is to automatically install, and execute + * custom third-party installers. + * + * @return Installer + */ + public function disablePlugins(): self + { + $this->installationManager->disablePlugins(); + + return $this; + } + + /** + * @return Installer + */ + public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter): self + { + $this->suggestedPackagesReporter = $suggestedPackagesReporter; + + return $this; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php b/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php new file mode 100644 index 0000000..9211325 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php @@ -0,0 +1,413 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Silencer; + +/** + * Utility to handle installation of package "bin"/binaries + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + * @author Helmut Hummel + */ +class BinaryInstaller +{ + /** @var string */ + protected $binDir; + /** @var string */ + protected $binCompat; + /** @var IOInterface */ + protected $io; + /** @var Filesystem */ + protected $filesystem; + /** @var string|null */ + private $vendorDir; + + /** + * @param Filesystem $filesystem + */ + public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null) + { + $this->binDir = $binDir; + $this->binCompat = $binCompat; + $this->io = $io; + $this->filesystem = $filesystem ?: new Filesystem(); + $this->vendorDir = $vendorDir; + } + + public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true): void + { + $binaries = $this->getBinaries($package); + if (!$binaries) { + return; + } + + Platform::workaroundFilesystemIssues(); + + foreach ($binaries as $bin) { + $binPath = $installPath.'/'.$bin; + if (!file_exists($binPath)) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); + continue; + } + if (is_dir($binPath)) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': found a directory at that path'); + continue; + } + if (!$this->filesystem->isAbsolutePath($binPath)) { + // in case a custom installer returned a relative path for the + // $package, we can now safely turn it into a absolute path (as we + // already checked the binary's existence). The following helpers + // will require absolute paths to work properly. + $binPath = realpath($binPath); + } + $this->initializeBinDir(); + $link = $this->binDir.'/'.basename($bin); + if (file_exists($link)) { + if (!is_link($link)) { + if ($warnOnOverwrite) { + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); + } + continue; + } + if (realpath($link) === realpath($binPath)) { + // It is a linked binary from a previous installation, which can be replaced with a proxy file + $this->filesystem->unlink($link); + } + } + + $binCompat = $this->binCompat; + if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) { + $binCompat = 'full'; + } + + if ($binCompat === "full") { + $this->installFullBinaries($binPath, $link, $bin, $package); + } else { + $this->installUnixyProxyBinaries($binPath, $link); + } + Silencer::call('chmod', $binPath, 0777 & ~umask()); + } + } + + public function removeBinaries(PackageInterface $package): void + { + $this->initializeBinDir(); + + $binaries = $this->getBinaries($package); + if (!$binaries) { + return; + } + foreach ($binaries as $bin) { + $link = $this->binDir.'/'.basename($bin); + if (is_link($link) || file_exists($link)) { // still checking for symlinks here for legacy support + $this->filesystem->unlink($link); + } + if (is_file($link.'.bat')) { + $this->filesystem->unlink($link.'.bat'); + } + } + + // attempt removing the bin dir in case it is left empty + if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) { + Silencer::call('rmdir', $this->binDir); + } + } + + public static function determineBinaryCaller(string $bin): string + { + if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) { + return 'call'; + } + + $handle = fopen($bin, 'r'); + $line = fgets($handle); + fclose($handle); + if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) { + return trim($match[1]); + } + + return 'php'; + } + + /** + * @return string[] + */ + protected function getBinaries(PackageInterface $package): array + { + return $package->getBinaries(); + } + + protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package): void + { + // add unixy support for cygwin and similar environments + if ('.bat' !== substr($binPath, -4)) { + $this->installUnixyProxyBinaries($binPath, $link); + $link .= '.bat'; + if (file_exists($link)) { + $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); + } + } + if (!file_exists($link)) { + file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); + Silencer::call('chmod', $link, 0777 & ~umask()); + } + } + + protected function installUnixyProxyBinaries(string $binPath, string $link): void + { + file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); + Silencer::call('chmod', $link, 0777 & ~umask()); + } + + protected function initializeBinDir(): void + { + $this->filesystem->ensureDirectoryExists($this->binDir); + $this->binDir = realpath($this->binDir); + } + + protected function generateWindowsProxyCode(string $bin, string $link): string + { + $binPath = $this->filesystem->findShortestPath($link, $bin); + $caller = self::determineBinaryCaller($bin); + + // if the target is a php file, we run the unixy proxy file + // to ensure that _composer_autoload_path gets defined, instead + // of running the binary directly + if ($caller === 'php') { + return "@ECHO OFF\r\n". + "setlocal DISABLEDELAYEDEXPANSION\r\n". + "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'')."\r\n". + "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". + "{$caller} \"%BIN_TARGET%\" %*\r\n"; + } + + return "@ECHO OFF\r\n". + "setlocal DISABLEDELAYEDEXPANSION\r\n". + "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n". + "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". + "{$caller} \"%BIN_TARGET%\" %*\r\n"; + } + + protected function generateUnixyProxyCode(string $bin, string $link): string + { + $binPath = $this->filesystem->findShortestPath($link, $bin); + + $binDir = ProcessExecutor::escape(dirname($binPath)); + $binFile = basename($binPath); + + $binContents = (string) file_get_contents($bin, false, null, 0, 500); + // For php files, we generate a PHP proxy instead of a shell one, + // which allows calling the proxy with a custom php process + if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { + // carry over the existing shebang if present, otherwise add our own + $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]); + $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, false, true); + $streamProxyCode = $streamHint = ''; + $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n"; + $phpunitHack1 = $phpunitHack2 = ''; + // Don't expose autoload path when vendor dir was not set in custom installers + if ($this->vendorDir !== null) { + // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already + $vendorDirReal = realpath($this->vendorDir); + if ($vendorDirReal === false) { + $vendorDirReal = $this->vendorDir; + } + $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n"; + } + // Add workaround for PHPUnit process isolation + if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) { + // workaround issue on PHPUnit 6.5+ running on PHP 8+ + $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath('.$binPathExported.'));'."\n"; + // workaround issue on all PHPUnit versions running on PHP <8 + $phpunitHack1 = "'phpvfscomposer://'."; + $phpunitHack2 = ' + $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); + $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; + } + if (trim($match[0]) !== 'realpath = realpath(\$opened_path) ?: \$opened_path; + \$opened_path = $phpunitHack1\$this->realpath; + \$this->handle = fopen(\$this->realpath, \$mode); + \$this->position = 0; + + return (bool) \$this->handle; + } + + public function stream_read(\$count) + { + \$data = fread(\$this->handle, \$count); + + if (\$this->position === 0) { + \$data = preg_replace('{^#!.*\\r?\\n}', '', \$data); + }$phpunitHack2 + + \$this->position += strlen(\$data); + + return \$data; + } + + public function stream_cast(\$castAs) + { + return \$this->handle; + } + + public function stream_close() + { + fclose(\$this->handle); + } + + public function stream_lock(\$operation) + { + return \$operation ? flock(\$this->handle, \$operation) : true; + } + + public function stream_seek(\$offset, \$whence) + { + if (0 === fseek(\$this->handle, \$offset, \$whence)) { + \$this->position = ftell(\$this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return \$this->position; + } + + public function stream_eof() + { + return feof(\$this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option(\$option, \$arg1, \$arg2) + { + return true; + } + + public function url_stat(\$path, \$flags) + { + \$path = substr(\$path, 17); + if (file_exists(\$path)) { + return stat(\$path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . $binPathExported); + } +} + +STREAMPROXY; + } + + return $proxyCode . "\n" . << /dev/null) +if [ -z "\$self" ]; then + self="\$selfArg" +fi + +dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd $binDir && pwd) + +if [ -d /proc/cygdrive ]; then + case \$(which php) in + \$(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=\$(cygpath -m "\$dir"); + ;; + esac +fi + +export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)" + +# If bash is sourcing this file, we have to source the target as well +bashSource="\$BASH_SOURCE" +if [ -n "\$bashSource" ]; then + if [ "\$bashSource" != "\$0" ]; then + source "\${dir}/$binFile" "\$@" + return + fi +fi + +exec "\${dir}/$binFile" "\$@" + +PROXY; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php b/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php new file mode 100644 index 0000000..d920b0e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Package\PackageInterface; + +/** + * Interface for the package installation manager that handle binary installation. + * + * @author Jordi Boggiano + */ +interface BinaryPresenceInterface +{ + /** + * Make sure binaries are installed for a given package. + * + * @param PackageInterface $package package instance + * + * @return void + */ + public function ensureBinariesPresence(PackageInterface $package); +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallationManager.php b/vendor/composer/composer/src/Composer/Installer/InstallationManager.php new file mode 100644 index 0000000..89169a6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallationManager.php @@ -0,0 +1,673 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\IO\ConsoleIO; +use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; +use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; +use Composer\Downloader\FileDownloader; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Loop; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Seld\Signal\SignalHandler; + +/** + * Package operation manager. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + * @author Nils Adermann + */ +class InstallationManager +{ + /** @var list */ + private $installers = []; + /** @var array */ + private $cache = []; + /** @var array> */ + private $notifiablePackages = []; + /** @var Loop */ + private $loop; + /** @var IOInterface */ + private $io; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var bool */ + private $outputProgress; + + public function __construct(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null) + { + $this->loop = $loop; + $this->io = $io; + $this->eventDispatcher = $eventDispatcher; + } + + public function reset(): void + { + $this->notifiablePackages = []; + FileDownloader::$downloadMetadata = []; + } + + /** + * Adds installer + * + * @param InstallerInterface $installer installer instance + */ + public function addInstaller(InstallerInterface $installer): void + { + array_unshift($this->installers, $installer); + $this->cache = []; + } + + /** + * Removes installer + * + * @param InstallerInterface $installer installer instance + */ + public function removeInstaller(InstallerInterface $installer): void + { + if (false !== ($key = array_search($installer, $this->installers, true))) { + array_splice($this->installers, $key, 1); + $this->cache = []; + } + } + + /** + * Disables plugins. + * + * We prevent any plugins from being instantiated by + * disabling the PluginManager. This ensures that no third-party + * code is ever executed. + */ + public function disablePlugins(): void + { + foreach ($this->installers as $i => $installer) { + if (!$installer instanceof PluginInstaller) { + continue; + } + + $installer->disablePlugins(); + } + } + + /** + * Returns installer for a specific package type. + * + * @param string $type package type + * + * @throws \InvalidArgumentException if installer for provided type is not registered + */ + public function getInstaller(string $type): InstallerInterface + { + $type = strtolower($type); + + if (isset($this->cache[$type])) { + return $this->cache[$type]; + } + + foreach ($this->installers as $installer) { + if ($installer->supports($type)) { + return $this->cache[$type] = $installer; + } + } + + throw new \InvalidArgumentException('Unknown installer type: '.$type); + } + + /** + * Checks whether provided package is installed in one of the registered installers. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + */ + public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool + { + if ($package instanceof AliasPackage) { + return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf()); + } + + return $this->getInstaller($package->getType())->isInstalled($repo, $package); + } + + /** + * Install binary for the given package. + * If the installer associated to this package doesn't handle that function, it'll do nothing. + * + * @param PackageInterface $package Package instance + */ + public function ensureBinariesPresence(PackageInterface $package): void + { + try { + $installer = $this->getInstaller($package->getType()); + } catch (\InvalidArgumentException $e) { + // no installer found for the current package type (@see `getInstaller()`) + return; + } + + // if the given installer support installing binaries + if ($installer instanceof BinaryPresenceInterface) { + $installer->ensureBinariesPresence($package); + } + } + + /** + * Executes solver operation. + * + * @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages + * @param OperationInterface[] $operations operations to execute + * @param bool $devMode whether the install is being run in dev mode + * @param bool $runScripts whether to dispatch script events + * @param bool $downloadOnly whether to only download packages + */ + public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void + { + /** @var array> $cleanupPromises */ + $cleanupPromises = []; + + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) { + $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $this->runCleanup($cleanupPromises); + $handler->exitWithLastSignal(); + }); + + try { + // execute operations in batches to make sure download-modifying-plugins are installed + // before the other packages get downloaded + $batches = []; + $batch = []; + foreach ($operations as $index => $operation) { + if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) { + $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); + if ($package->getType() === 'composer-plugin') { + $extra = $package->getExtra(); + if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { + if (count($batch) > 0) { + $batches[] = $batch; + } + $batches[] = [$index => $operation]; + $batch = []; + + continue; + } + } + } + $batch[$index] = $operation; + } + + if (count($batch) > 0) { + $batches[] = $batch; + } + + foreach ($batches as $batchToExecute) { + $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); + } + } catch (\Exception $e) { + $this->runCleanup($cleanupPromises); + + throw $e; + } finally { + $signalHandler->unregister(); + } + + if ($downloadOnly) { + return; + } + + // do a last write so that we write the repository even if nothing changed + // as that can trigger an update of some files like InstalledVersions.php if + // running a new composer version + $repo->write($devMode, $this); + } + + /** + * @param OperationInterface[] $operations List of operations to execute in this batch + * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners + * @phpstan-param array> $cleanupPromises + */ + private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void + { + $promises = []; + + foreach ($operations as $index => $operation) { + $opType = $operation->getOperationType(); + + // ignoring alias ops as they don't need to execute anything at this stage + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { + continue; + } + + if ($opType === 'update') { + /** @var UpdateOperation $operation */ + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ + $package = $operation->getPackage(); + $initialPackage = null; + } + $installer = $this->getInstaller($package->getType()); + + $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface { + // avoid calling cleanup if the download was not even initialized for a package + // as without installation source configured nothing will work + if (null === $package->getInstallationSource()) { + return \React\Promise\resolve(null); + } + + return $installer->cleanup($opType, $package, $initialPackage); + }; + + if ($opType !== 'uninstall') { + $promise = $installer->download($package, $initialPackage); + if (null !== $promise) { + $promises[] = $promise; + } + } + } + + // execute all downloads first + if (count($promises) > 0) { + $this->waitOnPromises($promises); + } + + if ($downloadOnly) { + $this->runCleanup($cleanupPromises); + return; + } + + // execute operations in batches to make sure every plugin is installed in the + // right order and activated before the packages depending on it are installed + $batches = []; + $batch = []; + foreach ($operations as $index => $operation) { + if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) { + $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); + if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { + if (count($batch) > 0) { + $batches[] = $batch; + } + $batches[] = [$index => $operation]; + $batch = []; + + continue; + } + } + $batch[$index] = $operation; + } + + if (count($batch) > 0) { + $batches[] = $batch; + } + + foreach ($batches as $batchToExecute) { + $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations); + } + } + + /** + * @param OperationInterface[] $operations List of operations to execute in this batch + * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners + * @phpstan-param array> $cleanupPromises + */ + private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void + { + $promises = []; + $postExecCallbacks = []; + + foreach ($operations as $index => $operation) { + $opType = $operation->getOperationType(); + + // ignoring alias ops as they don't need to execute anything + if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { + // output alias ops in debug verbosity as they have no output otherwise + if ($this->io->isDebug()) { + $this->io->writeError(' - ' . $operation->show(false)); + } + $this->{$opType}($repo, $operation); + + continue; + } + + if ($opType === 'update') { + /** @var UpdateOperation $operation */ + $package = $operation->getTargetPackage(); + $initialPackage = $operation->getInitialPackage(); + } else { + /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */ + $package = $operation->getPackage(); + $initialPackage = null; + } + + $installer = $this->getInstaller($package->getType()); + + $eventName = [ + 'install' => PackageEvents::PRE_PACKAGE_INSTALL, + 'update' => PackageEvents::PRE_PACKAGE_UPDATE, + 'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL, + ][$opType]; + + if ($runScripts && $this->eventDispatcher !== null) { + $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); + } + + $dispatcher = $this->eventDispatcher; + $io = $this->io; + + $promise = $installer->prepare($opType, $package, $initialPackage); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $promise = $promise->then(function () use ($opType, $repo, $operation) { + return $this->{$opType}($repo, $operation); + })->then($cleanupPromises[$index]) + ->then(function () use ($devMode, $repo): void { + $repo->write($devMode, $this); + }, static function ($e) use ($opType, $package, $io): void { + $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed'); + + throw $e; + }); + + $eventName = [ + 'install' => PackageEvents::POST_PACKAGE_INSTALL, + 'update' => PackageEvents::POST_PACKAGE_UPDATE, + 'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL, + ][$opType]; + + if ($runScripts && $dispatcher !== null) { + $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void { + $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); + }; + } + + $promises[] = $promise; + } + + // execute all prepare => installs/updates/removes => cleanup steps + if (count($promises) > 0) { + $this->waitOnPromises($promises); + } + + Platform::workaroundFilesystemIssues(); + + foreach ($postExecCallbacks as $cb) { + $cb(); + } + } + + /** + * @param array> $promises + */ + private function waitOnPromises(array $promises): void + { + $progress = null; + if ( + $this->outputProgress + && $this->io instanceof ConsoleIO + && !((bool) Platform::getEnv('CI')) + && !$this->io->isDebug() + && count($promises) > 1 + ) { + $progress = $this->io->getProgressBar(); + } + $this->loop->wait($promises, $progress); + if ($progress !== null) { + $progress->clear(); + // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing + if (!$this->io->isDecorated()) { + $this->io->writeError(''); + } + } + } + + /** + * Executes download operation. + * + * @phpstan-return PromiseInterface|null + */ + public function download(PackageInterface $package): ?PromiseInterface + { + $installer = $this->getInstaller($package->getType()); + $promise = $installer->cleanup("install", $package); + + return $promise; + } + + /** + * Executes install operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param InstallOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface + { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + $promise = $installer->install($repo, $package); + $this->markForNotification($package); + + return $promise; + } + + /** + * Executes update operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param UpdateOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface + { + $initial = $operation->getInitialPackage(); + $target = $operation->getTargetPackage(); + + $initialType = $initial->getType(); + $targetType = $target->getType(); + + if ($initialType === $targetType) { + $installer = $this->getInstaller($initialType); + $promise = $installer->update($repo, $initial, $target); + $this->markForNotification($target); + } else { + $promise = $this->getInstaller($initialType)->uninstall($repo, $initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $installer = $this->getInstaller($targetType); + $promise = $promise->then(static function () use ($installer, $repo, $target): PromiseInterface { + $promise = $installer->install($repo, $target); + if ($promise instanceof PromiseInterface) { + return $promise; + } + + return \React\Promise\resolve(null); + }); + } + + return $promise; + } + + /** + * Uninstalls package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param UninstallOperation $operation operation instance + * @phpstan-return PromiseInterface|null + */ + public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface + { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + + return $installer->uninstall($repo, $package); + } + + /** + * Executes markAliasInstalled operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param MarkAliasInstalledOperation $operation operation instance + */ + public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation): void + { + $package = $operation->getPackage(); + + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + } + + /** + * Executes markAlias operation. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param MarkAliasUninstalledOperation $operation operation instance + */ + public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation): void + { + $package = $operation->getPackage(); + + $repo->removePackage($package); + } + + /** + * Returns the installation path of a package + * + * @return string|null absolute path to install to, which does not end with a slash, or null if the package does not have anything installed on disk + */ + public function getInstallPath(PackageInterface $package): ?string + { + $installer = $this->getInstaller($package->getType()); + + return $installer->getInstallPath($package); + } + + public function setOutputProgress(bool $outputProgress): void + { + $this->outputProgress = $outputProgress; + } + + public function notifyInstalls(IOInterface $io): void + { + $promises = []; + + try { + foreach ($this->notifiablePackages as $repoUrl => $packages) { + // non-batch API, deprecated + if (str_contains($repoUrl, '%package%')) { + foreach ($packages as $package) { + $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); + + $params = [ + 'version' => $package->getPrettyVersion(), + 'version_normalized' => $package->getVersion(), + ]; + $opts = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => ['Content-type: application/x-www-form-urlencoded'], + 'content' => http_build_query($params, '', '&'), + 'timeout' => 3, + ], + ]; + + $promises[] = $this->loop->getHttpDownloader()->add($url, $opts); + } + + continue; + } + + $postData = ['downloads' => []]; + foreach ($packages as $package) { + $packageNotification = [ + 'name' => $package->getPrettyName(), + 'version' => $package->getVersion(), + ]; + if (strpos($repoUrl, 'packagist.org/') !== false) { + if (isset(FileDownloader::$downloadMetadata[$package->getName()])) { + $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()]; + } else { + $packageNotification['downloaded'] = false; + } + } + $postData['downloads'][] = $packageNotification; + } + + $opts = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => ['Content-Type: application/json'], + 'content' => json_encode($postData), + 'timeout' => 6, + ], + ]; + + $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts); + } + + $this->loop->wait($promises); + } catch (\Exception $e) { + } + + $this->reset(); + } + + private function markForNotification(PackageInterface $package): void + { + if ($package->getNotificationUrl() !== null) { + $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; + } + } + + /** + * @return void + * @phpstan-param array> $cleanupPromises + */ + private function runCleanup(array $cleanupPromises): void + { + $promises = []; + + $this->loop->abortJobs(); + + foreach ($cleanupPromises as $cleanup) { + $promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void { + $promise = $cleanup(); + if (!$promise instanceof PromiseInterface) { + $resolve(null); + } else { + $promise->then(static function () use ($resolve): void { + $resolve(null); + }); + } + }); + } + + if (count($promises) > 0) { + $this->loop->wait($promises); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php b/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php new file mode 100644 index 0000000..8cb699e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerEvent.php @@ -0,0 +1,85 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\DependencyResolver\Transaction; +use Composer\EventDispatcher\Event; +use Composer\IO\IOInterface; + +class InstallerEvent extends Event +{ + /** + * @var Composer + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var bool + */ + private $devMode; + + /** + * @var bool + */ + private $executeOperations; + + /** + * @var Transaction + */ + private $transaction; + + /** + * Constructor. + */ + public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, bool $executeOperations, Transaction $transaction) + { + parent::__construct($eventName); + + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + $this->executeOperations = $executeOperations; + $this->transaction = $transaction; + } + + public function getComposer(): Composer + { + return $this->composer; + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function isDevMode(): bool + { + return $this->devMode; + } + + public function isExecutingOperations(): bool + { + return $this->executeOperations; + } + + public function getTransaction(): ?Transaction + { + return $this->transaction; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php b/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php new file mode 100644 index 0000000..fc3da51 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerEvents.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +class InstallerEvents +{ + /** + * The PRE_OPERATIONS_EXEC event occurs before the lock file gets + * installed and operations are executed. + * + * The event listener method receives an Composer\Installer\InstallerEvent instance. + * + * @var string + */ + public const PRE_OPERATIONS_EXEC = 'pre-operations-exec'; +} diff --git a/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php b/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php new file mode 100644 index 0000000..7c92e91 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/InstallerInterface.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; +use InvalidArgumentException; +use React\Promise\PromiseInterface; + +/** + * Interface for the package installation manager. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface InstallerInterface +{ + /** + * Decides if the installer supports the given type + * + * @return bool + */ + public function supports(string $packageType); + + /** + * Checks that provided package is installed. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * + * @return bool + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Downloads the files needed to later install the given package. + * + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Installs specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Updates specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $initial already installed package version + * @param PackageInterface $target updated version + * @throws InvalidArgumentException if $initial package is not installed + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); + + /** + * Uninstalls specific package. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); + + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give + * all installers a change to cleanup things they did previously, so you need to keep track of changes + * applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null); + + /** + * Returns the absolute installation path of a package. + * + * @return string|null absolute path to install to, which MUST not end with a slash, or null if the package does not have anything installed on disk + */ + public function getInstallPath(PackageInterface $package); +} diff --git a/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php b/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php new file mode 100644 index 0000000..0626fb1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php @@ -0,0 +1,345 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; +use Composer\Util\Silencer; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; +use Composer\Downloader\DownloadManager; + +/** + * Package installation manager. + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + */ +class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface +{ + /** @var PartialComposer */ + protected $composer; + /** @var string */ + protected $vendorDir; + /** @var DownloadManager|null */ + protected $downloadManager; + /** @var IOInterface */ + protected $io; + /** @var string */ + protected $type; + /** @var Filesystem */ + protected $filesystem; + /** @var BinaryInstaller */ + protected $binaryInstaller; + + /** + * Initializes library installer. + * + * @param Filesystem $filesystem + * @param BinaryInstaller $binaryInstaller + */ + public function __construct(IOInterface $io, PartialComposer $composer, ?string $type = 'library', ?Filesystem $filesystem = null, ?BinaryInstaller $binaryInstaller = null) + { + $this->composer = $composer; + $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; + $this->io = $io; + $this->type = $type; + + $this->filesystem = $filesystem ?: new Filesystem(); + $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); + $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir); + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === $this->type || null === $this->type; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + return false; + } + + $installPath = $this->getInstallPath($package); + + if (Filesystem::isReadable($installPath)) { + return true; + } + + if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) { + return true; + } + + if (is_link($installPath)) { + if (realpath($installPath) === false) { + return false; + } + + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + // remove the binaries if it appears the package files are missing + if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) { + $this->binaryInstaller->removeBinaries($package); + } + + $promise = $this->installCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($package); + + return $promise->then(static function () use ($binaryInstaller, $installPath, $package, $repo): void { + $binaryInstaller->installBinaries($package, $installPath); + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + }); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $this->initializeVendorDir(); + + $this->binaryInstaller->removeBinaries($initial); + $promise = $this->updateCode($initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($target); + + return $promise->then(static function () use ($binaryInstaller, $installPath, $target, $initial, $repo): void { + $binaryInstaller->installBinaries($target, $installPath); + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + }); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + + $promise = $this->removeCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + $binaryInstaller = $this->binaryInstaller; + $downloadPath = $this->getPackageBasePath($package); + $filesystem = $this->filesystem; + + return $promise->then(static function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo): void { + $binaryInstaller->removeBinaries($package); + $repo->removePackage($package); + + if (strpos($package->getName(), '/')) { + $packageVendorDir = dirname($downloadPath); + if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { + Silencer::call('rmdir', $packageVendorDir); + } + } + }); + } + + /** + * @inheritDoc + * + * @return string + */ + public function getInstallPath(PackageInterface $package) + { + $this->initializeVendorDir(); + + $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName(); + $targetDir = $package->getTargetDir(); + + return $basePath . ($targetDir ? '/'.$targetDir : ''); + } + + /** + * Make sure binaries are installed for a given package. + * + * @param PackageInterface $package Package instance + */ + public function ensureBinariesPresence(PackageInterface $package) + { + $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false); + } + + /** + * Returns the base path of the package without target-dir path + * + * It is used for BC as getInstallPath tends to be overridden by + * installer plugins but not getPackageBasePath + * + * @return string + */ + protected function getPackageBasePath(PackageInterface $package) + { + $installPath = $this->getInstallPath($package); + $targetDir = $package->getTargetDir(); + + if ($targetDir) { + return Preg::replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath); + } + + return $installPath; + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function installCode(PackageInterface $package) + { + $downloadPath = $this->getInstallPath($package); + + return $this->getDownloadManager()->install($package, $downloadPath); + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function updateCode(PackageInterface $initial, PackageInterface $target) + { + $initialDownloadPath = $this->getInstallPath($initial); + $targetDownloadPath = $this->getInstallPath($target); + if ($targetDownloadPath !== $initialDownloadPath) { + // if the target and initial dirs intersect, we force a remove + install + // to avoid the rename wiping the target dir as part of the initial dir cleanup + if (strpos($initialDownloadPath, $targetDownloadPath) === 0 + || strpos($targetDownloadPath, $initialDownloadPath) === 0 + ) { + $promise = $this->removeCode($initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($target): PromiseInterface { + $promise = $this->installCode($target); + if ($promise instanceof PromiseInterface) { + return $promise; + } + + return \React\Promise\resolve(null); + }); + } + + $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); + } + + return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath); + } + + /** + * @return PromiseInterface|null + * @phpstan-return PromiseInterface|null + */ + protected function removeCode(PackageInterface $package) + { + $downloadPath = $this->getPackageBasePath($package); + + return $this->getDownloadManager()->remove($package, $downloadPath); + } + + /** + * @return void + */ + protected function initializeVendorDir() + { + $this->filesystem->ensureDirectoryExists($this->vendorDir); + $this->vendorDir = realpath($this->vendorDir); + } + + protected function getDownloadManager(): DownloadManager + { + assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages')); + + return $this->downloadManager; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php b/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php new file mode 100644 index 0000000..f61a537 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php @@ -0,0 +1,134 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\IO\IOInterface; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +/** + * Metapackage installation manager. + * + * @author Martin Hasoň + */ +class MetapackageInstaller implements InstallerInterface +{ + /** @var IOInterface */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === 'metapackage'; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + return $repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // noop + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->io->writeError(" - " . InstallOperation::format($package)); + + $repo->addPackage(clone $package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $this->io->writeError(" - " . UpdateOperation::format($initial, $target)); + + $repo->removePackage($initial); + $repo->addPackage(clone $target); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + + $this->io->writeError(" - " . UninstallOperation::format($package)); + + $repo->removePackage($package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + * + * @return null + */ + public function getInstallPath(PackageInterface $package) + { + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php b/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php new file mode 100644 index 0000000..22cf9f8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/NoopInstaller.php @@ -0,0 +1,118 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; + +/** + * Does not install anything but marks packages installed in the repo + * + * Useful for dry runs + * + * @author Jordi Boggiano + */ +class NoopInstaller implements InstallerInterface +{ + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return true; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + return $repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + if (!$repo->hasPackage($initial)) { + throw new \InvalidArgumentException('Package is not installed: '.$initial); + } + + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + if (!$repo->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + $repo->removePackage($package); + + return \React\Promise\resolve(null); + } + + /** + * @inheritDoc + */ + public function getInstallPath(PackageInterface $package) + { + $targetDir = $package->getTargetDir(); + + return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : ''); + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/PackageEvent.php b/vendor/composer/composer/src/Composer/Installer/PackageEvent.php new file mode 100644 index 0000000..1630f43 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PackageEvent.php @@ -0,0 +1,110 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Repository\RepositoryInterface; +use Composer\EventDispatcher\Event; + +/** + * The Package Event. + * + * @author Jordi Boggiano + */ +class PackageEvent extends Event +{ + /** + * @var Composer + */ + private $composer; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var bool + */ + private $devMode; + + /** + * @var RepositoryInterface + */ + private $localRepo; + + /** + * @var OperationInterface[] + */ + private $operations; + + /** + * @var OperationInterface The operation instance which is being executed + */ + private $operation; + + /** + * Constructor. + * + * @param OperationInterface[] $operations + */ + public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation) + { + parent::__construct($eventName); + + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + $this->localRepo = $localRepo; + $this->operations = $operations; + $this->operation = $operation; + } + + public function getComposer(): Composer + { + return $this->composer; + } + + public function getIO(): IOInterface + { + return $this->io; + } + + public function isDevMode(): bool + { + return $this->devMode; + } + + public function getLocalRepo(): RepositoryInterface + { + return $this->localRepo; + } + + /** + * @return OperationInterface[] + */ + public function getOperations(): array + { + return $this->operations; + } + + /** + * Returns the package instance. + */ + public function getOperation(): OperationInterface + { + return $this->operation; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/PackageEvents.php b/vendor/composer/composer/src/Composer/Installer/PackageEvents.php new file mode 100644 index 0000000..04f51a9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PackageEvents.php @@ -0,0 +1,75 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +/** + * Package Events. + * + * @author Jordi Boggiano + */ +class PackageEvents +{ + /** + * The PRE_PACKAGE_INSTALL event occurs before a package is installed. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_INSTALL = 'pre-package-install'; + + /** + * The POST_PACKAGE_INSTALL event occurs after a package is installed. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_INSTALL = 'post-package-install'; + + /** + * The PRE_PACKAGE_UPDATE event occurs before a package is updated. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_UPDATE = 'pre-package-update'; + + /** + * The POST_PACKAGE_UPDATE event occurs after a package is updated. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_UPDATE = 'post-package-update'; + + /** + * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; + + /** + * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. + * + * The event listener method receives a Composer\Installer\PackageEvent instance. + * + * @var string + */ + public const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; +} diff --git a/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php b/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php new file mode 100644 index 0000000..58ba0d7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/PluginInstaller.php @@ -0,0 +1,139 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\PartialComposer; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Plugin\PluginManager; +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use React\Promise\PromiseInterface; + +/** + * Installer for plugin packages + * + * @author Jordi Boggiano + * @author Nils Adermann + */ +class PluginInstaller extends LibraryInstaller +{ + public function __construct(IOInterface $io, PartialComposer $composer, ?Filesystem $fs = null, ?BinaryInstaller $binaryInstaller = null) + { + parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller); + } + + /** + * @inheritDoc + */ + public function supports(string $packageType) + { + return $packageType === 'composer-plugin' || $packageType === 'composer-installer'; + } + + public function disablePlugins(): void + { + $this->getPluginManager()->disablePlugins(); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) + { + // fail install process early if it is going to fail due to a plugin not being allowed + if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) { + $this->getPluginManager()->isPluginAllowed($package->getName(), false, true === ($package->getExtra()['plugin-optional'] ?? false)); + } + + return parent::prepare($type, $package, $prevPackage); + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) + { + $extra = $package->getExtra(); + if (empty($extra['class'])) { + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); + } + + return parent::download($package, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $promise = parent::install($repo, $package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($package, $repo): void { + try { + Platform::workaroundFilesystemIssues(); + $this->getPluginManager()->registerPackage($package, true); + } catch (\Exception $e) { + $this->rollbackInstall($e, $repo, $package); + } + }); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + $promise = parent::update($repo, $initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(null); + } + + return $promise->then(function () use ($initial, $target, $repo): void { + try { + Platform::workaroundFilesystemIssues(); + $this->getPluginManager()->deactivatePackage($initial); + $this->getPluginManager()->registerPackage($target, true); + } catch (\Exception $e) { + $this->rollbackInstall($e, $repo, $target); + } + }); + } + + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->getPluginManager()->uninstallPackage($package); + + return parent::uninstall($repo, $package); + } + + private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void + { + $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); + parent::uninstall($repo, $package); + throw $e; + } + + protected function getPluginManager(): PluginManager + { + assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.')); + $pluginManager = $this->composer->getPluginManager(); + + return $pluginManager; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php b/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php new file mode 100644 index 0000000..ccf439b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php @@ -0,0 +1,124 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use React\Promise\PromiseInterface; +use Composer\Package\PackageInterface; +use Composer\Downloader\DownloadManager; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Util\Filesystem; + +/** + * Project Installer is used to install a single package into a directory as + * root project. + * + * @author Benjamin Eberlei + */ +class ProjectInstaller implements InstallerInterface +{ + /** @var string */ + private $installPath; + /** @var DownloadManager */ + private $downloadManager; + /** @var Filesystem */ + private $filesystem; + + public function __construct(string $installPath, DownloadManager $dm, Filesystem $fs) + { + $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/'; + $this->downloadManager = $dm; + $this->filesystem = $fs; + } + + /** + * Decides if the installer supports the given type + */ + public function supports(string $packageType): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function download(PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + $installPath = $this->installPath; + if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { + throw new \InvalidArgumentException("Project directory $installPath is not empty."); + } + if (!is_dir($installPath)) { + mkdir($installPath, 0777, true); + } + + return $this->downloadManager->download($package, $installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface + { + return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); + } + + /** + * @inheritDoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface + { + return $this->downloadManager->install($package, $this->installPath); + } + + /** + * @inheritDoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): ?PromiseInterface + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * @inheritDoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface + { + throw new \InvalidArgumentException("not supported"); + } + + /** + * Returns the installation path of a package + * + * @return string configured install path + */ + public function getInstallPath(PackageInterface $package): string + { + return $this->installPath; + } +} diff --git a/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php b/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php new file mode 100644 index 0000000..f33349d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php @@ -0,0 +1,228 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepository; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Add suggested packages from different places to output them in the end. + * + * @author Haralan Dobrev + */ +class SuggestedPackagesReporter +{ + public const MODE_LIST = 1; + public const MODE_BY_PACKAGE = 2; + public const MODE_BY_SUGGESTION = 4; + + /** + * @var array + */ + protected $suggestedPackages = []; + + /** + * @var IOInterface + */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * @return array Suggested packages with source, target and reason keys. + */ + public function getPackages(): array + { + return $this->suggestedPackages; + } + + /** + * Add suggested packages to be listed after install + * + * Could be used to add suggested packages both from the installer + * or from CreateProjectCommand. + * + * @param string $source Source package which made the suggestion + * @param string $target Target package to be suggested + * @param string $reason Reason the target package to be suggested + */ + public function addPackage(string $source, string $target, string $reason): SuggestedPackagesReporter + { + $this->suggestedPackages[] = [ + 'source' => $source, + 'target' => $target, + 'reason' => $reason, + ]; + + return $this; + } + + /** + * Add all suggestions from a package. + */ + public function addSuggestionsFromPackage(PackageInterface $package): SuggestedPackagesReporter + { + $source = $package->getPrettyName(); + foreach ($package->getSuggests() as $target => $reason) { + $this->addPackage( + $source, + $target, + $reason + ); + } + + return $this; + } + + /** + * Output suggested packages. + * + * Do not list the ones already installed if installed repository provided. + * + * @param int $mode One of the MODE_* constants from this class + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + */ + public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void + { + $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); + + $suggesters = []; + $suggested = []; + foreach ($suggestedPackages as $suggestion) { + $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason']; + $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason']; + } + ksort($suggesters); + ksort($suggested); + + // Simple mode + if ($mode & self::MODE_LIST) { + foreach (array_keys($suggested) as $name) { + $this->io->write(sprintf('%s', $name)); + } + + return; + } + + // Grouped by package + if ($mode & self::MODE_BY_PACKAGE) { + foreach ($suggesters as $suggester => $suggestions) { + $this->io->write(sprintf('%s suggests:', $suggester)); + + foreach ($suggestions as $suggestion => $reason) { + $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason))); + } + $this->io->write(''); + } + } + + // Grouped by suggestion + if ($mode & self::MODE_BY_SUGGESTION) { + // Improve readability in full mode + if ($mode & self::MODE_BY_PACKAGE) { + $this->io->write(str_repeat('-', 78)); + } + foreach ($suggested as $suggestion => $suggesters) { + $this->io->write(sprintf('%s is suggested by:', $suggestion)); + + foreach ($suggesters as $suggester => $reason) { + $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason))); + } + $this->io->write(''); + } + } + + if ($onlyDependentsOf) { + $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo); + $diff = count($allSuggestedPackages) - count($suggestedPackages); + if ($diff) { + $this->io->write(''.$diff.' additional suggestions by transitive dependencies can be shown with --all'); + } + } + } + + /** + * Output number of new suggested packages and a hint to use suggest command. + * + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + */ + public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void + { + $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); + if ($suggestedPackages) { + $this->io->writeError(''.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.'); + } + } + + /** + * @param InstalledRepository|null $installedRepo If passed in, suggested packages which are installed already will be skipped + * @param PackageInterface|null $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown + * @return mixed[] + */ + private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): array + { + $suggestedPackages = $this->getPackages(); + $installedNames = []; + if (null !== $installedRepo && !empty($suggestedPackages)) { + foreach ($installedRepo->getPackages() as $package) { + $installedNames = array_merge( + $installedNames, + $package->getNames() + ); + } + } + + $sourceFilter = []; + if ($onlyDependentsOf) { + $sourceFilter = array_map(static function ($link): string { + return $link->getTarget(); + }, array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires())); + $sourceFilter[] = $onlyDependentsOf->getName(); + } + + $suggestions = []; + foreach ($suggestedPackages as $suggestion) { + if (in_array($suggestion['target'], $installedNames) || (\count($sourceFilter) > 0 && !in_array($suggestion['source'], $sourceFilter))) { + continue; + } + + $suggestions[] = $suggestion; + } + + return $suggestions; + } + + private function escapeOutput(string $string): string + { + return OutputFormatter::escape( + $this->removeControlCharacters($string) + ); + } + + private function removeControlCharacters(string $string): string + { + return Preg::replace( + '/[[:cntrl:]]/', + '', + str_replace("\n", ' ', $string) + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonFile.php b/vendor/composer/composer/src/Composer/Json/JsonFile.php new file mode 100644 index 0000000..785e46b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonFile.php @@ -0,0 +1,393 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use JsonSchema\Validator; +use Seld\JsonLint\JsonParser; +use Seld\JsonLint\ParsingException; +use Composer\Util\HttpDownloader; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; + +/** + * Reads/writes json files. + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class JsonFile +{ + public const LAX_SCHEMA = 1; + public const STRICT_SCHEMA = 2; + public const AUTH_SCHEMA = 3; + public const LOCK_SCHEMA = 4; + + /** @deprecated Use \JSON_UNESCAPED_SLASHES */ + public const JSON_UNESCAPED_SLASHES = 64; + /** @deprecated Use \JSON_PRETTY_PRINT */ + public const JSON_PRETTY_PRINT = 128; + /** @deprecated Use \JSON_UNESCAPED_UNICODE */ + public const JSON_UNESCAPED_UNICODE = 256; + + public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; + public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json'; + + public const INDENT_DEFAULT = ' '; + + /** @var string */ + private $path; + /** @var ?HttpDownloader */ + private $httpDownloader; + /** @var ?IOInterface */ + private $io; + /** @var string */ + private $indent = self::INDENT_DEFAULT; + + /** + * Initializes json file reader/parser. + * + * @param string $path path to a lockfile + * @param ?HttpDownloader $httpDownloader required for loading http/https json files + * @param ?IOInterface $io + * @throws \InvalidArgumentException + */ + public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null) + { + $this->path = $path; + + if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) { + throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed'); + } + $this->httpDownloader = $httpDownloader; + $this->io = $io; + } + + public function getPath(): string + { + return $this->path; + } + + /** + * Checks whether json file exists. + */ + public function exists(): bool + { + return is_file($this->path); + } + + /** + * Reads json file. + * + * @throws ParsingException + * @throws \RuntimeException + * @return mixed + */ + public function read() + { + try { + if ($this->httpDownloader) { + $json = $this->httpDownloader->get($this->path)->getBody(); + } else { + if (!Filesystem::isReadable($this->path)) { + throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); + } + if ($this->io && $this->io->isDebug()) { + $realpathInfo = ''; + $realpath = realpath($this->path); + if (false !== $realpath && $realpath !== $this->path) { + $realpathInfo = ' (' . $realpath . ')'; + } + $this->io->writeError('Reading ' . $this->path . $realpathInfo); + } + $json = file_get_contents($this->path); + } + } catch (TransportException $e) { + throw new \RuntimeException($e->getMessage(), 0, $e); + } catch (\Exception $e) { + throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage()); + } + + if ($json === false) { + throw new \RuntimeException('Could not read '.$this->path); + } + + $this->indent = self::detectIndenting($json); + + return static::parseJson($json, $this->path); + } + + /** + * Writes json file. + * + * @param mixed[] $hash writes hash into json file + * @param int $options json_encode options + * @throws \UnexpectedValueException|\Exception + * @return void + */ + public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) + { + if ($this->path === 'php://memory') { + file_put_contents($this->path, static::encode($hash, $options, $this->indent)); + + return; + } + + $dir = dirname($this->path); + if (!is_dir($dir)) { + if (file_exists($dir)) { + throw new \UnexpectedValueException( + realpath($dir).' exists and is not a directory.' + ); + } + if (!@mkdir($dir, 0777, true)) { + throw new \UnexpectedValueException( + $dir.' does not exist and could not be created.' + ); + } + } + + $retries = 3; + while ($retries--) { + try { + $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : '')); + break; + } catch (\Exception $e) { + if ($retries > 0) { + usleep(500000); + continue; + } + + throw $e; + } + } + } + + /** + * Modify file properties only if content modified + * + * @return int|false + */ + private function filePutContentsIfModified(string $path, string $content) + { + $currentContent = @file_get_contents($path); + if (false === $currentContent || $currentContent !== $content) { + return file_put_contents($path, $content); + } + + return 0; + } + + /** + * Validates the schema of the current json file according to composer-schema.json rules + * + * @param int $schema a JsonFile::*_SCHEMA constant + * @param string|null $schemaFile a path to the schema file + * @throws JsonValidationException + * @throws ParsingException + * @return true true on success + * + * @phpstan-param self::*_SCHEMA $schema + */ + public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool + { + if (!Filesystem::isReadable($this->path)) { + throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); + } + $content = file_get_contents($this->path); + $data = json_decode($content); + + if (null === $data && 'null' !== $content) { + self::validateSyntax($content, $this->path); + } + + return self::validateJsonSchema($this->path, $data, $schema, $schemaFile); + } + + /** + * Validates the schema of the current json file according to composer-schema.json rules + * + * @param mixed $data Decoded JSON data to validate + * @param int $schema a JsonFile::*_SCHEMA constant + * @param string|null $schemaFile a path to the schema file + * @throws JsonValidationException + * @return true true on success + * + * @phpstan-param self::*_SCHEMA $schema + */ + public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null): bool + { + $isComposerSchemaFile = false; + if (null === $schemaFile) { + if ($schema === self::LOCK_SCHEMA) { + $schemaFile = self::LOCK_SCHEMA_PATH; + } else { + $isComposerSchemaFile = true; + $schemaFile = self::COMPOSER_SCHEMA_PATH; + } + } + + // Prepend with file:// only when not using a special schema already (e.g. in the phar) + if (false === strpos($schemaFile, '://')) { + $schemaFile = 'file://' . $schemaFile; + } + + $schemaData = (object) ['$ref' => $schemaFile, '$schema' => "https://json-schema.org/draft-04/schema#"]; + + if ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) { + $schemaData = json_decode((string) file_get_contents($schemaFile)); + $schemaData->additionalProperties = false; + $schemaData->required = ['name', 'description']; + } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) { + $schemaData = (object) ['$ref' => $schemaFile.'#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"]; + } + + $validator = new Validator(); + // convert assoc arrays to objects + $data = json_decode((string) json_encode($data)); + $validator->validate($data, $schemaData); + + if (!$validator->isValid()) { + $errors = []; + foreach ($validator->getErrors() as $error) { + $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; + } + throw new JsonValidationException('"'.$source.'" does not match the expected JSON schema', $errors); + } + + return true; + } + + /** + * Encodes an array into (optionally pretty-printed) JSON + * + * @param mixed $data Data to encode into a formatted JSON string + * @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) + * @param string $indent Indentation string + * @return string Encoded json + */ + public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT): string + { + $json = json_encode($data, $options); + + if (false === $json) { + self::throwEncodeError(json_last_error()); + } + + if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT ) { + // Pretty printing and not using default indentation + return Preg::replaceCallback( + '#^ {4,}#m', + static function ($match) use ($indent): string { + return str_repeat($indent, (int)(strlen($match[0]) / 4)); + }, + $json + ); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @throws \RuntimeException + * @return never + */ + private static function throwEncodeError(int $code): void + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg); + } + + /** + * Parses json string and returns hash. + * + * @param null|string $json json string + * @param string $file the json file + * + * @throws ParsingException + * @return mixed + */ + public static function parseJson(?string $json, ?string $file = null) + { + if (null === $json) { + return null; + } + $data = json_decode($json, true); + if (null === $data && JSON_ERROR_NONE !== json_last_error()) { + self::validateSyntax($json, $file); + } + + return $data; + } + + /** + * Validates the syntax of a JSON string + * + * @param string $file + * @throws \UnexpectedValueException + * @throws ParsingException + * @return bool true on success + */ + protected static function validateSyntax(string $json, ?string $file = null): bool + { + $parser = new JsonParser(); + $result = $parser->lint($json); + if (null === $result) { + if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) { + if ($file === null) { + throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON'); + } else { + throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON'); + } + } + + return true; + } + + if ($file === null) { + throw new ParsingException('The input does not contain valid JSON' . "\n" . $result->getMessage(), + $result->getDetails()); + } else { + throw new ParsingException('"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(), + $result->getDetails()); + } + } + + public static function detectIndenting(?string $json): string + { + if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) { + return $match[1]; + } + return self::INDENT_DEFAULT; + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonFormatter.php b/vendor/composer/composer/src/Composer/Json/JsonFormatter.php new file mode 100644 index 0000000..fa1a3c5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonFormatter.php @@ -0,0 +1,132 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; + +/** + * Formats json strings used for php < 5.4 because the json_encode doesn't + * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE + * in these versions + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + * + * @deprecated Use json_encode or JsonFile::encode() with modern JSON_* flags to configure formatting - this class will be removed in 3.0 + */ +class JsonFormatter +{ + /** + * This code is based on the function found at: + * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ + * + * Originally licensed under MIT by Dave Perrett + * + * @param bool $unescapeUnicode Un escape unicode + * @param bool $unescapeSlashes Un escape slashes + */ + public static function format(string $json, bool $unescapeUnicode, bool $unescapeSlashes): string + { + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $outOfQuotes = true; + $buffer = ''; + $noescape = true; + + for ($i = 0; $i < $strLen; $i++) { + // Grab the next character in the string + $char = substr($json, $i, 1); + + // Are we inside a quoted string? + if ('"' === $char && $noescape) { + $outOfQuotes = !$outOfQuotes; + } + + if (!$outOfQuotes) { + $buffer .= $char; + $noescape = '\\' === $char ? !$noescape : true; + continue; + } + if ('' !== $buffer) { + if ($unescapeSlashes) { + $buffer = str_replace('\\/', '/', $buffer); + } + + if ($unescapeUnicode && function_exists('mb_convert_encoding')) { + // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha + $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string { + $l = strlen($match[1]); + + if ($l % 2) { + $code = hexdec($match[2]); + // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped + // see https://github.com/composer/composer/issues/7510 + if (0xD800 <= $code && 0xDFFF >= $code) { + return $match[0]; + } + + return str_repeat('\\', $l - 1) . mb_convert_encoding( + pack('H*', $match[2]), + 'UTF-8', + 'UCS-2BE' + ); + } + + return $match[0]; + }, $buffer); + } + + $result .= $buffer.$char; + $buffer = ''; + continue; + } + + if (':' === $char) { + // Add a space after the : character + $char .= ' '; + } elseif ('}' === $char || ']' === $char) { + $pos--; + $prevChar = substr($json, $i - 1, 1); + + if ('{' !== $prevChar && '[' !== $prevChar) { + // If this character is the end of an element, + // output a new line and indent the next line + $result .= $newLine; + $result .= str_repeat($indentStr, $pos); + } else { + // Collapse empty {} and [] + $result = rtrim($result); + } + } + + $result .= $char; + + // If the last character was the beginning of an element, + // output a new line and indent the next line + if (',' === $char || '{' === $char || '[' === $char) { + $result .= $newLine; + + if ('{' === $char || '[' === $char) { + $pos++; + } + + $result .= str_repeat($indentStr, $pos); + } + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonManipulator.php b/vendor/composer/composer/src/Composer/Json/JsonManipulator.php new file mode 100644 index 0000000..0b45d65 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonManipulator.php @@ -0,0 +1,580 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; + +/** + * @author Jordi Boggiano + */ +class JsonManipulator +{ + /** @var string */ + private const DEFINES = '(?(DEFINE) + (? -? (?= [1-9]|0(?!\d) ) \d++ (?:\.\d++)? (?:[eE] [+-]?+ \d++)? ) + (? true | false | null ) + (? " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) + (? \[ (?: (?&json) \s*+ (?: , (?&json) \s*+ )*+ )?+ \s*+ \] ) + (? \s*+ (?&string) \s*+ : (?&json) \s*+ ) + (? \{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \s*+ \} ) + (? \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) + )'; + + /** @var string */ + private $contents; + /** @var string */ + private $newline; + /** @var string */ + private $indent; + + public function __construct(string $contents) + { + $contents = trim($contents); + if ($contents === '') { + $contents = '{}'; + } + if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) { + throw new \InvalidArgumentException('The json file must be an object ({})'); + } + $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n"; + $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents; + $this->detectIndenting(); + } + + public function getContents(): string + { + return $this->contents . $this->newline; + } + + public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool + { + $decoded = JsonFile::parseJson($this->contents); + + // no link of that type yet + if (!isset($decoded[$type])) { + return $this->addMainKey($type, [$package => $constraint]); + } + + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P(?&json))(?P.*)}sx'; + if (!Preg::isMatch($regex, $this->contents, $matches)) { + return false; + } + assert(is_string($matches['start'])); + assert(is_string($matches['value'])); + assert(is_string($matches['end'])); + + $links = $matches['value']; + + // try to find existing link + $packageRegex = str_replace('/', '\\\\?/', preg_quote($package)); + $regex = '{'.self::DEFINES.'"(?P'.$packageRegex.')"(\s*:\s*)(?&string)}ix'; + if (Preg::isMatch($regex, $links, $packageMatches)) { + assert(is_string($packageMatches['package'])); + // update existing link + $existingPackage = $packageMatches['package']; + $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage)); + $links = Preg::replaceCallback('{'.self::DEFINES.'"'.$packageRegex.'"(?P\s*:\s*)(?&string)}ix', static function ($m) use ($existingPackage, $constraint): string { + return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"'; + }, $links); + } else { + if (Preg::isMatchStrictGroups('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) { + // link missing but non empty links + $links = Preg::replace( + '{'.preg_quote($match[1]).'$}', + // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588 + addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'), + $links + ); + } else { + // links empty + $links = '{' . $this->newline . + $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline . + $this->indent . '}'; + } + } + + if (true === $sortPackages) { + $requirements = json_decode($links, true); + $this->sortPackages($requirements); + $links = $this->format($requirements); + } + + $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end']; + + return true; + } + + /** + * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically. + * + * @link https://getcomposer.org/doc/02-libraries.md#platform-packages + * + * @param array $packages + */ + private function sortPackages(array &$packages = []): void + { + $prefix = static function ($requirement): string { + if (PlatformRepository::isPlatformPackage($requirement)) { + return Preg::replace( + [ + '/^php/', + '/^hhvm/', + '/^ext/', + '/^lib/', + '/^\D/', + ], + [ + '0-$0', + '1-$0', + '2-$0', + '3-$0', + '4-$0', + ], + $requirement + ); + } + + return '5-'.$requirement; + }; + + uksort($packages, static function ($a, $b) use ($prefix): int { + return strnatcmp($prefix($a), $prefix($b)); + }); + } + + /** + * @param array|false $config + */ + public function addRepository(string $name, $config, bool $append = true): bool + { + return $this->addSubNode('repositories', $name, $config, $append); + } + + public function removeRepository(string $name): bool + { + return $this->removeSubNode('repositories', $name); + } + + /** + * @param mixed $value + */ + public function addConfigSetting(string $name, $value): bool + { + return $this->addSubNode('config', $name, $value); + } + + public function removeConfigSetting(string $name): bool + { + return $this->removeSubNode('config', $name); + } + + /** + * @param mixed $value + */ + public function addProperty(string $name, $value): bool + { + if (strpos($name, 'suggest.') === 0) { + return $this->addSubNode('suggest', substr($name, 8), $value); + } + + if (strpos($name, 'extra.') === 0) { + return $this->addSubNode('extra', substr($name, 6), $value); + } + + if (strpos($name, 'scripts.') === 0) { + return $this->addSubNode('scripts', substr($name, 8), $value); + } + + return $this->addMainKey($name, $value); + } + + public function removeProperty(string $name): bool + { + if (strpos($name, 'suggest.') === 0) { + return $this->removeSubNode('suggest', substr($name, 8)); + } + + if (strpos($name, 'extra.') === 0) { + return $this->removeSubNode('extra', substr($name, 6)); + } + + if (strpos($name, 'scripts.') === 0) { + return $this->removeSubNode('scripts', substr($name, 8)); + } + + if (strpos($name, 'autoload.') === 0) { + return $this->removeSubNode('autoload', substr($name, 9)); + } + + if (strpos($name, 'autoload-dev.') === 0) { + return $this->removeSubNode('autoload-dev', substr($name, 13)); + } + + return $this->removeMainKey($name); + } + + /** + * @param mixed $value + */ + public function addSubNode(string $mainNode, string $name, $value, bool $append = true): bool + { + $decoded = JsonFile::parseJson($this->contents); + + $subName = null; + if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { + [$name, $subName] = explode('.', $name, 2); + } + + // no main node yet + if (!isset($decoded[$mainNode])) { + if ($subName !== null) { + $this->addMainKey($mainNode, [$name => [$subName => $value]]); + } else { + $this->addMainKey($mainNode, [$name => $value]); + } + + return true; + } + + // main node content not match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; + + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + // invalid match due to un-regexable content, abort + if (!@json_decode($children)) { + return false; + } + + // child exists + $childRegex = '{'.self::DEFINES.'(?P"'.preg_quote($name).'"\s*:\s*)(?P(?&json))(?P,?)}x'; + if (Preg::isMatch($childRegex, $children, $matches)) { + $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value): string { + if ($subName !== null && is_string($matches['content'])) { + $curVal = json_decode($matches['content'], true); + if (!is_array($curVal)) { + $curVal = []; + } + $curVal[$subName] = $value; + $value = $curVal; + } + + return $matches['start'] . $this->format($value, 1) . $matches['end']; + }, $children); + } elseif (Preg::isMatch('#^\{(?P\s*?)(?P\S+.*?)?(?P\s*)\}$#s', $children, $match)) { + $whitespace = $match['trailingspace']; + if (null !== $match['content']) { + if ($subName !== null) { + $value = [$subName => $value]; + } + + // child missing but non empty children + if ($append) { + $children = Preg::replace( + '#'.$whitespace.'}$#', + addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'), + $children + ); + } else { + $whitespace = $match['leadingspace']; + $children = Preg::replace( + '#^{'.$whitespace.'#', + addcslashes('{' . $whitespace . JsonFile::encode($name).': '.$this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'), + $children + ); + } + } else { + if ($subName !== null) { + $value = [$subName => $value]; + } + + // children present but empty + $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}'; + } + } else { + throw new \LogicException('Nothing matched above for: '.$children); + } + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { + return $m['start'] . $children . $m['end']; + }, $this->contents); + + return true; + } + + public function removeSubNode(string $mainNode, string $name): bool + { + $decoded = JsonFile::parseJson($this->contents); + + // no node or empty node + if (empty($decoded[$mainNode])) { + return true; + } + + // no node content match-able + $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. + preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; + try { + if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { + return false; + } + } catch (\RuntimeException $e) { + if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { + return false; + } + throw $e; + } + + assert(is_string($match['start'])); + assert(is_string($match['content'])); + assert(is_string($match['end'])); + + $children = $match['content']; + + // invalid match due to un-regexable content, abort + if (!@json_decode($children, true)) { + return false; + } + + $subName = null; + if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { + [$name, $subName] = explode('.', $name, 2); + } + + // no node to remove + if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { + return true; + } + + // try and find a match for the subkey + $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); + if (Preg::isMatch('{"'.$keyRegex.'"\s*:}i', $children)) { + // find best match for the value of "name" + if (Preg::isMatchAll('{'.self::DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { + $bestMatch = ''; + foreach ($matches[0] as $match) { + assert(is_string($match)); + if (strlen($bestMatch) < strlen($match)) { + $bestMatch = $match; + } + } + $childrenClean = Preg::replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count); + if (1 !== $count) { + $childrenClean = Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count); + if (1 !== $count) { + return false; + } + } + } + } else { + $childrenClean = $children; + } + + if (!isset($childrenClean)) { + throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new."); + } + + // no child data left, $name was the only key in + unset($match); + if (Preg::isMatch('#^\{\s*?(?P\S+.*?)?(?P\s*)\}$#s', $childrenClean, $match)) { + if (null === $match['content']) { + $newline = $this->newline; + $indent = $this->indent; + + $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use ($indent, $newline): string { + return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end']; + }, $this->contents); + + // we have a subname, so we restore the rest of $name + if ($subName !== null) { + $curVal = json_decode($children, true); + unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } + $this->addSubNode($mainNode, $name, $curVal[$name]); + } + + return true; + } + } + + $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean): string { + assert(is_string($matches['content'])); + if ($subName !== null) { + $curVal = json_decode($matches['content'], true); + unset($curVal[$name][$subName]); + if ($curVal[$name] === []) { + $curVal[$name] = new \ArrayObject(); + } + $childrenClean = $this->format($curVal, 0, true); + } + + return $matches['start'] . $childrenClean . $matches['end']; + }, $this->contents); + + return true; + } + + /** + * @param mixed $content + */ + public function addMainKey(string $key, $content): bool + { + $decoded = JsonFile::parseJson($this->contents); + $content = $this->format($content); + + // key exists already + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P.*)}sx'; + if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) { + // invalid match due to un-regexable content, abort + if (!@json_decode('{'.$matches['key'].'}')) { + return false; + } + + $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end']; + + return true; + } + + // append at the end of the file and keep whitespace + if (Preg::isMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) { + $this->contents = Preg::replace( + '#'.$match[1].'\}$#', + addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'), + $this->contents + ); + + return true; + } + + // append at the end of the file + $this->contents = Preg::replace( + '#\}$#', + addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'), + $this->contents + ); + + return true; + } + + public function removeMainKey(string $key): bool + { + $decoded = JsonFile::parseJson($this->contents); + + if (!array_key_exists($key, $decoded)) { + return true; + } + + // key exists already + $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. + '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P.*)}sx'; + if (Preg::isMatch($regex, $this->contents, $matches)) { + assert(is_string($matches['start'])); + assert(is_string($matches['removal'])); + assert(is_string($matches['end'])); + + // invalid match due to un-regexable content, abort + if (!@json_decode('{'.$matches['removal'].'}')) { + return false; + } + + // check that we are not leaving a dangling comma on the previous line if the last line was removed + if (Preg::isMatchStrictGroups('#,\s*$#', $matches['start']) && Preg::isMatch('#^\}$#', $matches['end'])) { + $matches['start'] = rtrim(Preg::replace('#,(\s*)$#', '$1', $matches['start']), $this->indent); + } + + $this->contents = $matches['start'] . $matches['end']; + if (Preg::isMatch('#^\{\s*\}\s*$#', $this->contents)) { + $this->contents = "{\n}"; + } + + return true; + } + + return false; + } + + public function removeMainKeyIfEmpty(string $key): bool + { + $decoded = JsonFile::parseJson($this->contents); + + if (!array_key_exists($key, $decoded)) { + return true; + } + + if (is_array($decoded[$key]) && count($decoded[$key]) === 0) { + return $this->removeMainKey($key); + } + + return true; + } + + /** + * @param mixed $data + */ + public function format($data, int $depth = 0, bool $wasObject = false): string + { + if ($data instanceof \stdClass || $data instanceof \ArrayObject) { + $data = (array) $data; + $wasObject = true; + } + + if (is_array($data)) { + if (\count($data) === 0) { + return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]'; + } + + if (array_is_list($data)) { + foreach ($data as $key => $val) { + $data[$key] = $this->format($val, $depth + 1); + } + + return '['.implode(', ', $data).']'; + } + + $out = '{' . $this->newline; + $elems = []; + foreach ($data as $key => $val) { + $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1); + } + + return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}'; + } + + return JsonFile::encode($data); + } + + protected function detectIndenting(): void + { + $this->indent = JsonFile::detectIndenting($this->contents); + } +} diff --git a/vendor/composer/composer/src/Composer/Json/JsonValidationException.php b/vendor/composer/composer/src/Composer/Json/JsonValidationException.php new file mode 100644 index 0000000..28c22fc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Json/JsonValidationException.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +use Exception; + +/** + * @author Jordi Boggiano + */ +class JsonValidationException extends Exception +{ + /** + * @var string[] + */ + protected $errors; + + /** + * @param string[] $errors + */ + public function __construct(string $message, array $errors = [], ?Exception $previous = null) + { + $this->errors = $errors; + parent::__construct((string) $message, 0, $previous); + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php b/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php new file mode 100644 index 0000000..88cd635 --- /dev/null +++ b/vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php @@ -0,0 +1,207 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\PHPStan; + +use Composer\Config; +use Composer\Json\JsonFile; +use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\ArrayType; +use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; + +final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension +{ + /** @var array */ + private $properties = []; + + public function __construct() + { + $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); + /** + * @var string $prop + */ + foreach ($schema['properties']['config']['properties'] as $prop => $conf) { + $type = $this->parseType($conf, $prop); + + $this->properties[$prop] = $type; + } + } + + public function getClass(): string + { + return Config::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return strtolower($methodReflection->getName()) === 'get'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + + if (count($args) < 1) { + return null; + } + + $keyType = $scope->getType($args[0]->value); + if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore function.alreadyNarrowedType (- depending on PHPStan version, this method will always exist, or not.) + $strings = $keyType->getConstantStrings(); + } else { + // for compat with old phpstan versions, we use a deprecated phpstan method. + $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore staticMethod.deprecated (ignore deprecation) + } + if ($strings !== []) { + $types = []; + foreach($strings as $string) { + if (!isset($this->properties[$string->getValue()])) { + return null; + } + $types[] = $this->properties[$string->getValue()]; + } + + return TypeCombinator::union(...$types); + } + + return null; + } + + /** + * @param array $def + */ + private function parseType(array $def, string $path): Type + { + if (isset($def['type'])) { + $types = []; + foreach ((array) $def['type'] as $type) { + switch ($type) { + case 'integer': + if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) { + $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0); + } else { + $types[] = new IntegerType(); + } + break; + + case 'string': + if ($path === 'cache-files-maxsize') { + // passthru, skip as it is always converted to int + } elseif ($path === 'discard-changes') { + $types[] = new ConstantStringType('stash'); + } elseif ($path === 'use-parent-dir') { + $types[] = new ConstantStringType('prompt'); + } elseif ($path === 'store-auths') { + $types[] = new ConstantStringType('prompt'); + } elseif ($path === 'platform-check') { + $types[] = new ConstantStringType('php-only'); + } elseif ($path === 'github-protocols') { + $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]); + } elseif (str_starts_with($path, 'preferred-install')) { + $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]); + } else { + $types[] = new StringType(); + } + break; + + case 'boolean': + if ($path === 'platform.additionalProperties') { + $types[] = new ConstantBooleanType(false); + } else { + $types[] = new BooleanType(); + } + break; + + case 'object': + $addlPropType = null; + if (isset($def['additionalProperties'])) { + $addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties'); + } + + if (isset($def['properties'])) { + $keyNames = []; + $valTypes = []; + $optionalKeys = []; + $propIndex = 0; + foreach ($def['properties'] as $propName => $propdef) { + $keyNames[] = new ConstantStringType($propName); + $valType = $this->parseType($propdef, $path.'.'.$propName); + if (!isset($def['required']) || !in_array($propName, $def['required'], true)) { + $valType = TypeCombinator::addNull($valType); + $optionalKeys[] = $propIndex; + } + $valTypes[] = $valType; + $propIndex++; + } + + if ($addlPropType !== null) { + $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes)); + } else { + $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys); + } + } else { + $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType()); + } + break; + + case 'array': + if (isset($def['items'])) { + $valType = $this->parseType($def['items'], $path.'.items'); + } else { + $valType = new MixedType(); + } + + $types[] = new ArrayType(new IntegerType(), $valType); + break; + + default: + $types[] = new MixedType(); + } + } + + $type = TypeCombinator::union(...$types); + } elseif (isset($def['enum'])) { + $type = TypeCombinator::union(...array_map(static function (string $value): ConstantStringType { + return new ConstantStringType($value); + }, $def['enum'])); + } else { + $type = new MixedType(); + } + + // allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore + if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) { + $type = TypeCombinator::addNull($type); + } + + // default null props + if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) { + $type = TypeCombinator::addNull($type); + } + + return $type; + } +} diff --git a/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php b/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php new file mode 100644 index 0000000..58a9e4b --- /dev/null +++ b/vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\PHPStan; + +use Composer\DependencyResolver\Rule; +use Composer\Package\BasePackage; +use Composer\Package\Link; +use Composer\Semver\Constraint\ConstraintInterface; +use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\Scope; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\IntegerType; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; +use PHPStan\Type\ObjectType; +use PHPStan\Type\TypeCombinator; +use PhpParser\Node\Identifier; + +final class RuleReasonDataReturnTypeExtension implements DynamicMethodReturnTypeExtension +{ + public function getClass(): string + { + return Rule::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return strtolower($methodReflection->getName()) === 'getreasondata'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason'))); + + $types = [ + Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType, new ObjectType(ConstraintInterface::class)]), + Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]), + Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class), + Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class), + Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType, new AccessoryNonEmptyStringType()), + Rule::RULE_LEARNED => new IntegerType(), + Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class), + Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class), + ]; + + foreach ($types as $const => $type) { + if ((new ConstantIntegerType($const))->isSuperTypeOf($reasonType)->yes()) { + return $type; + } + } + + return TypeCombinator::union(...$types); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/AliasPackage.php b/vendor/composer/composer/src/Composer/Package/AliasPackage.php new file mode 100644 index 0000000..932ea36 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/AliasPackage.php @@ -0,0 +1,398 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Version\VersionParser; + +/** + * @author Jordi Boggiano + */ +class AliasPackage extends BasePackage +{ + /** @var string */ + protected $version; + /** @var string */ + protected $prettyVersion; + /** @var bool */ + protected $dev; + /** @var bool */ + protected $rootPackageAlias = false; + /** + * @var string + * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + protected $stability; + /** @var bool */ + protected $hasSelfVersionRequires = false; + + /** @var BasePackage */ + protected $aliasOf; + /** @var Link[] */ + protected $requires; + /** @var Link[] */ + protected $devRequires; + /** @var Link[] */ + protected $conflicts; + /** @var Link[] */ + protected $provides; + /** @var Link[] */ + protected $replaces; + + /** + * All descendants' constructors should call this parent constructor + * + * @param BasePackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(BasePackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf->getName()); + + $this->version = $version; + $this->prettyVersion = $prettyVersion; + $this->aliasOf = $aliasOf; + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + + foreach (Link::$TYPES as $type) { + $links = $aliasOf->{'get' . ucfirst($type)}(); + $this->{$type} = $this->replaceSelfVersionDependencies($links, $type); + } + } + + /** + * @return BasePackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + /** + * @inheritDoc + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @inheritDoc + */ + public function getStability(): string + { + return $this->stability; + } + + /** + * @inheritDoc + */ + public function getPrettyVersion(): string + { + return $this->prettyVersion; + } + + /** + * @inheritDoc + */ + public function isDev(): bool + { + return $this->dev; + } + + /** + * @inheritDoc + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * @inheritDoc + * @return array + */ + public function getConflicts(): array + { + return $this->conflicts; + } + + /** + * @inheritDoc + * @return array + */ + public function getProvides(): array + { + return $this->provides; + } + + /** + * @inheritDoc + * @return array + */ + public function getReplaces(): array + { + return $this->replaces; + } + + /** + * @inheritDoc + */ + public function getDevRequires(): array + { + return $this->devRequires; + } + + /** + * Stores whether this is an alias created by an aliasing in the requirements of the root package or not + * + * Use by the policy for sorting manually aliased packages first, see #576 + */ + public function setRootPackageAlias(bool $value): void + { + $this->rootPackageAlias = $value; + } + + /** + * @see setRootPackageAlias + */ + public function isRootPackageAlias(): bool + { + return $this->rootPackageAlias; + } + + /** + * @param Link[] $links + * @param Link::TYPE_* $linkType + * + * @return Link[] + */ + protected function replaceSelfVersionDependencies(array $links, $linkType): array + { + // for self.version requirements, we use the original package's branch name instead, to avoid leaking the magic dev-master-alias to users + $prettyVersion = $this->prettyVersion; + if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) { + $prettyVersion = $this->aliasOf->getPrettyVersion(); + } + + if (\in_array($linkType, [Link::TYPE_CONFLICT, Link::TYPE_PROVIDE, Link::TYPE_REPLACE], true)) { + $newLinks = []; + foreach ($links as $link) { + // link is self.version, but must be replacing also the replaced version + if ('self.version' === $link->getPrettyConstraint()) { + $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); + } + } + $links = array_merge($links, $newLinks); + } else { + foreach ($links as $index => $link) { + if ('self.version' === $link->getPrettyConstraint()) { + if ($linkType === Link::TYPE_REQUIRE) { + $this->hasSelfVersionRequires = true; + } + $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); + } + } + } + + return $links; + } + + public function hasSelfVersionRequires(): bool + { + return $this->hasSelfVersionRequires; + } + + public function __toString(): string + { + return parent::__toString().' ('.($this->rootPackageAlias ? 'root ' : ''). 'alias of '.$this->aliasOf->getVersion().')'; + } + + /*************************************** + * Wrappers around the aliased package * + ***************************************/ + + public function getType(): string + { + return $this->aliasOf->getType(); + } + + public function getTargetDir(): ?string + { + return $this->aliasOf->getTargetDir(); + } + + public function getExtra(): array + { + return $this->aliasOf->getExtra(); + } + + public function setInstallationSource(?string $type): void + { + $this->aliasOf->setInstallationSource($type); + } + + public function getInstallationSource(): ?string + { + return $this->aliasOf->getInstallationSource(); + } + + public function getSourceType(): ?string + { + return $this->aliasOf->getSourceType(); + } + + public function getSourceUrl(): ?string + { + return $this->aliasOf->getSourceUrl(); + } + + public function getSourceUrls(): array + { + return $this->aliasOf->getSourceUrls(); + } + + public function getSourceReference(): ?string + { + return $this->aliasOf->getSourceReference(); + } + + public function setSourceReference(?string $reference): void + { + $this->aliasOf->setSourceReference($reference); + } + + public function setSourceMirrors(?array $mirrors): void + { + $this->aliasOf->setSourceMirrors($mirrors); + } + + public function getSourceMirrors(): ?array + { + return $this->aliasOf->getSourceMirrors(); + } + + public function getDistType(): ?string + { + return $this->aliasOf->getDistType(); + } + + public function getDistUrl(): ?string + { + return $this->aliasOf->getDistUrl(); + } + + public function getDistUrls(): array + { + return $this->aliasOf->getDistUrls(); + } + + public function getDistReference(): ?string + { + return $this->aliasOf->getDistReference(); + } + + public function setDistReference(?string $reference): void + { + $this->aliasOf->setDistReference($reference); + } + + public function getDistSha1Checksum(): ?string + { + return $this->aliasOf->getDistSha1Checksum(); + } + + public function setTransportOptions(array $options): void + { + $this->aliasOf->setTransportOptions($options); + } + + public function getTransportOptions(): array + { + return $this->aliasOf->getTransportOptions(); + } + + public function setDistMirrors(?array $mirrors): void + { + $this->aliasOf->setDistMirrors($mirrors); + } + + public function getDistMirrors(): ?array + { + return $this->aliasOf->getDistMirrors(); + } + + public function getAutoload(): array + { + return $this->aliasOf->getAutoload(); + } + + public function getDevAutoload(): array + { + return $this->aliasOf->getDevAutoload(); + } + + public function getIncludePaths(): array + { + return $this->aliasOf->getIncludePaths(); + } + + public function getPhpExt(): ?array + { + return $this->aliasOf->getPhpExt(); + } + + public function getReleaseDate(): ?\DateTimeInterface + { + return $this->aliasOf->getReleaseDate(); + } + + public function getBinaries(): array + { + return $this->aliasOf->getBinaries(); + } + + public function getSuggests(): array + { + return $this->aliasOf->getSuggests(); + } + + public function getNotificationUrl(): ?string + { + return $this->aliasOf->getNotificationUrl(); + } + + public function isDefaultBranch(): bool + { + return $this->aliasOf->isDefaultBranch(); + } + + public function setDistUrl(?string $url): void + { + $this->aliasOf->setDistUrl($url); + } + + public function setDistType(?string $type): void + { + $this->aliasOf->setDistType($type); + } + + public function setSourceDistReferences(string $reference): void + { + $this->aliasOf->setSourceDistReferences($reference); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php new file mode 100644 index 0000000..995e774 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use FilterIterator; +use Iterator; +use PharData; +use SplFileInfo; + +/** + * @phpstan-extends FilterIterator> + */ +class ArchivableFilesFilter extends FilterIterator +{ + /** @var string[] */ + private $dirs = []; + + /** + * @return bool true if the current element is acceptable, otherwise false. + */ + public function accept(): bool + { + $file = $this->getInnerIterator()->current(); + if ($file->isDir()) { + $this->dirs[] = (string) $file; + + return false; + } + + return true; + } + + public function addEmptyDir(PharData $phar, string $sources): void + { + foreach ($this->dirs as $filepath) { + $localname = str_replace($sources . "/", '', $filepath); + $phar->addEmptyDir($localname); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php new file mode 100644 index 0000000..2cf7ffc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php @@ -0,0 +1,113 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use FilesystemIterator; +use FilterIterator; +use Iterator; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * A Symfony Finder wrapper which locates files that should go into archives + * + * Handles .gitignore, .gitattributes and .hgignore files as well as composer's + * own exclude rules from composer.json + * + * @author Nils Adermann + * @phpstan-extends FilterIterator> + */ +class ArchivableFilesFinder extends FilterIterator +{ + /** + * @var Finder + */ + protected $finder; + + /** + * Initializes the internal Symfony Finder with appropriate filters + * + * @param string $sources Path to source files to be archived + * @param string[] $excludes Composer's own exclude rules from composer.json + * @param bool $ignoreFilters Ignore filters when looking for files + */ + public function __construct(string $sources, array $excludes, bool $ignoreFilters = false) + { + $fs = new Filesystem(); + + $sourcesRealPath = realpath($sources); + if ($sourcesRealPath === false) { + throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"'); + } + $sources = $fs->normalizePath($sourcesRealPath); + + if ($ignoreFilters) { + $filters = []; + } else { + $filters = [ + new GitExcludeFilter($sources), + new ComposerExcludeFilter($sources, $excludes), + ]; + } + + $this->finder = new Finder(); + + $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool { + $realpath = $file->getRealPath(); + if ($realpath === false) { + return false; + } + if ($file->isLink() && strpos($realpath, $sources) !== 0) { + return false; + } + + $relativePath = Preg::replace( + '#^'.preg_quote($sources, '#').'#', + '', + $fs->normalizePath($realpath) + ); + + $exclude = false; + foreach ($filters as $filter) { + $exclude = $filter->filter($relativePath, $exclude); + } + + return !$exclude; + }; + + $this->finder + ->in($sources) + ->filter($filter) + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->sortByName(); + + parent::__construct($this->finder->getIterator()); + } + + public function accept(): bool + { + /** @var SplFileInfo $current */ + $current = $this->getInnerIterator()->current(); + + if (!$current->isDir()) { + return true; + } + + $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS); + + return !$iterator->valid(); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php new file mode 100644 index 0000000..77c3ebe --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php @@ -0,0 +1,289 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Downloader\DownloadManager; +use Composer\Package\RootPackageInterface; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Loop; +use Composer\Util\SyncHelper; +use Composer\Json\JsonFile; +use Composer\Package\CompletePackageInterface; + +/** + * @author Matthieu Moquet + * @author Till Klampaeckel + */ +class ArchiveManager +{ + /** @var DownloadManager */ + protected $downloadManager; + /** @var Loop */ + protected $loop; + + /** + * @var ArchiverInterface[] + */ + protected $archivers = []; + + /** + * @var bool + */ + protected $overwriteFiles = true; + + /** + * @param DownloadManager $downloadManager A manager used to download package sources + */ + public function __construct(DownloadManager $downloadManager, Loop $loop) + { + $this->downloadManager = $downloadManager; + $this->loop = $loop; + } + + public function addArchiver(ArchiverInterface $archiver): void + { + $this->archivers[] = $archiver; + } + + /** + * Set whether existing archives should be overwritten + * + * @param bool $overwriteFiles New setting + * + * @return $this + */ + public function setOverwriteFiles(bool $overwriteFiles): self + { + $this->overwriteFiles = $overwriteFiles; + + return $this; + } + + /** + * @return array + * @internal + */ + public function getPackageFilenameParts(CompletePackageInterface $package): array + { + $baseName = $package->getArchiveName(); + if (null === $baseName) { + $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName()); + } + + $parts = [ + 'base' => $baseName, + ]; + + $distReference = $package->getDistReference(); + if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) { + $parts['dist_reference'] = $distReference; + $parts['dist_type'] = $package->getDistType(); + } else { + $parts['version'] = $package->getPrettyVersion(); + $parts['dist_reference'] = $distReference; + } + + $sourceReference = $package->getSourceReference(); + if (null !== $sourceReference) { + $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6); + } + + $parts = array_filter($parts, function (?string $part) { + return $part !== null; + }); + foreach ($parts as $key => $part) { + $parts[$key] = str_replace('/', '-', $part); + } + + return $parts; + } + + /** + * @param array $parts + * + * @return string + * @internal + */ + public function getPackageFilenameFromParts(array $parts): string + { + return implode('-', $parts); + } + + /** + * Generate a distinct filename for a particular version of a package. + * + * @param CompletePackageInterface $package The package to get a name for + * + * @return string A filename without an extension + */ + public function getPackageFilename(CompletePackageInterface $package): string + { + return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package)); + } + + /** + * Create an archive of the specified package. + * + * @param CompletePackageInterface $package The package to archive + * @param string $format The format of the archive (zip, tar, ...) + * @param string $targetDir The directory where to build the archive + * @param string|null $fileName The relative file name to use for the archive, or null to generate + * the package name. Note that the format will be appended to this name + * @param bool $ignoreFilters Ignore filters when looking for files in the package + * @throws \InvalidArgumentException + * @throws \RuntimeException + * @return string The path of the created archive + */ + public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = false): string + { + if (empty($format)) { + throw new \InvalidArgumentException('Format must be specified'); + } + + // Search for the most appropriate archiver + $usableArchiver = null; + foreach ($this->archivers as $archiver) { + if ($archiver->supports($format, $package->getSourceType())) { + $usableArchiver = $archiver; + break; + } + } + + // Checks the format/source type are supported before downloading the package + if (null === $usableArchiver) { + throw new \RuntimeException(sprintf('No archiver found to support %s format', $format)); + } + + $filesystem = new Filesystem(); + + if ($package instanceof RootPackageInterface) { + $sourcePath = realpath('.'); + } else { + // Directory used to download the sources + $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)); + $filesystem->ensureDirectoryExists($sourcePath); + + try { + // Download sources + $promise = $this->downloadManager->download($package, $sourcePath); + SyncHelper::await($this->loop, $promise); + $promise = $this->downloadManager->install($package, $sourcePath); + SyncHelper::await($this->loop, $promise); + } catch (\Exception $e) { + $filesystem->removeDirectory($sourcePath); + throw $e; + } + + // Check exclude from downloaded composer.json + if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { + $jsonFile = new JsonFile($composerJsonPath); + $jsonData = $jsonFile->read(); + if (!empty($jsonData['archive']['name'])) { + $package->setArchiveName($jsonData['archive']['name']); + } + if (!empty($jsonData['archive']['exclude'])) { + $package->setArchiveExcludes($jsonData['archive']['exclude']); + } + } + } + + $supportedFormats = $this->getSupportedFormats(); + $packageNameParts = null === $fileName ? + $this->getPackageFilenameParts($package) + : ['base' => $fileName]; + + $packageName = $this->getPackageFilenameFromParts($packageNameParts); + $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats); + + // Archive filename + $filesystem->ensureDirectoryExists($targetDir); + $target = realpath($targetDir).'/'.$packageName.'.'.$format; + $filesystem->ensureDirectoryExists(dirname($target)); + + if (!$this->overwriteFiles && file_exists($target)) { + return $target; + } + + // Create the archive + $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format; + $filesystem->ensureDirectoryExists(dirname($tempTarget)); + + $archivePath = $usableArchiver->archive( + $sourcePath, + $tempTarget, + $format, + array_merge($excludePatterns, $package->getArchiveExcludes()), + $ignoreFilters + ); + $filesystem->rename($archivePath, $target); + + // cleanup temporary download + if (!$package instanceof RootPackageInterface) { + $filesystem->removeDirectory($sourcePath); + } + $filesystem->remove($tempTarget); + + return $target; + } + + /** + * @param string[] $parts + * @param string[] $formats + * + * @return string[] + */ + private function buildExcludePatterns(array $parts, array $formats): array + { + $base = $parts['base']; + if (count($parts) > 1) { + $base .= '-*'; + } + + $patterns = []; + foreach ($formats as $format) { + $patterns[] = "$base.$format"; + } + + return $patterns; + } + + /** + * @return string[] + */ + private function getSupportedFormats(): array + { + // The problem is that the \Composer\Package\Archiver\ArchiverInterface + // doesn't provide method to get the supported formats. + // Supported formats are also hard-coded into the description of the + // --format option. + // See \Composer\Command\ArchiveCommand::configure(). + $formats = []; + foreach ($this->archivers as $archiver) { + $items = []; + switch (get_class($archiver)) { + case ZipArchiver::class: + $items = ['zip']; + break; + + case PharArchiver::class: + $items = ['zip', 'tar', 'tar.gz', 'tar.bz2']; + break; + } + + $formats = array_merge($formats, $items); + } + + return array_unique($formats); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php new file mode 100644 index 0000000..7ebc792 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php @@ -0,0 +1,44 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +/** + * @author Till Klampaeckel + * @author Matthieu Moquet + * @author Nils Adermann + */ +interface ArchiverInterface +{ + /** + * Create an archive from the sources. + * + * @param string $sources The sources directory + * @param string $target The target file + * @param string $format The format used for archive + * @param string[] $excludes A list of patterns for files to exclude + * @param bool $ignoreFilters Whether to ignore filters when looking for files + * + * @return string The path to the written archive file + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string; + + /** + * Format supported by the archiver. + * + * @param string $format The archive format + * @param ?string $sourceType The source type (git, svn, hg, etc.) + * + * @return bool true if the format is supported by the archiver + */ + public function supports(string $format, ?string $sourceType): bool; +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php new file mode 100644 index 0000000..e2af2b2 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php @@ -0,0 +1,152 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; +use Symfony\Component\Finder; + +/** + * @author Nils Adermann + */ +abstract class BaseExcludeFilter +{ + /** + * @var string + */ + protected $sourcePath; + + /** + * @var array array of [$pattern, $negate, $stripLeadingSlash] arrays + */ + protected $excludePatterns; + + /** + * @param string $sourcePath Directory containing sources to be filtered + */ + public function __construct(string $sourcePath) + { + $this->sourcePath = $sourcePath; + $this->excludePatterns = []; + } + + /** + * Checks the given path against all exclude patterns in this filter + * + * Negated patterns overwrite exclude decisions of previous filters. + * + * @param string $relativePath The file's path relative to the sourcePath + * @param bool $exclude Whether a previous filter wants to exclude this file + * + * @return bool Whether the file should be excluded + */ + public function filter(string $relativePath, bool $exclude): bool + { + foreach ($this->excludePatterns as $patternData) { + [$pattern, $negate, $stripLeadingSlash] = $patternData; + + if ($stripLeadingSlash) { + $path = substr($relativePath, 1); + } else { + $path = $relativePath; + } + + try { + if (Preg::isMatch($pattern, $path)) { + $exclude = !$negate; + } + } catch (\RuntimeException $e) { + // suppressed + } + } + + return $exclude; + } + + /** + * Processes a file containing exclude rules of different formats per line + * + * @param string[] $lines A set of lines to be parsed + * @param callable $lineParser The parser to be used on each line + * + * @return array Exclude patterns to be used in filter() + */ + protected function parseLines(array $lines, callable $lineParser): array + { + return array_filter( + array_map( + static function ($line) use ($lineParser) { + $line = trim($line); + + if (!$line || 0 === strpos($line, '#')) { + return null; + } + + return $lineParser($line); + }, + $lines + ), + static function ($pattern): bool { + return $pattern !== null; + } + ); + } + + /** + * Generates a set of exclude patterns for filter() from gitignore rules + * + * @param string[] $rules A list of exclude rules in gitignore syntax + * + * @return array Exclude patterns + */ + protected function generatePatterns(array $rules): array + { + $patterns = []; + foreach ($rules as $rule) { + $patterns[] = $this->generatePattern($rule); + } + + return $patterns; + } + + /** + * Generates an exclude pattern for filter() from a gitignore rule + * + * @param string $rule An exclude rule in gitignore syntax + * + * @return array{0: non-empty-string, 1: bool, 2: bool} An exclude pattern + */ + protected function generatePattern(string $rule): array + { + $negate = false; + $pattern = ''; + + if ($rule !== '' && $rule[0] === '!') { + $negate = true; + $rule = ltrim($rule, '!'); + } + + $firstSlashPosition = strpos($rule, '/'); + if (0 === $firstSlashPosition) { + $pattern = '^/'; + } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) { + $pattern = '/'; + } + + $rule = trim($rule, '/'); + + // remove delimiters as well as caret (^) and dollar sign ($) from the regex + $rule = substr(Finder\Glob::toRegex($rule), 2, -2); + + return ['{'.$pattern.$rule.'(?=$|/)}', $negate, false]; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php new file mode 100644 index 0000000..9806b77 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php @@ -0,0 +1,31 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +/** + * An exclude filter which processes composer's own exclude rules + * + * @author Nils Adermann + */ +class ComposerExcludeFilter extends BaseExcludeFilter +{ + /** + * @param string $sourcePath Directory containing sources to be filtered + * @param string[] $excludeRules An array of exclude rules from composer.json + */ + public function __construct(string $sourcePath, array $excludeRules) + { + parent::__construct($sourcePath); + $this->excludePatterns = $this->generatePatterns($excludeRules); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php b/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php new file mode 100644 index 0000000..917f9fc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Pcre\Preg; + +/** + * An exclude filter that processes gitattributes + * + * It respects export-ignore git attributes + * + * @author Nils Adermann + */ +class GitExcludeFilter extends BaseExcludeFilter +{ + /** + * Parses .gitattributes if it exists + */ + public function __construct(string $sourcePath) + { + parent::__construct($sourcePath); + + if (file_exists($sourcePath.'/.gitattributes')) { + $this->excludePatterns = array_merge( + $this->excludePatterns, + $this->parseLines( + file($sourcePath.'/.gitattributes'), + [$this, 'parseGitAttributesLine'] + ) + ); + } + } + + /** + * Callback parser which finds export-ignore rules in git attribute lines + * + * @param string $line A line from .gitattributes + * + * @return array{0: string, 1: bool, 2: bool}|null An exclude pattern for filter() + */ + public function parseGitAttributesLine(string $line): ?array + { + $parts = Preg::split('#\s+#', $line); + + if (count($parts) === 2 && $parts[1] === 'export-ignore') { + return $this->generatePattern($parts[0]); + } + + if (count($parts) === 2 && $parts[1] === '-export-ignore') { + return $this->generatePattern('!'.$parts[0]); + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php b/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php new file mode 100644 index 0000000..6a64480 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php @@ -0,0 +1,142 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use PharData; + +/** + * @author Till Klampaeckel + * @author Nils Adermann + * @author Matthieu Moquet + */ +class PharArchiver implements ArchiverInterface +{ + /** @var array */ + protected static $formats = [ + 'zip' => \Phar::ZIP, + 'tar' => \Phar::TAR, + 'tar.gz' => \Phar::TAR, + 'tar.bz2' => \Phar::TAR, + ]; + + /** @var array */ + protected static $compressFormats = [ + 'tar.gz' => \Phar::GZ, + 'tar.bz2' => \Phar::BZ2, + ]; + + /** + * @inheritDoc + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string + { + $sources = realpath($sources); + + // Phar would otherwise load the file which we don't want + if (file_exists($target)) { + unlink($target); + } + + try { + $filename = substr($target, 0, strrpos($target, $format) - 1); + + // Check if compress format + if (isset(static::$compressFormats[$format])) { + // Current compress format supported base on tar + $target = $filename . '.tar'; + } + + $phar = new \PharData( + $target, + \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO, + '', + static::$formats[$format] + ); + $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); + $filesOnly = new ArchivableFilesFilter($files); + $phar->buildFromIterator($filesOnly, $sources); + $filesOnly->addEmptyDir($phar, $sources); + + if (!file_exists($target)) { + $target = $filename . '.' . $format; + unset($phar); + + if ($format === 'tar') { + // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk + file_put_contents($target, str_repeat("\0", 10240)); + } elseif ($format === 'zip') { + // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record) + $eocd = pack( + 'VvvvvVVv', + 0x06054b50, // End of central directory signature + 0, // Number of this disk + 0, // Disk where central directory starts + 0, // Number of central directory records on this disk + 0, // Total number of central directory records + 0, // Size of central directory (bytes) + 0, // Offset of start of central directory + 0 // Comment length + ); + + file_put_contents($target, $eocd); + } elseif ($format === 'tar.gz' || $format === 'tar.bz2') { + if (!PharData::canCompress(static::$compressFormats[$format])) { + throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); + } + if ($format === 'tar.gz' && function_exists('gzcompress')) { + file_put_contents($target, gzcompress(str_repeat("\0", 10240))); + } elseif ($format === 'tar.bz2' && function_exists('bzcompress')) { + file_put_contents($target, bzcompress(str_repeat("\0", 10240))); + } + } + + return $target; + } + + if (isset(static::$compressFormats[$format])) { + // Check can be compressed? + if (!PharData::canCompress(static::$compressFormats[$format])) { + throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); + } + + // Delete old tar + unlink($target); + + // Compress the new tar + $phar->compress(static::$compressFormats[$format]); + + // Make the correct filename + $target = $filename . '.' . $format; + } + + return $target; + } catch (\UnexpectedValueException $e) { + $message = sprintf( + "Could not create archive '%s' from '%s': %s", + $target, + $sources, + $e->getMessage() + ); + + throw new \RuntimeException($message, $e->getCode(), $e); + } + } + + /** + * @inheritDoc + */ + public function supports(string $format, ?string $sourceType): bool + { + return isset(static::$formats[$format]); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php b/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php new file mode 100644 index 0000000..bc6829a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php @@ -0,0 +1,114 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Archiver; + +use Composer\Util\Filesystem; +use Composer\Util\Platform; +use ZipArchive; + +/** + * @author Jan Prieser + */ +class ZipArchiver implements ArchiverInterface +{ + /** @var array */ + protected static $formats = [ + 'zip' => true, + ]; + + /** + * @inheritDoc + */ + public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string + { + $fs = new Filesystem(); + $sourcesRealpath = realpath($sources); + if (false !== $sourcesRealpath) { + $sources = $sourcesRealpath; + } + unset($sourcesRealpath); + $sources = $fs->normalizePath($sources); + + $zip = new ZipArchive(); + $res = $zip->open($target, ZipArchive::CREATE); + if ($res === true) { + $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); + foreach ($files as $file) { + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + $filepath = $file->getPathname(); + $relativePath = $file->getRelativePathname(); + + if (Platform::isWindows()) { + $relativePath = strtr($relativePath, '\\', '/'); + } + + if ($file->isDir()) { + $zip->addEmptyDir($relativePath); + } else { + $zip->addFile($filepath, $relativePath); + } + + /** + * setExternalAttributesName() is only available with libzip 0.11.2 or above + */ + if (method_exists($zip, 'setExternalAttributesName')) { + $perms = fileperms($filepath); + + /** + * Ensure to preserve the permission umasks for the filepath in the archive. + */ + $zip->setExternalAttributesName($relativePath, ZipArchive::OPSYS_UNIX, $perms << 16); + } + } + if ($zip->close()) { + if (!file_exists($target)) { + // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record) + $eocd = pack( + 'VvvvvVVv', + 0x06054b50, // End of central directory signature + 0, // Number of this disk + 0, // Disk where central directory starts + 0, // Number of central directory records on this disk + 0, // Total number of central directory records + 0, // Size of central directory (bytes) + 0, // Offset of start of central directory + 0 // Comment length + ); + file_put_contents($target, $eocd); + } + + return $target; + } + } + $message = sprintf( + "Could not create archive '%s' from '%s': %s", + $target, + $sources, + $zip->getStatusString() + ); + throw new \RuntimeException($message); + } + + /** + * @inheritDoc + */ + public function supports(string $format, ?string $sourceType): bool + { + return isset(static::$formats[$format]) && $this->compressionAvailable(); + } + + private function compressionAvailable(): bool + { + return class_exists('ZipArchive'); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/BasePackage.php b/vendor/composer/composer/src/Composer/Package/BasePackage.php new file mode 100644 index 0000000..764fdb4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/BasePackage.php @@ -0,0 +1,284 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Repository\RepositoryInterface; +use Composer\Repository\PlatformRepository; + +/** + * Base class for packages providing name storage and default match implementation + * + * @author Nils Adermann + */ +abstract class BasePackage implements PackageInterface +{ + /** + * @phpstan-var array + * @internal + */ + public static $supportedLinkTypes = [ + 'require' => ['description' => 'requires', 'method' => Link::TYPE_REQUIRE], + 'conflict' => ['description' => 'conflicts', 'method' => Link::TYPE_CONFLICT], + 'provide' => ['description' => 'provides', 'method' => Link::TYPE_PROVIDE], + 'replace' => ['description' => 'replaces', 'method' => Link::TYPE_REPLACE], + 'require-dev' => ['description' => 'requires (for development)', 'method' => Link::TYPE_DEV_REQUIRE], + ]; + + public const STABILITY_STABLE = 0; + public const STABILITY_RC = 5; + public const STABILITY_BETA = 10; + public const STABILITY_ALPHA = 15; + public const STABILITY_DEV = 20; + + public const STABILITIES = [ + 'stable' => self::STABILITY_STABLE, + 'RC' => self::STABILITY_RC, + 'beta' => self::STABILITY_BETA, + 'alpha' => self::STABILITY_ALPHA, + 'dev' => self::STABILITY_DEV, + ]; + + /** + * @deprecated + * @readonly + * @var array, self::STABILITY_*> + * @phpstan-ignore property.readOnlyByPhpDocDefaultValue + */ + public static $stabilities = self::STABILITIES; + + /** + * READ-ONLY: The package id, public for fast access in dependency solver + * @var int + * @internal + */ + public $id; + /** @var string */ + protected $name; + /** @var string */ + protected $prettyName; + /** @var ?RepositoryInterface */ + protected $repository = null; + + /** + * All descendants' constructors should call this parent constructor + * + * @param string $name The package's name + */ + public function __construct(string $name) + { + $this->prettyName = $name; + $this->name = strtolower($name); + $this->id = -1; + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return $this->name; + } + + /** + * @inheritDoc + */ + public function getPrettyName(): string + { + return $this->prettyName; + } + + /** + * @inheritDoc + */ + public function getNames($provides = true): array + { + $names = [ + $this->getName() => true, + ]; + + if ($provides) { + foreach ($this->getProvides() as $link) { + $names[$link->getTarget()] = true; + } + } + + foreach ($this->getReplaces() as $link) { + $names[$link->getTarget()] = true; + } + + return array_keys($names); + } + + /** + * @inheritDoc + */ + public function setId(int $id): void + { + $this->id = $id; + } + + /** + * @inheritDoc + */ + public function getId(): int + { + return $this->id; + } + + /** + * @inheritDoc + */ + public function setRepository(RepositoryInterface $repository): void + { + if ($this->repository && $repository !== $this->repository) { + throw new \LogicException(sprintf( + 'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".', + $this->getPrettyName(), + $repository->getRepoName(), + $this->repository->getRepoName() + )); + } + $this->repository = $repository; + } + + /** + * @inheritDoc + */ + public function getRepository(): ?RepositoryInterface + { + return $this->repository; + } + + /** + * checks if this package is a platform package + */ + public function isPlatform(): bool + { + return $this->getRepository() instanceof PlatformRepository; + } + + /** + * Returns package unique name, constructed from name, version and release type. + */ + public function getUniqueName(): string + { + return $this->getName().'-'.$this->getVersion(); + } + + public function equals(PackageInterface $package): bool + { + $self = $this; + if ($this instanceof AliasPackage) { + $self = $this->getAliasOf(); + } + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + return $package === $self; + } + + /** + * Converts the package into a readable and unique string + */ + public function __toString(): string + { + return $this->getUniqueName(); + } + + public function getPrettyString(): string + { + return $this->getPrettyName().' '.$this->getPrettyVersion(); + } + + /** + * @inheritDoc + */ + public function getFullPrettyVersion(bool $truncate = true, int $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV): string + { + if ($displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV && + (!$this->isDev() || !\in_array($this->getSourceType(), ['hg', 'git'])) + ) { + return $this->getPrettyVersion(); + } + + switch ($displayMode) { + case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV: + case PackageInterface::DISPLAY_SOURCE_REF: + $reference = $this->getSourceReference(); + break; + case PackageInterface::DISPLAY_DIST_REF: + $reference = $this->getDistReference(); + break; + default: + throw new \UnexpectedValueException('Display mode '.$displayMode.' is not supported'); + } + + if (null === $reference) { + return $this->getPrettyVersion(); + } + + // if source reference is a sha1 hash -- truncate + if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') { + return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7); + } + + return $this->getPrettyVersion() . ' ' . $reference; + } + + /** + * @phpstan-return self::STABILITY_* + */ + public function getStabilityPriority(): int + { + return self::STABILITIES[$this->getStability()]; + } + + public function __clone() + { + $this->repository = null; + $this->id = -1; + } + + /** + * Build a regexp from a package name, expanding * globs as required + * + * @param non-empty-string $wrap Wrap the cleaned string by the given string + * @return non-empty-string + */ + public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string + { + $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern)); + + return sprintf($wrap, $cleanedAllowPattern); + } + + /** + * Build a regexp from package names, expanding * globs as required + * + * @param string[] $packageNames + * @param non-empty-string $wrap + * @return non-empty-string + */ + public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD'): string + { + $packageNames = array_map( + static function ($packageName): string { + return BasePackage::packageNameToRegexp($packageName, '%s'); + }, + $packageNames + ); + + return sprintf($wrap, implode('|', $packageNames)); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php b/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php new file mode 100644 index 0000000..70a7a28 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php @@ -0,0 +1,152 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Comparer; + +use Composer\Util\Platform; + +/** + * class Comparer + * + * @author Hector Prats + */ +class Comparer +{ + /** @var string Source directory */ + private $source; + /** @var string Target directory */ + private $update; + /** @var array{changed?: string[], removed?: string[], added?: string[]} */ + private $changed; + + public function setSource(string $source): void + { + $this->source = $source; + } + + public function setUpdate(string $update): void + { + $this->update = $update; + } + + /** + * @return array{changed?: string[], removed?: string[], added?: string[]}|false false if no change + */ + public function getChanged(bool $explicated = false) + { + $changed = $this->changed; + if (!count($changed)) { + return false; + } + if ($explicated) { + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')'; + } + } + } + + return $changed; + } + + /** + * @return string empty string if no changes + */ + public function getChangedAsString(bool $toString = false, bool $explicated = false): string + { + $changed = $this->getChanged($explicated); + if (false === $changed) { + return ''; + } + + $strings = []; + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $strings[] = $item."\r\n"; + } + } + + return trim(implode("\r\n", $strings)); + } + + public function doCompare(): void + { + $source = []; + $destination = []; + $this->changed = []; + $currentDirectory = Platform::getCwd(); + chdir($this->source); + $source = $this->doTree('.', $source); + if (!is_array($source)) { + return; + } + chdir($currentDirectory); + chdir($this->update); + $destination = $this->doTree('.', $destination); + if (!is_array($destination)) { + exit; + } + chdir($currentDirectory); + foreach ($source as $dir => $value) { + foreach ($value as $file => $hash) { + if (isset($destination[$dir][$file])) { + if ($hash !== $destination[$dir][$file]) { + $this->changed['changed'][] = $dir.'/'.$file; + } + } else { + $this->changed['removed'][] = $dir.'/'.$file; + } + } + } + foreach ($destination as $dir => $value) { + foreach ($value as $file => $hash) { + if (!isset($source[$dir][$file])) { + $this->changed['added'][] = $dir.'/'.$file; + } + } + } + } + + /** + * @param mixed[] $array + * + * @return array>|false + */ + private function doTree(string $dir, array &$array) + { + if ($dh = opendir($dir)) { + while ($file = readdir($dh)) { + if ($file !== '.' && $file !== '..') { + if (is_link($dir.'/'.$file)) { + $array[$dir][$file] = readlink($dir.'/'.$file); + } elseif (is_dir($dir.'/'.$file)) { + if (!count($array)) { + $array[0] = 'Temp'; + } + if (!$this->doTree($dir.'/'.$file, $array)) { + return false; + } + } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) { + $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file); + } + } + } + if (count($array) > 1 && isset($array['0'])) { + unset($array['0']); + } + + return $array; + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php b/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php new file mode 100644 index 0000000..78106fa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.php @@ -0,0 +1,167 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * @author Jordi Boggiano + */ +class CompleteAliasPackage extends AliasPackage implements CompletePackageInterface +{ + /** @var CompletePackage */ + protected $aliasOf; + + /** + * All descendants' constructors should call this parent constructor + * + * @param CompletePackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(CompletePackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf, $version, $prettyVersion); + } + + /** + * @return CompletePackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + public function getScripts(): array + { + return $this->aliasOf->getScripts(); + } + + public function setScripts(array $scripts): void + { + $this->aliasOf->setScripts($scripts); + } + + public function getRepositories(): array + { + return $this->aliasOf->getRepositories(); + } + + public function setRepositories(array $repositories): void + { + $this->aliasOf->setRepositories($repositories); + } + + public function getLicense(): array + { + return $this->aliasOf->getLicense(); + } + + public function setLicense(array $license): void + { + $this->aliasOf->setLicense($license); + } + + public function getKeywords(): array + { + return $this->aliasOf->getKeywords(); + } + + public function setKeywords(array $keywords): void + { + $this->aliasOf->setKeywords($keywords); + } + + public function getDescription(): ?string + { + return $this->aliasOf->getDescription(); + } + + public function setDescription(?string $description): void + { + $this->aliasOf->setDescription($description); + } + + public function getHomepage(): ?string + { + return $this->aliasOf->getHomepage(); + } + + public function setHomepage(?string $homepage): void + { + $this->aliasOf->setHomepage($homepage); + } + + public function getAuthors(): array + { + return $this->aliasOf->getAuthors(); + } + + public function setAuthors(array $authors): void + { + $this->aliasOf->setAuthors($authors); + } + + public function getSupport(): array + { + return $this->aliasOf->getSupport(); + } + + public function setSupport(array $support): void + { + $this->aliasOf->setSupport($support); + } + + public function getFunding(): array + { + return $this->aliasOf->getFunding(); + } + + public function setFunding(array $funding): void + { + $this->aliasOf->setFunding($funding); + } + + public function isAbandoned(): bool + { + return $this->aliasOf->isAbandoned(); + } + + public function getReplacementPackage(): ?string + { + return $this->aliasOf->getReplacementPackage(); + } + + public function setAbandoned($abandoned): void + { + $this->aliasOf->setAbandoned($abandoned); + } + + public function getArchiveName(): ?string + { + return $this->aliasOf->getArchiveName(); + } + + public function setArchiveName(?string $name): void + { + $this->aliasOf->setArchiveName($name); + } + + public function getArchiveExcludes(): array + { + return $this->aliasOf->getArchiveExcludes(); + } + + public function setArchiveExcludes(array $excludes): void + { + $this->aliasOf->setArchiveExcludes($excludes); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompletePackage.php b/vendor/composer/composer/src/Composer/Package/CompletePackage.php new file mode 100644 index 0000000..0d87082 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompletePackage.php @@ -0,0 +1,246 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Package containing additional metadata that is not used by the solver + * + * @author Nils Adermann + */ +class CompletePackage extends Package implements CompletePackageInterface +{ + /** @var mixed[] */ + protected $repositories = []; + /** @var string[] */ + protected $license = []; + /** @var string[] */ + protected $keywords = []; + /** @var array */ + protected $authors = []; + /** @var ?string */ + protected $description = null; + /** @var ?string */ + protected $homepage = null; + /** @var array Map of script name to array of handlers */ + protected $scripts = []; + /** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */ + protected $support = []; + /** @var array */ + protected $funding = []; + /** @var bool|string */ + protected $abandoned = false; + /** @var ?string */ + protected $archiveName = null; + /** @var string[] */ + protected $archiveExcludes = []; + + /** + * @inheritDoc + */ + public function setScripts(array $scripts): void + { + $this->scripts = $scripts; + } + + /** + * @inheritDoc + */ + public function getScripts(): array + { + return $this->scripts; + } + + /** + * @inheritDoc + */ + public function setRepositories(array $repositories): void + { + $this->repositories = $repositories; + } + + /** + * @inheritDoc + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * @inheritDoc + */ + public function setLicense(array $license): void + { + $this->license = $license; + } + + /** + * @inheritDoc + */ + public function getLicense(): array + { + return $this->license; + } + + /** + * @inheritDoc + */ + public function setKeywords(array $keywords): void + { + $this->keywords = $keywords; + } + + /** + * @inheritDoc + */ + public function getKeywords(): array + { + return $this->keywords; + } + + /** + * @inheritDoc + */ + public function setAuthors(array $authors): void + { + $this->authors = $authors; + } + + /** + * @inheritDoc + */ + public function getAuthors(): array + { + return $this->authors; + } + + /** + * @inheritDoc + */ + public function setDescription(?string $description): void + { + $this->description = $description; + } + + /** + * @inheritDoc + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @inheritDoc + */ + public function setHomepage(?string $homepage): void + { + $this->homepage = $homepage; + } + + /** + * @inheritDoc + */ + public function getHomepage(): ?string + { + return $this->homepage; + } + + /** + * @inheritDoc + */ + public function setSupport(array $support): void + { + $this->support = $support; + } + + /** + * @inheritDoc + */ + public function getSupport(): array + { + return $this->support; + } + + /** + * @inheritDoc + */ + public function setFunding(array $funding): void + { + $this->funding = $funding; + } + + /** + * @inheritDoc + */ + public function getFunding(): array + { + return $this->funding; + } + + /** + * @inheritDoc + */ + public function isAbandoned(): bool + { + return (bool) $this->abandoned; + } + + /** + * @inheritDoc + */ + public function setAbandoned($abandoned): void + { + $this->abandoned = $abandoned; + } + + /** + * @inheritDoc + */ + public function getReplacementPackage(): ?string + { + return \is_string($this->abandoned) ? $this->abandoned : null; + } + + /** + * @inheritDoc + */ + public function setArchiveName(?string $name): void + { + $this->archiveName = $name; + } + + /** + * @inheritDoc + */ + public function getArchiveName(): ?string + { + return $this->archiveName; + } + + /** + * @inheritDoc + */ + public function setArchiveExcludes(array $excludes): void + { + $this->archiveExcludes = $excludes; + } + + /** + * @inheritDoc + */ + public function getArchiveExcludes(): array + { + return $this->archiveExcludes; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php b/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php new file mode 100644 index 0000000..e4db57d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Defines package metadata that is not necessarily needed for solving and installing packages + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Nils Adermann + */ +interface CompletePackageInterface extends PackageInterface +{ + /** + * Returns the scripts of this package + * + * @return array Map of script name to array of handlers + */ + public function getScripts(): array; + + /** + * @param array $scripts + */ + public function setScripts(array $scripts): void; + + /** + * Returns an array of repositories + * + * @return mixed[] Repositories + */ + public function getRepositories(): array; + + /** + * Set the repositories + * + * @param mixed[] $repositories + */ + public function setRepositories(array $repositories): void; + + /** + * Returns the package license, e.g. MIT, BSD, GPL + * + * @return string[] The package licenses + */ + public function getLicense(): array; + + /** + * Set the license + * + * @param string[] $license + */ + public function setLicense(array $license): void; + + /** + * Returns an array of keywords relating to the package + * + * @return string[] + */ + public function getKeywords(): array; + + /** + * Set the keywords + * + * @param string[] $keywords + */ + public function setKeywords(array $keywords): void; + + /** + * Returns the package description + * + * @return ?string + */ + public function getDescription(): ?string; + + /** + * Set the description + */ + public function setDescription(string $description): void; + + /** + * Returns the package homepage + * + * @return ?string + */ + public function getHomepage(): ?string; + + /** + * Set the homepage + */ + public function setHomepage(string $homepage): void; + + /** + * Returns an array of authors of the package + * + * Each item can contain name/homepage/email keys + * + * @return array + */ + public function getAuthors(): array; + + /** + * Set the authors + * + * @param array $authors + */ + public function setAuthors(array $authors): void; + + /** + * Returns the support information + * + * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} + */ + public function getSupport(): array; + + /** + * Set the support information + * + * @param array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} $support + */ + public function setSupport(array $support): void; + + /** + * Returns an array of funding options for the package + * + * Each item will contain type and url keys + * + * @return array + */ + public function getFunding(): array; + + /** + * Set the funding + * + * @param array $funding + */ + public function setFunding(array $funding): void; + + /** + * Returns if the package is abandoned or not + */ + public function isAbandoned(): bool; + + /** + * If the package is abandoned and has a suggested replacement, this method returns it + */ + public function getReplacementPackage(): ?string; + + /** + * @param bool|string $abandoned + */ + public function setAbandoned($abandoned): void; + + /** + * Returns default base filename for archive + * + * @return ?string + */ + public function getArchiveName(): ?string; + + /** + * Sets default base filename for archive + */ + public function setArchiveName(string $name): void; + + /** + * Returns a list of patterns to exclude from package archives + * + * @return string[] + */ + public function getArchiveExcludes(): array; + + /** + * Sets a list of patterns to be excluded from archives + * + * @param string[] $excludes + */ + public function setArchiveExcludes(array $excludes): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php b/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php new file mode 100644 index 0000000..9333bd9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php @@ -0,0 +1,172 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Dumper; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\RootPackageInterface; + +/** + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class ArrayDumper +{ + /** + * @return array + */ + public function dump(PackageInterface $package): array + { + $keys = [ + 'binaries' => 'bin', + 'type', + 'extra', + 'installationSource' => 'installation-source', + 'autoload', + 'devAutoload' => 'autoload-dev', + 'notificationUrl' => 'notification-url', + 'includePaths' => 'include-path', + 'phpExt' => 'php-ext', + ]; + + $data = []; + $data['name'] = $package->getPrettyName(); + $data['version'] = $package->getPrettyVersion(); + $data['version_normalized'] = $package->getVersion(); + + if ($package->getTargetDir() !== null) { + $data['target-dir'] = $package->getTargetDir(); + } + + if ($package->getSourceType() !== null) { + $data['source']['type'] = $package->getSourceType(); + $data['source']['url'] = $package->getSourceUrl(); + if (null !== ($value = $package->getSourceReference())) { + $data['source']['reference'] = $value; + } + if ($mirrors = $package->getSourceMirrors()) { + $data['source']['mirrors'] = $mirrors; + } + } + + if ($package->getDistType() !== null) { + $data['dist']['type'] = $package->getDistType(); + $data['dist']['url'] = $package->getDistUrl(); + if (null !== ($value = $package->getDistReference())) { + $data['dist']['reference'] = $value; + } + if (null !== ($value = $package->getDistSha1Checksum())) { + $data['dist']['shasum'] = $value; + } + if ($mirrors = $package->getDistMirrors()) { + $data['dist']['mirrors'] = $mirrors; + } + } + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + $links = $package->{'get'.ucfirst($opts['method'])}(); + if (\count($links) === 0) { + continue; + } + foreach ($links as $link) { + $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); + } + ksort($data[$type]); + } + + $packages = $package->getSuggests(); + if (\count($packages) > 0) { + ksort($packages); + $data['suggest'] = $packages; + } + + if ($package->getReleaseDate() instanceof \DateTimeInterface) { + $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339); + } + + if ($package->isDefaultBranch()) { + $data['default-branch'] = true; + } + + $data = $this->dumpValues($package, $keys, $data); + + if ($package instanceof CompletePackageInterface) { + if ($package->getArchiveName()) { + $data['archive']['name'] = $package->getArchiveName(); + } + if ($package->getArchiveExcludes()) { + $data['archive']['exclude'] = $package->getArchiveExcludes(); + } + + $keys = [ + 'scripts', + 'license', + 'authors', + 'description', + 'homepage', + 'keywords', + 'repositories', + 'support', + 'funding', + ]; + + $data = $this->dumpValues($package, $keys, $data); + + if (isset($data['keywords']) && \is_array($data['keywords'])) { + sort($data['keywords']); + } + + if ($package->isAbandoned()) { + $data['abandoned'] = $package->getReplacementPackage() ?: true; + } + } + + if ($package instanceof RootPackageInterface) { + $minimumStability = $package->getMinimumStability(); + if ($minimumStability !== '') { + $data['minimum-stability'] = $minimumStability; + } + } + + if (\count($package->getTransportOptions()) > 0) { + $data['transport-options'] = $package->getTransportOptions(); + } + + return $data; + } + + /** + * @param array $keys + * @param array $data + * + * @return array + */ + private function dumpValues(PackageInterface $package, array $keys, array $data): array + { + foreach ($keys as $method => $key) { + if (is_numeric($method)) { + $method = $key; + } + + $getter = 'get'.ucfirst($method); + $value = $package->{$getter}(); + + if (null !== $value && !(\is_array($value) && 0 === \count($value))) { + $data[$key] = $value; + } + } + + return $data; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Link.php b/vendor/composer/composer/src/Composer/Package/Link.php new file mode 100644 index 0000000..7b19f83 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Link.php @@ -0,0 +1,140 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Represents a link between two packages, represented by their names + * + * @author Nils Adermann + */ +class Link +{ + public const TYPE_REQUIRE = 'requires'; + public const TYPE_DEV_REQUIRE = 'devRequires'; + public const TYPE_PROVIDE = 'provides'; + public const TYPE_CONFLICT = 'conflicts'; + public const TYPE_REPLACE = 'replaces'; + + /** + * Special type + * @internal + */ + public const TYPE_DOES_NOT_REQUIRE = 'does not require'; + + private const TYPE_UNKNOWN = 'relates to'; + + /** + * Will be converted into a constant once the min PHP version allows this + * + * @internal + * @var string[] + * @phpstan-var array + */ + public static $TYPES = [ + self::TYPE_REQUIRE, + self::TYPE_DEV_REQUIRE, + self::TYPE_PROVIDE, + self::TYPE_CONFLICT, + self::TYPE_REPLACE, + ]; + + /** + * @var string + */ + protected $source; + + /** + * @var string + */ + protected $target; + + /** + * @var ConstraintInterface + */ + protected $constraint; + + /** + * @var string + * @phpstan-var string $description + */ + protected $description; + + /** + * @var ?string + */ + protected $prettyConstraint; + + /** + * Creates a new package link. + * + * @param ConstraintInterface $constraint Constraint applying to the target of this link + * @param self::TYPE_* $description Used to create a descriptive string representation + */ + public function __construct( + string $source, + string $target, + ConstraintInterface $constraint, + $description = self::TYPE_UNKNOWN, + ?string $prettyConstraint = null + ) { + $this->source = strtolower($source); + $this->target = strtolower($target); + $this->constraint = $constraint; + $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description; + $this->prettyConstraint = $prettyConstraint; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getSource(): string + { + return $this->source; + } + + public function getTarget(): string + { + return $this->target; + } + + public function getConstraint(): ConstraintInterface + { + return $this->constraint; + } + + /** + * @throws \UnexpectedValueException If no pretty constraint was provided + */ + public function getPrettyConstraint(): string + { + if (null === $this->prettyConstraint) { + throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this)); + } + + return $this->prettyConstraint; + } + + public function __toString(): string + { + return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')'; + } + + public function getPrettyString(PackageInterface $sourcePackage): string + { + return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString(); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php new file mode 100644 index 0000000..887f291 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php @@ -0,0 +1,469 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\RootPackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\RootAliasPackage; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; + +/** + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class ArrayLoader implements LoaderInterface +{ + /** @var VersionParser */ + protected $versionParser; + /** @var bool */ + protected $loadOptions; + + public function __construct(?VersionParser $parser = null, bool $loadOptions = false) + { + if (!$parser) { + $parser = new VersionParser; + } + $this->versionParser = $parser; + $this->loadOptions = $loadOptions; + } + + /** + * @inheritDoc + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage + { + if ($class !== 'Composer\Package\CompletePackage' && $class !== 'Composer\Package\RootPackage') { + trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); + } + + $package = $this->createObject($config, $class); + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + if (!isset($config[$type]) || !is_array($config[$type])) { + continue; + } + $method = 'set'.ucfirst($opts['method']); + $package->{$method}( + $this->parseLinks( + $package->getName(), + $package->getPrettyVersion(), + $opts['method'], + $config[$type] + ) + ); + } + + $package = $this->configureObject($package, $config); + + return $package; + } + + /** + * @param array> $versions + * + * @return list + */ + public function loadPackages(array $versions): array + { + $packages = []; + $linkCache = []; + + foreach ($versions as $version) { + $package = $this->createObject($version, 'Composer\Package\CompletePackage'); + + $this->configureCachedLinks($linkCache, $package, $version); + $package = $this->configureObject($package, $version); + + $packages[] = $package; + } + + return $packages; + } + + /** + * @template PackageClass of CompletePackage + * + * @param mixed[] $config package data + * @param string $class FQCN to be instantiated + * + * @return CompletePackage|RootPackage + * + * @phpstan-param class-string $class + */ + private function createObject(array $config, string $class): CompletePackage + { + if (!isset($config['name'])) { + throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').'); + } + if (!isset($config['version']) || !is_scalar($config['version'])) { + throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.'); + } + if (!is_string($config['version'])) { + $config['version'] = (string) $config['version']; + } + + // handle already normalized versions + if (isset($config['version_normalized']) && is_string($config['version_normalized'])) { + $version = $config['version_normalized']; + + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) { + $version = $this->versionParser->normalize($config['version']); + } + } else { + $version = $this->versionParser->normalize($config['version']); + } + + return new $class($config['name'], $version, $config['version']); + } + + /** + * @param CompletePackage $package + * @param mixed[] $config package data + * + * @return RootPackage|RootAliasPackage|CompletePackage|CompleteAliasPackage + */ + private function configureObject(PackageInterface $package, array $config): BasePackage + { + if (!$package instanceof CompletePackage) { + throw new \LogicException('ArrayLoader expects instances of the Composer\Package\CompletePackage class to function correctly'); + } + + $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library'); + + if (isset($config['target-dir'])) { + $package->setTargetDir($config['target-dir']); + } + + if (isset($config['extra']) && \is_array($config['extra'])) { + $package->setExtra($config['extra']); + } + + if (isset($config['bin'])) { + if (!\is_array($config['bin'])) { + $config['bin'] = [$config['bin']]; + } + foreach ($config['bin'] as $key => $bin) { + $config['bin'][$key] = ltrim($bin, '/'); + } + $package->setBinaries($config['bin']); + } + + if (isset($config['installation-source'])) { + $package->setInstallationSource($config['installation-source']); + } + + if (isset($config['default-branch']) && $config['default-branch'] === true) { + $package->setIsDefaultBranch(true); + } + + if (isset($config['source'])) { + if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) { + throw new \UnexpectedValueException(sprintf( + "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.", + $config['name'], + json_encode($config['source']) + )); + } + $package->setSourceType($config['source']['type']); + $package->setSourceUrl($config['source']['url']); + $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null); + if (isset($config['source']['mirrors'])) { + $package->setSourceMirrors($config['source']['mirrors']); + } + } + + if (isset($config['dist'])) { + if (!isset($config['dist']['type'], $config['dist']['url'])) { + throw new \UnexpectedValueException(sprintf( + "Package %s's dist key should be specified as ". + "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.", + $config['name'], + json_encode($config['dist']) + )); + } + $package->setDistType($config['dist']['type']); + $package->setDistUrl($config['dist']['url']); + $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null); + $package->setDistSha1Checksum($config['dist']['shasum'] ?? null); + if (isset($config['dist']['mirrors'])) { + $package->setDistMirrors($config['dist']['mirrors']); + } + } + + if (isset($config['suggest']) && \is_array($config['suggest'])) { + foreach ($config['suggest'] as $target => $reason) { + if ('self.version' === trim($reason)) { + $config['suggest'][$target] = $package->getPrettyVersion(); + } + } + $package->setSuggests($config['suggest']); + } + + if (isset($config['autoload'])) { + $package->setAutoload($config['autoload']); + } + + if (isset($config['autoload-dev'])) { + $package->setDevAutoload($config['autoload-dev']); + } + + if (isset($config['include-path'])) { + $package->setIncludePaths($config['include-path']); + } + + if (isset($config['php-ext'])) { + $package->setPhpExt($config['php-ext']); + } + + if (!empty($config['time'])) { + $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; + + try { + $date = new \DateTime($time, new \DateTimeZone('UTC')); + $package->setReleaseDate($date); + } catch (\Exception $e) { + } + } + + if (!empty($config['notification-url'])) { + $package->setNotificationUrl($config['notification-url']); + } + + if ($package instanceof CompletePackageInterface) { + if (!empty($config['archive']['name'])) { + $package->setArchiveName($config['archive']['name']); + } + if (!empty($config['archive']['exclude'])) { + $package->setArchiveExcludes($config['archive']['exclude']); + } + + if (isset($config['scripts']) && \is_array($config['scripts'])) { + foreach ($config['scripts'] as $event => $listeners) { + $config['scripts'][$event] = (array) $listeners; + } + foreach (['composer', 'php', 'putenv'] as $reserved) { + if (isset($config['scripts'][$reserved])) { + trigger_error('The `'.$reserved.'` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED); + } + } + $package->setScripts($config['scripts']); + } + + if (!empty($config['description']) && \is_string($config['description'])) { + $package->setDescription($config['description']); + } + + if (!empty($config['homepage']) && \is_string($config['homepage'])) { + $package->setHomepage($config['homepage']); + } + + if (!empty($config['keywords']) && \is_array($config['keywords'])) { + $package->setKeywords(array_map('strval', $config['keywords'])); + } + + if (!empty($config['license'])) { + $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]); + } + + if (!empty($config['authors']) && \is_array($config['authors'])) { + $package->setAuthors($config['authors']); + } + + if (isset($config['support']) && \is_array($config['support'])) { + $package->setSupport($config['support']); + } + + if (!empty($config['funding']) && \is_array($config['funding'])) { + $package->setFunding($config['funding']); + } + + if (isset($config['abandoned'])) { + $package->setAbandoned($config['abandoned']); + } + } + + if ($this->loadOptions && isset($config['transport-options'])) { + $package->setTransportOptions($config['transport-options']); + } + + if ($aliasNormalized = $this->getBranchAlias($config)) { + $prettyAlias = Preg::replace('{(\.9{7})+}', '.x', $aliasNormalized); + + if ($package instanceof RootPackage) { + return new RootAliasPackage($package, $aliasNormalized, $prettyAlias); + } + + return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias); + } + + return $package; + } + + /** + * @param array>>> $linkCache + * @param mixed[] $config + */ + private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void + { + $name = $package->getName(); + $prettyVersion = $package->getPrettyVersion(); + + foreach (BasePackage::$supportedLinkTypes as $type => $opts) { + if (isset($config[$type])) { + $method = 'set'.ucfirst($opts['method']); + + $links = []; + foreach ($config[$type] as $prettyTarget => $constraint) { + $target = strtolower($prettyTarget); + + // recursive links are not supported + if ($target === $name) { + continue; + } + + if ($constraint === 'self.version') { + $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint); + } else { + if (!isset($linkCache[$name][$type][$target][$constraint])) { + $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)]; + } + + [$target, $link] = $linkCache[$name][$type][$target][$constraint]; + $links[$target] = $link; + } + } + + $package->{$method}($links); + } + } + } + + /** + * @param string $source source package name + * @param string $sourceVersion source package version (pretty version ideally) + * @param string $description link description (e.g. requires, replaces, ..) + * @param array $links array of package name => constraint mappings + * + * @return Link[] + * + * @phpstan-param Link::TYPE_* $description + */ + public function parseLinks(string $source, string $sourceVersion, string $description, array $links): array + { + $res = []; + foreach ($links as $target => $constraint) { + if (!is_string($constraint)) { + continue; + } + $target = strtolower((string) $target); + $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint); + } + + return $res; + } + + /** + * @param string $source source package name + * @param string $sourceVersion source package version (pretty version ideally) + * @param Link::TYPE_* $description link description (e.g. requires, replaces, ..) + * @param string $target target package name + * @param string $prettyConstraint constraint string + */ + private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint): Link + { + if (!\is_string($prettyConstraint)) { + throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\gettype($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')'); + } + if ('self.version' === $prettyConstraint) { + $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); + } else { + $parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint); + } + + return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint); + } + + /** + * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists + * + * @param mixed[] $config the entire package config + * + * @return string|null normalized version of the branch alias or null if there is none + */ + public function getBranchAlias(array $config): ?string + { + if (!isset($config['version']) || !is_scalar($config['version'])) { + throw new \UnexpectedValueException('no/invalid version defined'); + } + if (!is_string($config['version'])) { + $config['version'] = (string) $config['version']; + } + + if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) { + return null; + } + + if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) { + foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { + $sourceBranch = (string) $sourceBranch; + + // ensure it is an alias to a -dev package + if ('-dev' !== substr($targetBranch, -4)) { + continue; + } + + // normalize without -dev and ensure it's a numeric branch that is parseable + if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) { + $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS; + } else { + $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); + } + if ('-dev' !== substr($validatedTargetBranch, -4)) { + continue; + } + + // ensure that it is the current branch aliasing itself + if (strtolower($config['version']) !== strtolower($sourceBranch)) { + continue; + } + + // If using numeric aliases ensure the alias is a valid subversion + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) + && (stripos($targetPrefix, $sourcePrefix) !== 0) + ) { + continue; + } + + return $validatedTargetBranch; + } + } + + if ( + isset($config['default-branch']) + && $config['default-branch'] === true + && false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version'])) + ) { + return VersionParser::DEFAULT_BRANCH_ALIAS; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php b/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php new file mode 100644 index 0000000..51f0db8 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +/** + * @author Jordi Boggiano + */ +class InvalidPackageException extends \Exception +{ + /** @var list */ + private $errors; + /** @var list */ + private $warnings; + /** @var mixed[] package config */ + private $data; + + /** + * @param list $errors + * @param list $warnings + * @param mixed[] $data + */ + public function __construct(array $errors, array $warnings, array $data) + { + $this->errors = $errors; + $this->warnings = $warnings; + $this->data = $data; + parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings))); + } + + /** + * @return mixed[] + */ + public function getData(): array + { + return $this->data; + } + + /** + * @return list + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return list + */ + public function getWarnings(): array + { + return $this->warnings; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php new file mode 100644 index 0000000..3ad5787 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\RootPackage; +use Composer\Package\RootAliasPackage; + +/** + * @author Konstantin Kudryashiv + */ +class JsonLoader +{ + /** @var LoaderInterface */ + private $loader; + + public function __construct(LoaderInterface $loader) + { + $this->loader = $loader; + } + + /** + * @param string|JsonFile $json A filename, json string or JsonFile instance to load the package from + * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage + */ + public function load($json): BasePackage + { + if ($json instanceof JsonFile) { + $config = $json->read(); + } elseif (file_exists($json)) { + $config = JsonFile::parseJson(file_get_contents($json), $json); + } elseif (is_string($json)) { + $config = JsonFile::parseJson($json); + } else { + throw new \InvalidArgumentException(sprintf( + "JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.", + gettype($json) + )); + } + + return $this->loader->load($config); + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php b/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php new file mode 100644 index 0000000..cbf1b4c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.php @@ -0,0 +1,39 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\RootAliasPackage; +use Composer\Package\RootPackage; +use Composer\Package\BasePackage; + +/** + * Defines a loader that takes an array to create package instances + * + * @author Jordi Boggiano + */ +interface LoaderInterface +{ + /** + * Converts a package from an array to a real instance + * + * @param mixed[] $config package data + * @param string $class FQCN to be instantiated + * + * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage + * + * @phpstan-param class-string $class + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage; +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php new file mode 100644 index 0000000..1e278a1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php @@ -0,0 +1,315 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Package\RootAliasPackage; +use Composer\Pcre\Preg; +use Composer\Repository\RepositoryFactory; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Package\RootPackage; +use Composer\Repository\RepositoryManager; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; + +/** + * ArrayLoader built for the sole purpose of loading the root package + * + * Sets additional defaults and loads repositories + * + * @author Jordi Boggiano + */ +class RootPackageLoader extends ArrayLoader +{ + /** + * @var RepositoryManager + */ + private $manager; + + /** + * @var Config + */ + private $config; + + /** + * @var VersionGuesser + */ + private $versionGuesser; + + /** + * @var IOInterface|null + */ + private $io; + + public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null) + { + parent::__construct($parser); + + $this->manager = $manager; + $this->config = $config; + if (null === $versionGuesser) { + $processExecutor = new ProcessExecutor($io); + $processExecutor->enableAsync(); + $versionGuesser = new VersionGuesser($config, $processExecutor, $this->versionParser); + } + $this->versionGuesser = $versionGuesser; + $this->io = $io; + } + + /** + * @inheritDoc + * + * @return RootPackage|RootAliasPackage + * + * @phpstan-param class-string $class + */ + public function load(array $config, string $class = 'Composer\Package\RootPackage', ?string $cwd = null): BasePackage + { + if ($class !== 'Composer\Package\RootPackage') { + trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); + } + + if (!isset($config['name'])) { + $config['name'] = '__root__'; + } elseif ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) { + throw new \RuntimeException('Your package name '.$err); + } + $autoVersioned = false; + if (!isset($config['version'])) { + $commit = null; + + // override with env var if available + if (Platform::getEnv('COMPOSER_ROOT_VERSION')) { + $config['version'] = $this->versionGuesser->getRootVersionFromEnv(); + } else { + $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true)); + if ($versionData) { + $config['version'] = $versionData['pretty_version']; + $config['version_normalized'] = $versionData['version']; + $commit = $versionData['commit']; + } + } + + if (!isset($config['version'])) { + if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) { + $this->io->warning( + sprintf( + "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version", + $config['name'] + ) + ); + } + $config['version'] = '1.0.0'; + $autoVersioned = true; + } + + if ($commit) { + $config['source'] = [ + 'type' => '', + 'url' => '', + 'reference' => $commit, + ]; + $config['dist'] = [ + 'type' => '', + 'url' => '', + 'reference' => $commit, + ]; + } + } + + /** @var RootPackage|RootAliasPackage $package */ + $package = parent::load($config, $class); + if ($package instanceof RootAliasPackage) { + $realPackage = $package->getAliasOf(); + } else { + $realPackage = $package; + } + + if (!$realPackage instanceof RootPackage) { + throw new \LogicException('Expecting a Composer\Package\RootPackage at this point'); + } + + if ($autoVersioned) { + $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION); + } + + if (isset($config['minimum-stability'])) { + $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); + } + + $aliases = []; + $stabilityFlags = []; + $references = []; + foreach (['require', 'require-dev'] as $linkType) { + if (isset($config[$linkType])) { + $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; + $method = 'get'.ucfirst($linkInfo['method']); + $links = []; + foreach ($realPackage->{$method}() as $link) { + $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); + } + $aliases = $this->extractAliases($links, $aliases); + $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags); + $references = self::extractReferences($links, $references); + + if (isset($links[$config['name']])) { + throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . + 'Did you accidentally name your root package after an external package?', $config['name'])); + } + } + } + + foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { + if (isset($config[$linkType])) { + foreach ($config[$linkType] as $linkName => $constraint) { + if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) { + throw new \RuntimeException($linkType.'.'.$err); + } + } + } + } + + $realPackage->setAliases($aliases); + $realPackage->setStabilityFlags($stabilityFlags); + $realPackage->setReferences($references); + + if (isset($config['prefer-stable'])) { + $realPackage->setPreferStable((bool) $config['prefer-stable']); + } + + if (isset($config['config'])) { + $realPackage->setConfig($config['config']); + } + + $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); + foreach ($repos as $repo) { + $this->manager->addRepository($repo); + } + $realPackage->setRepositories($this->config->getRepositories()); + + return $package; + } + + /** + * @param array $requires + * @param list $aliases + * + * @return list + */ + private function extractAliases(array $requires, array $aliases): array + { + foreach ($requires as $reqName => $reqVersion) { + if (Preg::isMatchStrictGroups('{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}', $reqVersion, $match)) { + $aliases[] = [ + 'package' => strtolower($reqName), + 'version' => $this->versionParser->normalize($match[1], $reqVersion), + 'alias' => $match[2], + 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion), + ]; + } elseif (strpos($reqVersion, ' as ') !== false) { + throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".'); + } + } + + return $aliases; + } + + /** + * @internal + * + * @param array $requires + * @param array $stabilityFlags + * @param key-of $minimumStability + * + * @return array + * + * @phpstan-param array $stabilityFlags + * @phpstan-return array + */ + public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array + { + $stabilities = BasePackage::STABILITIES; + $minimumStability = $stabilities[$minimumStability]; + foreach ($requires as $reqName => $reqVersion) { + $constraints = []; + + // extract all sub-constraints in case it is an OR/AND multi-constraint + $orSplit = Preg::split('{\s*\|\|?\s*}', trim($reqVersion)); + foreach ($orSplit as $orConstraint) { + $andSplit = Preg::split('{(?< ,]) *(? $stability) { + continue; + } + $stabilityFlags[$name] = $stability; + $matched = true; + } + } + + if ($matched) { + continue; + } + + foreach ($constraints as $constraint) { + // infer flags for requirements that have an explicit -dev or -beta version specified but only + // for those that are more unstable than the minimumStability or existing flags + $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $constraint); + if (Preg::isMatch('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { + $name = strtolower($reqName); + $stability = $stabilities[$stabilityName]; + if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) { + continue; + } + $stabilityFlags[$name] = $stability; + } + } + } + + return $stabilityFlags; + } + + /** + * @internal + * + * @param array $requires + * @param array $references + * + * @return array + */ + public static function extractReferences(array $requires, array $references): array + { + foreach ($requires as $reqName => $reqVersion) { + $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); + if (Preg::isMatchStrictGroups('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) { + $name = strtolower($reqName); + $references[$name] = $match[1]; + } + } + + return $references; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php b/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php new file mode 100644 index 0000000..e3600d6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -0,0 +1,657 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Loader; + +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Version\VersionParser; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Intervals; +use Composer\Spdx\SpdxLicenses; + +/** + * @author Jordi Boggiano + */ +class ValidatingArrayLoader implements LoaderInterface +{ + public const CHECK_ALL = 3; + public const CHECK_UNBOUND_CONSTRAINTS = 1; + public const CHECK_STRICT_CONSTRAINTS = 2; + + /** @var LoaderInterface */ + private $loader; + /** @var VersionParser */ + private $versionParser; + /** @var list */ + private $errors; + /** @var list */ + private $warnings; + /** @var mixed[] */ + private $config; + /** @var int One or more of self::CHECK_* constants */ + private $flags; + + /** + * @param true $strictName + */ + public function __construct(LoaderInterface $loader, bool $strictName = true, ?VersionParser $parser = null, int $flags = 0) + { + $this->loader = $loader; + $this->versionParser = $parser ?? new VersionParser(); + $this->flags = $flags; + + if ($strictName !== true) { // @phpstan-ignore-line + trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', E_USER_DEPRECATED); + } + } + + /** + * @inheritDoc + */ + public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage + { + $this->errors = []; + $this->warnings = []; + $this->config = $config; + + $this->validateString('name', true); + if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) { + $this->errors[] = 'name : '.$err; + } + + if (isset($this->config['version'])) { + if (!is_scalar($this->config['version'])) { + $this->validateString('version'); + } else { + if (!is_string($this->config['version'])) { + $this->config['version'] = (string) $this->config['version']; + } + try { + $this->versionParser->normalize($this->config['version']); + } catch (\Exception $e) { + $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage(); + unset($this->config['version']); + } + } + } + + if (isset($this->config['config']['platform'])) { + foreach ((array) $this->config['config']['platform'] as $key => $platform) { + if (false === $platform) { + continue; + } + if (!is_string($platform)) { + $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.gettype($platform).' '.var_export($platform, true).'): expected string or false'; + continue; + } + try { + $this->versionParser->normalize($platform); + } catch (\Exception $e) { + $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage(); + } + } + } + + $this->validateRegex('type', '[A-Za-z0-9-]+'); + $this->validateString('target-dir'); + $this->validateArray('extra'); + + if (isset($this->config['bin'])) { + if (is_string($this->config['bin'])) { + $this->validateString('bin'); + } else { + $this->validateFlatArray('bin'); + } + } + + $this->validateArray('scripts'); // TODO validate event names & listener syntax + $this->validateString('description'); + $this->validateUrl('homepage'); + $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+'); + + $releaseDate = null; + $this->validateString('time'); + if (isset($this->config['time'])) { + try { + $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); + } catch (\Exception $e) { + $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); + unset($this->config['time']); + } + } + + if (isset($this->config['license'])) { + // validate main data types + if (is_array($this->config['license']) || is_string($this->config['license'])) { + $licenses = (array) $this->config['license']; + + foreach ($licenses as $index => $license) { + if (!is_string($license)) { + $this->warnings[] = sprintf( + 'License %s should be a string.', + json_encode($license) + ); + unset($licenses[$index]); + } + } + + // check for license validity on newly updated branches/tags + if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) { + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer + if ('proprietary' === $license) { + continue; + } + $licenseToValidate = str_replace('proprietary', 'MIT', $license); + if (!$licenseValidator->validate($licenseToValidate)) { + if ($licenseValidator->validate(trim($licenseToValidate))) { + $this->warnings[] = sprintf( + 'License %s must not contain extra spaces, make sure to trim it.', + json_encode($license) + ); + } else { + $this->warnings[] = sprintf( + 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . + 'If the software is closed-source, you may use "proprietary" as license.', + json_encode($license) + ); + } + } + } + } + + $this->config['license'] = array_values($licenses); + } else { + $this->warnings[] = sprintf( + 'License must be a string or array of strings, got %s.', + json_encode($this->config['license']) + ); + unset($this->config['license']); + } + } + + if ($this->validateArray('authors')) { + foreach ($this->config['authors'] as $key => $author) { + if (!is_array($author)) { + $this->errors[] = 'authors.'.$key.' : should be an array, '.gettype($author).' given'; + unset($this->config['authors'][$key]); + continue; + } + foreach (['homepage', 'email', 'name', 'role'] as $authorData) { + if (isset($author[$authorData]) && !is_string($author[$authorData])) { + $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string'; + unset($this->config['authors'][$key][$authorData]); + } + } + if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) { + $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL'; + unset($this->config['authors'][$key]['homepage']); + } + if (isset($author['email']) && false === filter_var($author['email'], FILTER_VALIDATE_EMAIL)) { + $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address'; + unset($this->config['authors'][$key]['email']); + } + if (\count($this->config['authors'][$key]) === 0) { + unset($this->config['authors'][$key]); + } + } + if (\count($this->config['authors']) === 0) { + unset($this->config['authors']); + } + } + + if ($this->validateArray('support') && !empty($this->config['support'])) { + foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) { + if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) { + $this->errors[] = 'support.'.$key.' : invalid value, must be a string'; + unset($this->config['support'][$key]); + } + } + + if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) { + $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address'; + unset($this->config['support']['email']); + } + + if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) { + $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc:/// or ircs:// URL'; + unset($this->config['support']['irc']); + } + + foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) { + if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { + $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL'; + unset($this->config['support'][$key]); + } + } + if (empty($this->config['support'])) { + unset($this->config['support']); + } + } + + if ($this->validateArray('funding') && !empty($this->config['funding'])) { + foreach ($this->config['funding'] as $key => $fundingOption) { + if (!is_array($fundingOption)) { + $this->errors[] = 'funding.'.$key.' : should be an array, '.gettype($fundingOption).' given'; + unset($this->config['funding'][$key]); + continue; + } + foreach (['type', 'url'] as $fundingData) { + if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) { + $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string'; + unset($this->config['funding'][$key][$fundingData]); + } + } + if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) { + $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL'; + unset($this->config['funding'][$key]['url']); + } + if (empty($this->config['funding'][$key])) { + unset($this->config['funding'][$key]); + } + } + if (empty($this->config['funding'])) { + unset($this->config['funding']); + } + } + + $this->validateArray('php-ext'); + if (isset($this->config['php-ext']) && !in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) { + $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions'; + unset($this->config['php-ext']); + } + + $unboundConstraint = new Constraint('=', '10000000-dev'); + + foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { + if ($this->validateArray($linkType) && isset($this->config[$linkType])) { + foreach ($this->config[$linkType] as $package => $constraint) { + $package = (string) $package; + if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) { + $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself'; + unset($this->config[$linkType][$package]); + continue; + } + if ($err = self::hasPackageNamingError($package, true)) { + $this->warnings[] = $linkType.'.'.$err; + } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) { + $this->errors[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; + } + if (!is_string($constraint)) { + $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint'; + unset($this->config[$linkType][$package]); + } elseif ('self.version' !== $constraint) { + try { + $linkConstraint = $this->versionParser->parseConstraints($constraint); + } catch (\Exception $e) { + $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')'; + unset($this->config[$linkType][$package]); + continue; + } + + // check requires for unbound constraints on non-platform packages + if ( + ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS) + && 'require' === $linkType + && $linkConstraint->matches($unboundConstraint) + && !PlatformRepository::isPlatformPackage($package) + ) { + $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided'; + } elseif ( + // check requires for exact constraints + ($this->flags & self::CHECK_STRICT_CONSTRAINTS) + && 'require' === $linkType + && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true) + && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint) + ) { + $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; + } + + $compacted = Intervals::compactConstraint($linkConstraint); + if ($compacted instanceof MatchNoneConstraint) { + $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')'; + } + } + + if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) { + $this->errors[] = $linkType.'.'.$package.' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule'; + unset($this->config[$linkType][$package]); + } + } + } + } + + if ($this->validateArray('suggest') && isset($this->config['suggest'])) { + foreach ($this->config['suggest'] as $package => $description) { + if (!is_string($description)) { + $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested'; + unset($this->config['suggest'][$package]); + } + } + } + + if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) { + if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { + $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES)); + unset($this->config['minimum-stability']); + } + } + + if ($this->validateArray('autoload') && isset($this->config['autoload'])) { + $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap']; + foreach ($this->config['autoload'] as $type => $typeConfig) { + if (!in_array($type, $types)) { + $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); + unset($this->config['autoload'][$type]); + } + if ($type === 'psr-4') { + foreach ($typeConfig as $namespace => $dirs) { + if ($namespace !== '' && '\\' !== substr((string) $namespace, -1)) { + $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\'; + } + } + } + } + } + + if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) { + $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; + // Unset the psr-4 setting, since unsetting target-dir might + // interfere with other settings. + unset($this->config['autoload']['psr-4']); + } + + foreach (['source', 'dist'] as $srcType) { + if ($this->validateArray($srcType) && !empty($this->config[$srcType])) { + if (!isset($this->config[$srcType]['type'])) { + $this->errors[] = $srcType . '.type : must be present'; + } + if (!isset($this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : must be present'; + } + if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : must be present'; + } + if (isset($this->config[$srcType]['type']) && !is_string($this->config[$srcType]['type'])) { + $this->errors[] = $srcType . '.type : should be a string, '.gettype($this->config[$srcType]['type']).' given'; + } + if (isset($this->config[$srcType]['url']) && !is_string($this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : should be a string, '.gettype($this->config[$srcType]['url']).' given'; + } + if (isset($this->config[$srcType]['reference']) && !is_string($this->config[$srcType]['reference']) && !is_int($this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : should be a string or int, '.gettype($this->config[$srcType]['reference']).' given'; + } + if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['reference'])) { + $this->errors[] = $srcType . '.reference : must not start with a "-", "'.$this->config[$srcType]['reference'].'" given'; + } + if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['url'])) { + $this->errors[] = $srcType . '.url : must not start with a "-", "'.$this->config[$srcType]['url'].'" given'; + } + } + } + + // TODO validate repositories + // TODO validate package repositories' packages using this recursively + + $this->validateFlatArray('include-path'); + $this->validateArray('transport-options'); + + // branch alias validation + if (isset($this->config['extra']['branch-alias'])) { + if (!is_array($this->config['extra']['branch-alias'])) { + $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases'; + } else { + foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { + if (!is_string($targetBranch)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.json_encode($targetBranch).') must be a string, "'.gettype($targetBranch).'" received.'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // ensure it is an alias to a -dev package + if ('-dev' !== substr($targetBranch, -4)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // normalize without -dev and ensure it's a numeric branch that is parseable + $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); + if ('-dev' !== substr($validatedTargetBranch, -4)) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + + continue; + } + + // If using numeric aliases ensure the alias is a valid subversion + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) + && (stripos($targetPrefix, $sourcePrefix) !== 0) + ) { + $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version'; + unset($this->config['extra']['branch-alias'][$sourceBranch]); + } + } + } + } + + if ($this->errors) { + throw new InvalidPackageException($this->errors, $this->warnings, $config); + } + + $package = $this->loader->load($this->config, $class); + $this->config = []; + + return $package; + } + + /** + * @return list + */ + public function getWarnings(): array + { + return $this->warnings; + } + + /** + * @return list + */ + public function getErrors(): array + { + return $this->errors; + } + + public static function hasPackageNamingError(string $name, bool $isLink = false): ?string + { + if (PlatformRepository::isPlatformPackage($name)) { + return null; + } + + if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) { + return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".'; + } + + $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9']; + $bits = explode('/', strtolower($name)); + if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) { + return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.'; + } + + if (Preg::isMatch('{\.json$}', $name)) { + return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.'; + } + + if (Preg::isMatch('{[A-Z]}', $name)) { + if ($isLink) { + return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.'; + } + + $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); + $suggestName = strtolower($suggestName); + + return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.'; + } + + return null; + } + + /** + * @phpstan-param non-empty-string $property + * @phpstan-param non-empty-string $regex + */ + private function validateRegex(string $property, string $regex, bool $mandatory = false): bool + { + if (!$this->validateString($property, $mandatory)) { + return false; + } + + if (!Preg::isMatch('{^'.$regex.'$}u', $this->config[$property])) { + $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex; + if ($mandatory) { + $this->errors[] = $message; + } else { + $this->warnings[] = $message; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateString(string $property, bool $mandatory = false): bool + { + if (isset($this->config[$property]) && !is_string($this->config[$property])) { + $this->errors[] = $property.' : should be a string, '.gettype($this->config[$property]).' given'; + unset($this->config[$property]); + + return false; + } + + if (!isset($this->config[$property]) || trim($this->config[$property]) === '') { + if ($mandatory) { + $this->errors[] = $property.' : must be present'; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateArray(string $property, bool $mandatory = false): bool + { + if (isset($this->config[$property]) && !is_array($this->config[$property])) { + $this->errors[] = $property.' : should be an array, '.gettype($this->config[$property]).' given'; + unset($this->config[$property]); + + return false; + } + + if (!isset($this->config[$property]) || !count($this->config[$property])) { + if ($mandatory) { + $this->errors[] = $property.' : must be present and contain at least one element'; + } + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @phpstan-param non-empty-string $property + * @phpstan-param non-empty-string|null $regex + */ + private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = false): bool + { + if (!$this->validateArray($property, $mandatory)) { + return false; + } + + $pass = true; + foreach ($this->config[$property] as $key => $value) { + if (!is_string($value) && !is_numeric($value)) { + $this->errors[] = $property.'.'.$key.' : must be a string or int, '.gettype($value).' given'; + unset($this->config[$property][$key]); + $pass = false; + + continue; + } + + if ($regex && !Preg::isMatch('{^'.$regex.'$}u', (string) $value)) { + $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex; + unset($this->config[$property][$key]); + $pass = false; + } + } + + return $pass; + } + + /** + * @phpstan-param non-empty-string $property + */ + private function validateUrl(string $property, bool $mandatory = false): bool + { + if (!$this->validateString($property, $mandatory)) { + return false; + } + + if (!$this->filterUrl($this->config[$property])) { + $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL'; + unset($this->config[$property]); + + return false; + } + + return true; + } + + /** + * @param mixed $value + * @param string[] $schemes + */ + private function filterUrl($value, array $schemes = ['http', 'https']): bool + { + if ($value === '') { + return true; + } + + $bits = parse_url($value); + if (empty($bits['scheme']) || empty($bits['host'])) { + return false; + } + + if (!in_array($bits['scheme'], $schemes, true)) { + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Locker.php b/vendor/composer/composer/src/Composer/Package/Locker.php new file mode 100644 index 0000000..38cd8ef --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Locker.php @@ -0,0 +1,630 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Json\JsonFile; +use Composer\Installer\InstallationManager; +use Composer\Pcre\Preg; +use Composer\Repository\InstalledRepository; +use Composer\Repository\LockArrayRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RootPackageRepository; +use Composer\Util\ProcessExecutor; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Version\VersionParser; +use Composer\Plugin\PluginInterface; +use Composer\Util\Git as GitUtil; +use Composer\IO\IOInterface; +use Seld\JsonLint\ParsingException; + +/** + * Reads/writes project lockfile (composer.lock). + * + * @author Konstantin Kudryashiv + * @author Jordi Boggiano + */ +class Locker +{ + /** @var JsonFile */ + private $lockFile; + /** @var InstallationManager */ + private $installationManager; + /** @var string */ + private $hash; + /** @var string */ + private $contentHash; + /** @var ArrayLoader */ + private $loader; + /** @var ArrayDumper */ + private $dumper; + /** @var ProcessExecutor */ + private $process; + /** @var mixed[]|null */ + private $lockDataCache = null; + /** @var bool */ + private $virtualFileWritten = false; + + /** + * Initializes packages locker. + * + * @param JsonFile $lockFile lockfile loader + * @param InstallationManager $installationManager installation manager instance + * @param string $composerFileContents The contents of the composer file + */ + public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, string $composerFileContents, ?ProcessExecutor $process = null) + { + $this->lockFile = $lockFile; + $this->installationManager = $installationManager; + $this->hash = hash('md5', $composerFileContents); + $this->contentHash = self::getContentHash($composerFileContents); + $this->loader = new ArrayLoader(null, true); + $this->dumper = new ArrayDumper(); + $this->process = $process ?? new ProcessExecutor($io); + } + + /** + * @internal + */ + public function getJsonFile(): JsonFile + { + return $this->lockFile; + } + + /** + * Returns the md5 hash of the sorted content of the composer file. + * + * @param string $composerFileContents The contents of the composer file. + */ + public static function getContentHash(string $composerFileContents): string + { + $content = JsonFile::parseJson($composerFileContents, 'composer.json'); + + $relevantKeys = [ + 'name', + 'version', + 'require', + 'require-dev', + 'conflict', + 'replace', + 'provide', + 'minimum-stability', + 'prefer-stable', + 'repositories', + 'extra', + ]; + + $relevantContent = []; + + foreach (array_intersect($relevantKeys, array_keys($content)) as $key) { + $relevantContent[$key] = $content[$key]; + } + if (isset($content['config']['platform'])) { + $relevantContent['config']['platform'] = $content['config']['platform']; + } + + ksort($relevantContent); + + return hash('md5', JsonFile::encode($relevantContent, 0)); + } + + /** + * Checks whether locker has been locked (lockfile found). + */ + public function isLocked(): bool + { + if (!$this->virtualFileWritten && !$this->lockFile->exists()) { + return false; + } + + $data = $this->getLockData(); + + return isset($data['packages']); + } + + /** + * Checks whether the lock file is still up to date with the current hash + */ + public function isFresh(): bool + { + $lock = $this->lockFile->read(); + + if (!empty($lock['content-hash'])) { + // There is a content hash key, use that instead of the file hash + return $this->contentHash === $lock['content-hash']; + } + + // BC support for old lock files without content-hash + if (!empty($lock['hash'])) { + return $this->hash === $lock['hash']; + } + + // should not be reached unless the lock file is corrupted, so assume it's out of date + return false; + } + + /** + * Searches and returns an array of locked packages, retrieved from registered repositories. + * + * @param bool $withDevReqs true to retrieve the locked dev packages + * @throws \RuntimeException + */ + public function getLockedRepository(bool $withDevReqs = false): LockArrayRepository + { + $lockData = $this->getLockData(); + $packages = new LockArrayRepository(); + + $lockedPackages = $lockData['packages']; + if ($withDevReqs) { + if (isset($lockData['packages-dev'])) { + $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']); + } else { + throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.'); + } + } + + if (empty($lockedPackages)) { + return $packages; + } + + if (isset($lockedPackages[0]['name'])) { + $packageByName = []; + foreach ($lockedPackages as $info) { + $package = $this->loader->load($info); + $packages->addPackage($package); + $packageByName[$package->getName()] = $package; + + if ($package instanceof AliasPackage) { + $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf(); + } + } + + if (isset($lockData['aliases'])) { + foreach ($lockData['aliases'] as $alias) { + if (isset($packageByName[$alias['package']])) { + $aliasPkg = new CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']); + $aliasPkg->setRootPackageAlias(true); + $packages->addPackage($aliasPkg); + } + } + } + + return $packages; + } + + throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.'); + } + + /** + * @return string[] Names of dependencies installed through require-dev + */ + public function getDevPackageNames(): array + { + $names = []; + $lockData = $this->getLockData(); + if (isset($lockData['packages-dev'])) { + foreach ($lockData['packages-dev'] as $package) { + $names[] = strtolower($package['name']); + } + } + + return $names; + } + + /** + * Returns the platform requirements stored in the lock file + * + * @param bool $withDevReqs if true, the platform requirements from the require-dev block are also returned + * @return \Composer\Package\Link[] + */ + public function getPlatformRequirements(bool $withDevReqs = false): array + { + $lockData = $this->getLockData(); + $requirements = []; + + if (!empty($lockData['platform'])) { + $requirements = $this->loader->parseLinks( + '__root__', + '1.0.0', + Link::TYPE_REQUIRE, + $lockData['platform'] ?? [] + ); + } + + if ($withDevReqs && !empty($lockData['platform-dev'])) { + $devRequirements = $this->loader->parseLinks( + '__root__', + '1.0.0', + Link::TYPE_REQUIRE, + $lockData['platform-dev'] ?? [] + ); + + $requirements = array_merge($requirements, $devRequirements); + } + + return $requirements; + } + + /** + * @return key-of + */ + public function getMinimumStability(): string + { + $lockData = $this->getLockData(); + + return $lockData['minimum-stability'] ?? 'stable'; + } + + /** + * @return array + */ + public function getStabilityFlags(): array + { + $lockData = $this->getLockData(); + + return $lockData['stability-flags'] ?? []; + } + + public function getPreferStable(): ?bool + { + $lockData = $this->getLockData(); + + // return null if not set to allow caller logic to choose the + // right behavior since old lock files have no prefer-stable + return $lockData['prefer-stable'] ?? null; + } + + public function getPreferLowest(): ?bool + { + $lockData = $this->getLockData(); + + // return null if not set to allow caller logic to choose the + // right behavior since old lock files have no prefer-lowest + return $lockData['prefer-lowest'] ?? null; + } + + /** + * @return array + */ + public function getPlatformOverrides(): array + { + $lockData = $this->getLockData(); + + return $lockData['platform-overrides'] ?? []; + } + + /** + * @return string[][] + * + * @phpstan-return list + */ + public function getAliases(): array + { + $lockData = $this->getLockData(); + + return $lockData['aliases'] ?? []; + } + + /** + * @return string + */ + public function getPluginApi() + { + $lockData = $this->getLockData(); + + return $lockData['plugin-api-version'] ?? '1.1.0'; + } + + /** + * @return array + */ + public function getLockData(): array + { + if (null !== $this->lockDataCache) { + return $this->lockDataCache; + } + + if (!$this->lockFile->exists()) { + throw new \LogicException('No lockfile found. Unable to read locked packages'); + } + + return $this->lockDataCache = $this->lockFile->read(); + } + + /** + * Locks provided data into lockfile. + * + * @param PackageInterface[] $packages array of packages + * @param PackageInterface[]|null $devPackages array of dev packages or null if installed without --dev + * @param array $platformReqs array of package name => constraint for required platform packages + * @param array $platformDevReqs array of package name => constraint for dev-required platform packages + * @param string[][] $aliases array of aliases + * @param array $stabilityFlags + * @param array $platformOverrides + * @param bool $write Whether to actually write data to disk, useful in tests and for --dry-run + * + * @phpstan-param list $aliases + */ + public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = true): bool + { + // keep old default branch names normalized to DEFAULT_BRANCH_ALIAS for BC as that is how Composer 1 outputs the lock file + // when loading the lock file the version is anyway ignored in Composer 2, so it has no adverse effect + $aliases = array_map(static function ($alias): array { + if (in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS; + } + + return $alias; + }, $aliases); + + $lock = [ + '_readme' => ['This file locks the dependencies of your project to a known state', + 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', + 'This file is @gener'.'ated automatically', ], + 'content-hash' => $this->contentHash, + 'packages' => $this->lockPackages($packages), + 'packages-dev' => null, + 'aliases' => $aliases, + 'minimum-stability' => $minimumStability, + 'stability-flags' => $stabilityFlags, + 'prefer-stable' => $preferStable, + 'prefer-lowest' => $preferLowest, + ]; + + if (null !== $devPackages) { + $lock['packages-dev'] = $this->lockPackages($devPackages); + } + + $lock['platform'] = $platformReqs; + $lock['platform-dev'] = $platformDevReqs; + if (\count($platformOverrides) > 0) { + $lock['platform-overrides'] = $platformOverrides; + } + $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION; + + $lock = $this->fixupJsonDataType($lock); + + try { + $isLocked = $this->isLocked(); + } catch (ParsingException $e) { + $isLocked = false; + } + if (!$isLocked || $lock !== $this->getLockData()) { + if ($write) { + $this->lockFile->write($lock); + $this->lockDataCache = null; + $this->virtualFileWritten = false; + } else { + $this->virtualFileWritten = true; + $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock)); + } + + return true; + } + + return false; + } + + /** + * Updates the lock file's hash in-place from a given composer.json's JsonFile + * + * This does not reload or require any packages, and retains the filemtime of the lock file. + * + * Use this only to update the lock file hash after updating a composer.json in ways that are guaranteed NOT to impact the dependency resolution. + * + * This is a risky method, use carefully. + * + * @param (callable(array): array)|null $dataProcessor Receives the lock data and can process it before it gets written to disk + */ + public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void + { + $contents = file_get_contents($composerJson->getPath()); + if (false === $contents) { + throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); + } + + $lockMtime = filemtime($this->lockFile->getPath()); + $lockData = $this->lockFile->read(); + $lockData['content-hash'] = Locker::getContentHash($contents); + if ($dataProcessor !== null) { + $lockData = $dataProcessor($lockData); + } + + $this->lockFile->write($this->fixupJsonDataType($lockData)); + $this->lockDataCache = null; + $this->virtualFileWritten = false; + if (is_int($lockMtime)) { + @touch($this->lockFile->getPath(), $lockMtime); + } + } + + /** + * Ensures correct data types and ordering for the JSON lock format + * + * @param array $lockData + * @return array + */ + private function fixupJsonDataType(array $lockData): array + { + foreach (['stability-flags', 'platform', 'platform-dev'] as $key) { + if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) { + $lockData[$key] = new \stdClass(); + } + } + + if (is_array($lockData['stability-flags'])) { + ksort($lockData['stability-flags']); + } + + return $lockData; + } + + /** + * @param PackageInterface[] $packages + * + * @return mixed[][] + * + * @phpstan-return list> + */ + private function lockPackages(array $packages): array + { + $locked = []; + + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + $name = $package->getPrettyName(); + $version = $package->getPrettyVersion(); + + if (!$name || !$version) { + throw new \LogicException(sprintf( + 'Package "%s" has no version or name and can not be locked', + $package + )); + } + + $spec = $this->dumper->dump($package); + unset($spec['version_normalized']); + + // always move time to the end of the package definition + $time = $spec['time'] ?? null; + unset($spec['time']); + if ($package->isDev() && $package->getInstallationSource() === 'source') { + // use the exact commit time of the current reference if it's a dev package + $time = $this->getPackageTime($package) ?: $time; + } + if (null !== $time) { + $spec['time'] = $time; + } + + unset($spec['installation-source']); + + $locked[] = $spec; + } + + usort($locked, static function ($a, $b) { + $comparison = strcmp($a['name'], $b['name']); + + if (0 !== $comparison) { + return $comparison; + } + + // If it is the same package, compare the versions to make the order deterministic + return strcmp($a['version'], $b['version']); + }); + + return $locked; + } + + /** + * Returns the packages's datetime for its source reference. + * + * @param PackageInterface $package The package to scan. + * @return string|null The formatted datetime or null if none was found. + */ + private function getPackageTime(PackageInterface $package): ?string + { + if (!function_exists('proc_open')) { + return null; + } + + $path = $this->installationManager->getInstallPath($package); + if ($path === null) { + return null; + } + $path = realpath($path); + $sourceType = $package->getSourceType(); + $datetime = null; + + if ($path && in_array($sourceType, ['git', 'hg'])) { + $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); + switch ($sourceType) { + case 'git': + GitUtil::cleanEnv(); + + $command = array_merge(['git', 'log', '-n1', '--pretty=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process)); + if (0 === $this->process->execute($command, $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) { + $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); + } + break; + + case 'hg': + if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) { + $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC')); + } + break; + } + } + + return $datetime ? $datetime->format(DATE_RFC3339) : null; + } + + /** + * @return array + */ + public function getMissingRequirementInfo(RootPackageInterface $package, bool $includeDev): array + { + $missingRequirementInfo = []; + $missingRequirements = false; + $sets = [['repo' => $this->getLockedRepository(false), 'method' => 'getRequires', 'description' => 'Required']]; + if ($includeDev === true) { + $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)']; + } + $rootRepo = new RootPackageRepository(clone $package); + + foreach ($sets as $set) { + $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]); + + foreach (call_user_func([$package, $set['method']]) as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + continue; + } + if ($link->getPrettyConstraint() === 'self.version') { + continue; + } + if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) { + $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget()); + + if ($results !== []) { + $provider = reset($results); + $description = $provider->getPrettyVersion(); + if ($provider->getName() !== $link->getTarget()) { + foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) { + foreach (call_user_func([$provider, $method]) as $providerLink) { + if ($providerLink->getTarget() === $link->getTarget()) { + $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion()); + break 2; + } + } + } + } + $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".'; + } else { + $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.'; + } + $missingRequirements = true; + } + } + } + + if ($missingRequirements) { + $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.'; + $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md'; + $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r'; + } + + return $missingRequirementInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Package.php b/vendor/composer/composer/src/Composer/Package/Package.php new file mode 100644 index 0000000..faa2a07 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Package.php @@ -0,0 +1,737 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Util\ComposerMirror; + +/** + * Core package definitions that are needed to resolve dependencies and install packages + * + * @author Nils Adermann + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-import-type DevAutoloadRules from PackageInterface + * @phpstan-import-type PhpExtConfig from PackageInterface + */ +class Package extends BasePackage +{ + /** @var string */ + protected $type; + /** @var ?string */ + protected $targetDir; + /** @var 'source'|'dist'|null */ + protected $installationSource; + /** @var ?string */ + protected $sourceType; + /** @var ?string */ + protected $sourceUrl; + /** @var ?string */ + protected $sourceReference; + /** @var ?list */ + protected $sourceMirrors; + /** @var ?non-empty-string */ + protected $distType; + /** @var ?non-empty-string */ + protected $distUrl; + /** @var ?string */ + protected $distReference; + /** @var ?string */ + protected $distSha1Checksum; + /** @var ?list */ + protected $distMirrors; + /** @var string */ + protected $version; + /** @var string */ + protected $prettyVersion; + /** @var ?\DateTimeInterface */ + protected $releaseDate; + /** @var mixed[] */ + protected $extra = []; + /** @var string[] */ + protected $binaries = []; + /** @var bool */ + protected $dev; + /** + * @var string + * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + protected $stability; + /** @var ?string */ + protected $notificationUrl; + + /** @var array */ + protected $requires = []; + /** @var array */ + protected $conflicts = []; + /** @var array */ + protected $provides = []; + /** @var array */ + protected $replaces = []; + /** @var array */ + protected $devRequires = []; + /** @var array */ + protected $suggests = []; + /** + * @var array + * @phpstan-var AutoloadRules + */ + protected $autoload = []; + /** + * @var array + * @phpstan-var DevAutoloadRules + */ + protected $devAutoload = []; + /** @var string[] */ + protected $includePaths = []; + /** @var bool */ + protected $isDefaultBranch = false; + /** @var mixed[] */ + protected $transportOptions = []; + /** + * @var array|null + * @phpstan-var PhpExtConfig|null + */ + protected $phpExt = null; + + /** + * Creates a new in memory package. + * + * @param string $name The package's name + * @param string $version The package's version + * @param string $prettyVersion The package's non-normalized version + */ + public function __construct(string $name, string $version, string $prettyVersion) + { + parent::__construct($name); + + $this->version = $version; + $this->prettyVersion = $prettyVersion; + + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + } + + /** + * @inheritDoc + */ + public function isDev(): bool + { + return $this->dev; + } + + public function setType(string $type): void + { + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function getType(): string + { + return $this->type ?: 'library'; + } + + /** + * @inheritDoc + */ + public function getStability(): string + { + return $this->stability; + } + + public function setTargetDir(?string $targetDir): void + { + $this->targetDir = $targetDir; + } + + /** + * @inheritDoc + */ + public function getTargetDir(): ?string + { + if (null === $this->targetDir) { + return null; + } + + return ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/'); + } + + /** + * @param mixed[] $extra + */ + public function setExtra(array $extra): void + { + $this->extra = $extra; + } + + /** + * @inheritDoc + */ + public function getExtra(): array + { + return $this->extra; + } + + /** + * @param string[] $binaries + */ + public function setBinaries(array $binaries): void + { + $this->binaries = $binaries; + } + + /** + * @inheritDoc + */ + public function getBinaries(): array + { + return $this->binaries; + } + + /** + * @inheritDoc + */ + public function setInstallationSource(?string $type): void + { + $this->installationSource = $type; + } + + /** + * @inheritDoc + */ + public function getInstallationSource(): ?string + { + return $this->installationSource; + } + + public function setSourceType(?string $type): void + { + $this->sourceType = $type; + } + + /** + * @inheritDoc + */ + public function getSourceType(): ?string + { + return $this->sourceType; + } + + public function setSourceUrl(?string $url): void + { + $this->sourceUrl = $url; + } + + /** + * @inheritDoc + */ + public function getSourceUrl(): ?string + { + return $this->sourceUrl; + } + + public function setSourceReference(?string $reference): void + { + $this->sourceReference = $reference; + } + + /** + * @inheritDoc + */ + public function getSourceReference(): ?string + { + return $this->sourceReference; + } + + public function setSourceMirrors(?array $mirrors): void + { + $this->sourceMirrors = $mirrors; + } + + /** + * @inheritDoc + */ + public function getSourceMirrors(): ?array + { + return $this->sourceMirrors; + } + + /** + * @inheritDoc + */ + public function getSourceUrls(): array + { + return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source'); + } + + /** + * @param string $type + */ + public function setDistType(?string $type): void + { + $this->distType = $type === '' ? null : $type; + } + + /** + * @inheritDoc + */ + public function getDistType(): ?string + { + return $this->distType; + } + + /** + * @param string|null $url + */ + public function setDistUrl(?string $url): void + { + $this->distUrl = $url === '' ? null : $url; + } + + /** + * @inheritDoc + */ + public function getDistUrl(): ?string + { + return $this->distUrl; + } + + /** + * @param string $reference + */ + public function setDistReference(?string $reference): void + { + $this->distReference = $reference; + } + + /** + * @inheritDoc + */ + public function getDistReference(): ?string + { + return $this->distReference; + } + + /** + * @param string $sha1checksum + */ + public function setDistSha1Checksum(?string $sha1checksum): void + { + $this->distSha1Checksum = $sha1checksum; + } + + /** + * @inheritDoc + */ + public function getDistSha1Checksum(): ?string + { + return $this->distSha1Checksum; + } + + public function setDistMirrors(?array $mirrors): void + { + $this->distMirrors = $mirrors; + } + + /** + * @inheritDoc + */ + public function getDistMirrors(): ?array + { + return $this->distMirrors; + } + + /** + * @inheritDoc + */ + public function getDistUrls(): array + { + return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist'); + } + + /** + * @inheritDoc + */ + public function getTransportOptions(): array + { + return $this->transportOptions; + } + + /** + * @inheritDoc + */ + public function setTransportOptions(array $options): void + { + $this->transportOptions = $options; + } + + /** + * @inheritDoc + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @inheritDoc + */ + public function getPrettyVersion(): string + { + return $this->prettyVersion; + } + + public function setReleaseDate(?\DateTimeInterface $releaseDate): void + { + $this->releaseDate = $releaseDate; + } + + /** + * @inheritDoc + */ + public function getReleaseDate(): ?\DateTimeInterface + { + return $this->releaseDate; + } + + /** + * Set the required packages + * + * @param array $requires A set of package links + */ + public function setRequires(array $requires): void + { + if (isset($requires[0])) { // @phpstan-ignore-line + $requires = $this->convertLinksToMap($requires, 'setRequires'); + } + + $this->requires = $requires; + } + + /** + * @inheritDoc + */ + public function getRequires(): array + { + return $this->requires; + } + + /** + * Set the conflicting packages + * + * @param array $conflicts A set of package links + */ + public function setConflicts(array $conflicts): void + { + if (isset($conflicts[0])) { // @phpstan-ignore-line + $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts'); + } + + $this->conflicts = $conflicts; + } + + /** + * @inheritDoc + * @return array + */ + public function getConflicts(): array + { + return $this->conflicts; + } + + /** + * Set the provided virtual packages + * + * @param array $provides A set of package links + */ + public function setProvides(array $provides): void + { + if (isset($provides[0])) { // @phpstan-ignore-line + $provides = $this->convertLinksToMap($provides, 'setProvides'); + } + + $this->provides = $provides; + } + + /** + * @inheritDoc + * @return array + */ + public function getProvides(): array + { + return $this->provides; + } + + /** + * Set the packages this one replaces + * + * @param array $replaces A set of package links + */ + public function setReplaces(array $replaces): void + { + if (isset($replaces[0])) { // @phpstan-ignore-line + $replaces = $this->convertLinksToMap($replaces, 'setReplaces'); + } + + $this->replaces = $replaces; + } + + /** + * @inheritDoc + * @return array + */ + public function getReplaces(): array + { + return $this->replaces; + } + + /** + * Set the recommended packages + * + * @param array $devRequires A set of package links + */ + public function setDevRequires(array $devRequires): void + { + if (isset($devRequires[0])) { // @phpstan-ignore-line + $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires'); + } + + $this->devRequires = $devRequires; + } + + /** + * @inheritDoc + */ + public function getDevRequires(): array + { + return $this->devRequires; + } + + /** + * Set the suggested packages + * + * @param array $suggests A set of package names/comments + */ + public function setSuggests(array $suggests): void + { + $this->suggests = $suggests; + } + + /** + * @inheritDoc + */ + public function getSuggests(): array + { + return $this->suggests; + } + + /** + * Set the autoload mapping + * + * @param array $autoload Mapping of autoloading rules + * + * @phpstan-param AutoloadRules $autoload + */ + public function setAutoload(array $autoload): void + { + $this->autoload = $autoload; + } + + /** + * @inheritDoc + */ + public function getAutoload(): array + { + return $this->autoload; + } + + /** + * Set the dev autoload mapping + * + * @param array $devAutoload Mapping of dev autoloading rules + * + * @phpstan-param DevAutoloadRules $devAutoload + */ + public function setDevAutoload(array $devAutoload): void + { + $this->devAutoload = $devAutoload; + } + + /** + * @inheritDoc + */ + public function getDevAutoload(): array + { + return $this->devAutoload; + } + + /** + * Sets the list of paths added to PHP's include path. + * + * @param string[] $includePaths List of directories. + */ + public function setIncludePaths(array $includePaths): void + { + $this->includePaths = $includePaths; + } + + /** + * @inheritDoc + */ + public function getIncludePaths(): array + { + return $this->includePaths; + } + + /** + * Sets the settings for php extension packages + * + * @param array|null $phpExt + * + * @phpstan-param PhpExtConfig|null $phpExt + */ + public function setPhpExt(?array $phpExt): void + { + $this->phpExt = $phpExt; + } + + /** + * @inheritDoc + */ + public function getPhpExt(): ?array + { + return $this->phpExt; + } + + /** + * Sets the notification URL + */ + public function setNotificationUrl(string $notificationUrl): void + { + $this->notificationUrl = $notificationUrl; + } + + /** + * @inheritDoc + */ + public function getNotificationUrl(): ?string + { + return $this->notificationUrl; + } + + public function setIsDefaultBranch(bool $defaultBranch): void + { + $this->isDefaultBranch = $defaultBranch; + } + + /** + * @inheritDoc + */ + public function isDefaultBranch(): bool + { + return $this->isDefaultBranch; + } + + /** + * @inheritDoc + */ + public function setSourceDistReferences(string $reference): void + { + $this->setSourceReference($reference); + + // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL + // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? + if ( + $this->getDistUrl() !== null + && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl()) + ) { + $this->setDistReference($reference); + $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl())); + } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it + $this->setDistReference($reference); + } + } + + /** + * Replaces current version and pretty version with passed values. + * It also sets stability. + * + * @param string $version The package's normalized version + * @param string $prettyVersion The package's non-normalized version + */ + public function replaceVersion(string $version, string $prettyVersion): void + { + $this->version = $version; + $this->prettyVersion = $prettyVersion; + + $this->stability = VersionParser::parseStability($version); + $this->dev = $this->stability === 'dev'; + } + + /** + * @param mixed[]|null $mirrors + * + * @return list + * + * @phpstan-param list|null $mirrors + */ + protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType): array + { + if (!$url) { + return []; + } + + if ($urlType === 'dist' && false !== strpos($url, '%')) { + $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion); + } + + $urls = [$url]; + if ($mirrors) { + foreach ($mirrors as $mirror) { + if ($urlType === 'dist') { + $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion); + } elseif ($urlType === 'source' && $type === 'git') { + $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type); + } elseif ($urlType === 'source' && $type === 'hg') { + $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type); + } else { + continue; + } + if (!\in_array($mirrorUrl, $urls)) { + $func = $mirror['preferred'] ? 'array_unshift' : 'array_push'; + $func($urls, $mirrorUrl); + } + } + } + + return $urls; + } + + /** + * @param array $links + * @return array + */ + private function convertLinksToMap(array $links, string $source): array + { + trigger_error('Package::'.$source.' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.'); + $newLinks = []; + foreach ($links as $link) { + $newLinks[$link->getTarget()] = $link; + } + + return $newLinks; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/PackageInterface.php b/vendor/composer/composer/src/Composer/Package/PackageInterface.php new file mode 100644 index 0000000..68e22fa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/PackageInterface.php @@ -0,0 +1,405 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +use Composer\Repository\RepositoryInterface; + +/** + * Defines the essential information a package has that is used during solving/installation + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Jordi Boggiano + * + * @phpstan-type AutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list, exclude-from-classmap?: list} + * @phpstan-type DevAutoloadRules array{psr-0?: array, psr-4?: array, classmap?: list, files?: list} + * @phpstan-type PhpExtConfig array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, download-url-method?: string, os-families?: non-empty-list, os-families-exclude?: non-empty-list, configure-options?: list} + */ +interface PackageInterface +{ + public const DISPLAY_SOURCE_REF_IF_DEV = 0; + public const DISPLAY_SOURCE_REF = 1; + public const DISPLAY_DIST_REF = 2; + + /** + * Returns the package's name without version info, thus not a unique identifier + * + * @return string package name + */ + public function getName(): string; + + /** + * Returns the package's pretty (i.e. with proper case) name + * + * @return string package name + */ + public function getPrettyName(): string; + + /** + * Returns a set of names that could refer to this package + * + * No version or release type information should be included in any of the + * names. Provided or replaced package names need to be returned as well. + * + * @param bool $provides Whether provided names should be included + * + * @return string[] An array of strings referring to this package + */ + public function getNames(bool $provides = true): array; + + /** + * Allows the solver to set an id for this package to refer to it. + */ + public function setId(int $id): void; + + /** + * Retrieves the package's id set through setId + * + * @return int The previously set package id + */ + public function getId(): int; + + /** + * Returns whether the package is a development virtual package or a concrete one + */ + public function isDev(): bool; + + /** + * Returns the package type, e.g. library + * + * @return string The package type + */ + public function getType(): string; + + /** + * Returns the package targetDir property + * + * @return ?string The package targetDir + */ + public function getTargetDir(): ?string; + + /** + * Returns the package extra data + * + * @return mixed[] The package extra data + */ + public function getExtra(): array; + + /** + * Sets source from which this package was installed (source/dist). + * + * @param ?string $type source/dist + * @phpstan-param 'source'|'dist'|null $type + */ + public function setInstallationSource(?string $type): void; + + /** + * Returns source from which this package was installed (source/dist). + * + * @return ?string source/dist + * @phpstan-return 'source'|'dist'|null + */ + public function getInstallationSource(): ?string; + + /** + * Returns the repository type of this package, e.g. git, svn + * + * @return ?string The repository type + */ + public function getSourceType(): ?string; + + /** + * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git + * + * @return ?string The repository url + */ + public function getSourceUrl(): ?string; + + /** + * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git + * + * @return list + */ + public function getSourceUrls(): array; + + /** + * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git + * + * @return ?string The repository reference + */ + public function getSourceReference(): ?string; + + /** + * Returns the source mirrors of this package + * + * @return ?list + */ + public function getSourceMirrors(): ?array; + + /** + * @param null|list $mirrors + */ + public function setSourceMirrors(?array $mirrors): void; + + /** + * Returns the type of the distribution archive of this version, e.g. zip, tarball + * + * @return ?string The repository type + */ + public function getDistType(): ?string; + + /** + * Returns the url of the distribution archive of this version + * + * @return ?non-empty-string + */ + public function getDistUrl(): ?string; + + /** + * Returns the urls of the distribution archive of this version, including mirrors + * + * @return non-empty-string[] + */ + public function getDistUrls(): array; + + /** + * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git + * + * @return ?string + */ + public function getDistReference(): ?string; + + /** + * Returns the sha1 checksum for the distribution archive of this version + * + * Can be an empty string which should be treated as null + * + * @return ?string + */ + public function getDistSha1Checksum(): ?string; + + /** + * Returns the dist mirrors of this package + * + * @return ?list + */ + public function getDistMirrors(): ?array; + + /** + * @param null|list $mirrors + */ + public function setDistMirrors(?array $mirrors): void; + + /** + * Returns the version of this package + * + * @return string version + */ + public function getVersion(): string; + + /** + * Returns the pretty (i.e. non-normalized) version string of this package + * + * @return string version + */ + public function getPrettyVersion(): string; + + /** + * Returns the pretty version string plus a git or hg commit hash of this package + * + * @see getPrettyVersion + * + * @param bool $truncate If the source reference is a sha1 hash, truncate it + * @param int $displayMode One of the DISPLAY_ constants on this interface determining display of references + * @return string version + * + * @phpstan-param self::DISPLAY_SOURCE_REF_IF_DEV|self::DISPLAY_SOURCE_REF|self::DISPLAY_DIST_REF $displayMode + */ + public function getFullPrettyVersion(bool $truncate = true, int $displayMode = self::DISPLAY_SOURCE_REF_IF_DEV): string; + + /** + * Returns the release date of the package + * + * @return ?\DateTimeInterface + */ + public function getReleaseDate(): ?\DateTimeInterface; + + /** + * Returns the stability of this package: one of (dev, alpha, beta, RC, stable) + * + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public function getStability(): string; + + /** + * Returns a set of links to packages which need to be installed before + * this package can be installed + * + * @return array A map of package links defining required packages, indexed by the require package's name + */ + public function getRequires(): array; + + /** + * Returns a set of links to packages which must not be installed at the + * same time as this package + * + * @return Link[] An array of package links defining conflicting packages + */ + public function getConflicts(): array; + + /** + * Returns a set of links to virtual packages that are provided through + * this package + * + * @return Link[] An array of package links defining provided packages + */ + public function getProvides(): array; + + /** + * Returns a set of links to packages which can alternatively be + * satisfied by installing this package + * + * @return Link[] An array of package links defining replaced packages + */ + public function getReplaces(): array; + + /** + * Returns a set of links to packages which are required to develop + * this package. These are installed if in dev mode. + * + * @return array A map of package links defining packages required for development, indexed by the require package's name + */ + public function getDevRequires(): array; + + /** + * Returns a set of package names and reasons why they are useful in + * combination with this package. + * + * @return array An array of package suggestions with descriptions + * @phpstan-return array + */ + public function getSuggests(): array; + + /** + * Returns an associative array of autoloading rules + * + * {"": {""}} + * + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. + * + * @return array Mapping of autoloading rules + * @phpstan-return AutoloadRules + */ + public function getAutoload(): array; + + /** + * Returns an associative array of dev autoloading rules + * + * {"": {""}} + * + * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to + * directories for autoloading using the type specified. + * + * @return array Mapping of dev autoloading rules + * @phpstan-return DevAutoloadRules + */ + public function getDevAutoload(): array; + + /** + * Returns a list of directories which should get added to PHP's + * include path. + * + * @return string[] + */ + public function getIncludePaths(): array; + + /** + * Returns the settings for php extension packages + * + * @return array|null + * + * @phpstan-return PhpExtConfig|null + */ + public function getPhpExt(): ?array; + + /** + * Stores a reference to the repository that owns the package + */ + public function setRepository(RepositoryInterface $repository): void; + + /** + * Returns a reference to the repository that owns the package + * + * @return ?RepositoryInterface + */ + public function getRepository(): ?RepositoryInterface; + + /** + * Returns the package binaries + * + * @return string[] + */ + public function getBinaries(): array; + + /** + * Returns package unique name, constructed from name and version. + */ + public function getUniqueName(): string; + + /** + * Returns the package notification url + * + * @return ?string + */ + public function getNotificationUrl(): ?string; + + /** + * Converts the package into a readable and unique string + */ + public function __toString(): string; + + /** + * Converts the package into a pretty readable string + */ + public function getPrettyString(): string; + + public function isDefaultBranch(): bool; + + /** + * Returns a list of options to download package dist files + * + * @return mixed[] + */ + public function getTransportOptions(): array; + + /** + * Configures the list of options to download package dist files + * + * @param mixed[] $options + */ + public function setTransportOptions(array $options): void; + + public function setSourceReference(?string $reference): void; + + public function setDistUrl(?string $url): void; + + public function setDistType(?string $type): void; + + public function setDistReference(?string $reference): void; + + /** + * Set dist and source references and update dist URL for ones that contain a reference + */ + public function setSourceDistReferences(string $reference): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php b/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php new file mode 100644 index 0000000..57768d5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootAliasPackage.php @@ -0,0 +1,223 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * @author Jordi Boggiano + */ +class RootAliasPackage extends CompleteAliasPackage implements RootPackageInterface +{ + /** @var RootPackage */ + protected $aliasOf; + + /** + * All descendants' constructors should call this parent constructor + * + * @param RootPackage $aliasOf The package this package is an alias of + * @param string $version The version the alias must report + * @param string $prettyVersion The alias's non-normalized version + */ + public function __construct(RootPackage $aliasOf, string $version, string $prettyVersion) + { + parent::__construct($aliasOf, $version, $prettyVersion); + } + + /** + * @return RootPackage + */ + public function getAliasOf() + { + return $this->aliasOf; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return $this->aliasOf->getAliases(); + } + + /** + * @inheritDoc + */ + public function getMinimumStability(): string + { + return $this->aliasOf->getMinimumStability(); + } + + /** + * @inheritDoc + */ + public function getStabilityFlags(): array + { + return $this->aliasOf->getStabilityFlags(); + } + + /** + * @inheritDoc + */ + public function getReferences(): array + { + return $this->aliasOf->getReferences(); + } + + /** + * @inheritDoc + */ + public function getPreferStable(): bool + { + return $this->aliasOf->getPreferStable(); + } + + /** + * @inheritDoc + */ + public function getConfig(): array + { + return $this->aliasOf->getConfig(); + } + + /** + * @inheritDoc + */ + public function setRequires(array $requires): void + { + $this->requires = $this->replaceSelfVersionDependencies($requires, Link::TYPE_REQUIRE); + + $this->aliasOf->setRequires($requires); + } + + /** + * @inheritDoc + */ + public function setDevRequires(array $devRequires): void + { + $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, Link::TYPE_DEV_REQUIRE); + + $this->aliasOf->setDevRequires($devRequires); + } + + /** + * @inheritDoc + */ + public function setConflicts(array $conflicts): void + { + $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, Link::TYPE_CONFLICT); + $this->aliasOf->setConflicts($conflicts); + } + + /** + * @inheritDoc + */ + public function setProvides(array $provides): void + { + $this->provides = $this->replaceSelfVersionDependencies($provides, Link::TYPE_PROVIDE); + $this->aliasOf->setProvides($provides); + } + + /** + * @inheritDoc + */ + public function setReplaces(array $replaces): void + { + $this->replaces = $this->replaceSelfVersionDependencies($replaces, Link::TYPE_REPLACE); + $this->aliasOf->setReplaces($replaces); + } + + /** + * @inheritDoc + */ + public function setAutoload(array $autoload): void + { + $this->aliasOf->setAutoload($autoload); + } + + /** + * @inheritDoc + */ + public function setDevAutoload(array $devAutoload): void + { + $this->aliasOf->setDevAutoload($devAutoload); + } + + /** + * @inheritDoc + */ + public function setStabilityFlags(array $stabilityFlags): void + { + $this->aliasOf->setStabilityFlags($stabilityFlags); + } + + /** + * @inheritDoc + */ + public function setMinimumStability(string $minimumStability): void + { + $this->aliasOf->setMinimumStability($minimumStability); + } + + /** + * @inheritDoc + */ + public function setPreferStable(bool $preferStable): void + { + $this->aliasOf->setPreferStable($preferStable); + } + + /** + * @inheritDoc + */ + public function setConfig(array $config): void + { + $this->aliasOf->setConfig($config); + } + + /** + * @inheritDoc + */ + public function setReferences(array $references): void + { + $this->aliasOf->setReferences($references); + } + + /** + * @inheritDoc + */ + public function setAliases(array $aliases): void + { + $this->aliasOf->setAliases($aliases); + } + + /** + * @inheritDoc + */ + public function setSuggests(array $suggests): void + { + $this->aliasOf->setSuggests($suggests); + } + + /** + * @inheritDoc + */ + public function setExtra(array $extra): void + { + $this->aliasOf->setExtra($extra); + } + + public function __clone() + { + parent::__clone(); + $this->aliasOf = clone $this->aliasOf; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/RootPackage.php b/vendor/composer/composer/src/Composer/Package/RootPackage.php new file mode 100644 index 0000000..a0f8e65 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootPackage.php @@ -0,0 +1,132 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * The root package represents the project's composer.json and contains additional metadata + * + * @author Jordi Boggiano + */ +class RootPackage extends CompletePackage implements RootPackageInterface +{ + public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set'; + + /** @var key-of */ + protected $minimumStability = 'stable'; + /** @var bool */ + protected $preferStable = false; + /** @var array Map of package name to stability constant */ + protected $stabilityFlags = []; + /** @var mixed[] */ + protected $config = []; + /** @var array Map of package name to reference/commit hash */ + protected $references = []; + /** @var list */ + protected $aliases = []; + + /** + * @inheritDoc + */ + public function setMinimumStability(string $minimumStability): void + { + $this->minimumStability = $minimumStability; + } + + /** + * @inheritDoc + */ + public function getMinimumStability(): string + { + return $this->minimumStability; + } + + /** + * @inheritDoc + */ + public function setStabilityFlags(array $stabilityFlags): void + { + $this->stabilityFlags = $stabilityFlags; + } + + /** + * @inheritDoc + */ + public function getStabilityFlags(): array + { + return $this->stabilityFlags; + } + + /** + * @inheritDoc + */ + public function setPreferStable(bool $preferStable): void + { + $this->preferStable = $preferStable; + } + + /** + * @inheritDoc + */ + public function getPreferStable(): bool + { + return $this->preferStable; + } + + /** + * @inheritDoc + */ + public function setConfig(array $config): void + { + $this->config = $config; + } + + /** + * @inheritDoc + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * @inheritDoc + */ + public function setReferences(array $references): void + { + $this->references = $references; + } + + /** + * @inheritDoc + */ + public function getReferences(): array + { + return $this->references; + } + + /** + * @inheritDoc + */ + public function setAliases(array $aliases): void + { + $this->aliases = $aliases; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return $this->aliases; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php b/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php new file mode 100644 index 0000000..8a08060 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/RootPackageInterface.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package; + +/** + * Defines additional fields that are only needed for the root package + * + * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice. + * + * @author Jordi Boggiano + * + * @phpstan-import-type AutoloadRules from PackageInterface + * @phpstan-import-type DevAutoloadRules from PackageInterface + */ +interface RootPackageInterface extends CompletePackageInterface +{ + /** + * Returns a set of package names and their aliases + * + * @return list + */ + public function getAliases(): array; + + /** + * Returns the minimum stability of the package + * + * @return key-of + */ + public function getMinimumStability(): string; + + /** + * Returns the stability flags to apply to dependencies + * + * array('foo/bar' => 'dev') + * + * @return array + */ + public function getStabilityFlags(): array; + + /** + * Returns a set of package names and source references that must be enforced on them + * + * array('foo/bar' => 'abcd1234') + * + * @return array + */ + public function getReferences(): array; + + /** + * Returns true if the root package prefers picking stable packages over unstable ones + */ + public function getPreferStable(): bool; + + /** + * Returns the root package's configuration + * + * @return mixed[] + */ + public function getConfig(): array; + + /** + * Set the required packages + * + * @param Link[] $requires A set of package links + */ + public function setRequires(array $requires): void; + + /** + * Set the recommended packages + * + * @param Link[] $devRequires A set of package links + */ + public function setDevRequires(array $devRequires): void; + + /** + * Set the conflicting packages + * + * @param Link[] $conflicts A set of package links + */ + public function setConflicts(array $conflicts): void; + + /** + * Set the provided virtual packages + * + * @param Link[] $provides A set of package links + */ + public function setProvides(array $provides): void; + + /** + * Set the packages this one replaces + * + * @param Link[] $replaces A set of package links + */ + public function setReplaces(array $replaces): void; + + /** + * Set the autoload mapping + * + * @param array $autoload Mapping of autoloading rules + * @phpstan-param AutoloadRules $autoload + */ + public function setAutoload(array $autoload): void; + + /** + * Set the dev autoload mapping + * + * @param array $devAutoload Mapping of dev autoloading rules + * @phpstan-param DevAutoloadRules $devAutoload + */ + public function setDevAutoload(array $devAutoload): void; + + /** + * Set the stabilityFlags + * + * @phpstan-param array $stabilityFlags + */ + public function setStabilityFlags(array $stabilityFlags): void; + + /** + * Set the minimumStability + * + * @phpstan-param key-of $minimumStability + */ + public function setMinimumStability(string $minimumStability): void; + + /** + * Set the preferStable + */ + public function setPreferStable(bool $preferStable): void; + + /** + * Set the config + * + * @param mixed[] $config + */ + public function setConfig(array $config): void; + + /** + * Set the references + * + * @param array $references + */ + public function setReferences(array $references): void; + + /** + * Set the aliases + * + * @param list $aliases + */ + public function setAliases(array $aliases): void; + + /** + * Set the suggested packages + * + * @param array $suggests A set of package names/comments + */ + public function setSuggests(array $suggests): void; + + /** + * @param mixed[] $extra + */ + public function setExtra(array $extra): void; +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php b/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php new file mode 100644 index 0000000..7e0182a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php @@ -0,0 +1,49 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Package\BasePackage; + +/** + * @author Jordi Boggiano + */ +class StabilityFilter +{ + /** + * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags + * + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param string[] $names The package name(s) to check for stability flags + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + * @return bool true if any package name is acceptable + */ + public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability): bool + { + foreach ($names as $name) { + // allow if package matches the package-specific stability flag + if (isset($stabilityFlags[$name])) { + if (BasePackage::STABILITIES[$stability] <= $stabilityFlags[$name]) { + return true; + } + } elseif (isset($acceptableStabilities[$stability])) { + // allow if package matches the global stability requirement and has no exception + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php b/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php new file mode 100644 index 0000000..b100d0e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Package\PackageInterface; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Intervals; +use Composer\Util\Platform; + +/** + * @author Jordi Boggiano + * @internal + */ +class VersionBumper +{ + /** + * Given a constraint, this returns a new constraint with + * the lower bound bumped to match the given package's version. + * + * For example: + * * ^1.0 + 1.2.1 -> ^1.2.1 + * * ^1.2 + 1.2.0 -> ^1.2 + * * ^1.2.0 + 1.3.0 -> ^1.3.0 + * * ^1.2 || ^2.3 + 1.3.0 -> ^1.3 || ^2.3 + * * ^1.2 || ^2.3 + 2.4.0 -> ^1.2 || ^2.4 + * * ^3@dev + 3.2.99999-dev -> ^3.2@dev + * * ~2 + 2.0-beta.1 -> ~2 + * * ~2.0.0 + 2.0.3 -> ~2.0.3 + * * ~2.0 + 2.0.3 -> ^2.0.3 + * * dev-master + dev-master -> dev-master + * * * + 1.2.3 -> >=1.2.3 + */ + public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string + { + $parser = new VersionParser(); + $prettyConstraint = $constraint->getPrettyString(); + if (str_starts_with($constraint->getPrettyString(), 'dev-')) { + return $prettyConstraint; + } + + $version = $package->getVersion(); + if (str_starts_with($package->getVersion(), 'dev-')) { + $loader = new ArrayLoader($parser); + $dumper = new ArrayDumper(); + $extra = $loader->getBranchAlias($dumper->dump($package)); + + // dev packages without branch alias cannot be processed + if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) { + return $prettyConstraint; + } + + $version = $extra; + } + + $intervals = Intervals::get($constraint); + + // complex constraints with branch names are not bumped + if (\count($intervals['branches']['names']) > 0) { + return $prettyConstraint; + } + + $major = Preg::replace('{^(\d+).*}', '$1', $version); + $versionWithoutSuffix = Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version); + $newPrettyConstraint = '^'.$versionWithoutSuffix; + + // not a simple stable version, abort + if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) { + return $prettyConstraint; + } + + $pattern = '{ + (?<=,|\ |\||^) # leading separator + (?P + \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything + | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2 + | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc + | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc + | \* # full wildcard + ) + (?=,|$|\ |\||@) # trailing separator + }x'; + if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) { + $modified = $prettyConstraint; + foreach (array_reverse($matches['constraint']) as $match) { + assert(is_string($match[0])); + $suffix = ''; + if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) { + $suffix = '.0'; + } + if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) { + // take as many version bits from the current version as we have in the constraint to bump it without making it more specific + $versionBits = explode('.', $versionWithoutSuffix); + $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0'); + $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1)); + } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) { + $replacement = '>='.$versionWithoutSuffix.$suffix; + } else { + $replacement = $newPrettyConstraint.$suffix; + } + $modified = substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0])); + } + + // if it is strictly equal to the previous one then no need to change anything + $newConstraint = $parser->parseConstraints($modified); + if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) { + return $prettyConstraint; + } + + return $modified; + } + + return $prettyConstraint; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php b/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php new file mode 100644 index 0000000..7e0f827 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php @@ -0,0 +1,449 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Repository\Vcs\HgDriver; +use Composer\IO\NullIO; +use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Util\Git as GitUtil; +use Composer\Util\HttpDownloader; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Svn as SvnUtil; +use React\Promise\CancellablePromiseInterface; +use Symfony\Component\Process\Process; + +/** + * Try to guess the current version number based on different VCS configuration. + * + * @author Jordi Boggiano + * @author Samuel Roze + * + * @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null}|array{version: string, commit: string|null, pretty_version: string|null, feature_version: string|null, feature_pretty_version: string|null} + */ +class VersionGuesser +{ + /** + * @var Config + */ + private $config; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * @var SemverVersionParser + */ + private $versionParser; + + /** + * @var IOInterface|null + */ + private $io; + + public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser, ?IOInterface $io = null) + { + $this->config = $config; + $this->process = $process; + $this->versionParser = $versionParser; + $this->io = $io; + } + + /** + * @param array $packageConfig + * @param string $path Path to guess into + * + * @phpstan-return Version|null + */ + public function guessVersion(array $packageConfig, string $path): ?array + { + if (!function_exists('proc_open')) { + return null; + } + + // bypass version guessing in bash completions as it takes time to create + // new processes and the root version is usually not that important + if (Platform::isInputCompletionProcess()) { + return null; + } + + $versionData = $this->guessGitVersion($packageConfig, $path); + if (null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessHgVersion($packageConfig, $path); + if (null !== $versionData && null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessFossilVersion($path); + if (null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + $versionData = $this->guessSvnVersion($packageConfig, $path); + if (null !== $versionData && null !== $versionData['version']) { + return $this->postprocess($versionData); + } + + return null; + } + + /** + * @phpstan-param Version $versionData + * + * @phpstan-return Version + */ + private function postprocess(array $versionData): array + { + if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) { + unset($versionData['feature_version'], $versionData['feature_pretty_version']); + } + + if ('-dev' === substr($versionData['version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['version'])) { + $versionData['pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['version']); + } + + if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['feature_version'])) { + $versionData['feature_pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['feature_version']); + } + + return $versionData; + } + + /** + * @param array $packageConfig + * + * @return array{version: string|null, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null} + */ + private function guessGitVersion(array $packageConfig, string $path): array + { + GitUtil::cleanEnv(); + $commit = null; + $version = null; + $prettyVersion = null; + $featureVersion = null; + $featurePrettyVersion = null; + $isDetached = false; + + // try to fetch current version from git branch + if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) { + $branches = []; + $isFeatureBranch = false; + + // find current branch and collect all branch names + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { + if ( + $match[1] === '(no branch)' + || strpos($match[1], '(detached ') === 0 + || strpos($match[1], '(HEAD detached at') === 0 + ) { + $version = 'dev-' . $match[2]; + $prettyVersion = $version; + $isFeatureBranch = true; + $isDetached = true; + } else { + $version = $this->versionParser->normalizeBranch($match[1]); + $prettyVersion = 'dev-' . $match[1]; + $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]); + } + + $commit = $match[2]; + } + + if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) { + if (Preg::isMatchStrictGroups('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) { + $branches[] = $match[1]; + } + } + } + + if ($isFeatureBranch) { + $featureVersion = $version; + $featurePrettyVersion = $prettyVersion; + + // try to find the best (nearest) version branch to assume this feature's version + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path); + $version = $result['version']; + $prettyVersion = $result['pretty_version']; + } + } + GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io); + + if (!$version || $isDetached) { + $result = $this->versionFromGitTags($path); + if ($result) { + $version = $result['version']; + $prettyVersion = $result['pretty_version']; + $featureVersion = null; + $featurePrettyVersion = null; + } + } + + if (null === $commit) { + $command = array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process)); + if (0 === $this->process->execute($command, $output, $path)) { + $commit = trim($output) ?: null; + } + } + + if ($featureVersion) { + return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion]; + } + + return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion]; + } + + /** + * @return array{version: string, pretty_version: string}|null + */ + private function versionFromGitTags(string $path): ?array + { + // try to fetch current version from git tags + if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) { + try { + $version = $this->versionParser->normalize(trim($output)); + + return ['version' => $version, 'pretty_version' => trim($output)]; + } catch (\Exception $e) { + } + } + + return null; + } + + /** + * @param array $packageConfig + * + * @return array{version: string|null, commit: ''|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}|null + */ + private function guessHgVersion(array $packageConfig, string $path): ?array + { + // try to fetch current version from hg branch + if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) { + $branch = trim($output); + $version = $this->versionParser->normalizeBranch($branch); + $isFeatureBranch = 0 === strpos($version, 'dev-'); + + if (VersionParser::DEFAULT_BRANCH_ALIAS === $version) { + return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch]; + } + + if (!$isFeatureBranch) { + return ['version' => $version, 'commit' => null, 'pretty_version' => $version]; + } + + // re-use the HgDriver to fetch branches (this properly includes bookmarks) + $io = new NullIO(); + $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process); + $branches = array_map('strval', array_keys($driver->getBranches())); + + // try to find the best (nearest) version branch to assume this feature's version + $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path); + $result['commit'] = ''; + $result['feature_version'] = $version; + $result['feature_pretty_version'] = $version; + + return $result; + } + + return null; + } + + /** + * @param array $packageConfig + * @param list $branches + * @param list $scmCmdline + * + * @return array{version: string|null, pretty_version: string|null} + */ + private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array + { + $prettyVersion = $version; + + // ignore feature branches if they have no branch-alias or self.version is used + // and find the branch they came from to use as a version instead + if (!isset($packageConfig['extra']['branch-alias'][$version]) + || strpos(json_encode($packageConfig), '"self.version"') + ) { + $branch = Preg::replace('{^dev-}', '', $version); + $length = PHP_INT_MAX; + + // return directly, if branch is configured to be non-feature branch + if (!$this->isFeatureBranch($packageConfig, $branch)) { + return ['version' => $version, 'pretty_version' => $prettyVersion]; + } + + // sort local branches first then remote ones + // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, 1.9 is picked + // and sort using natural sort so that 1.10 will appear before 1.9 + usort($branches, static function ($a, $b): int { + $aRemote = 0 === strpos($a, 'remotes/'); + $bRemote = 0 === strpos($b, 'remotes/'); + + if ($aRemote !== $bRemote) { + return $aRemote ? 1 : -1; + } + + return strnatcasecmp($b, $a); + }); + + $promises = []; + $this->process->setMaxJobs(30); + try { + $lastIndex = -1; + foreach ($branches as $index => $candidate) { + $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate); + + // do not compare against itself or other feature branches + if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) { + continue; + } + + $cmdLine = array_map(static function (string $component) use ($candidate, $branch) { + return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component); + }, $scmCmdline); + $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { + if (!$process->isSuccessful()) { + return; + } + + $output = $process->getOutput(); + // overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version) + // as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version + // but if it doesn't and they have equal diffs, then it probably is based on the old version + if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) { + $lastIndex = $index; + $length = strlen($output); + $version = $this->versionParser->normalizeBranch($candidateVersion); + $prettyVersion = 'dev-' . $candidateVersion; + if ($length === 0) { + foreach ($promises as $promise) { + // to support react/promise 2.x we wrap the promise in a resolve() call for safety + \React\Promise\resolve($promise)->cancel(); + } + } + } + }); + } + + $this->process->wait(); + } finally { + $this->process->resetMaxJobs(); + } + } + + return ['version' => $version, 'pretty_version' => $prettyVersion]; + } + + /** + * @param array $packageConfig + */ + private function isFeatureBranch(array $packageConfig, ?string $branchName): bool + { + $nonFeatureBranches = ''; + if (!empty($packageConfig['non-feature-branches'])) { + $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']); + } + + return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\d+\..+)$}', $branchName, $match); + } + + /** + * @return array{version: string|null, commit: '', pretty_version: string|null} + */ + private function guessFossilVersion(string $path): array + { + $version = null; + $prettyVersion = null; + + // try to fetch current version from fossil + if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) { + $branch = trim($output); + $version = $this->versionParser->normalizeBranch($branch); + $prettyVersion = 'dev-' . $branch; + } + + // try to fetch current version from fossil tags + if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) { + try { + $version = $this->versionParser->normalize(trim($output)); + $prettyVersion = trim($output); + } catch (\Exception $e) { + } + } + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + + /** + * @param array $packageConfig + * + * @return array{version: string, commit: '', pretty_version: string}|null + */ + private function guessSvnVersion(array $packageConfig, string $path): ?array + { + SvnUtil::cleanEnv(); + + // try to fetch current version from svn + if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) { + $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; + $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; + $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; + + $urlPattern = '#.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))#'; + + if (Preg::isMatch($urlPattern, $output, $matches)) { + if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { + // we are in a branches path + $version = $this->versionParser->normalizeBranch($matches[3]); + $prettyVersion = 'dev-' . $matches[3]; + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + + assert(is_string($matches[1])); + $prettyVersion = trim($matches[1]); + if ($prettyVersion === 'trunk') { + $version = 'dev-trunk'; + } else { + $version = $this->versionParser->normalize($prettyVersion); + } + + return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; + } + } + + return null; + } + + public function getRootVersionFromEnv(): string + { + $version = Platform::getEnv('COMPOSER_ROOT_VERSION'); + if (!is_string($version) || $version === '') { + throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty'); + } + if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) { + $version = $match[1].'.x-dev'; + } + + return $version; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php b/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php new file mode 100644 index 0000000..b2859c1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionParser.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Pcre\Preg; +use Composer\Repository\PlatformRepository; +use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Semver\Semver; +use Composer\Semver\Constraint\ConstraintInterface; + +class VersionParser extends SemverVersionParser +{ + public const DEFAULT_BRANCH_ALIAS = '9999999-dev'; + + /** @var array Constraint parsing cache */ + private static $constraints = []; + + /** + * @inheritDoc + */ + public function parseConstraints($constraints): ConstraintInterface + { + if (!isset(self::$constraints[$constraints])) { + self::$constraints[$constraints] = parent::parseConstraints($constraints); + } + + return self::$constraints[$constraints]; + } + + /** + * Parses an array of strings representing package/version pairs. + * + * The parsing results in an array of arrays, each of which + * contain a 'name' key with value and optionally a 'version' key with value. + * + * @param string[] $pairs a set of package/version pairs separated by ":", "=" or " " + * + * @return list + */ + public function parseNameVersionPairs(array $pairs): array + { + $pairs = array_values($pairs); + $result = []; + + for ($i = 0, $count = count($pairs); $i < $count; $i++) { + $pair = Preg::replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i])); + if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/') && !Preg::isMatch('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $pairs[$i + 1]) && !PlatformRepository::isPlatformPackage($pairs[$i + 1])) { + $pair .= ' '.$pairs[$i + 1]; + $i++; + } + + if (strpos($pair, ' ')) { + [$name, $version] = explode(' ', $pair, 2); + $result[] = ['name' => $name, 'version' => $version]; + } else { + $result[] = ['name' => $pair]; + } + } + + return $result; + } + + public static function isUpgrade(string $normalizedFrom, string $normalizedTo): bool + { + if ($normalizedFrom === $normalizedTo) { + return true; + } + + if (in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS; + } + if (in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], true)) { + $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS; + } + + if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) { + return true; + } + + $sorted = Semver::sort([$normalizedTo, $normalizedFrom]); + + return $sorted[0] === $normalizedFrom; + } +} diff --git a/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php b/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php new file mode 100644 index 0000000..7c0c61a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php @@ -0,0 +1,273 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; +use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; +use Composer\Composer; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Pcre\Preg; +use Composer\Repository\RepositorySet; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Selects the best possible version for a package + * + * @author Ryan Weaver + * @author Jordi Boggiano + */ +class VersionSelector +{ + /** @var RepositorySet */ + private $repositorySet; + + /** @var array */ + private $platformConstraints = []; + + /** @var VersionParser */ + private $parser; + + /** + * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages + */ + public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null) + { + $this->repositorySet = $repositorySet; + if ($platformRepo) { + foreach ($platformRepo->getPackages() as $package) { + $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion()); + } + } + } + + /** + * Given a package name and optional version, returns the latest PackageInterface + * that matches. + * + * @param string $targetPackageVersion + * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter + * @param IOInterface|null $io If passed, warnings will be output there in case versions cannot be selected due to platform requirements + * @param callable(PackageInterface):bool|bool $showWarnings + * @return PackageInterface|false + */ + public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) + { + if (!isset(BasePackage::STABILITIES[$preferredStability])) { + // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version + throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability); + } + + if (null === $platformRequirementFilter) { + $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); + } elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) { + trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED); + $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter); + } + + $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; + $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags); + + $minPriority = BasePackage::STABILITIES[$preferredStability]; + usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) { + $aPriority = $a->getStabilityPriority(); + $bPriority = $b->getStabilityPriority(); + + // A is less stable than our preferred stability, + // and B is more stable than A, select B + if ($minPriority < $aPriority && $bPriority < $aPriority) { + return 1; + } + + // A is less stable than our preferred stability, + // and B is less stable than A, select A + if ($minPriority < $aPriority && $aPriority < $bPriority) { + return -1; + } + + // A is more stable than our preferred stability, + // and B is less stable than preferred stability, select A + if ($minPriority >= $aPriority && $minPriority < $bPriority) { + return -1; + } + + // select highest version of the two + return version_compare($b->getVersion(), $a->getVersion()); + }); + + if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) { + /** @var array $alreadyWarnedNames */ + $alreadyWarnedNames = []; + /** @var array $alreadySeenNames */ + $alreadySeenNames = []; + + foreach ($candidates as $pkg) { + $reqs = $pkg->getRequires(); + $skip = false; + foreach ($reqs as $name => $link) { + if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) { + continue; + } + if (isset($this->platformConstraints[$name])) { + foreach ($this->platformConstraints[$name] as $providedConstraint) { + if ($link->getConstraint()->matches($providedConstraint)) { + // constraint satisfied, go to next require + continue 2; + } + if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) { + $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint()); + if ($filteredConstraint->matches($providedConstraint)) { + // constraint satisfied with the upper bound ignored, go to next require + continue 2; + } + } + } + + // constraint not satisfied + $reason = 'is not satisfied by your platform'; + } else { + // Package requires a platform package that is unknown on current platform. + // It means that current platform cannot validate this constraint and so package is not installable. + $reason = 'is missing from your platform'; + } + + $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); + $alreadySeenNames[$pkg->getName()] = true; + if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) { + $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]); + $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true; + $latest = $isLatestVersion ? "'s latest version" : ''; + $io->writeError( + 'Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.', + true, + $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE + ); + } + + // skip candidate + $skip = true; + } + + if ($skip) { + continue; + } + + $package = $pkg; + break; + } + } else { + $package = count($candidates) > 0 ? $candidates[0] : null; + } + + if (!isset($package)) { + return false; + } + + // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias + if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $package = $package->getAliasOf(); + } + + return $package; + } + + /** + * Given a concrete version, this returns a ^ constraint (when possible) + * that should be used, for example, in composer.json. + * + * For example: + * * 1.2.1 -> ^1.2 + * * 1.2.1.2 -> ^1.2 + * * 1.2 -> ^1.2 + * * v3.2.1 -> ^3.2 + * * 2.0-beta.1 -> ^2.0@beta + * * dev-master -> ^2.1@dev (dev version with alias) + * * dev-master -> dev-master (dev versions are untouched) + */ + public function findRecommendedRequireVersion(PackageInterface $package): string + { + // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify + // the requires and have only one required version to change when bumping the php requirement + if (0 === strpos($package->getName(), 'ext-')) { + $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); + if ($phpVersion === $extVersion) { + return '*'; + } + } + + $version = $package->getVersion(); + if (!$package->isDev()) { + return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); + } + + $loader = new ArrayLoader($this->getParser()); + $dumper = new ArrayDumper(); + $extra = $loader->getBranchAlias($dumper->dump($package)); + if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) { + $extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count); + if ($count > 0) { + $extra = str_replace('.9999999', '.0', $extra); + + return $this->transformVersion($extra, $extra, 'dev'); + } + } + + return $package->getPrettyVersion(); + } + + private function transformVersion(string $version, string $prettyVersion, string $stability): string + { + // attempt to transform 2.1.1 to 2.1 + // this allows you to upgrade through minor versions + $semanticVersionParts = explode('.', $version); + + // check to see if we have a semver-looking version + if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) { + // remove the last parts (i.e. the patch version number and any extra) + if ($semanticVersionParts[0] === '0') { + unset($semanticVersionParts[3]); + } else { + unset($semanticVersionParts[2], $semanticVersionParts[3]); + } + $version = implode('.', $semanticVersionParts); + } else { + return $prettyVersion; + } + + // append stability flag if not default + if ($stability !== 'stable') { + $version .= '@'.$stability; + } + + // 2.1 -> ^2.1 + return '^' . $version; + } + + private function getParser(): VersionParser + { + if ($this->parser === null) { + $this->parser = new VersionParser(); + } + + return $this->parser; + } +} diff --git a/vendor/composer/composer/src/Composer/PartialComposer.php b/vendor/composer/composer/src/Composer/PartialComposer.php new file mode 100644 index 0000000..f4b7910 --- /dev/null +++ b/vendor/composer/composer/src/Composer/PartialComposer.php @@ -0,0 +1,130 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Package\RootPackageInterface; +use Composer\Util\Loop; +use Composer\Repository\RepositoryManager; +use Composer\Installer\InstallationManager; +use Composer\EventDispatcher\EventDispatcher; + +/** + * @author Jordi Boggiano + */ +class PartialComposer +{ + /** + * @var bool + */ + private $global = false; + + /** + * @var RootPackageInterface + */ + private $package; + + /** + * @var Loop + */ + private $loop; + + /** + * @var Repository\RepositoryManager + */ + private $repositoryManager; + + /** + * @var Installer\InstallationManager + */ + private $installationManager; + + /** + * @var Config + */ + private $config; + + /** + * @var EventDispatcher + */ + private $eventDispatcher; + + public function setPackage(RootPackageInterface $package): void + { + $this->package = $package; + } + + public function getPackage(): RootPackageInterface + { + return $this->package; + } + + public function setConfig(Config $config): void + { + $this->config = $config; + } + + public function getConfig(): Config + { + return $this->config; + } + + public function setLoop(Loop $loop): void + { + $this->loop = $loop; + } + + public function getLoop(): Loop + { + return $this->loop; + } + + public function setRepositoryManager(RepositoryManager $manager): void + { + $this->repositoryManager = $manager; + } + + public function getRepositoryManager(): RepositoryManager + { + return $this->repositoryManager; + } + + public function setInstallationManager(InstallationManager $manager): void + { + $this->installationManager = $manager; + } + + public function getInstallationManager(): InstallationManager + { + return $this->installationManager; + } + + public function setEventDispatcher(EventDispatcher $eventDispatcher): void + { + $this->eventDispatcher = $eventDispatcher; + } + + public function getEventDispatcher(): EventDispatcher + { + return $this->eventDispatcher; + } + + public function isGlobal(): bool + { + return $this->global; + } + + public function setGlobal(): void + { + $this->global = true; + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php b/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php new file mode 100644 index 0000000..284b0ba --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/HhvmDetector.php @@ -0,0 +1,61 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetector +{ + /** @var string|false|null */ + private static $hhvmVersion = null; + /** @var ?ExecutableFinder */ + private $executableFinder; + /** @var ?ProcessExecutor */ + private $processExecutor; + + public function __construct(?ExecutableFinder $executableFinder = null, ?ProcessExecutor $processExecutor = null) + { + $this->executableFinder = $executableFinder; + $this->processExecutor = $processExecutor; + } + + public function reset(): void + { + self::$hhvmVersion = null; + } + + public function getVersion(): ?string + { + if (null !== self::$hhvmVersion) { + return self::$hhvmVersion ?: null; + } + + self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if (self::$hhvmVersion === null && !Platform::isWindows()) { + self::$hhvmVersion = false; + $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); + $hhvmPath = $this->executableFinder->find('hhvm'); + if ($hhvmPath !== null) { + $this->processExecutor = $this->processExecutor ?? new ProcessExecutor(); + $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion); + if ($exitCode !== 0) { + self::$hhvmVersion = false; + } + } + } + + return self::$hhvmVersion ?: null; + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/Runtime.php b/vendor/composer/composer/src/Composer/Platform/Runtime.php new file mode 100644 index 0000000..940c02d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/Runtime.php @@ -0,0 +1,106 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +class Runtime +{ + /** + * @param class-string $class + */ + public function hasConstant(string $constant, ?string $class = null): bool + { + return defined(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param class-string $class + * + * @return mixed + */ + public function getConstant(string $constant, ?string $class = null) + { + return constant(ltrim($class.'::'.$constant, ':')); + } + + public function hasFunction(string $fn): bool + { + return function_exists($fn); + } + + /** + * @param mixed[] $arguments + * + * @return mixed + */ + public function invoke(callable $callable, array $arguments = []) + { + return $callable(...$arguments); + } + + /** + * @param class-string $class + */ + public function hasClass(string $class): bool + { + return class_exists($class, false); + } + + /** + * @template T of object + * @param mixed[] $arguments + * + * @phpstan-param class-string $class + * @phpstan-return T + * + * @throws \ReflectionException + */ + public function construct(string $class, array $arguments = []): object + { + if (empty($arguments)) { + return new $class; + } + + $refl = new \ReflectionClass($class); + + return $refl->newInstanceArgs($arguments); + } + + /** @return string[] */ + public function getExtensions(): array + { + return get_loaded_extensions(); + } + + public function getExtensionVersion(string $extension): string + { + $version = phpversion($extension); + if ($version === false) { + $version = '0'; + } + + return $version; + } + + /** + * @throws \ReflectionException + */ + public function getExtensionInfo(string $extension): string + { + $reflector = new \ReflectionExtension($extension); + + ob_start(); + $reflector->info(); + + return ob_get_clean(); + } +} diff --git a/vendor/composer/composer/src/Composer/Platform/Version.php b/vendor/composer/composer/src/Composer/Platform/Version.php new file mode 100644 index 0000000..56abb1b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Platform/Version.php @@ -0,0 +1,92 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Pcre\Preg; + +/** + * @author Lars Strojny + */ +class Version +{ + /** + * @param bool $isFips Set by the method + * + * @param-out bool $isFips + */ + public static function parseOpenssl(string $opensslVersion, ?bool &$isFips): ?string + { + $isFips = false; + + if (!Preg::isMatchStrictGroups('/^(?[0-9.]+)(?[a-z]{0,2})(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)(?:-\w+)?(?: \(.+?\))?$/', $opensslVersion, $matches)) { + return null; + } + + // OpenSSL 1 used 1.2.3a style versioning, 3+ uses semver + $patch = ''; + if (version_compare($matches['version'], '3.0.0', '<')) { + $patch = '.'.self::convertAlphaVersionToIntVersion($matches['patch']); + } + + $isFips = strpos($matches['suffix'], 'fips') !== false; + $suffix = strtr('-'.ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']); + + return rtrim($matches['version'].$patch.$suffix, '-'); + } + + public static function parseLibjpeg(string $libjpegVersion): ?string + { + if (!Preg::isMatchStrictGroups('/^(?\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { + return null; + } + + return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']); + } + + public static function parseZoneinfoVersion(string $zoneinfoVersion): ?string + { + if (!Preg::isMatchStrictGroups('/^(?\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { + return null; + } + + return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']); + } + + /** + * "" => 0, "a" => 1, "zg" => 33 + */ + private static function convertAlphaVersionToIntVersion(string $alpha): int + { + return strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord', str_split($alpha))); + } + + public static function convertLibxpmVersionId(int $versionId): string + { + return self::convertVersionId($versionId, 100); + } + + public static function convertOpenldapVersionId(int $versionId): string + { + return self::convertVersionId($versionId, 100); + } + + private static function convertVersionId(int $versionId, int $base): string + { + return sprintf( + '%d.%d.%d', + $versionId / ($base * $base), + (int) ($versionId / $base) % $base, + $versionId % $base + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php b/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php new file mode 100644 index 0000000..b2237b4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php @@ -0,0 +1,23 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin\Capability; + +/** + * Marker interface for Plugin capabilities. + * Every new Capability which is added to the Plugin API must implement this interface. + * + * @api + */ +interface Capability +{ +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php b/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php new file mode 100644 index 0000000..884b955 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin\Capability; + +/** + * Commands Provider Interface + * + * This capability will receive an array with 'composer' and 'io' keys as + * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface + * instances. It also contains a 'plugin' key containing the plugin instance that + * created the capability. + * + * @author Jérémy Derussé + */ +interface CommandProvider extends Capability +{ + /** + * Retrieves an array of commands + * + * @return \Composer\Command\BaseCommand[] + */ + public function getCommands(); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/Capable.php b/vendor/composer/composer/src/Composer/Plugin/Capable.php new file mode 100644 index 0000000..9a45833 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/Capable.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +/** + * Plugins which need to expose various implementations + * of the Composer Plugin Capabilities must have their + * declared Plugin class implementing this interface. + * + * @api + */ +interface Capable +{ + /** + * Method by which a Plugin announces its API implementations, through an array + * with a special structure. + * + * The key must be a string, representing a fully qualified class/interface name + * which Composer Plugin API exposes. + * The value must be a string as well, representing the fully qualified class name + * of the implementing class. + * + * @tutorial + * + * return array( + * 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider', + * 'Composer\Plugin\Capability\Validator' => 'My\Validator', + * ); + * + * @return string[] + */ + public function getCapabilities(); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php b/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php new file mode 100644 index 0000000..ece2cf5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/CommandEvent.php @@ -0,0 +1,80 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * An event for all commands. + * + * @author Nils Adermann + */ +class CommandEvent extends Event +{ + /** + * @var string + */ + private $commandName; + + /** + * @var InputInterface + */ + private $input; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Constructor. + * + * @param string $name The event name + * @param string $commandName The command name + * @param mixed[] $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, string $commandName, InputInterface $input, OutputInterface $output, array $args = [], array $flags = []) + { + parent::__construct($name, $args, $flags); + $this->commandName = $commandName; + $this->input = $input; + $this->output = $output; + } + + /** + * Returns the command input interface + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Retrieves the command output interface + */ + public function getOutput(): OutputInterface + { + return $this->output; + } + + /** + * Retrieves the name of the command being run + */ + public function getCommandName(): string + { + return $this->commandName; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php b/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php new file mode 100644 index 0000000..a23815e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php @@ -0,0 +1,19 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use UnexpectedValueException; + +class PluginBlockedException extends UnexpectedValueException +{ +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php b/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php new file mode 100644 index 0000000..5a82018 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginEvents.php @@ -0,0 +1,82 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +/** + * The Plugin Events. + * + * @author Nils Adermann + */ +class PluginEvents +{ + /** + * The INIT event occurs after a Composer instance is done being initialized + * + * The event listener method receives a + * Composer\EventDispatcher\Event instance. + * + * @var string + */ + public const INIT = 'init'; + + /** + * The COMMAND event occurs as a command begins + * + * The event listener method receives a + * Composer\Plugin\CommandEvent instance. + * + * @var string + */ + public const COMMAND = 'command'; + + /** + * The PRE_FILE_DOWNLOAD event occurs before downloading a file + * + * The event listener method receives a + * Composer\Plugin\PreFileDownloadEvent instance. + * + * @var string + */ + public const PRE_FILE_DOWNLOAD = 'pre-file-download'; + + /** + * The POST_FILE_DOWNLOAD event occurs after downloading a package dist file + * + * The event listener method receives a + * Composer\Plugin\PostFileDownloadEvent instance. + * + * @var string + */ + public const POST_FILE_DOWNLOAD = 'post-file-download'; + + /** + * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options + * + * The event listener method receives a + * Composer\Plugin\PreCommandRunEvent instance. + * + * @var string + */ + public const PRE_COMMAND_RUN = 'pre-command-run'; + + /** + * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets + * you filter the list of packages which is going to enter the Solver + * + * The event listener method receives a + * Composer\Plugin\PrePoolCreateEvent instance. + * + * @var string + */ + public const PRE_POOL_CREATE = 'pre-pool-create'; +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php b/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php new file mode 100644 index 0000000..22cdf78 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginInterface.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\IO\IOInterface; + +/** + * Plugin interface + * + * @author Nils Adermann + */ +interface PluginInterface +{ + /** + * Version number of the internal composer-plugin-api package + * + * This is used to denote the API version of Plugin specific + * features, but is also bumped to a new major if Composer + * includes a major break in internal APIs which are susceptible + * to be used by plugins. + * + * @var string + */ + public const PLUGIN_API_VERSION = '2.6.0'; + + /** + * Apply plugin modifications to Composer + * + * @return void + */ + public function activate(Composer $composer, IOInterface $io); + + /** + * Remove any hooks from Composer + * + * This will be called when a plugin is deactivated before being + * uninstalled, but also before it gets upgraded to a new version + * so the old one can be deactivated and the new one activated. + * + * @return void + */ + public function deactivate(Composer $composer, IOInterface $io); + + /** + * Prepare the plugin to be uninstalled + * + * This will be called after deactivate. + * + * @return void + */ + public function uninstall(Composer $composer, IOInterface $io); +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PluginManager.php b/vendor/composer/composer/src/Composer/Plugin/PluginManager.php new file mode 100644 index 0000000..5873f85 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PluginManager.php @@ -0,0 +1,793 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Installer\InstallerInterface; +use Composer\IO\IOInterface; +use Composer\Package\BasePackage; +use Composer\Package\CompletePackage; +use Composer\Package\Locker; +use Composer\Package\Package; +use Composer\Package\RootPackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\PartialComposer; +use Composer\Pcre\Preg; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\InstalledRepository; +use Composer\Repository\RepositoryUtils; +use Composer\Repository\RootPackageRepository; +use Composer\Package\PackageInterface; +use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; +use Composer\Plugin\Capability\Capability; +use Composer\Util\PackageSorter; + +/** + * Plugin manager + * + * @author Nils Adermann + * @author Jordi Boggiano + */ +class PluginManager +{ + /** @var Composer */ + protected $composer; + /** @var IOInterface */ + protected $io; + /** @var PartialComposer|null */ + protected $globalComposer; + /** @var VersionParser */ + protected $versionParser; + /** @var bool|'local'|'global' */ + protected $disablePlugins = false; + + /** @var array */ + protected $plugins = []; + /** @var array> */ + protected $registeredPlugins = []; + + /** + * @var array|null + */ + private $allowPluginRules; + + /** + * @var array|null + */ + private $allowGlobalPluginRules; + + /** @var bool */ + private $runningInGlobalDir = false; + + /** @var int */ + private static $classCounter = 0; + + /** + * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins + */ + public function __construct(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false) + { + $this->io = $io; + $this->composer = $composer; + $this->globalComposer = $globalComposer; + $this->versionParser = new VersionParser(); + $this->disablePlugins = $disablePlugins; + $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker()); + $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false); + } + + public function setRunningInGlobalDir(bool $runningInGlobalDir): void + { + $this->runningInGlobalDir = $runningInGlobalDir; + } + + /** + * Loads all plugins from currently installed plugin packages + */ + public function loadInstalledPlugins(): void + { + if (!$this->arePluginsDisabled('local')) { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + $this->loadRepository($repo, false, $this->composer->getPackage()); + } + + if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { + $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); + } + } + + /** + * Deactivate all plugins from currently installed plugin packages + */ + public function deactivateInstalledPlugins(): void + { + if (!$this->arePluginsDisabled('local')) { + $repo = $this->composer->getRepositoryManager()->getLocalRepository(); + $this->deactivateRepository($repo, false); + } + + if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { + $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); + } + } + + /** + * Gets all currently active plugin instances + * + * @return array plugins + */ + public function getPlugins(): array + { + return $this->plugins; + } + + /** + * Gets global composer or null when main composer is not fully loaded + */ + public function getGlobalComposer(): ?PartialComposer + { + return $this->globalComposer; + } + + /** + * Register a plugin package, activate it etc. + * + * If it's of type composer-installer it is registered as an installer + * instead for BC + * + * @param bool $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception + * @param bool $isGlobalPlugin Set to true to denote plugins which are installed in the global Composer directory + * + * @throws \UnexpectedValueException + */ + public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void + { + if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as plugins are disabled.'); + return; + } + + if ($package->getType() === 'composer-plugin') { + $requiresComposer = null; + foreach ($package->getRequires() as $link) { /** @var Link $link */ + if ('composer-plugin-api' === $link->getTarget()) { + $requiresComposer = $link->getConstraint(); + break; + } + } + + if (!$requiresComposer) { + throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); + } + + $currentPluginApiVersion = $this->getPluginApiVersion(); + $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); + + if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) { + $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).'); + } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { + $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); + + return; + } + + if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && version_compare($package->getVersion(), '1.9.8', '<')) { + $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.'); + + return; + } + } + + if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, true === ($package->getExtra()['plugin-optional'] ?? false))) { + $this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'as it is not in config.allow-plugins', true, IOInterface::DEBUG); + + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (isset($this->registeredPlugins[$package->getName()])) { + return; + } + $this->registeredPlugins[$package->getName()] = []; + + $extra = $package->getExtra(); + if (empty($extra['class'])) { + throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); + } + $classes = is_array($extra['class']) ? $extra['class'] : [$extra['class']]; + + $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); + $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; + + $rootPackage = clone $this->composer->getPackage(); + + // clear files autoload rules from the root package as the root dependencies are not + // necessarily all present yet when booting this runtime autoloader + $rootPackageAutoloads = $rootPackage->getAutoload(); + $rootPackageAutoloads['files'] = []; + $rootPackage->setAutoload($rootPackageAutoloads); + $rootPackageAutoloads = $rootPackage->getDevAutoload(); + $rootPackageAutoloads['files'] = []; + $rootPackage->setDevAutoload($rootPackageAutoloads); + unset($rootPackageAutoloads); + + $rootPackageRepo = new RootPackageRepository($rootPackage); + $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]); + if ($globalRepo) { + $installedRepo->addRepository($globalRepo); + } + + $autoloadPackages = [$package->getName() => $package]; + $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package); + + $generator = $this->composer->getAutoloadGenerator(); + $autoloads = [[$rootPackage, '']]; + foreach ($autoloadPackages as $autoloadPackage) { + if ($autoloadPackage === $rootPackage) { + continue; + } + + $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); + if ($installPath === null) { + continue; + } + $autoloads[] = [$autoloadPackage, $installPath]; + } + + $map = $generator->parseAutoloads($autoloads, $rootPackage); + $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); + $classLoader->register(false); + + foreach ($map['files'] as $fileIdentifier => $file) { + // exclude laminas/laminas-zendframework-bridge:src/autoload.php as it breaks Composer in some conditions + // see https://github.com/composer/composer/issues/10349 and https://github.com/composer/composer/issues/10401 + // this hack can be removed once this deprecated package stop being installed + if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') { + continue; + } + \Composer\Autoload\composerRequire($fileIdentifier, $file); + } + + foreach ($classes as $class) { + if (class_exists($class, false)) { + $class = trim($class, '\\'); + $path = $classLoader->findFile($class); + $code = file_get_contents($path); + $separatorPos = strrpos($class, '\\'); + $className = $class; + if ($separatorPos) { + $className = substr($class, $separatorPos + 1); + } + $code = Preg::replace('{^((?:(?:final|readonly)\s+)*(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1); + $code = strtr($code, [ + '__FILE__' => var_export($path, true), + '__DIR__' => var_export(dirname($path), true), + '__CLASS__' => var_export($class, true), + ]); + $code = Preg::replace('/^\s*<\?(php)?/i', '', $code, 1); + eval($code); + $class .= '_composer_tmp'.self::$classCounter; + self::$classCounter++; + } + + if ($oldInstallerPlugin) { + if (!is_a($class, 'Composer\Installer\InstallerInterface', true)) { + throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Installer\InstallerInterface'); + } + $this->io->writeError('Loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.'); + $installer = new $class($this->io, $this->composer); + $this->composer->getInstallationManager()->addInstaller($installer); + $this->registeredPlugins[$package->getName()][] = $installer; + } elseif (class_exists($class)) { + if (!is_a($class, 'Composer\Plugin\PluginInterface', true)) { + throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Plugin\PluginInterface'); + } + $plugin = new $class(); + $this->addPlugin($plugin, $isGlobalPlugin, $package); + $this->registeredPlugins[$package->getName()][] = $plugin; + } elseif ($failOnMissingClasses) { + throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); + } + } + } + + /** + * Deactivates a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @throws \UnexpectedValueException + */ + public function deactivatePackage(PackageInterface $package): void + { + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + $plugins = $this->registeredPlugins[$package->getName()]; + foreach ($plugins as $plugin) { + if ($plugin instanceof InstallerInterface) { + $this->composer->getInstallationManager()->removeInstaller($plugin); + } else { + $this->removePlugin($plugin); + } + } + unset($this->registeredPlugins[$package->getName()]); + } + + /** + * Uninstall a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @throws \UnexpectedValueException + */ + public function uninstallPackage(PackageInterface $package): void + { + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + $plugins = $this->registeredPlugins[$package->getName()]; + foreach ($plugins as $plugin) { + if ($plugin instanceof InstallerInterface) { + $this->composer->getInstallationManager()->removeInstaller($plugin); + } else { + $this->removePlugin($plugin); + $this->uninstallPlugin($plugin); + } + } + unset($this->registeredPlugins[$package->getName()]); + } + + /** + * Returns the version of the internal composer-plugin-api package. + */ + protected function getPluginApiVersion(): string + { + return PluginInterface::PLUGIN_API_VERSION; + } + + /** + * Adds a plugin, activates it and registers it with the event dispatcher + * + * Ideally plugin packages should be registered via registerPackage, but if you use Composer + * programmatically and want to register a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + * @param ?PackageInterface $sourcePackage Package from which the plugin comes from + */ + public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, ?PackageInterface $sourcePackage = null): void + { + if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { + return; + } + + if ($sourcePackage === null) { + trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED); + } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, true === ($sourcePackage->getExtra()['plugin-optional'] ?? false))) { + $this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').' as it is not in config.allow-plugins', true, IOInterface::DEBUG); + + return; + } + + $details = []; + if ($sourcePackage) { + $details[] = 'from '.$sourcePackage->getName(); + } + if ($isGlobalPlugin || $this->runningInGlobalDir) { + $details[] = 'installed globally'; + } + $this->io->writeError('Loading plugin '.get_class($plugin).($details ? ' ('.implode(', ', $details).')' : ''), true, IOInterface::DEBUG); + $this->plugins[] = $plugin; + $plugin->activate($this->composer, $this->io); + + if ($plugin instanceof EventSubscriberInterface) { + $this->composer->getEventDispatcher()->addSubscriber($plugin); + } + } + + /** + * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance + * + * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function removePlugin(PluginInterface $plugin): void + { + $index = array_search($plugin, $this->plugins, true); + if ($index === false) { + return; + } + + $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG); + unset($this->plugins[$index]); + $plugin->deactivate($this->composer, $this->io); + + $this->composer->getEventDispatcher()->removeListener($plugin); + } + + /** + * Notifies a plugin it is being uninstalled and should clean up + * + * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function uninstallPlugin(PluginInterface $plugin): void + { + $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG); + $plugin->uninstall($this->composer, $this->io); + } + + /** + * Load all plugins and installers from a repository + * + * If a plugin requires another plugin, the required one will be loaded first + * + * Note that plugins in the specified repository that rely on events that + * have fired prior to loading will be missed. This means you likely want to + * call this method as early as possible. + * + * @param RepositoryInterface $repo Repository to scan for plugins to install + * + * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage + * + * @throws \RuntimeException + */ + private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void + { + $packages = $repo->getPackages(); + + $weights = []; + foreach ($packages as $package) { + if ($package->getType() === 'composer-plugin') { + $extra = $package->getExtra(); + if ($package->getName() === 'composer/installers' || true === ($extra['plugin-modifies-install-path'] ?? false)) { + $weights[$package->getName()] = -10000; + } + } + } + + $sortedPackages = PackageSorter::sortPackages($packages, $weights); + if (!$isGlobalRepo) { + $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); + } + + foreach ($sortedPackages as $package) { + if (!($package instanceof CompletePackage)) { + continue; + } + + if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) { + continue; + } + + if ( + !$isGlobalRepo + && !in_array($package, $requiredPackages, true) + && !$this->isPluginAllowed($package->getName(), false, true, false) + ) { + $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.'); + continue; + } + + if ('composer-plugin' === $package->getType()) { + $this->registerPackage($package, false, $isGlobalRepo); + // Backward compatibility + } elseif ('composer-installer' === $package->getType()) { + $this->registerPackage($package, false, $isGlobalRepo); + } + } + } + + /** + * Deactivate all plugins and installers from a repository + * + * If a plugin requires another plugin, the required one will be deactivated last + * + * @param RepositoryInterface $repo Repository to scan for plugins to install + */ + private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo): void + { + $packages = $repo->getPackages(); + $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); + + foreach ($sortedPackages as $package) { + if (!($package instanceof CompletePackage)) { + continue; + } + if ('composer-plugin' === $package->getType()) { + $this->deactivatePackage($package); + // Backward compatibility + } elseif ('composer-installer' === $package->getType()) { + $this->deactivatePackage($package); + } + } + } + + /** + * Recursively generates a map of package names to packages for all deps + * + * @param InstalledRepository $installedRepo Set of local repos + * @param array $collected Current state of the map for recursion + * @param PackageInterface $package The package to analyze + * + * @return array Map of package names to packages + */ + private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package): array + { + foreach ($package->getRequires() as $requireLink) { + foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) { + if (!isset($collected[$requiredPackage->getName()])) { + $collected[$requiredPackage->getName()] = $requiredPackage; + $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage); + } + } + } + + return $collected; + } + + /** + * Retrieves the path a package is installed to. + * + * @param bool $global Whether this is a global package + * + * @return string|null Install path + */ + private function getInstallPath(PackageInterface $package, bool $global = false): ?string + { + if (!$global) { + return $this->composer->getInstallationManager()->getInstallPath($package); + } + + assert(null !== $this->globalComposer); + + return $this->globalComposer->getInstallationManager()->getInstallPath($package); + } + + /** + * @throws \RuntimeException On empty or non-string implementation class name value + * @return null|string The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it + */ + protected function getCapabilityImplementationClassName(PluginInterface $plugin, string $capability): ?string + { + if (!($plugin instanceof Capable)) { + return null; + } + + $capabilities = (array) $plugin->getCapabilities(); + + if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) { + return trim($capabilities[$capability]); + } + + if ( + array_key_exists($capability, $capabilities) + && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability])) + ) { + throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], true)); + } + + return null; + } + + /** + * @template CapabilityClass of Capability + * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide + * an implementation of. + * @param array $ctorArgs Arguments passed to Capability's constructor. + * Keeping it an array will allow future values to be passed w\o changing the signature. + * @phpstan-param class-string $capabilityClassName + * @phpstan-return null|CapabilityClass + */ + public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []): ?Capability + { + if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) { + if (!class_exists($capabilityClass)) { + throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist."); + } + + $ctorArgs['plugin'] = $plugin; + $capabilityObj = new $capabilityClass($ctorArgs); + + // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9 + if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) { + throw new \RuntimeException( + 'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.' + ); + } + + return $capabilityObj; + } + + return null; + } + + /** + * @template CapabilityClass of Capability + * @param class-string $capabilityClassName The fully qualified name of the API interface which the plugin may provide + * an implementation of. + * @param array $ctorArgs Arguments passed to Capability's constructor. + * Keeping it an array will allow future values to be passed w\o changing the signature. + * @return CapabilityClass[] + */ + public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []): array + { + $capabilities = []; + foreach ($this->getPlugins() as $plugin) { + $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs); + if (null !== $capability) { + $capabilities[] = $capability; + } + } + + return $capabilities; + } + + /** + * @param array|bool $allowPluginsConfig + * @return array|null + */ + private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array + { + if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) { + return null; + } + + if (true === $allowPluginsConfig) { + return ['{}' => true]; + } + + if (false === $allowPluginsConfig) { + return ['{}' => false]; + } + + $rules = []; + foreach ($allowPluginsConfig as $pattern => $allow) { + $rules[BasePackage::packageNameToRegexp($pattern)] = $allow; + } + + return $rules; + } + + /** + * @internal + * + * @param 'local'|'global' $type + * @return bool + */ + public function arePluginsDisabled($type) + { + return $this->disablePlugins === true || $this->disablePlugins === $type; + } + + /** + * @internal + */ + public function disablePlugins(): void + { + $this->disablePlugins = true; + } + + /** + * @internal + */ + public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool + { + if ($isGlobalPlugin) { + $rules = &$this->allowGlobalPluginRules; + } else { + $rules = &$this->allowPluginRules; + } + + // This is a BC mode for lock files created pre-Composer-2.2 where the expectation of + // an allow-plugins config being present cannot be made. + if ($rules === null) { + if (!$this->io->isInteractive()) { + $this->io->writeError('For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins'); + $this->io->writeError('This warning will become an exception once you run composer update!'); + + $rules = ['{}' => true]; + + // if no config is defined we allow all plugins for BC + return true; + } + + // keep going and prompt the user + $rules = []; + } + + foreach ($rules as $pattern => $allow) { + if (Preg::isMatch($pattern, $package)) { + return $allow === true; + } + } + + if ($package === 'composer/package-versions-deprecated') { + return false; + } + + if ($this->io->isInteractive() && $prompt) { + $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; + + $this->io->writeError(''.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); + $attempts = 0; + while (true) { + // do not allow more than 5 prints of the help message, at some point assume the + // input is not interactive and bail defaulting to a disabled plugin + $default = '?'; + if ($attempts > 5) { + $this->io->writeError('Too many failed prompts, aborting.'); + break; + } + + switch ($answer = $this->io->ask('Do you trust "'.$package.'" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $default)) { + case 'y': + case 'n': + case 'd': + $allow = $answer === 'y'; + + // persist answer in current rules to avoid prompting again if the package gets reloaded + $rules[BasePackage::packageNameToRegexp($package)] = $allow; + + // persist answer in composer.json if it wasn't simply discarded + if ($answer === 'y' || $answer === 'n') { + $allowPlugins = $composer->getConfig()->get('allow-plugins'); + if (is_array($allowPlugins)) { + $allowPlugins[$package] = $allow; + if ($composer->getConfig()->get('sort-packages')) { + ksort($allowPlugins); + } + $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins); + $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]); + } + } + + return $allow; + + case '?': + default: + $attempts++; + $this->io->writeError([ + 'y - add package to allow-plugins in composer.json and let it run immediately', + 'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts', + 'd - discard this, do not change composer.json and do not allow the plugin to run', + '? - print help', + ]); + break; + } + } + } elseif ($optional) { + return false; + } + + throw new PluginBlockedException( + $package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL. + 'You can run "composer '.($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL. + 'See https://getcomposer.org/allow-plugins' + ); + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php b/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php new file mode 100644 index 0000000..a8d9a02 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php @@ -0,0 +1,139 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Package\PackageInterface; + +/** + * The post file download event. + * + * @author Nils Adermann + */ +class PostFileDownloadEvent extends Event +{ + /** + * @var string + */ + private $fileName; + + /** + * @var string|null + */ + private $checksum; + + /** + * @var string + */ + private $url; + + /** + * @var mixed + */ + private $context; + + /** + * @var string + */ + private $type; + + /** + * Constructor. + * + * @param string $name The event name + * @param string|null $fileName The file name + * @param string|null $checksum The checksum + * @param string $url The processed url + * @param string $type The type (package or metadata). + * @param mixed $context Additional context for the download. + */ + public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null) + { + /** @phpstan-ignore instanceof.alwaysFalse, booleanAnd.alwaysFalse */ + if ($context === null && $type instanceof PackageInterface) { + $context = $type; + $type = 'package'; + trigger_error('PostFileDownloadEvent::__construct should receive a $type=package and the package object in $context since Composer 2.1.', E_USER_DEPRECATED); + } + + parent::__construct($name); + $this->fileName = $fileName; + $this->checksum = $checksum; + $this->url = $url; + $this->context = $context; + $this->type = $type; + } + + /** + * Retrieves the target file name location. + * + * If this download is of type metadata, null is returned. + */ + public function getFileName(): ?string + { + return $this->fileName; + } + + /** + * Gets the checksum. + */ + public function getChecksum(): ?string + { + return $this->checksum; + } + + /** + * Gets the processed URL. + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * Returns the context of this download, if any. + * + * If this download is of type package, the package object is returned. If + * this download is of type metadata, an array{response: Response, repository: RepositoryInterface} is returned. + * + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * Get the package. + * + * If this download is of type metadata, null is returned. + * + * @return \Composer\Package\PackageInterface|null The package. + * @deprecated Use getContext instead + */ + public function getPackage(): ?PackageInterface + { + trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', E_USER_DEPRECATED); + $context = $this->getContext(); + + return $context instanceof PackageInterface ? $context : null; + } + + /** + * Returns the type of this download (package, metadata). + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php b/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php new file mode 100644 index 0000000..7bd7a46 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php @@ -0,0 +1,63 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PreCommandRunEvent extends Event +{ + /** + * @var InputInterface + */ + private $input; + + /** + * @var string + */ + private $command; + + /** + * Constructor. + * + * @param string $name The event name + * @param string $command The command about to be executed + */ + public function __construct(string $name, InputInterface $input, string $command) + { + parent::__construct($name); + $this->input = $input; + $this->command = $command; + } + + /** + * Returns the console input + */ + public function getInput(): InputInterface + { + return $this->input; + } + + /** + * Returns the command about to be executed + */ + public function getCommand(): string + { + return $this->command; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php b/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php new file mode 100644 index 0000000..be36882 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php @@ -0,0 +1,158 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Util\HttpDownloader; + +/** + * The pre file download event. + * + * @author Nils Adermann + */ +class PreFileDownloadEvent extends Event +{ + /** + * @var HttpDownloader + */ + private $httpDownloader; + + /** + * @var non-empty-string + */ + private $processedUrl; + + /** + * @var string|null + */ + private $customCacheKey; + + /** + * @var string + */ + private $type; + + /** + * @var mixed + */ + private $context; + + /** + * @var mixed[] + */ + private $transportOptions = []; + + /** + * Constructor. + * + * @param string $name The event name + * @param mixed $context + * @param non-empty-string $processedUrl + */ + public function __construct(string $name, HttpDownloader $httpDownloader, string $processedUrl, string $type, $context = null) + { + parent::__construct($name); + $this->httpDownloader = $httpDownloader; + $this->processedUrl = $processedUrl; + $this->type = $type; + $this->context = $context; + } + + public function getHttpDownloader(): HttpDownloader + { + return $this->httpDownloader; + } + + /** + * Retrieves the processed URL that will be downloaded. + * + * @return non-empty-string + */ + public function getProcessedUrl(): string + { + return $this->processedUrl; + } + + /** + * Sets the processed URL that will be downloaded. + * + * @param non-empty-string $processedUrl New processed URL + */ + public function setProcessedUrl(string $processedUrl): void + { + $this->processedUrl = $processedUrl; + } + + /** + * Retrieves a custom package cache key for this download. + */ + public function getCustomCacheKey(): ?string + { + return $this->customCacheKey; + } + + /** + * Sets a custom package cache key for this download. + * + * @param string|null $customCacheKey New cache key + */ + public function setCustomCacheKey(?string $customCacheKey): void + { + $this->customCacheKey = $customCacheKey; + } + + /** + * Returns the type of this download (package, metadata). + */ + public function getType(): string + { + return $this->type; + } + + /** + * Returns the context of this download, if any. + * + * If this download is of type package, the package object is returned. + * If the type is metadata, an array{repository: RepositoryInterface} is returned. + * + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * Returns transport options for the download. + * + * Only available for events with type metadata, for packages set the transport options on the package itself. + * + * @return mixed[] + */ + public function getTransportOptions(): array + { + return $this->transportOptions; + } + + /** + * Sets transport options for the download. + * + * Only available for events with type metadata, for packages set the transport options on the package itself. + * + * @param mixed[] $options + */ + public function setTransportOptions(array $options): void + { + $this->transportOptions = $options; + } +} diff --git a/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php b/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php new file mode 100644 index 0000000..e7ea7a0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Composer\Repository\RepositoryInterface; +use Composer\DependencyResolver\Request; +use Composer\Package\BasePackage; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PrePoolCreateEvent extends Event +{ + /** + * @var RepositoryInterface[] + */ + private $repositories; + /** + * @var Request + */ + private $request; + /** + * @var int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $acceptableStabilities; + /** + * @var int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $stabilityFlags; + /** + * @var array[] of package => version => [alias, alias_normalized] + * @phpstan-var array> + */ + private $rootAliases; + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + /** + * @var BasePackage[] + */ + private $packages; + /** + * @var BasePackage[] + */ + private $unacceptableFixedPackages; + + /** + * @param string $name The event name + * @param RepositoryInterface[] $repositories + * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @param int[] $stabilityFlags array of package name => BasePackage::STABILITY_* value + * @param array[] $rootAliases array of package => version => [alias, alias_normalized] + * @param string[] $rootReferences + * @param BasePackage[] $packages + * @param BasePackage[] $unacceptableFixedPackages + * + * @phpstan-param array $acceptableStabilities + * @phpstan-param array $stabilityFlags + * @phpstan-param array> $rootAliases + * @phpstan-param array $rootReferences + */ + public function __construct(string $name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages) + { + parent::__construct($name); + + $this->repositories = $repositories; + $this->request = $request; + $this->acceptableStabilities = $acceptableStabilities; + $this->stabilityFlags = $stabilityFlags; + $this->rootAliases = $rootAliases; + $this->rootReferences = $rootReferences; + $this->packages = $packages; + $this->unacceptableFixedPackages = $unacceptableFixedPackages; + } + + /** + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + public function getRequest(): Request + { + return $this->request; + } + + /** + * @return int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-return array + */ + public function getAcceptableStabilities(): array + { + return $this->acceptableStabilities; + } + + /** + * @return int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-return array + */ + public function getStabilityFlags(): array + { + return $this->stabilityFlags; + } + + /** + * @return array[] of package => version => [alias, alias_normalized] + * @phpstan-return array> + */ + public function getRootAliases(): array + { + return $this->rootAliases; + } + + /** + * @return string[] + * @phpstan-return array + */ + public function getRootReferences(): array + { + return $this->rootReferences; + } + + /** + * @return BasePackage[] + */ + public function getPackages(): array + { + return $this->packages; + } + + /** + * @return BasePackage[] + */ + public function getUnacceptableFixedPackages(): array + { + return $this->unacceptableFixedPackages; + } + + /** + * @param BasePackage[] $packages + */ + public function setPackages(array $packages): void + { + $this->packages = $packages; + } + + /** + * @param BasePackage[] $packages + */ + public function setUnacceptableFixedPackages(array $packages): void + { + $this->unacceptableFixedPackages = $packages; + } +} diff --git a/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php b/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php new file mode 100644 index 0000000..9cbc74e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php @@ -0,0 +1,93 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Question; + +use Composer\Pcre\Preg; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Question\Question; + +/** + * Represents a yes/no question + * Enforces strict responses rather than non-standard answers counting as default + * Based on Symfony\Component\Console\Question\ConfirmationQuestion + * + * @author Theo Tonge + */ +class StrictConfirmationQuestion extends Question +{ + /** @var non-empty-string */ + private $trueAnswerRegex; + /** @var non-empty-string */ + private $falseAnswerRegex; + + /** + * Constructor.s + * + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param non-empty-string $trueAnswerRegex A regex to match the "yes" answer + * @param non-empty-string $falseAnswerRegex A regex to match the "no" answer + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i') + { + parent::__construct($question, $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->falseAnswerRegex = $falseAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + $this->setValidator($this->getDefaultValidator()); + } + + /** + * Returns the default answer normalizer. + */ + private function getDefaultNormalizer(): callable + { + $default = $this->getDefault(); + $trueRegex = $this->trueAnswerRegex; + $falseRegex = $this->falseAnswerRegex; + + return static function ($answer) use ($default, $trueRegex, $falseRegex) { + if (is_bool($answer)) { + return $answer; + } + if (empty($answer) && !empty($default)) { + return $default; + } + + if (Preg::isMatch($trueRegex, $answer)) { + return true; + } + + if (Preg::isMatch($falseRegex, $answer)) { + return false; + } + + return null; + }; + } + + /** + * Returns the default answer validator. + */ + private function getDefaultValidator(): callable + { + return static function ($answer): bool { + if (!is_bool($answer)) { + throw new InvalidArgumentException('Please answer yes, y, no, or n.'); + } + + return $answer; + }; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php b/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php new file mode 100644 index 0000000..950f283 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; + +/** + * Repositories that allow fetching security advisory data + * + * @author Jordi Boggiano + * @internal + */ +interface AdvisoryProviderInterface +{ + public function hasSecurityAdvisories(): bool; + + /** + * @param array $packageConstraintMap Map of package name to constraint (can be MatchAllConstraint to fetch all advisories) + * @return ($allowPartialAdvisories is true ? array{namesFound: string[], advisories: array>} : array{namesFound: string[], advisories: array>}) + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array; +} diff --git a/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php new file mode 100644 index 0000000..71a26ec --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ArrayRepository.php @@ -0,0 +1,341 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\StabilityFilter; +use Composer\Pcre\Preg; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * A repository implementation that simply stores packages in an array + * + * @author Nils Adermann + */ +class ArrayRepository implements RepositoryInterface +{ + /** @var ?array */ + protected $packages = null; + + /** + * @var ?array indexed by package unique name and used to cache hasPackage calls + */ + protected $packageMap = null; + + /** + * @param array $packages + */ + public function __construct(array $packages = []) + { + foreach ($packages as $package) { + $this->addPackage($package); + } + } + + public function getRepoName() + { + return 'array repo (defining '.$this->count().' package'.($this->count() > 1 ? 's' : '').')'; + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) + { + $packages = $this->getPackages(); + + $result = []; + $namesFound = []; + foreach ($packages as $package) { + if (array_key_exists($package->getName(), $packageNameMap)) { + if ( + (!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) + && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) + && !isset($alreadyLoaded[$package->getName()][$package->getVersion()]) + ) { + // add selected packages which match stability requirements + $result[spl_object_hash($package)] = $package; + // add the aliased package for packages where the alias matches + if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + + $namesFound[$package->getName()] = true; + } + } + + // add aliases of packages that were selected, even if the aliases did not match + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + if (isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package)] = $package; + } + } + } + + return ['namesFound' => array_keys($namesFound), 'packages' => $result]; + } + + /** + * @inheritDoc + */ + public function findPackage(string $name, $constraint) + { + $name = strtolower($name); + + if (!$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + foreach ($this->getPackages() as $package) { + if ($name === $package->getName()) { + $pkgConstraint = new Constraint('==', $package->getVersion()); + if ($constraint->matches($pkgConstraint)) { + return $package; + } + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function findPackages(string $name, $constraint = null) + { + // normalize name + $name = strtolower($name); + $packages = []; + + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + foreach ($this->getPackages() as $package) { + if ($name === $package->getName()) { + if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) { + $packages[] = $package; + } + } + } + + return $packages; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null) + { + if ($mode === self::SEARCH_FULLTEXT) { + $regex = '{(?:'.implode('|', Preg::split('{\s+}', preg_quote($query))).')}i'; + } else { + // vendor/name searches expect the caller to have preg_quoted the query + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + } + + $matches = []; + foreach ($this->getPackages() as $package) { + $name = $package->getName(); + if ($mode === self::SEARCH_VENDOR) { + [$name] = explode('/', $name); + } + if (isset($matches[$name])) { + continue; + } + if (null !== $type && $package->getType() !== $type) { + continue; + } + + if (Preg::isMatch($regex, $name) + || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription())) + ) { + if ($mode === self::SEARCH_VENDOR) { + $matches[$name] = [ + 'name' => $name, + 'description' => null, + ]; + } else { + $matches[$name] = [ + 'name' => $package->getPrettyName(), + 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null, + ]; + + if ($package instanceof CompletePackageInterface && $package->isAbandoned()) { + $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: true; + } + } + } + } + + return array_values($matches); + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package) + { + if ($this->packageMap === null) { + $this->packageMap = []; + foreach ($this->getPackages() as $repoPackage) { + $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage; + } + } + + return isset($this->packageMap[$package->getUniqueName()]); + } + + /** + * Adds a new package to the repository + * + * @return void + */ + public function addPackage(PackageInterface $package) + { + if (!$package instanceof BasePackage) { + throw new \InvalidArgumentException('Only subclasses of BasePackage are supported'); + } + if (null === $this->packages) { + $this->initialize(); + } + $package->setRepository($this); + $this->packages[] = $package; + + if ($package instanceof AliasPackage) { + $aliasedPackage = $package->getAliasOf(); + if (null === $aliasedPackage->getRepository()) { + $this->addPackage($aliasedPackage); + } + } + + // invalidate package map cache + $this->packageMap = null; + } + + /** + * @inheritDoc + */ + public function getProviders(string $packageName) + { + $result = []; + + foreach ($this->getPackages() as $candidate) { + if (isset($result[$candidate->getName()])) { + continue; + } + foreach ($candidate->getProvides() as $link) { + if ($packageName === $link->getTarget()) { + $result[$candidate->getName()] = [ + 'name' => $candidate->getName(), + 'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null, + 'type' => $candidate->getType(), + ]; + continue 2; + } + } + } + + return $result; + } + + /** + * @return AliasPackage|CompleteAliasPackage + */ + protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias) + { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + if ($package instanceof CompletePackage) { + return new CompleteAliasPackage($package, $alias, $prettyAlias); + } + + return new AliasPackage($package, $alias, $prettyAlias); + } + + /** + * Removes package from repository. + * + * @param PackageInterface $package package instance + * + * @return void + */ + public function removePackage(PackageInterface $package) + { + $packageId = $package->getUniqueName(); + + foreach ($this->getPackages() as $key => $repoPackage) { + if ($packageId === $repoPackage->getUniqueName()) { + array_splice($this->packages, $key, 1); + + // invalidate package map cache + $this->packageMap = null; + + return; + } + } + } + + /** + * @inheritDoc + */ + public function getPackages() + { + if (null === $this->packages) { + $this->initialize(); + } + + if (null === $this->packages) { + throw new \LogicException('initialize failed to initialize the packages array'); + } + + return $this->packages; + } + + /** + * Returns the number of packages in this repository + * + * @return 0|positive-int Number of packages + */ + public function count(): int + { + if (null === $this->packages) { + $this->initialize(); + } + + return count($this->packages); + } + + /** + * Initializes the packages array. Mostly meant as an extension point. + * + * @return void + */ + protected function initialize() + { + $this->packages = []; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php b/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php new file mode 100644 index 0000000..78176fa --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php @@ -0,0 +1,143 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\BasePackage; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\LoaderInterface; +use Composer\Util\Platform; +use Composer\Util\Tar; +use Composer\Util\Zip; + +/** + * @author Serge Smertin + */ +class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** @var LoaderInterface */ + protected $loader; + + /** @var string */ + protected $lookup; + /** @var array{url: string} */ + protected $repoConfig; + /** @var IOInterface */ + private $io; + + /** + * @param array{url: string} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io) + { + parent::__construct(); + if (!extension_loaded('zip')) { + throw new \RuntimeException('The artifact repository requires PHP\'s zip extension'); + } + + $this->loader = new ArrayLoader(); + $this->lookup = Platform::expandPath($repoConfig['url']); + $this->io = $io; + $this->repoConfig = $repoConfig; + } + + public function getRepoName() + { + return 'artifact repo ('.$this->lookup.')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + protected function initialize() + { + parent::initialize(); + + $this->scanDirectory($this->lookup); + } + + private function scanDirectory(string $path): void + { + $io = $this->io; + + $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); + $iterator = new \RecursiveIteratorIterator($directory); + $regex = new \RegexIterator($iterator, '/^.+\.(zip|tar|gz|tgz)$/i'); + foreach ($regex as $file) { + /* @var $file \SplFileInfo */ + if (!$file->isFile()) { + continue; + } + + $package = $this->getComposerInformation($file); + if (!$package) { + $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE); + continue; + } + + $template = 'Found package %s (%s) in file %s'; + $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE); + + $this->addPackage($package); + } + } + + /** + * @return ?BasePackage + */ + private function getComposerInformation(\SplFileInfo $file): ?BasePackage + { + $json = null; + $fileType = null; + $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION); + if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) { + $fileType = 'tar'; + } elseif ($fileExtension === 'zip') { + $fileType = 'zip'; + } else { + throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.'); + } + + try { + if ($fileType === 'tar') { + $json = Tar::getComposerJson($file->getPathname()); + } else { + $json = Zip::getComposerJson($file->getPathname()); + } + } catch (\Exception $exception) { + $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE); + } + + if (null === $json) { + return null; + } + + $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); + $package['dist'] = [ + 'type' => $fileType, + 'url' => strtr($file->getPathname(), '\\', '/'), + 'shasum' => hash_file('sha1', $file->getRealPath()), + ]; + + try { + $package = $this->loader->load($package); + } catch (\UnexpectedValueException $e) { + throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e); + } + + return $package; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php b/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php new file mode 100644 index 0000000..1784dad --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php @@ -0,0 +1,55 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; + +/** + * Provides getCanonicalPackages() to various repository implementations + * + * @internal + */ +trait CanonicalPackagesTrait +{ + /** + * Get unique packages (at most one package of each name), with aliases resolved and removed. + * + * @return PackageInterface[] + */ + public function getCanonicalPackages() + { + $packages = $this->getPackages(); + + // get at most one package of each name, preferring non-aliased ones + $packagesByName = []; + foreach ($packages as $package) { + if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { + $packagesByName[$package->getName()] = $package; + } + } + + $canonicalPackages = []; + + // unfold aliased packages + foreach ($packagesByName as $package) { + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + + $canonicalPackages[] = $package; + } + + return $canonicalPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php b/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php new file mode 100644 index 0000000..fe93fb7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ComposerRepository.php @@ -0,0 +1,1792 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\Advisory\SecurityAdvisory; +use Composer\Package\BasePackage; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\StabilityFilter; +use Composer\Json\JsonFile; +use Composer\Cache; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Plugin\PostFileDownloadEvent; +use Composer\Semver\CompilingMatcher; +use Composer\Util\HttpDownloader; +use Composer\Util\Loop; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PreFileDownloadEvent; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Downloader\TransportException; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Util\Http\Response; +use Composer\MetadataMinifier\MetadataMinifier; +use Composer\Util\Url; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface, AdvisoryProviderInterface +{ + /** + * @var mixed[] + * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} + */ + private $repoConfig; + /** @var mixed[] */ + private $options; + /** @var non-empty-string */ + private $url; + /** @var non-empty-string */ + private $baseUrl; + /** @var IOInterface */ + private $io; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var Loop */ + private $loop; + /** @var Cache */ + protected $cache; + /** @var ?non-empty-string */ + protected $notifyUrl = null; + /** @var ?non-empty-string */ + protected $searchUrl = null; + /** @var ?non-empty-string a URL containing %package% which can be queried to get providers of a given name */ + protected $providersApiUrl = null; + /** @var bool */ + protected $hasProviders = false; + /** @var ?non-empty-string */ + protected $providersUrl = null; + /** @var ?non-empty-string */ + protected $listUrl = null; + /** @var bool Indicates whether a comprehensive list of packages this repository might provide is expressed in the repository root. **/ + protected $hasAvailablePackageList = false; + /** @var ?array */ + protected $availablePackages = null; + /** @var ?array */ + protected $availablePackagePatterns = null; + /** @var ?non-empty-string */ + protected $lazyProvidersUrl = null; + /** @var ?array */ + protected $providerListing; + /** @var ArrayLoader */ + protected $loader; + /** @var bool */ + private $allowSslDowngrade = false; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var ?array> */ + private $sourceMirrors; + /** @var ?list */ + private $distMirrors; + /** @var bool */ + private $degradedMode = false; + /** @var mixed[]|true */ + private $rootData; + /** @var bool */ + private $hasPartialPackages = false; + /** @var ?array */ + private $partialPackagesByName = null; + /** @var bool */ + private $displayedWarningAboutNonMatchingPackageIndex = false; + /** @var array{metadata: bool, api-url: string|null}|null */ + private $securityAdvisoryConfig = null; + + /** + * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times + * useful for v2 metadata repositories with lazy providers + * @phpstan-var array + */ + private $freshMetadataUrls = []; + + /** + * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times + * useful for v2 metadata repositories with lazy providers + * @phpstan-var array + */ + private $packagesNotFoundCache = []; + + /** + * @var VersionParser + */ + private $versionParser; + + /** + * @param array $repoConfig + * @phpstan-param array{url: non-empty-string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null) + { + parent::__construct(); + if (!Preg::isMatch('{^[\w.]+\??://}', $repoConfig['url'])) { + if (($localFilePath = realpath($repoConfig['url'])) !== false) { + // it is a local path, add file scheme + $repoConfig['url'] = 'file://'.$localFilePath; + } else { + // otherwise, assume http as the default protocol + $repoConfig['url'] = 'http://'.$repoConfig['url']; + } + } + $repoConfig['url'] = rtrim($repoConfig['url'], '/'); + if ($repoConfig['url'] === '') { + throw new \InvalidArgumentException('The repository url must not be an empty string'); + } + + if (str_starts_with($repoConfig['url'], 'https?')) { + $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6); + } + + $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/')); + if ($urlBits === false || empty($urlBits['scheme'])) { + throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); + } + + if (!isset($repoConfig['options'])) { + $repoConfig['options'] = []; + } + if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) { + $this->allowSslDowngrade = true; + } + + $this->options = $repoConfig['options']; + $this->url = $repoConfig['url']; + + // force url for packagist.org to repo.packagist.org + if (Preg::isMatch('{^(?Phttps?)://packagist\.org/?$}i', $this->url, $match)) { + $this->url = $match['proto'].'://repo.packagist.org'; + } + + $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); + assert($baseUrl !== ''); + $this->baseUrl = $baseUrl; + $this->io = $io; + $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_'); + $this->cache->setReadOnly($config->get('cache-read-only')); + $this->versionParser = new VersionParser(); + $this->loader = new ArrayLoader($this->versionParser); + $this->httpDownloader = $httpDownloader; + $this->eventDispatcher = $eventDispatcher; + $this->repoConfig = $repoConfig; + $this->loop = new Loop($this->httpDownloader); + } + + public function getRepoName() + { + return 'composer repo ('.Url::sanitize($this->url).')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + /** + * @inheritDoc + */ + public function findPackage(string $name, $constraint) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + $name = strtolower($name); + if (!$constraint instanceof ConstraintInterface) { + $constraint = $this->versionParser->parseConstraints($constraint); + } + + if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $constraint, true); + } + + if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { + return null; + } + + $packages = $this->loadAsyncPackages([$name => $constraint]); + + if (count($packages['packages']) > 0) { + return reset($packages['packages']); + } + + return null; + } + + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint, true); + } + } + + return null; + } + + return parent::findPackage($name, $constraint); + } + + /** + * @inheritDoc + */ + public function findPackages(string $name, $constraint = null) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + $name = strtolower($name); + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $constraint = $this->versionParser->parseConstraints($constraint); + } + + if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $constraint); + } + + if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { + return []; + } + + $result = $this->loadAsyncPackages([$name => $constraint]); + + return $result['packages']; + } + + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint); + } + } + + return []; + } + + return parent::findPackages($name, $constraint); + } + + /** + * @param array $packages + * + * @return BasePackage|array|null + */ + private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = false) + { + if (null === $constraint) { + if ($returnFirstMatch) { + return reset($packages); + } + + return $packages; + } + + $filteredPackages = []; + + foreach ($packages as $package) { + $pkgConstraint = new Constraint('==', $package->getVersion()); + + if ($constraint->matches($pkgConstraint)) { + if ($returnFirstMatch) { + return $package; + } + + $filteredPackages[] = $package; + } + } + + if ($returnFirstMatch) { + return null; + } + + return $filteredPackages; + } + + public function getPackages() + { + $hasProviders = $this->hasProviders(); + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages) && !$this->availablePackagePatterns) { + $packageMap = []; + foreach ($this->availablePackages as $name) { + $packageMap[$name] = new MatchAllConstraint(); + } + + $result = $this->loadAsyncPackages($packageMap); + + return array_values($result['packages']); + } + + if ($this->hasPartialPackages()) { + if (!is_array($this->partialPackagesByName)) { + throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); + } + + return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages'); + } + + throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.'); + } + + if ($hasProviders) { + throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.'); + } + + return parent::getPackages(); + } + + /** + * @param string|null $packageFilter Package pattern filter which can include "*" as a wildcard + * + * @return string[] + */ + public function getPackageNames(?string $packageFilter = null) + { + $hasProviders = $this->hasProviders(); + + $filterResults = + /** + * @param list $results + * @return list + */ + static function (array $results): array { + return $results; + } + ; + if (null !== $packageFilter && '' !== $packageFilter) { + $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter); + $filterResults = + /** + * @param list $results + * @return list + */ + static function (array $results) use ($packageFilterRegex): array { + /** @var list $results */ + return Preg::grep($packageFilterRegex, $results); + } + ; + } + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages)) { + return $filterResults(array_keys($this->availablePackages)); + } + + if ($this->listUrl) { + // no need to call $filterResults here as the $packageFilter is applied in the function itself + return $this->loadPackageList($packageFilter); + } + + if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) { + return $filterResults(array_keys($this->partialPackagesByName)); + } + + return []; + } + + if ($hasProviders) { + return $filterResults($this->getProviderNames()); + } + + $names = []; + foreach ($this->getPackages() as $package) { + $names[] = $package->getPrettyName(); + } + + return $filterResults($names); + } + + /** + * @return list + */ + private function getVendorNames(): array + { + $cacheKey = 'vendor-list.txt'; + $cacheAge = $this->cache->getAge($cacheKey); + if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { + $cachedData = explode("\n", $cachedData); + + return $cachedData; + } + + $names = $this->getPackageNames(); + + $uniques = []; + foreach ($names as $name) { + $uniques[explode('/', $name, 2)[0]] = true; + } + + $vendors = array_keys($uniques); + + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, implode("\n", $vendors)); + } + + return $vendors; + } + + /** + * @return list + */ + private function loadPackageList(?string $packageFilter = null): array + { + if (null === $this->listUrl) { + throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList'); + } + + $url = $this->listUrl; + if (is_string($packageFilter) && $packageFilter !== '') { + $url .= '?filter='.urlencode($packageFilter); + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + return $result['packageNames']; + } + + $cacheKey = 'package-list.txt'; + $cacheAge = $this->cache->getAge($cacheKey); + if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { + $cachedData = explode("\n", $cachedData); + + return $cachedData; + } + + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, implode("\n", $result['packageNames'])); + } + + return $result['packageNames']; + } + + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) + { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) { + return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + } + + $packages = []; + $namesFound = []; + + if ($hasProviders || $this->hasPartialPackages()) { + foreach ($packageNameMap as $name => $constraint) { + $matches = []; + + // if a repo has no providers but only partial packages and the partial packages are missing + // then we don't want to call whatProvides as it would try to load from the providers and fail + if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { + continue; + } + + $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + foreach ($candidates as $candidate) { + if ($candidate->getName() !== $name) { + throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); + } + $namesFound[$name] = true; + + if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { + $matches[spl_object_hash($candidate)] = $candidate; + if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); + } + } + } + + // add aliases of matched packages even if they did not match the constraint + foreach ($candidates as $candidate) { + if ($candidate instanceof AliasPackage) { + if (isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate)] = $candidate; + } + } + } + $packages = array_merge($packages, $matches); + + unset($packageNameMap[$name]); + } + } + + if ($this->lazyProvidersUrl && count($packageNameMap)) { + if ($this->hasAvailablePackageList) { + foreach ($packageNameMap as $name => $constraint) { + if (!$this->lazyProvidersRepoContains(strtolower($name))) { + unset($packageNameMap[$name]); + } + } + } + + $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + $packages = array_merge($packages, $result['packages']); + $namesFound = array_merge($namesFound, $result['namesFound']); + } + + return ['namesFound' => array_keys($namesFound), 'packages' => $packages]; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null) + { + $this->loadRootServerFile(600); + + if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { + $url = str_replace(['%query%', '%type%'], [urlencode($query), $type], $this->searchUrl); + + $search = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + if (empty($search['results'])) { + return []; + } + + $results = []; + foreach ($search['results'] as $result) { + // do not show virtual packages in results as they are not directly useful from a composer perspective + if (!empty($result['virtual'])) { + continue; + } + + $results[] = $result; + } + + return $results; + } + + if ($mode === self::SEARCH_VENDOR) { + $results = []; + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + + $vendorNames = $this->getVendorNames(); + foreach (Preg::grep($regex, $vendorNames) as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + if ($this->hasProviders() || $this->lazyProvidersUrl) { + // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present + if (Preg::isMatchStrictGroups('{^\^(?P(?P[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i', $query, $match) && $this->listUrl !== null) { + $url = $this->listUrl . '?vendor='.urlencode($match['vendor']).'&filter='.urlencode($match['query'].'*'); + $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); + + $results = []; + foreach ($result['packageNames'] as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + $results = []; + $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; + + $packageNames = $this->getPackageNames(); + foreach (Preg::grep($regex, $packageNames) as $name) { + $results[] = ['name' => $name, 'description' => '']; + } + + return $results; + } + + return parent::search($query, $mode); + } + + public function hasSecurityAdvisories(): bool + { + $this->loadRootServerFile(600); + + return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null); + } + + /** + * @inheritDoc + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array + { + $this->loadRootServerFile(600); + if (null === $this->securityAdvisoryConfig) { + return ['namesFound' => [], 'advisories' => []]; + } + + $advisories = []; + $namesFound = []; + + $apiUrl = $this->securityAdvisoryConfig['api-url']; + + // respect available-package-patterns / available-packages directives from the repo + if ($this->hasAvailablePackageList) { + foreach ($packageConstraintMap as $name => $constraint) { + if (!$this->lazyProvidersRepoContains(strtolower($name))) { + unset($packageConstraintMap[$name]); + } + } + } + + $parser = new VersionParser(); + /** + * @param array $data + * @param string $name + * @return ($allowPartialAdvisories is false ? SecurityAdvisory|null : PartialSecurityAdvisory|SecurityAdvisory|null) + */ + $create = function (array $data, string $name) use ($parser, $allowPartialAdvisories, &$packageConstraintMap): ?PartialSecurityAdvisory { + $advisory = PartialSecurityAdvisory::create($name, $data, $parser); + if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { + throw new \RuntimeException('Advisory for '.$name.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true)); + } + if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) { + return null; + } + + return $advisory; + }; + + if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) { + $promises = []; + foreach ($packageConstraintMap as $name => $constraint) { + $name = strtolower($name); + + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { + continue; + } + + $promises[] = $this->startCachedAsyncDownload($name, $name) + ->then(static function (array $spec) use (&$advisories, &$namesFound, &$packageConstraintMap, $name, $create): void { + [$response, ] = $spec; + + if (!isset($response['security-advisories']) || !is_array($response['security-advisories'])) { + return; + } + + $namesFound[$name] = true; + if (count($response['security-advisories']) > 0) { + $advisories[$name] = array_filter(array_map( + static function ($data) use ($name, $create) { + return $create($data, $name); + }, + $response['security-advisories'] + )); + } + unset($packageConstraintMap[$name]); + }); + } + + $this->loop->wait($promises); + } + + if ($apiUrl !== null && count($packageConstraintMap) > 0) { + $options = $this->options; + $options['http']['method'] = 'POST'; + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded'; + $options['http']['timeout'] = 10; + $options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]); + + $response = $this->httpDownloader->get($apiUrl, $options); + $warned = false; + /** @var string $name */ + foreach ($response->decodeJson()['advisories'] as $name => $list) { + if (!isset($packageConstraintMap[$name])) { + if (!$warned) { + $this->io->writeError(''.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).''); + $warned = true; + } + continue; + } + if (count($list) > 0) { + $advisories[$name] = array_filter(array_map( + static function ($data) use ($name, $create) { + return $create($data, $name); + }, + $list + )); + } + $namesFound[$name] = true; + } + } + + return ['namesFound' => array_keys($namesFound), 'advisories' => array_filter($advisories)]; + } + + public function getProviders(string $packageName) + { + $this->loadRootServerFile(); + $result = []; + + if ($this->providersApiUrl) { + try { + $apiResult = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson(); + } catch (TransportException $e) { + if ($e->getStatusCode() === 404) { + return $result; + } + throw $e; + } + + foreach ($apiResult['providers'] as $provider) { + $result[$provider['name']] = $provider; + } + + return $result; + } + + if ($this->hasPartialPackages()) { + if (!is_array($this->partialPackagesByName)) { + throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); + } + foreach ($this->partialPackagesByName as $versions) { + foreach ($versions as $candidate) { + if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) { + continue; + } + $result[$candidate['name']] = [ + 'name' => $candidate['name'], + 'description' => $candidate['description'] ?? '', + 'type' => $candidate['type'] ?? '', + ]; + } + } + } + + if ($this->packages) { + $result = array_merge($result, parent::getProviders($packageName)); + } + + return $result; + } + + /** + * @return string[] + */ + private function getProviderNames(): array + { + $this->loadRootServerFile(); + + if (null === $this->providerListing) { + $data = $this->loadRootServerFile(); + if (is_array($data)) { + $this->loadProviderListings($data); + } + } + + if ($this->lazyProvidersUrl) { + // Can not determine list of provided packages for lazy repositories + return []; + } + + if (null !== $this->providersUrl && null !== $this->providerListing) { + return array_keys($this->providerListing); + } + + return []; + } + + protected function configurePackageTransportOptions(PackageInterface $package): void + { + foreach ($package->getDistUrls() as $url) { + if (strpos($url, $this->baseUrl) === 0) { + $package->setTransportOptions($this->options); + + return; + } + } + } + + private function hasProviders(): bool + { + $this->loadRootServerFile(); + + return $this->hasProviders; + } + + /** + * @param string $name package name + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + * @param array> $alreadyLoaded + * + * @return array + */ + private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array + { + $packagesSource = null; + if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { + return []; + } + + if (null === $this->providerListing) { + $data = $this->loadRootServerFile(); + if (is_array($data)) { + $this->loadProviderListings($data); + } + } + + $useLastModifiedCheck = false; + if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) { + $hash = null; + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + $useLastModifiedCheck = true; + } elseif ($this->providersUrl) { + // package does not exist in this repo + if (!isset($this->providerListing[$name])) { + return []; + } + + $hash = $this->providerListing[$name]['sha256']; + $url = str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + } else { + return []; + } + + $packages = null; + if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { + $packages = json_decode($this->cache->read($cacheKey), true); + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + } elseif ($useLastModifiedCheck) { + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again + if (isset($alreadyLoaded[$name])) { + $packages = $contents; + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + } elseif (isset($contents['last-modified'])) { + $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); + $packages = true === $response ? $contents : $response; + $packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')'; + } + } + } + + if (!$packages) { + try { + $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); + $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; + } catch (TransportException $e) { + // 404s are acceptable for lazy provider repos + if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), [404, 499], true)) { + $packages = ['packages' => []]; + $packagesSource = 'not-found file ('.Url::sanitize($url).')'; + if ($e->getStatusCode() === 499) { + $this->io->error('' . $e->getMessage() . ''); + } + } else { + throw $e; + } + } + } + + $loadingPartialPackage = false; + } else { + $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]]; + $packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')'; + $loadingPartialPackage = true; + } + + $result = []; + $versionsToLoad = []; + foreach ($packages['packages'] as $versions) { + foreach ($versions as $version) { + $normalizedName = strtolower($version['name']); + + // only load the actual named package, not other packages that might find themselves in the same file + if ($normalizedName !== $name) { + continue; + } + + if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) { + continue; + } + + if (!isset($versionsToLoad[$version['uid']])) { + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } + + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$name][$version['version_normalized']])) { + continue; + } + + if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) { + $versionsToLoad[$version['uid']] = $version; + } + } + } + } + + // load acceptable packages in the providers + $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); + $uids = array_keys($versionsToLoad); + + foreach ($loadedPackages as $index => $package) { + $package->setRepository($this); + $uid = $uids[$index]; + + if ($package instanceof AliasPackage) { + $aliased = $package->getAliasOf(); + $aliased->setRepository($this); + + $result[$uid] = $aliased; + $result[$uid.'-alias'] = $package; + } else { + $result[$uid] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + protected function initialize() + { + parent::initialize(); + + $repoData = $this->loadDataFromServer(); + + foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) { + $this->addPackage($package); + } + } + + /** + * Adds a new package to the repository + */ + public function addPackage(PackageInterface $package) + { + parent::addPackage($package); + $this->configurePackageTransportOptions($package); + } + + /** + * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only + * packages matching it will be loaded + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + * @param array> $alreadyLoaded + * + * @return array{namesFound: array, packages: array} + */ + private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array + { + $this->loadRootServerFile(); + + $packages = []; + $namesFound = []; + $promises = []; + + if (null === $this->lazyProvidersUrl) { + throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); + } + + // load ~dev versions of the packages as well if needed + foreach ($packageNames as $name => $constraint) { + if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) { + $packageNames[$name.'~dev'] = $constraint; + } + // if only dev stability is requested, we skip loading the non dev file + if (isset($acceptableStabilities['dev']) && count($acceptableStabilities) === 1 && count($stabilityFlags) === 0) { + unset($packageNames[$name]); + } + } + + foreach ($packageNames as $name => $constraint) { + $name = strtolower($name); + + $realName = Preg::replace('{~dev$}', '', $name); + // skip platform packages, root package and composer-plugin-api + if (PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) { + continue; + } + + $promises[] = $this->startCachedAsyncDownload($name, $realName) + ->then(function (array $spec) use (&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded): void { + [$response, $packagesSource] = $spec; + if (null === $response || !isset($response['packages'][$realName])) { + return; + } + + $versions = $response['packages'][$realName]; + + if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { + $versions = MetadataMinifier::expand($versions); + } + + $namesFound[$realName] = true; + $versionsToLoad = []; + foreach ($versions as $version) { + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { + // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it + $version['version_normalized'] = $this->versionParser->normalize($version['version']); + } + + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$realName][$version['version_normalized']])) { + continue; + } + + if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { + $versionsToLoad[] = $version; + } + } + + $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); + foreach ($loadedPackages as $package) { + $package->setRepository($this); + $packages[spl_object_hash($package)] = $package; + + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $package->getAliasOf()->setRepository($this); + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + }); + } + + $this->loop->wait($promises); + + return ['namesFound' => $namesFound, 'packages' => $packages]; + } + + /** + * @phpstan-return PromiseInterface + */ + private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface + { + if (null === $this->lazyProvidersUrl) { + throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url'); + } + + $name = strtolower($fileName); + $packageName = $packageName ?? $name; + + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '~').'.json'; + + $lastModified = null; + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + $lastModified = $contents['last-modified'] ?? null; + } + + return $this->asyncFetchFile($url, $cacheKey, $lastModified) + ->then(static function ($response) use ($url, $cacheKey, $contents, $packageName): array { + $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; + + if (true === $response) { + $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; + $response = $contents; + } + + if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories'])) { + return [null, $packagesSource]; + } + + return [$response, $packagesSource]; + }); + } + + /** + * @param string $name package name (must be lowercased already) + * @param array $versionData + * @param array|null $acceptableStabilities + * @phpstan-param array, BasePackage::STABILITY_*>|null $acceptableStabilities + * @param array|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array|null $stabilityFlags + */ + private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null): bool + { + $versions = [$versionData['version_normalized']]; + + if ($alias = $this->loader->getBranchAlias($versionData)) { + $versions[] = $alias; + } + + foreach ($versions as $version) { + if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) { + continue; + } + + if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) { + continue; + } + + return true; + } + + return false; + } + + private function getPackagesJsonUrl(): string + { + $jsonUrlParts = parse_url(strtr($this->url, '\\', '/')); + + if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) { + return $this->url; + } + + return $this->url . '/packages.json'; + } + + /** + * @return array<'providers'|'provider-includes'|'packages'|'providers-url'|'notify-batch'|'search'|'mirrors'|'providers-lazy-url'|'metadata-url'|'available-packages'|'available-package-patterns', mixed>|true + */ + protected function loadRootServerFile(?int $rootMaxAge = null) + { + if (null !== $this->rootData) { + return $this->rootData; + } + + if (!extension_loaded('openssl') && strpos($this->url, 'https') === 0) { + throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url); + } + + if ($cachedData = $this->cache->read('packages.json')) { + $cachedData = json_decode($cachedData, true); + if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== false && $age <= $rootMaxAge) { + $data = $cachedData; + } elseif (isset($cachedData['last-modified'])) { + $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']); + $data = true === $response ? $cachedData : $response; + } + } + + if (!isset($data)) { + $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, true); + } + + if (!empty($data['notify-batch'])) { + $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); + } elseif (!empty($data['notify'])) { + $this->notifyUrl = $this->canonicalizeUrl($data['notify']); + } + + if (!empty($data['search'])) { + $this->searchUrl = $this->canonicalizeUrl($data['search']); + } + + if (!empty($data['mirrors'])) { + foreach ($data['mirrors'] as $mirror) { + if (!empty($mirror['git-url'])) { + $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])]; + } + if (!empty($mirror['hg-url'])) { + $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])]; + } + if (!empty($mirror['dist-url'])) { + $this->distMirrors[] = [ + 'url' => $this->canonicalizeUrl($mirror['dist-url']), + 'preferred' => !empty($mirror['preferred']), + ]; + } + } + } + + if (!empty($data['providers-lazy-url'])) { + $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']); + $this->hasProviders = true; + + $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); + } + + // metadata-url indicates V2 repo protocol so it takes over from all the V1 types + // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else, + // V2 also supports async loading + if (!empty($data['metadata-url'])) { + $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); + $this->providersUrl = null; + $this->hasProviders = false; + $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); + $this->allowSslDowngrade = false; + + // provides a list of package names that are available in this repo + // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo + // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it + if (!empty($data['available-packages'])) { + $availPackages = array_map('strtolower', $data['available-packages']); + $this->availablePackages = array_combine($availPackages, $availPackages); + $this->hasAvailablePackageList = true; + } + + // Provides a list of package name patterns (using * wildcards to match any substring, e.g. "vendor/*") that are available in this repo + // Disables lazy-provider behavior as with available-packages, but may allow much more compact expression of packages covered by this repository. + // Over-specifying covered packages is safe, but may result in increased traffic to your repository. + if (!empty($data['available-package-patterns'])) { + $this->availablePackagePatterns = array_map(static function ($pattern): string { + return BasePackage::packageNameToRegexp($pattern); + }, $data['available-package-patterns']); + $this->hasAvailablePackageList = true; + } + + // Remove legacy keys as most repos need to be compatible with Composer v1 + // as well but we are not interested in the old format anymore at this point + unset($data['providers-url'], $data['providers'], $data['providers-includes']); + + if (isset($data['security-advisories']) && is_array($data['security-advisories'])) { + $this->securityAdvisoryConfig = [ + 'metadata' => $data['security-advisories']['metadata'] ?? false, + 'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null, + ]; + if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) { + throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.'); + } + } + } + + if ($this->allowSslDowngrade) { + $this->url = str_replace('https://', 'http://', $this->url); + $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl); + } + + if (!empty($data['providers-url'])) { + $this->providersUrl = $this->canonicalizeUrl($data['providers-url']); + $this->hasProviders = true; + } + + if (!empty($data['list'])) { + $this->listUrl = $this->canonicalizeUrl($data['list']); + } + + if (!empty($data['providers']) || !empty($data['providers-includes'])) { + $this->hasProviders = true; + } + + if (!empty($data['providers-api'])) { + $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']); + } + + return $this->rootData = $data; + } + + /** + * @param string $url + * @return non-empty-string + */ + private function canonicalizeUrl(string $url): string + { + if (strlen($url) === 0) { + throw new \InvalidArgumentException('Expected a string with a value and not an empty string'); + } + + if (str_starts_with($url, '/')) { + if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) { + return $matches[0] . $url; + } + + return $this->url; + } + + return $url; + } + + /** + * @return mixed[] + */ + private function loadDataFromServer(): array + { + $data = $this->loadRootServerFile(); + if (true === $data) { + throw new \LogicException('loadRootServerFile should not return true during initialization'); + } + + return $this->loadIncludes($data); + } + + private function hasPartialPackages(): bool + { + if ($this->hasPartialPackages && null === $this->partialPackagesByName) { + $this->initializePartialPackages(); + } + + return $this->hasPartialPackages; + } + + /** + * @param array{providers?: mixed[], provider-includes?: mixed[]} $data + */ + private function loadProviderListings($data): void + { + if (isset($data['providers'])) { + if (!is_array($this->providerListing)) { + $this->providerListing = []; + } + $this->providerListing = array_merge($this->providerListing, $data['providers']); + } + + if ($this->providersUrl && isset($data['provider-includes'])) { + $includes = $data['provider-includes']; + foreach ($includes as $include => $metadata) { + $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include); + $cacheKey = str_replace(['%hash%','$'], '', $include); + if ($this->cache->sha256($cacheKey) === $metadata['sha256']) { + $includedData = json_decode($this->cache->read($cacheKey), true); + } else { + $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']); + } + + $this->loadProviderListings($includedData); + } + } + } + + /** + * @param mixed[] $data + * + * @return mixed[] + */ + private function loadIncludes(array $data): array + { + $packages = []; + + // legacy repo handling + if (!isset($data['packages']) && !isset($data['includes'])) { + foreach ($data as $pkg) { + if (isset($pkg['versions']) && is_array($pkg['versions'])) { + foreach ($pkg['versions'] as $metadata) { + $packages[] = $metadata; + } + } + } + + return $packages; + } + + if (isset($data['packages'])) { + foreach ($data['packages'] as $package => $versions) { + $packageName = strtolower((string) $package); + foreach ($versions as $version => $metadata) { + $packages[] = $metadata; + if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== strtolower((string) ($metadata['name'] ?? ''))) { + $this->displayedWarningAboutNonMatchingPackageIndex = true; + $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $metadata['name'] ?? '', $this->baseUrl)); + } + } + } + } + + if (isset($data['includes'])) { + foreach ($data['includes'] as $include => $metadata) { + if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) { + $includedData = json_decode($this->cache->read((string) $include), true); + } else { + $includedData = $this->fetchFile($include); + } + $packages = array_merge($packages, $this->loadIncludes($includedData)); + } + } + + return $packages; + } + + /** + * @param mixed[] $packages + * + * @return list + */ + private function createPackages(array $packages, ?string $source = null): array + { + if (!$packages) { + return []; + } + + try { + foreach ($packages as &$data) { + if (!isset($data['notification-url'])) { + $data['notification-url'] = $this->notifyUrl; + } + } + + $packageInstances = $this->loader->loadPackages($packages); + + foreach ($packageInstances as $package) { + if (isset($this->sourceMirrors[$package->getSourceType()])) { + $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); + } + $package->setDistMirrors($this->distMirrors); + $this->configurePackageTransportOptions($package); + } + + return $packageInstances; + } catch (\Exception $e) { + throw new \RuntimeException('Could not load packages '.($packages[0]['name'] ?? json_encode($packages)).' in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e); + } + } + + /** + * @return array + */ + protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = false) + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + if (null === $cacheKey) { + $cacheKey = $filename; + $filename = $this->baseUrl.'/'.$filename; + } + + // url-encode $ signs in URLs as bad proxies choke on them + if (($pos = strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) { + $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1); + } + + $retries = 3; + while ($retries--) { + try { + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + $response = $this->httpDownloader->get($filename, $options); + $json = (string) $response->getBody(); + if ($sha256 && $sha256 !== hash('sha256', $json)) { + // undo downgrade before trying again if http seems to be hijacked or modifying content somehow + if ($this->allowSslDowngrade) { + $this->url = str_replace('http://', 'https://', $this->url); + $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl); + $filename = str_replace('http://', 'https://', $filename); + } + + if ($retries > 0) { + usleep(100000); + + continue; + } + + // TODO use scarier wording once we know for sure it doesn't do false positives anymore + throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); + } + + if ($this->eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($this->io, $this->url, $data); + + if ($cacheKey && !$this->cache->isReadOnly()) { + if ($storeLastModifiedTime) { + $lastModifiedDate = $response->getHeader('last-modified'); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, 0); + } + } + $this->cache->write($cacheKey, $json); + } + + $response->collect(); + + break; + } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + throw $e; + } + + if ($e instanceof RepositorySecurityException) { + throw $e; + } + + if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { + if (!$this->degradedMode) { + $this->io->writeError(''.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $this->degradedMode = true; + $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey); + + break; + } + + throw $e; + } + } + + if (!isset($data)) { + throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new."); + } + + return $data; + } + + /** + * @return array|true + */ + private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime) + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + try { + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; + $response = $this->httpDownloader->get($filename, $options); + $json = (string) $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { + return true; + } + + if ($this->eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($this->io, $this->url, $data); + + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, 0); + } + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, $json); + } + + return $data; + } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + throw $e; + } + + if (!$this->degradedMode) { + $this->io->writeError(''.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $this->degradedMode = true; + + return true; + } + } + + /** + * @phpstan-return PromiseInterface|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json + */ + private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface + { + if ('' === $filename) { + throw new \InvalidArgumentException('$filename should not be an empty string'); + } + + if (isset($this->packagesNotFoundCache[$filename])) { + return \React\Promise\resolve(['packages' => []]); + } + + if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) { + // make it look like we got a 304 response + /** @var PromiseInterface $promise */ + $promise = \React\Promise\resolve(true); + + return $promise; + } + + $httpDownloader = $this->httpDownloader; + $options = $this->options; + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); + $preFileDownloadEvent->setTransportOptions($this->options); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $filename = $preFileDownloadEvent->getProcessedUrl(); + $options = $preFileDownloadEvent->getTransportOptions(); + } + + if ($lastModifiedTime) { + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; + } + + $io = $this->io; + $url = $this->url; + $cache = $this->cache; + $degradedMode = &$this->degradedMode; + $eventDispatcher = $this->eventDispatcher; + + /** + * @return array|true true if the response was a 304 and the cache is fresh + */ + $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) { + // package not found is acceptable for a v2 protocol repository + if ($response->getStatusCode() === 404) { + $this->packagesNotFoundCache[$filename] = true; + + return ['packages' => []]; + } + + $json = (string) $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { + $this->freshMetadataUrls[$filename] = true; + + return true; + } + + if ($eventDispatcher) { + $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); + $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); + } + + $data = $response->decodeJson(); + HttpDownloader::outputWarnings($io, $url, $data); + + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + if (!$cache->isReadOnly()) { + $cache->write($cacheKey, $json); + } + $this->freshMetadataUrls[$filename] = true; + + return $data; + }; + + $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) { + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + $this->packagesNotFoundCache[$filename] = true; + + return false; + } + + if (!$degradedMode) { + $io->writeError(''.$url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); + } + $degradedMode = true; + + // if the file is in the cache, we fake a 304 Not Modified to allow the process to continue + if ($lastModifiedTime) { + return $accept(new Response(['url' => $url], 304, [], '')); + } + + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + return $accept(new Response(['url' => $url], 404, [], '')); + } + + throw $e; + }; + + return $httpDownloader->add($filename, $options)->then($accept, $reject); + } + + /** + * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url + * + * This should only be called once + */ + private function initializePartialPackages(): void + { + $rootData = $this->loadRootServerFile(); + if ($rootData === true) { + return; + } + + $this->partialPackagesByName = []; + foreach ($rootData['packages'] as $package => $versions) { + foreach ($versions as $version) { + $versionPackageName = strtolower((string) ($version['name'] ?? '')); + $this->partialPackagesByName[$versionPackageName][] = $version; + if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== strtolower($package)) { + $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $version['name'] ?? '', $this->baseUrl)); + $this->displayedWarningAboutNonMatchingPackageIndex = true; + } + } + } + + // wipe rootData as it is fully consumed at this point and this saves some memory + $this->rootData = true; + } + + /** + * Checks if the package name is present in this lazy providers repo + * + * @return bool true if the package name is present in availablePackages or matched by availablePackagePatterns + */ + protected function lazyProvidersRepoContains(string $name) + { + if (!$this->hasAvailablePackageList) { + throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true'); + } + + if (is_array($this->availablePackages) && isset($this->availablePackages[$name])) { + return true; + } + + if (is_array($this->availablePackagePatterns)) { + foreach ($this->availablePackagePatterns as $providerRegex) { + if (Preg::isMatch($providerRegex, $name)) { + return true; + } + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php b/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php new file mode 100644 index 0000000..8ee5326 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/CompositeRepository.php @@ -0,0 +1,203 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; + +/** + * Composite repository. + * + * @author Beau Simensen + */ +class CompositeRepository implements RepositoryInterface +{ + /** + * List of repositories + * @var RepositoryInterface[] + */ + private $repositories; + + /** + * Constructor + * @param RepositoryInterface[] $repositories + */ + public function __construct(array $repositories) + { + $this->repositories = []; + foreach ($repositories as $repo) { + $this->addRepository($repo); + } + } + + public function getRepoName(): string + { + return 'composite repo ('.implode(', ', array_map(static function ($repo): string { + return $repo->getRepoName(); + }, $this->repositories)).')'; + } + + /** + * Returns all the wrapped repositories + * + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package): bool + { + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + if ($repository->hasPackage($package)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function findPackage($name, $constraint): ?BasePackage + { + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $package = $repository->findPackage($name, $constraint); + if (null !== $package) { + return $package; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function findPackages($name, $constraint = null): array + { + $packages = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $packages[] = $repository->findPackages($name, $constraint); + } + + return $packages ? array_merge(...$packages) : []; + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array + { + $packages = []; + $namesFound = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + $packages[] = $result['packages']; + $namesFound[] = $result['namesFound']; + } + + return [ + 'packages' => $packages ? array_merge(...$packages) : [], + 'namesFound' => $namesFound ? array_unique(array_merge(...$namesFound)) : [], + ]; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null): array + { + $matches = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $matches[] = $repository->search($query, $mode, $type); + } + + return \count($matches) > 0 ? array_merge(...$matches) : []; + } + + /** + * @inheritDoc + */ + public function getPackages(): array + { + $packages = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $packages[] = $repository->getPackages(); + } + + return $packages ? array_merge(...$packages) : []; + } + + /** + * @inheritDoc + */ + public function getProviders($packageName): array + { + $results = []; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $results[] = $repository->getProviders($packageName); + } + + return $results ? array_merge(...$results) : []; + } + + public function removePackage(PackageInterface $package): void + { + foreach ($this->repositories as $repository) { + if ($repository instanceof WritableRepositoryInterface) { + $repository->removePackage($package); + } + } + } + + /** + * @inheritDoc + */ + public function count(): int + { + $total = 0; + foreach ($this->repositories as $repository) { + /* @var $repository RepositoryInterface */ + $total += $repository->count(); + } + + return $total; + } + + /** + * Add a repository. + */ + public function addRepository(RepositoryInterface $repository): void + { + if ($repository instanceof self) { + foreach ($repository->getRepositories() as $repo) { + $this->addRepository($repo); + } + } else { + $this->repositories[] = $repository; + } + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php new file mode 100644 index 0000000..a56540f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Configurable repository interface. + * + * @author Lukas Homza + */ +interface ConfigurableRepositoryInterface +{ + /** + * @return mixed[] + */ + public function getRepoConfig(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php b/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php new file mode 100644 index 0000000..6ae6b14 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/FilesystemRepository.php @@ -0,0 +1,417 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\PackageInterface; +use Composer\Package\RootAliasPackage; +use Composer\Package\RootPackageInterface; +use Composer\Package\AliasPackage; +use Composer\Package\Dumper\ArrayDumper; +use Composer\Installer\InstallationManager; +use Composer\Pcre\Preg; +use Composer\Util\Filesystem; +use Composer\Util\Platform; + +/** + * Filesystem repository. + * + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +class FilesystemRepository extends WritableArrayRepository +{ + /** @var JsonFile */ + protected $file; + /** @var bool */ + private $dumpVersions; + /** @var ?RootPackageInterface */ + private $rootPackage; + /** @var Filesystem */ + private $filesystem; + /** @var bool|null */ + private $devMode = null; + + /** + * Initializes filesystem repository. + * + * @param JsonFile $repositoryFile repository json file + * @param ?RootPackageInterface $rootPackage Must be provided if $dumpVersions is true + */ + public function __construct(JsonFile $repositoryFile, bool $dumpVersions = false, ?RootPackageInterface $rootPackage = null, ?Filesystem $filesystem = null) + { + parent::__construct(); + $this->file = $repositoryFile; + $this->dumpVersions = $dumpVersions; + $this->rootPackage = $rootPackage; + $this->filesystem = $filesystem ?: new Filesystem; + if ($dumpVersions && !$rootPackage) { + throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true'); + } + } + + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode() + { + return $this->devMode; + } + + /** + * Initializes repository (reads file, or remote address). + */ + protected function initialize() + { + parent::initialize(); + + if (!$this->file->exists()) { + return; + } + + try { + $data = $this->file->read(); + if (isset($data['packages'])) { + $packages = $data['packages']; + } else { + $packages = $data; + } + + if (isset($data['dev-package-names'])) { + $this->setDevPackageNames($data['dev-package-names']); + } + if (isset($data['dev'])) { + $this->devMode = $data['dev']; + } + + if (!is_array($packages)) { + throw new \UnexpectedValueException('Could not parse package list from the repository'); + } + } catch (\Exception $e) { + throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage()); + } + + $loader = new ArrayLoader(null, true); + foreach ($packages as $packageData) { + $package = $loader->load($packageData); + $this->addPackage($package); + } + } + + public function reload() + { + $this->packages = null; + $this->initialize(); + } + + /** + * Writes writable repository. + */ + public function write(bool $devMode, InstallationManager $installationManager) + { + $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []]; + $dumper = new ArrayDumper(); + + // make sure the directory is created so we can realpath it + // as realpath() does some additional normalizations with network paths that normalizePath does not + // and we need to find shortest path correctly + $repoDir = dirname($this->file->getPath()); + $this->filesystem->ensureDirectoryExists($repoDir); + + $repoDir = $this->filesystem->normalizePath(realpath($repoDir)); + $installPaths = []; + + foreach ($this->getCanonicalPackages() as $package) { + $pkgArray = $dumper->dump($package); + $path = $installationManager->getInstallPath($package); + $installPath = null; + if ('' !== $path && null !== $path) { + $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path); + $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true); + } + $installPaths[$package->getName()] = $installPath; + + $pkgArray['install-path'] = $installPath; + $data['packages'][] = $pkgArray; + + // only write to the files the names which are really installed, as we receive the full list + // of dev package names before they get installed during composer install + if (in_array($package->getName(), $this->devPackageNames, true)) { + $data['dev-package-names'][] = $package->getName(); + } + } + + sort($data['dev-package-names']); + usort($data['packages'], static function ($a, $b): int { + return strcmp($a['name'], $b['name']); + }); + + $this->file->write($data); + + if ($this->dumpVersions) { + $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir); + + $this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', 'dumpToPhpCode($versions) . ';'."\n"); + $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php'); + + // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility + if ($installedVersionsClass !== false) { + $this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); + + // make sure the in memory state is up to date with on disk + \Composer\InstalledVersions::reload($versions); + + // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been + // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the + // in memory loaded data from above + try { + $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir'); + $reflProp->setAccessible(true); + $reflProp->setValue(null, strtr($repoDir, '\\', '/')); + + $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir'); + $reflProp->setAccessible(true); + $reflProp->setValue(null, true); + } catch (\ReflectionException $e) { + if (!Preg::isMatch('{Property .*? does not exist}i', $e->getMessage())) { + throw $e; + } + // noop, if outdated class is loaded we do not want to cause trouble + } + } + } + } + + /** + * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it + * + * @internal + */ + public static function safelyLoadInstalledVersions(string $path): bool + { + $installedVersionsData = @file_get_contents($path); + $pattern = <<<'REGEX' +{(?(DEFINE) + (? -? \s*+ \d++ (?:\.\d++)? ) + (? true | false | null ) + (? (?&string) (?: \s*+ \. \s*+ (?&string))*+ ) + (? (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) ) + (? array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) ) + (? (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) ) +) +^<\?php\s++return\s++(?&array)\s*+;$}ix +REGEX; + if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) { + \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData))); + + return true; + } + + return false; + } + + /** + * @param array $array + */ + private function dumpToPhpCode(array $array = [], int $level = 0): string + { + $lines = "array(\n"; + $level++; + + foreach ($array as $key => $value) { + $lines .= str_repeat(' ', $level); + $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => '; + + if (is_array($value)) { + if (!empty($value)) { + $lines .= $this->dumpToPhpCode($value, $level); + } else { + $lines .= "array(),\n"; + } + } elseif ($key === 'install_path' && is_string($value)) { + if ($this->filesystem->isAbsolutePath($value)) { + $lines .= var_export($value, true) . ",\n"; + } else { + $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n"; + } + } elseif (is_string($value)) { + $lines .= var_export($value, true) . ",\n"; + } elseif (is_bool($value)) { + $lines .= ($value ? 'true' : 'false') . ",\n"; + } elseif (is_null($value)) { + $lines .= "null,\n"; + } else { + throw new \UnexpectedValueException('Unexpected type '.gettype($value)); + } + } + + $lines .= str_repeat(' ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n"); + + return $lines; + } + + /** + * @param array $installPaths + * + * @return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir): array + { + $devPackages = array_flip($this->devPackageNames); + $packages = $this->getPackages(); + if (null === $this->rootPackage) { + throw new \LogicException('It should not be possible to dump packages if no root package is given'); + } + $packages[] = $rootPackage = $this->rootPackage; + + while ($rootPackage instanceof RootAliasPackage) { + $rootPackage = $rootPackage->getAliasOf(); + $packages[] = $rootPackage; + } + $versions = [ + 'root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages), + 'versions' => [], + ]; + + // add real installed packages + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + continue; + } + + $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); + } + + // add provided/replaced packages + foreach ($packages as $package) { + $isDevPackage = isset($devPackages[$package->getName()]); + foreach ($package->getReplaces() as $replace) { + // exclude platform replaces as when they are really there we can not check for their presence + if (PlatformRepository::isPlatformPackage($replace->getTarget())) { + continue; + } + if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) { + $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage; + } elseif (!$isDevPackage) { + $versions['versions'][$replace->getTarget()]['dev_requirement'] = false; + } + $replaced = $replace->getPrettyConstraint(); + if ($replaced === 'self.version') { + $replaced = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) { + $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced; + } + } + foreach ($package->getProvides() as $provide) { + // exclude platform provides as when they are really there we can not check for their presence + if (PlatformRepository::isPlatformPackage($provide->getTarget())) { + continue; + } + if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) { + $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage; + } elseif (!$isDevPackage) { + $versions['versions'][$provide->getTarget()]['dev_requirement'] = false; + } + $provided = $provide->getPrettyConstraint(); + if ($provided === 'self.version') { + $provided = $package->getPrettyVersion(); + } + if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) { + $versions['versions'][$provide->getTarget()]['provided'][] = $provided; + } + } + } + + // add aliases + foreach ($packages as $package) { + if (!$package instanceof AliasPackage) { + continue; + } + $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion(); + if ($package instanceof RootPackageInterface) { + $versions['root']['aliases'][] = $package->getPrettyVersion(); + } + } + + ksort($versions['versions']); + ksort($versions); + + foreach ($versions['versions'] as $name => $version) { + foreach (['aliases', 'replaced', 'provided'] as $key) { + if (isset($versions['versions'][$name][$key])) { + sort($versions['versions'][$name][$key], SORT_NATURAL); + } + } + } + + return $versions; + } + + /** + * @param array $installPaths + * @param array $devPackages + * @return array{pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev_requirement: bool} + */ + private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages): array + { + $reference = null; + if ($package->getInstallationSource()) { + $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference(); + } + if (null === $reference) { + $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; + } + + if ($package instanceof RootPackageInterface) { + $to = $this->filesystem->normalizePath(realpath(Platform::getCwd())); + $installPath = $this->filesystem->findShortestPath($repoDir, $to, true); + } else { + $installPath = $installPaths[$package->getName()]; + } + + $data = [ + 'pretty_version' => $package->getPrettyVersion(), + 'version' => $package->getVersion(), + 'reference' => $reference, + 'type' => $package->getType(), + 'install_path' => $installPath, + 'aliases' => [], + 'dev_requirement' => isset($devPackages[$package->getName()]), + ]; + + return $data; + } + + /** + * @param array $installPaths + * @param array $devPackages + * @return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages) + { + $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); + + return [ + 'name' => $package->getName(), + 'pretty_version' => $data['pretty_version'], + 'version' => $data['version'], + 'reference' => $data['reference'], + 'type' => $data['type'], + 'install_path' => $data['install_path'], + 'aliases' => $data['aliases'], + 'dev' => $devMode, + ]; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/FilterRepository.php b/vendor/composer/composer/src/Composer/Repository/FilterRepository.php new file mode 100644 index 0000000..bd9b54d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/FilterRepository.php @@ -0,0 +1,234 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Package\BasePackage; +use Composer\Pcre\Preg; + +/** + * Filters which packages are seen as canonical on this repo by loadPackages + * + * @author Jordi Boggiano + */ +class FilterRepository implements RepositoryInterface, AdvisoryProviderInterface +{ + /** @var ?string */ + private $only = null; + /** @var ?non-empty-string */ + private $exclude = null; + /** @var bool */ + private $canonical = true; + /** @var RepositoryInterface */ + private $repo; + + /** + * @param array{only?: array, exclude?: array, canonical?: bool} $options + */ + public function __construct(RepositoryInterface $repo, array $options) + { + if (isset($options['only'])) { + if (!is_array($options['only'])) { + throw new \InvalidArgumentException('"only" key for repository '.$repo->getRepoName().' should be an array'); + } + $this->only = BasePackage::packageNamesToRegexp($options['only']); + } + if (isset($options['exclude'])) { + if (!is_array($options['exclude'])) { + throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array'); + } + $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']); + } + if ($this->exclude && $this->only) { + throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName()); + } + if (isset($options['canonical'])) { + if (!is_bool($options['canonical'])) { + throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean'); + } + $this->canonical = $options['canonical']; + } + + $this->repo = $repo; + } + + public function getRepoName(): string + { + return $this->repo->getRepoName(); + } + + /** + * Returns the wrapped repositories + */ + public function getRepository(): RepositoryInterface + { + return $this->repo; + } + + /** + * @inheritDoc + */ + public function hasPackage(PackageInterface $package): bool + { + return $this->repo->hasPackage($package); + } + + /** + * @inheritDoc + */ + public function findPackage($name, $constraint): ?BasePackage + { + if (!$this->isAllowed($name)) { + return null; + } + + return $this->repo->findPackage($name, $constraint); + } + + /** + * @inheritDoc + */ + public function findPackages($name, $constraint = null): array + { + if (!$this->isAllowed($name)) { + return []; + } + + return $this->repo->findPackages($name, $constraint); + } + + /** + * @inheritDoc + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array + { + foreach ($packageNameMap as $name => $constraint) { + if (!$this->isAllowed($name)) { + unset($packageNameMap[$name]); + } + } + + if (!$packageNameMap) { + return ['namesFound' => [], 'packages' => []]; + } + + $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); + if (!$this->canonical) { + $result['namesFound'] = []; + } + + return $result; + } + + /** + * @inheritDoc + */ + public function search(string $query, int $mode = 0, ?string $type = null): array + { + $result = []; + + foreach ($this->repo->search($query, $mode, $type) as $package) { + if ($this->isAllowed($package['name'])) { + $result[] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function getPackages(): array + { + $result = []; + foreach ($this->repo->getPackages() as $package) { + if ($this->isAllowed($package->getName())) { + $result[] = $package; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function getProviders($packageName): array + { + $result = []; + foreach ($this->repo->getProviders($packageName) as $name => $provider) { + if ($this->isAllowed($provider['name'])) { + $result[$name] = $provider; + } + } + + return $result; + } + + /** + * @inheritDoc + */ + public function count(): int + { + if ($this->repo->count() > 0) { + return count($this->getPackages()); + } + + return 0; + } + + public function hasSecurityAdvisories(): bool + { + if (!$this->repo instanceof AdvisoryProviderInterface) { + return false; + } + + return $this->repo->hasSecurityAdvisories(); + } + + /** + * @inheritDoc + */ + public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array + { + if (!$this->repo instanceof AdvisoryProviderInterface) { + return ['namesFound' => [], 'advisories' => []]; + } + + foreach ($packageConstraintMap as $name => $constraint) { + if (!$this->isAllowed($name)) { + unset($packageConstraintMap[$name]); + } + } + + return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories); + } + + private function isAllowed(string $name): bool + { + if (!$this->only && !$this->exclude) { + return true; + } + + if ($this->only) { + return Preg::isMatch($this->only, $name); + } + + if ($this->exclude === null) { + return true; + } + + return !Preg::isMatch($this->exclude, $name); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php new file mode 100644 index 0000000..971276f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installed array repository. + * + * This is used as an in-memory InstalledRepository mostly for testing purposes + * + * @author Jordi Boggiano + */ +class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface +{ + public function getRepoName(): string + { + return 'installed '.parent::getRepoName(); + } + + /** + * @inheritDoc + */ + public function isFresh(): bool + { + // this is not a completely correct implementation but there is no way to + // distinguish an empty repo and a newly created one given this is all in-memory + return $this->count() === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php new file mode 100644 index 0000000..393a384 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installed filesystem repository. + * + * @author Jordi Boggiano + */ +class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface +{ + public function getRepoName() + { + return 'installed '.parent::getRepoName(); + } + + /** + * @inheritDoc + */ + public function isFresh() + { + return !$this->file->exists(); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php b/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php new file mode 100644 index 0000000..3520fde --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledRepository.php @@ -0,0 +1,277 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Package\RootPackageInterface; +use Composer\Package\Link; + +/** + * Installed repository is a composite of all installed repo types. + * + * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily. + * + * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository + * + * @author Jordi Boggiano + */ +class InstalledRepository extends CompositeRepository +{ + /** + * @param ConstraintInterface|string|null $constraint + * + * @return BasePackage[] + */ + public function findPackagesWithReplacersAndProviders(string $name, $constraint = null): array + { + $name = strtolower($name); + + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($constraint); + } + + $matches = []; + foreach ($this->getRepositories() as $repo) { + foreach ($repo->getPackages() as $candidate) { + if ($name === $candidate->getName()) { + if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { + $matches[] = $candidate; + } + continue; + } + + foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { + if ( + $name === $link->getTarget() + && ($constraint === null || $constraint->matches($link->getConstraint())) + ) { + $matches[] = $candidate; + continue 2; + } + } + } + } + + return $matches; + } + + /** + * Returns a list of links causing the requested needle packages to be installed, as an associative array with the + * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship + * as values. If recursive lookup was requested a third value is returned containing an identically formed array up + * to the root package. That third value will be false in case a circular recursion was detected. + * + * @param string|string[] $needle The package name(s) to inspect. + * @param ConstraintInterface|null $constraint Optional constraint to filter by. + * @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed. + * @param bool $recurse Whether to recursively expand the requirement tree up to the root package. + * @param string[] $packagesFound Used internally when recurring + * + * @return array[] An associative array of arrays as described above. + * @phpstan-return array|false}> + */ + public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array + { + $needles = array_map('strtolower', (array) $needle); + $results = []; + + // initialize the array with the needles before any recursion occurs + if (null === $packagesFound) { + $packagesFound = $needles; + } + + // locate root package for use below + $rootPackage = null; + foreach ($this->getPackages() as $package) { + if ($package instanceof RootPackageInterface) { + $rootPackage = $package; + break; + } + } + + // Loop over all currently installed packages. + foreach ($this->getPackages() as $package) { + $links = $package->getRequires(); + + // each loop needs its own "tree" as we want to show the complete dependent set of every needle + // without warning all the time about finding circular deps + $packagesInTree = $packagesFound; + + // Replacements are considered valid reasons for a package to be installed during forward resolution + if (!$invert) { + $links += $package->getReplaces(); + + // On forward search, check if any replaced package was required and add the replaced + // packages to the list of needles. Contrary to the cross-reference link check below, + // replaced packages are the target of links. + foreach ($package->getReplaces() as $link) { + foreach ($needles as $needle) { + if ($link->getSource() === $needle) { + if ($constraint === null || ($link->getConstraint()->matches($constraint) === true)) { + // already displayed this node's dependencies, cutting short + if (in_array($link->getTarget(), $packagesInTree)) { + $results[] = [$package, $link, false]; + continue; + } + $packagesInTree[] = $link->getTarget(); + $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : []; + $results[] = [$package, $link, $dependents]; + $needles[] = $link->getTarget(); + } + } + } + } + unset($needle); + } + + // Require-dev is only relevant for the root package + if ($package instanceof RootPackageInterface) { + $links += $package->getDevRequires(); + } + + // Cross-reference all discovered links to the needles + foreach ($links as $link) { + foreach ($needles as $needle) { + if ($link->getTarget() === $needle) { + if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) { + // already displayed this node's dependencies, cutting short + if (in_array($link->getSource(), $packagesInTree)) { + $results[] = [$package, $link, false]; + continue; + } + $packagesInTree[] = $link->getSource(); + $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : []; + $results[] = [$package, $link, $dependents]; + } + } + } + } + + // When inverting, we need to check for conflicts of the needles against installed packages + if ($invert && in_array($package->getName(), $needles, true)) { + foreach ($package->getConflicts() as $link) { + foreach ($this->findPackages($link->getTarget()) as $pkg) { + $version = new Constraint('=', $pkg->getVersion()); + if ($link->getConstraint()->matches($version) === $invert) { + $results[] = [$package, $link, false]; + } + } + } + } + + // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting + foreach ($package->getConflicts() as $link) { + if (in_array($link->getTarget(), $needles, true)) { + foreach ($this->findPackages($link->getTarget()) as $pkg) { + $version = new Constraint('=', $pkg->getVersion()); + if ($link->getConstraint()->matches($version) === $invert) { + $results[] = [$package, $link, false]; + } + } + } + } + + // When inverting, we need to check for conflicts of the needles' requirements against installed packages + if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) { + foreach ($package->getRequires() as $link) { + if (PlatformRepository::isPlatformPackage($link->getTarget())) { + if ($this->findPackage($link->getTarget(), $link->getConstraint())) { + continue; + } + + $platformPkg = $this->findPackage($link->getTarget(), '*'); + $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing'; + $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_REQUIRE, $link->getPrettyConstraint().' '.$description), false]; + + continue; + } + + foreach ($this->getPackages() as $pkg) { + if (!in_array($link->getTarget(), $pkg->getNames())) { + continue; + } + + $version = new Constraint('=', $pkg->getVersion()); + + if ($link->getTarget() !== $pkg->getName()) { + foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) { + if ($link->getTarget() === $prov->getTarget()) { + $version = $prov->getConstraint(); + break; + } + } + } + + if (!$link->getConstraint()->matches($version)) { + // if we have a root package (we should but can not guarantee..) we show + // the root requires as well to perhaps allow to find an issue there + if ($rootPackage) { + foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) { + if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) { + $results[] = [$package, $link, false]; + $results[] = [$rootPackage, $rootReq, false]; + continue 3; + } + } + + $results[] = [$package, $link, false]; + $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), false]; + } else { + // no root so let's just print whatever we found + $results[] = [$package, $link, false]; + } + } + + continue 2; + } + } + } + } + + ksort($results); + + return $results; + } + + public function getRepoName(): string + { + return 'installed repo ('.implode(', ', array_map(static function ($repo): string { + return $repo->getRepoName(); + }, $this->getRepositories())).')'; + } + + /** + * @inheritDoc + */ + public function addRepository(RepositoryInterface $repository): void + { + if ( + $repository instanceof LockArrayRepository + || $repository instanceof InstalledRepositoryInterface + || $repository instanceof RootPackageRepository + || $repository instanceof PlatformRepository + ) { + parent::addRepository($repository); + + return; + } + + throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')'); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php new file mode 100644 index 0000000..3b89d1d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php @@ -0,0 +1,33 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Installable repository interface. + * + * Just used to tag installed repositories so the base classes can act differently on Alias packages + * + * @author Jordi Boggiano + */ +interface InstalledRepositoryInterface extends WritableRepositoryInterface +{ + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode(); + + /** + * @return bool true if packages were never installed in this repository + */ + public function isFresh(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php b/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php new file mode 100644 index 0000000..9dbd6f0 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Exception thrown when a package repository is utterly broken + * + * @author Jordi Boggiano + */ +class InvalidRepositoryException extends \Exception +{ +} diff --git a/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php new file mode 100644 index 0000000..da07758 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php @@ -0,0 +1,30 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Lock array repository. + * + * Regular array repository, only uses a different type to identify the lock file as the source of info + * + * @author Nils Adermann + */ +class LockArrayRepository extends ArrayRepository +{ + use CanonicalPackagesTrait; + + public function getRepoName(): string + { + return 'lock repo'; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PackageRepository.php b/vendor/composer/composer/src/Composer/Repository/PackageRepository.php new file mode 100644 index 0000000..67aadc4 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PackageRepository.php @@ -0,0 +1,68 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Pcre\Preg; + +/** + * Package repository. + * + * @author Jordi Boggiano + */ +class PackageRepository extends ArrayRepository +{ + /** @var mixed[] */ + private $config; + + /** + * Initializes filesystem repository. + * + * @param array{package: mixed[]} $config package definition + */ + public function __construct(array $config) + { + parent::__construct(); + $this->config = $config['package']; + + // make sure we have an array of package definitions + if (!is_numeric(key($this->config))) { + $this->config = [$this->config]; + } + } + + /** + * Initializes repository (reads file, or remote address). + */ + protected function initialize(): void + { + parent::initialize(); + + $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), true); + foreach ($this->config as $package) { + try { + $package = $loader->load($package); + } catch (\Exception $e) { + throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package)); + } + + $this->addPackage($package); + } + } + + public function getRepoName(): string + { + return Preg::replace('{^array }', 'package ', parent::getRepoName()); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PathRepository.php b/vendor/composer/composer/src/Composer/Repository/PathRepository.php new file mode 100644 index 0000000..0b8d992 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PathRepository.php @@ -0,0 +1,253 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Version\VersionGuesser; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Git as GitUtil; + +/** + * This repository allows installing local packages that are not necessarily under their own VCS. + * + * The local packages will be symlinked when possible, else they will be copied. + * + * @code + * "require": { + * "/": "*" + * }, + * "repositories": [ + * { + * "type": "path", + * "url": "../../relative/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/several/packages/*" + * }, + * { + * "type": "path", + * "url": "../../relative/path/to/package/", + * "options": { + * "symlink": false + * } + * }, + * { + * "type": "path", + * "url": "../../relative/path/to/package/", + * "options": { + * "reference": "none" + * } + * }, + * ] + * @endcode + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** + * @var ArrayLoader + */ + private $loader; + + /** + * @var VersionGuesser + */ + private $versionGuesser; + + /** + * @var string + */ + private $url; + + /** + * @var mixed[] + * @phpstan-var array{url: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} + */ + private $repoConfig; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * @var array{symlink?: bool, reference?: string, relative?: bool, versions?: array} + */ + private $options; + + /** + * Initializes path repository. + * + * @param array{url?: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array}} $repoConfig + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null) + { + if (!isset($repoConfig['url'])) { + throw new \RuntimeException('You must specify the `url` configuration for the path repository'); + } + + $this->loader = new ArrayLoader(null, true); + $this->url = Platform::expandPath($repoConfig['url']); + $this->process = $process ?? new ProcessExecutor($io); + $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io); + $this->repoConfig = $repoConfig; + $this->options = $repoConfig['options'] ?? []; + if (!isset($this->options['relative'])) { + $filesystem = new Filesystem(); + $this->options['relative'] = !$filesystem->isAbsolutePath($this->url); + } + + parent::__construct(); + } + + public function getRepoName(): string + { + return 'path repo ('.Url::sanitize($this->repoConfig['url']).')'; + } + + public function getRepoConfig(): array + { + return $this->repoConfig; + } + + /** + * Initializes path repository. + * + * This method will basically read the folder and add the found package. + */ + protected function initialize(): void + { + parent::initialize(); + + $urlMatches = $this->getUrlMatches(); + + if (empty($urlMatches)) { + if (Preg::isMatch('{[*{}]}', $this->url)) { + $url = $this->url; + while (Preg::isMatch('{[*{}]}', $url)) { + $url = dirname($url); + } + // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty + if (is_dir($url)) { + return; + } + } + + throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist'); + } + + foreach ($urlMatches as $url) { + $path = realpath($url) . DIRECTORY_SEPARATOR; + $composerFilePath = $path.'composer.json'; + + if (!file_exists($composerFilePath)) { + continue; + } + + $json = file_get_contents($composerFilePath); + $package = JsonFile::parseJson($json, $composerFilePath); + $package['dist'] = [ + 'type' => 'path', + 'url' => $url, + ]; + $reference = $this->options['reference'] ?? 'auto'; + if ('none' === $reference) { + $package['dist']['reference'] = null; + } elseif ('config' === $reference || 'auto' === $reference) { + $package['dist']['reference'] = hash('sha1', $json . serialize($this->options)); + } + + // copy symlink/relative options to transport options + $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]); + // use the version provided as option if available + if (isset($package['name'], $this->options['versions'][$package['name']])) { + $package['version'] = $this->options['versions'][$package['name']]; + } + + // carry over the root package version if this path repo is in the same git repository as root package + if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) { + if ( + 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path) + && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2) + && $ref1 === $ref2 + ) { + $package['version'] = $this->versionGuesser->getRootVersionFromEnv(); + } + } + + $output = ''; + if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute(array_merge(['git', 'log', '-n1', '--pretty=%H'], GitUtil::getNoShowSignatureFlags($this->process)), $output, $path)) { + $package['dist']['reference'] = trim($output); + } + + if (!isset($package['version'])) { + $versionData = $this->versionGuesser->guessVersion($package, $path); + if (is_array($versionData) && $versionData['pretty_version']) { + // if there is a feature branch detected, we add a second packages with the feature branch version + if (!empty($versionData['feature_pretty_version'])) { + $package['version'] = $versionData['feature_pretty_version']; + $this->addPackage($this->loader->load($package)); + } + + $package['version'] = $versionData['pretty_version']; + } else { + $package['version'] = 'dev-main'; + } + } + + try { + $this->addPackage($this->loader->load($package)); + } catch (\Exception $e) { + throw new \RuntimeException('Failed loading the package in '.$composerFilePath, 0, $e); + } + } + } + + /** + * Get a list of all (possibly relative) path names matching given url (supports globbing). + * + * @return string[] + */ + private function getUrlMatches(): array + { + $flags = GLOB_MARK | GLOB_ONLYDIR; + + if (defined('GLOB_BRACE')) { + $flags |= GLOB_BRACE; + } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) { + throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url); + } + + // Ensure environment-specific path separators are normalized to URL separators + return array_map(static function ($val): string { + return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); + }, glob($this->url, $flags)); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PearRepository.php b/vendor/composer/composer/src/Composer/Repository/PearRepository.php new file mode 100644 index 0000000..facc417 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PearRepository.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Builds list of package from PEAR channel. + * + * Packages read from channel are named as 'pear-{channelName}/{packageName}' + * and has aliased as 'pear-{channelAlias}/{packageName}' + * + * @author Benjamin Eberlei + * @author Jordi Boggiano + * @deprecated + * @private + */ +class PearRepository extends ArrayRepository +{ + public function __construct() + { + throw new \InvalidArgumentException('The PEAR repository has been removed from Composer 2.x'); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php b/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php new file mode 100644 index 0000000..4c60249 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/PlatformRepository.php @@ -0,0 +1,768 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Composer; +use Composer\Package\CompletePackage; +use Composer\Package\CompletePackageInterface; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\Pcre\Preg; +use Composer\Platform\HhvmDetector; +use Composer\Platform\Runtime; +use Composer\Platform\Version; +use Composer\Plugin\PluginInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Util\Silencer; +use Composer\XdebugHandler\XdebugHandler; + +/** + * @author Jordi Boggiano + */ +class PlatformRepository extends ArrayRepository +{ + /** + * @deprecated use PlatformRepository::isPlatformPackage(string $name) instead + * @private + */ + public const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD'; + + /** + * @var ?string + */ + private static $lastSeenPlatformPhp = null; + + /** + * @var VersionParser + */ + private $versionParser; + + /** + * Defines overrides so that the platform can be mocked + * + * Keyed by package name (lowercased) + * + * @var array + */ + private $overrides = []; + + /** + * Stores which packages have been disabled and their actual version + * + * @var array + */ + private $disabledPackages = []; + + /** @var Runtime */ + private $runtime; + /** @var HhvmDetector */ + private $hhvmDetector; + + /** + * @param array $overrides + */ + public function __construct(array $packages = [], array $overrides = [], ?Runtime $runtime = null, ?HhvmDetector $hhvmDetector = null) + { + $this->runtime = $runtime ?: new Runtime(); + $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); + foreach ($overrides as $name => $version) { + if (!is_string($version) && false !== $version) { // @phpstan-ignore-line + throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.gettype($version).' '.var_export($version, true)); + } + if ($name === 'php' && $version === false) { + throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.'); + } + $this->overrides[strtolower($name)] = ['name' => $name, 'version' => $version]; + } + parent::__construct($packages); + } + + public function getRepoName(): string + { + return 'platform repo'; + } + + public function isPlatformPackageDisabled(string $name): bool + { + return isset($this->disabledPackages[$name]); + } + + /** + * @return array + */ + public function getDisabledPackages(): array + { + return $this->disabledPackages; + } + + protected function initialize(): void + { + parent::initialize(); + + $libraries = []; + + $this->versionParser = new VersionParser(); + + // Add each of the override versions as options. + // Later we might even replace the extensions instead. + foreach ($this->overrides as $override) { + // Check that it's a platform package. + if (!self::isPlatformPackage($override['name'])) { + throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']); + } + + if ($override['version'] !== false) { + $this->addOverriddenPackage($override); + } + } + + $prettyVersion = Composer::getVersion(); + $version = $this->versionParser->normalize($prettyVersion); + $composer = new CompletePackage('composer', $version, $prettyVersion); + $composer->setDescription('Composer package'); + $this->addPackage($composer); + + $prettyVersion = PluginInterface::PLUGIN_API_VERSION; + $version = $this->versionParser->normalize($prettyVersion); + $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); + $composerPluginApi->setDescription('The Composer Plugin API'); + $this->addPackage($composerPluginApi); + + $prettyVersion = Composer::RUNTIME_API_VERSION; + $version = $this->versionParser->normalize($prettyVersion); + $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion); + $composerRuntimeApi->setDescription('The Composer Runtime API'); + $this->addPackage($composerRuntimeApi); + + try { + $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); + $version = $this->versionParser->normalize($prettyVersion); + } + + $php = new CompletePackage('php', $version, $prettyVersion); + $php->setDescription('The PHP interpreter'); + $this->addPackage($php); + + if ($this->runtime->getConstant('PHP_DEBUG')) { + $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); + $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); + $this->addPackage($phpdebug); + } + + if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { + $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); + $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); + $this->addPackage($phpzts); + } + + if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { + $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); + $php64->setDescription('The PHP interpreter, 64bit'); + $this->addPackage($php64); + } + + // The AF_INET6 constant is only defined if ext-sockets is available but + // IPv6 support might still be available. + if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== false) { + $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); + $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); + $this->addPackage($phpIpv6); + } + + $loadedExtensions = $this->runtime->getExtensions(); + + // Extensions scanning + foreach ($loadedExtensions as $name) { + if (in_array($name, ['standard', 'Core'])) { + continue; + } + + $this->addExtension($name, $this->runtime->getExtensionVersion($name)); + } + + // Check for Xdebug in a restarted process + if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) { + $this->addExtension('xdebug', $prettyVersion); + } + + // Another quick loop, just for possible libraries + // Doing it this way to know that functions or constants exist before + // relying on them. + foreach ($loadedExtensions as $name) { + switch ($name) { + case 'amqp': + $info = $this->runtime->getExtensionInfo($name); + + // librabbitmq version => 0.9.0 + if (Preg::isMatch('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { + $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); + } + + // AMQP protocol version => 0-9-1 + if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { + $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); + } + break; + + case 'bz2': + $info = $this->runtime->getExtensionInfo($name); + + // BZip2 Version => 1.0.6, 6-Sept-2010 + if (Preg::isMatch('/^BZip2 Version => (?.*),/im', $info, $matches)) { + $this->addLibrary($libraries, $name, $matches['version']); + } + break; + + case 'curl': + $curlVersion = $this->runtime->invoke('curl_version'); + $this->addLibrary($libraries, $name, $curlVersion['version']); + + $info = $this->runtime->getExtensionInfo($name); + + // SSL Version => OpenSSL/1.0.1t + if (Preg::isMatchStrictGroups('{^SSL Version => (?[^/]+)/(?.+)$}im', $info, $sslMatches)) { + $library = strtolower($sslMatches['library']); + if ($library === 'openssl') { + $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); + $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []); + } else { + if ($library === '(securetransport) openssl') { + $shortlib = 'securetransport'; + } else { + $shortlib = $library; + } + $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']); + } + } + + // libSSH Version => libssh2/1.4.3 + if (Preg::isMatchStrictGroups('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { + $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); + } + + // ZLib Version => 1.2.8 + if (Preg::isMatchStrictGroups('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { + $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version'); + } + break; + + case 'date': + $info = $this->runtime->getExtensionInfo($name); + + // timelib version => 2018.03 + if (Preg::isMatchStrictGroups('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { + $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version'); + } + + // Timezone Database => internal + if (Preg::isMatchStrictGroups('/^Timezone Database => (?internal|external)$/im', $info, $zoneinfoSourceMatches)) { + $external = $zoneinfoSourceMatches['source'] === 'external'; + if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) { + // If the timezonedb is provided by ext/timezonedb, register that version as a replacement + if ($external && in_array('timezonedb', $loadedExtensions, true)) { + $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']); + } else { + $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); + } + } + } + break; + + case 'fileinfo': + $info = $this->runtime->getExtensionInfo($name); + + // libmagic => 537 + if (Preg::isMatch('/^libmagic => (?.+)$/im', $info, $magicMatches)) { + $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); + } + break; + + case 'gd': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION')); + + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { + $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); + } + + if (Preg::isMatchStrictGroups('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { + $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); + } + + if (Preg::isMatchStrictGroups('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { + $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); + } + + if (Preg::isMatchStrictGroups('/^libXpm Version => (?\d+)$/im', $info, $libxpmMatches)) { + $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); + } + + break; + + case 'gmp': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION')); + break; + + case 'iconv': + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION')); + break; + + case 'intl': + $info = $this->runtime->getExtensionInfo($name); + + $description = 'The ICU unicode and globalization support library'; + // Truthy check is for testing only so we can make the condition fail + if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { + $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); + } elseif (Preg::isMatch('/^ICU version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, 'icu', $matches['version'], $description); + } + + // ICU TZData version => 2019c + if (Preg::isMatchStrictGroups('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) { + $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); + } + + // Add a separate version for the CLDR library version + if ($this->runtime->hasClass('ResourceBundle')) { + $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]); + if ($resourceBundle !== null) { + $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); + } + } + + if ($this->runtime->hasClass('IntlChar')) { + $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); + } + break; + + case 'imagick': + // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability) + $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); + // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org + // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org + if (Preg::isMatch('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches)) { + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } + + $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']); + } + break; + + case 'ldap': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^Vendor Version => (?\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { + $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap'); + } + break; + + case 'libxml': + // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml + $libxmlProvides = array_map(static function ($extension): string { + return $extension . '-libxml'; + }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'])); + $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); + + break; + + case 'mbstring': + $info = $this->runtime->getExtensionInfo($name); + + // libmbfl version => 1.3.2 + if (Preg::isMatch('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { + $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); + } + + if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { + $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); + + // Multibyte regex (oniguruma) version => 5.9.5 + // oniguruma version => 6.9.0 + } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/im', $info, $onigurumaMatches)) { + $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); + } + + break; + + case 'memcached': + $info = $this->runtime->getExtensionInfo($name); + + // libmemcached version => 1.0.18 + if (Preg::isMatch('/^libmemcached version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version'); + } + break; + + case 'openssl': + // OpenSSL 1.1.1g 21 Apr 2020 + if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { + $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); + $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); + } + break; + + case 'pcre': + $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); + + $info = $this->runtime->getExtensionInfo($name); + + // PCRE Unicode Version => 12.1.0 + if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { + $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); + } + + break; + + case 'mysqlnd': + case 'pdo_mysql': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { + $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); + } + break; + + case 'mongodb': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { + $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); + } + + if (Preg::isMatchStrictGroups('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { + $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); + } + break; + + case 'pgsql': + if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) { + $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); + break; + } + // intentional fall-through to next case... + + case 'pdo_pgsql': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?.*)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name); + } + break; + + case 'pq': + $info = $this->runtime->getExtensionInfo($name); + + // Used Library => Compiled => Linked + // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 + if (Preg::isMatch('/^libpq => (?.+) => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name); + } + break; + + case 'rdkafka': + if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) { + /** + * Interpreted as hex \c MM.mm.rr.xx: + * - MM = Major + * - mm = minor + * - rr = revision + * - xx = pre-release id (0xff is the final release) + * + * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it + */ + $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION'); + $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0x7F000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name); + } + break; + + case 'libsodium': + case 'sodium': + if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) { + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + } + break; + + case 'sqlite3': + case 'pdo_sqlite': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^SQLite Library => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-sqlite', $matches['version']); + } + break; + + case 'ssh2': + $info = $this->runtime->getExtensionInfo($name); + + if (Preg::isMatch('/^libssh2 version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libssh2', $matches['version']); + } + break; + + case 'xsl': + $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); + + $info = $this->runtime->getExtensionInfo('xsl'); + if (Preg::isMatch('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); + } + break; + + case 'yaml': + $info = $this->runtime->getExtensionInfo('yaml'); + + if (Preg::isMatch('/^LibYAML Version => (?.+)$/im', $info, $matches)) { + $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml'); + } + break; + + case 'zip': + if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { + $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); + } + break; + + case 'zlib': + if ($this->runtime->hasConstant('ZLIB_VERSION')) { + $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION')); + + // Linked Version => 1.2.8 + } elseif (Preg::isMatch('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { + $this->addLibrary($libraries, $name, $matches['version']); + } + break; + + default: + break; + } + } + + $hhvmVersion = $this->hhvmDetector->getVersion(); + if ($hhvmVersion) { + try { + $prettyVersion = $hhvmVersion; + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion); + $version = $this->versionParser->normalize($prettyVersion); + } + + $hhvm = new CompletePackage('hhvm', $version, $prettyVersion); + $hhvm->setDescription('The HHVM Runtime (64bit)'); + $this->addPackage($hhvm); + } + } + + /** + * @inheritDoc + */ + public function addPackage(PackageInterface $package): void + { + if (!$package instanceof CompletePackage) { + throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package)); + } + + // Skip if overridden + if (isset($this->overrides[$package->getName()])) { + if ($this->overrides[$package->getName()]['version'] === false) { + $this->addDisabledPackage($package); + + return; + } + + $overrider = $this->findPackage($package->getName(), '*'); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + if ($overrider instanceof CompletePackageInterface) { + $overrider->setDescription($overrider->getDescription().', '.$actualText); + } + + return; + } + + // Skip if PHP is overridden and we are adding a php-* package + if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) { + $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + $overrider->setDescription($overrider->getDescription().', '.$actualText); + + return; + } + + parent::addPackage($package); + } + + /** + * @param array{version: string, name: string} $override + */ + private function addOverriddenPackage(array $override, ?string $name = null): CompletePackage + { + $version = $this->versionParser->normalize($override['version']); + $package = new CompletePackage($name ?: $override['name'], $version, $override['version']); + $package->setDescription('Package overridden via config.platform'); + $package->setExtra(['config.platform' => true]); + parent::addPackage($package); + + if ($package->getName() === 'php') { + self::$lastSeenPlatformPhp = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); + } + + return $package; + } + + private function addDisabledPackage(CompletePackage $package): void + { + $package->setDescription($package->getDescription().'. Package disabled via config.platform'); + $package->setExtra(['config.platform' => true]); + + $this->disabledPackages[$package->getName()] = $package; + } + + /** + * Parses the version and adds a new package to the repository + */ + private function addExtension(string $name, string $prettyVersion): void + { + $extraDescription = null; + + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + $extraDescription = ' (actual version: '.$prettyVersion.')'; + if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) { + $prettyVersion = $match[1]; + } else { + $prettyVersion = '0'; + } + $version = $this->versionParser->normalize($prettyVersion); + } + + $packageName = $this->buildPackageName($name); + $ext = new CompletePackage($packageName, $version, $prettyVersion); + $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); + $ext->setType('php-ext'); + + if ($name === 'uuid') { + $ext->setReplaces([ + 'lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion()), + ]); + } + + $this->addPackage($ext); + } + + private function buildPackageName(string $name): string + { + return 'ext-' . str_replace(' ', '-', strtolower($name)); + } + + /** + * @param array $libraries + * @param array $replaces + * @param array $provides + */ + private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void + { + if (null === $prettyVersion) { + return; + } + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + return; + } + + // avoid adding the same lib twice even if two conflicting extensions provide the same lib + // see https://github.com/composer/composer/issues/12082 + if (isset($libraries['lib-'.$name])) { + return; + } + $libraries['lib-'.$name] = true; + + if ($description === null) { + $description = 'The '.$name.' library'; + } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + + $replaceLinks = []; + foreach ($replaces as $replace) { + $replace = strtolower($replace); + $replaceLinks[$replace] = new Link('lib-'.$name, 'lib-'.$replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion()); + } + $provideLinks = []; + foreach ($provides as $provide) { + $provide = strtolower($provide); + $provideLinks[$provide] = new Link('lib-'.$name, 'lib-'.$provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion()); + } + $lib->setReplaces($replaceLinks); + $lib->setProvides($provideLinks); + + $this->addPackage($lib); + } + + /** + * Check if a package name is a platform package. + */ + public static function isPlatformPackage(string $name): bool + { + static $cache = []; + + if (isset($cache[$name])) { + return $cache[$name]; + } + + return $cache[$name] = Preg::isMatch(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name); + } + + /** + * Returns the last seen config.platform.php version if defined + * + * This is a best effort attempt for internal purposes, retrieve the real + * packages from a PlatformRepository instance if you need a version guaranteed to + * be correct. + * + * @internal + */ + public static function getPlatformPhpVersion(): ?string + { + return self::$lastSeenPlatformPhp; + } + + public function search(string $query, int $mode = 0, ?string $type = null): array + { + // suppress vendor search as there are no vendors to match in platform packages + if ($mode === self::SEARCH_VENDOR) { + return []; + } + + return parent::search($query, $mode, $type); + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php b/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php new file mode 100644 index 0000000..52da0d6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php @@ -0,0 +1,192 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; +use Composer\Json\JsonFile; + +/** + * @author Jordi Boggiano + */ +class RepositoryFactory +{ + /** + * @return array|mixed + */ + public static function configFromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false) + { + if (0 === strpos($repository, 'http')) { + $repoConfig = ['type' => 'composer', 'url' => $repository]; + } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) { + $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config)); + $data = $json->read(); + if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { + $repoConfig = ['type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')]; + } elseif ($allowFilesystem) { + $repoConfig = ['type' => 'filesystem', 'json' => $json]; + } else { + throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository."); + } + } elseif (strpos($repository, '{') === 0) { + // assume it is a json object that makes a repo config + $repoConfig = JsonFile::parseJson($repository); + } else { + throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object."); + } + + return $repoConfig; + } + + public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false, ?RepositoryManager $rm = null): RepositoryInterface + { + $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem); + + return static::createRepo($io, $config, $repoConfig, $rm); + } + + /** + * @param array $repoConfig + */ + public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?RepositoryManager $rm = null): RepositoryInterface + { + if (!$rm) { + @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED); + $rm = static::manager($io, $config); + } + $repos = self::createRepos($rm, [$repoConfig]); + + return reset($repos); + } + + /** + * @return RepositoryInterface[] + */ + public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?RepositoryManager $rm = null): array + { + if (null === $rm) { + @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED); + } + + if (null === $config) { + $config = Factory::createConfig($io); + } + if (null !== $io) { + $io->loadConfiguration($config); + } + if (null === $rm) { + if (null === $io) { + throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); + } + $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); + } + + return self::createRepos($rm, $config->getRepositories()); + } + + /** + * @param EventDispatcher $eventDispatcher + * @param HttpDownloader $httpDownloader + */ + public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null): RepositoryManager + { + if ($httpDownloader === null) { + $httpDownloader = Factory::createHttpDownloader($io, $config); + } + if ($process === null) { + $process = new ProcessExecutor($io); + $process->enableAsync(); + } + + $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); + $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); + $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); + $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); + $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('bitbucket', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); + $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); + + return $rm; + } + + /** + * @return RepositoryInterface[] + */ + public static function defaultReposWithDefaultManager(IOInterface $io): array + { + $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io)); + $io->loadConfiguration($config); + + return RepositoryFactory::defaultRepos($io, $config, $manager); + } + + /** + * @param array $repoConfigs + * + * @return RepositoryInterface[] + */ + private static function createRepos(RepositoryManager $rm, array $repoConfigs): array + { + $repos = []; + + foreach ($repoConfigs as $index => $repo) { + if (is_string($repo)) { + throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); + } + if (!is_array($repo)) { + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given'); + } + if (!isset($repo['type'])) { + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined'); + } + + $name = self::generateRepositoryName($index, $repo, $repos); + if ($repo['type'] === 'filesystem') { + $repos[$name] = new FilesystemRepository($repo['json']); + } else { + $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index); + } + } + + return $repos; + } + + /** + * @param int|string $index + * @param array{url?: string} $repo + * @param array $existingRepos + */ + public static function generateRepositoryName($index, array $repo, array $existingRepos): string + { + $name = is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index; + while (isset($existingRepos[$name])) { + $name .= '2'; + } + + return $name; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php new file mode 100644 index 0000000..f90c96d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Package\BasePackage; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Repository interface. + * + * @author Nils Adermann + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface RepositoryInterface extends \Countable +{ + public const SEARCH_FULLTEXT = 0; + public const SEARCH_NAME = 1; + public const SEARCH_VENDOR = 2; + + /** + * Checks if specified package registered (installed). + * + * @param PackageInterface $package package instance + * + * @return bool + */ + public function hasPackage(PackageInterface $package); + + /** + * Searches for the first match of a package by name and version. + * + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against + * + * @return BasePackage|null + */ + public function findPackage(string $name, $constraint); + + /** + * Searches for all packages matching a name and optionally a version. + * + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against + * + * @return BasePackage[] + */ + public function findPackages(string $name, $constraint = null); + + /** + * Returns list of registered packages. + * + * @return BasePackage[] + */ + public function getPackages(); + + /** + * Returns list of registered packages with the supplied name + * + * - The packages returned are the packages found which match the constraints, acceptable stability and stability flags provided + * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories + * + * @param ConstraintInterface[] $packageNameMap package names pointing to constraints + * @param array $acceptableStabilities array of stability => BasePackage::STABILITY_* value + * @param array $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @param array> $alreadyLoaded an array of package name => package version => package + * + * @return array + * + * @phpstan-param array, BasePackage::STABILITY_*> $acceptableStabilities + * @phpstan-param array $packageNameMap + * @phpstan-return array{namesFound: array, packages: array} + */ + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []); + + /** + * Searches the repository for packages containing the query + * + * @param string $query search query, for SEARCH_NAME and SEARCH_VENDOR regular expressions metacharacters are supported by implementations, and user input should be escaped through preg_quote by callers + * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only, default is SEARCH_FULLTEXT + * @param ?string $type The type of package to search for. Defaults to all types of packages + * + * @return array[] an array of array('name' => '...', 'description' => '...'|null, 'abandoned' => 'string'|true|unset) For SEARCH_VENDOR the name will be in "vendor" form + * @phpstan-return list + */ + public function search(string $query, int $mode = 0, ?string $type = null); + + /** + * Returns a list of packages providing a given package name + * + * Packages which have the same name as $packageName should not be returned, only those that have a "provide" on it. + * + * @param string $packageName package name which must be provided + * + * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') + * @phpstan-return array + */ + public function getProviders(string $packageName); + + /** + * Returns a name representing this repository to the user + * + * This is best effort and definitely can not always be very precise + * + * @return string + */ + public function getRepoName(); +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php b/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php new file mode 100644 index 0000000..65dad87 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryManager.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Package\PackageInterface; +use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; + +/** + * Repositories manager. + * + * @author Jordi Boggiano + * @author Konstantin Kudryashov + * @author François Pluchino + */ +class RepositoryManager +{ + /** @var InstalledRepositoryInterface */ + private $localRepository; + /** @var list */ + private $repositories = []; + /** @var array> */ + private $repositoryClasses = []; + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var ProcessExecutor */ + private $process; + + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null) + { + $this->io = $io; + $this->config = $config; + $this->httpDownloader = $httpDownloader; + $this->eventDispatcher = $eventDispatcher; + $this->process = $process ?? new ProcessExecutor($io); + } + + /** + * Searches for a package by its name and version in managed repositories. + * + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + */ + public function findPackage(string $name, $constraint): ?PackageInterface + { + foreach ($this->repositories as $repository) { + /** @var RepositoryInterface $repository */ + if ($package = $repository->findPackage($name, $constraint)) { + return $package; + } + } + + return null; + } + + /** + * Searches for all packages matching a name and optionally a version in managed repositories. + * + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + * + * @return PackageInterface[] + */ + public function findPackages(string $name, $constraint): array + { + $packages = []; + + foreach ($this->getRepositories() as $repository) { + $packages = array_merge($packages, $repository->findPackages($name, $constraint)); + } + + return $packages; + } + + /** + * Adds repository + * + * @param RepositoryInterface $repository repository instance + */ + public function addRepository(RepositoryInterface $repository): void + { + $this->repositories[] = $repository; + } + + /** + * Adds a repository to the beginning of the chain + * + * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin. + * + * @param RepositoryInterface $repository repository instance + */ + public function prependRepository(RepositoryInterface $repository): void + { + array_unshift($this->repositories, $repository); + } + + /** + * Returns a new repository for a specific installation type. + * + * @param string $type repository type + * @param array $config repository configuration + * @param string $name repository name + * @throws \InvalidArgumentException if repository for provided type is not registered + */ + public function createRepository(string $type, array $config, ?string $name = null): RepositoryInterface + { + if (!isset($this->repositoryClasses[$type])) { + throw new \InvalidArgumentException('Repository type is not registered: '.$type); + } + + if (isset($config['packagist']) && false === $config['packagist']) { + $this->io->writeError('Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition'); + } + + $class = $this->repositoryClasses[$type]; + + if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) { + $filterConfig = $config; + unset($config['only'], $config['exclude'], $config['canonical']); + } + + $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process); + + if (isset($filterConfig)) { + $repository = new FilterRepository($repository, $filterConfig); + } + + return $repository; + } + + /** + * Stores repository class for a specific installation type. + * + * @param string $type installation type + * @param class-string $class class name of the repo implementation + */ + public function setRepositoryClass(string $type, $class): void + { + $this->repositoryClasses[$type] = $class; + } + + /** + * Returns all repositories, except local one. + * + * @return RepositoryInterface[] + */ + public function getRepositories(): array + { + return $this->repositories; + } + + /** + * Sets local repository for the project. + * + * @param InstalledRepositoryInterface $repository repository instance + */ + public function setLocalRepository(InstalledRepositoryInterface $repository): void + { + $this->localRepository = $repository; + } + + /** + * Returns local repository for the project. + */ + public function getLocalRepository(): InstalledRepositoryInterface + { + return $this->localRepository; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php b/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php new file mode 100644 index 0000000..c432314 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Thrown when a security problem, like a broken or missing signature + * + * @author Eric Daspet + */ +class RepositorySecurityException extends \Exception +{ +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositorySet.php b/vendor/composer/composer/src/Composer/Repository/RepositorySet.php new file mode 100644 index 0000000..dcde363 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositorySet.php @@ -0,0 +1,421 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\DependencyResolver\PoolOptimizer; +use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\PoolBuilder; +use Composer\DependencyResolver\Request; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Advisory\SecurityAdvisory; +use Composer\Advisory\PartialSecurityAdvisory; +use Composer\IO\IOInterface; +use Composer\IO\NullIO; +use Composer\Package\BasePackage; +use Composer\Package\AliasPackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackage; +use Composer\Package\PackageInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Package\Version\StabilityFilter; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * @author Nils Adermann + * + * @see RepositoryUtils for ways to work with single repos + */ +class RepositorySet +{ + /** + * Packages are returned even though their stability does not match the required stability + */ + public const ALLOW_UNACCEPTABLE_STABILITIES = 1; + /** + * Packages will be looked up in all repositories, even after they have been found in a higher prio one + */ + public const ALLOW_SHADOWED_REPOSITORIES = 2; + + /** + * @var array[] + * @phpstan-var array> + */ + private $rootAliases; + + /** + * @var string[] + * @phpstan-var array + */ + private $rootReferences; + + /** @var RepositoryInterface[] */ + private $repositories = []; + + /** + * @var int[] array of stability => BasePackage::STABILITY_* value + * @phpstan-var array, BasePackage::STABILITY_*> + */ + private $acceptableStabilities; + + /** + * @var int[] array of package name => BasePackage::STABILITY_* value + * @phpstan-var array + */ + private $stabilityFlags; + + /** + * @var ConstraintInterface[] + * @phpstan-var array + */ + private $rootRequires; + + /** + * @var array + */ + private $temporaryConstraints; + + /** @var bool */ + private $locked = false; + /** @var bool */ + private $allowInstalledRepositories = false; + + /** + * In most cases if you are looking to use this class as a way to find packages from repositories + * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including + * aliases, pinned references and other special cases. + * + * @param key-of $minimumStability + * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value + * @phpstan-param array $stabilityFlags + * @param array[] $rootAliases + * @phpstan-param list $rootAliases + * @param string[] $rootReferences an array of package name => source reference + * @phpstan-param array $rootReferences + * @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package + * @phpstan-param array $rootRequires + * @param array $temporaryConstraints Runtime temporary constraints that will be used to filter packages + */ + public function __construct(string $minimumStability = 'stable', array $stabilityFlags = [], array $rootAliases = [], array $rootReferences = [], array $rootRequires = [], array $temporaryConstraints = []) + { + $this->rootAliases = self::getRootAliasesPerPackage($rootAliases); + $this->rootReferences = $rootReferences; + + $this->acceptableStabilities = []; + foreach (BasePackage::STABILITIES as $stability => $value) { + if ($value <= BasePackage::STABILITIES[$minimumStability]) { + $this->acceptableStabilities[$stability] = $value; + } + } + $this->stabilityFlags = $stabilityFlags; + $this->rootRequires = $rootRequires; + foreach ($rootRequires as $name => $constraint) { + if (PlatformRepository::isPlatformPackage($name)) { + unset($this->rootRequires[$name]); + } + } + + $this->temporaryConstraints = $temporaryConstraints; + } + + public function allowInstalledRepositories(bool $allow = true): void + { + $this->allowInstalledRepositories = $allow; + } + + /** + * @return ConstraintInterface[] an array of package name => constraint from the root package, platform requirements excluded + * @phpstan-return array + */ + public function getRootRequires(): array + { + return $this->rootRequires; + } + + /** + * @return array Runtime temporary constraints that will be used to filter packages + */ + public function getTemporaryConstraints(): array + { + return $this->temporaryConstraints; + } + + /** + * Adds a repository to this repository set + * + * The first repos added have a higher priority. As soon as a package is found in any + * repository the search for that package ends, and following repos will not be consulted. + * + * @param RepositoryInterface $repo A package repository + */ + public function addRepository(RepositoryInterface $repo): void + { + if ($this->locked) { + throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); + } + + if ($repo instanceof CompositeRepository) { + $repos = $repo->getRepositories(); + } else { + $repos = [$repo]; + } + + foreach ($repos as $repo) { + $this->repositories[] = $repo; + } + } + + /** + * Find packages providing or matching a name and optionally meeting a constraint in all repositories + * + * Returned in the order of repositories, matching priority + * + * @param int $flags any of the ALLOW_* constants from this class to tweak what is returned + * @return BasePackage[] + */ + public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0): array + { + $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0; + $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0; + + $packages = []; + if ($loadFromAllRepos) { + foreach ($this->repositories as $repository) { + $packages[] = $repository->findPackages($name, $constraint) ?: []; + } + } else { + foreach ($this->repositories as $repository) { + $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); + + $packages[] = $result['packages']; + foreach ($result['namesFound'] as $nameFound) { + // avoid loading the same package again from other repositories once it has been found + if ($name === $nameFound) { + break 2; + } + } + } + } + + $candidates = $packages ? array_merge(...$packages) : []; + + // when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again + if ($ignoreStability || !$loadFromAllRepos) { + return $candidates; + } + + $result = []; + foreach ($candidates as $candidate) { + if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { + $result[] = $candidate; + } + } + + return $result; + } + + /** + * @param string[] $packageNames + * @return ($allowPartialAdvisories is true ? array> : array>) + */ + public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories = false): array + { + $map = []; + foreach ($packageNames as $name) { + $map[$name] = new MatchAllConstraint(); + } + + return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories); + } + + /** + * @param PackageInterface[] $packages + * @return ($allowPartialAdvisories is true ? array> : array>) + */ + public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = false): array + { + $map = []; + foreach ($packages as $package) { + // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + continue; + } + if (isset($map[$package->getName()])) { + $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false); + } else { + $map[$package->getName()] = new Constraint('=', $package->getVersion()); + } + } + + return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories); + } + + /** + * @param array $packageConstraintMap + * @return ($allowPartialAdvisories is true ? array> : array>) + */ + private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories): array + { + $repoAdvisories = []; + foreach ($this->repositories as $repository) { + if (!$repository instanceof AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) { + continue; + } + + $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories']; + } + + $advisories = array_merge_recursive([], ...$repoAdvisories); + ksort($advisories); + + return $advisories; + } + + /** + * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...') + * @phpstan-return array + */ + public function getProviders(string $packageName): array + { + $providers = []; + foreach ($this->repositories as $repository) { + if ($repoProviders = $repository->getProviders($packageName)) { + $providers = array_merge($providers, $repoProviders); + } + } + + return $providers; + } + + /** + * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability + * + * @param string[] $names + * @param key-of $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev' + */ + public function isPackageAcceptable(array $names, string $stability): bool + { + return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability); + } + + /** + * Create a pool for dependency resolution from the packages in this repository set. + * + * @param list $ignoredTypes Packages of those types are ignored + * @param list|null $allowedTypes Only packages of those types are allowed if set to non-null + */ + public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null): Pool + { + $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints); + $poolBuilder->setIgnoredTypes($ignoredTypes); + $poolBuilder->setAllowedTypes($allowedTypes); + + foreach ($this->repositories as $repo) { + if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { + throw new \LogicException('The pool can not accept packages from an installed repository'); + } + } + + $this->locked = true; + + return $poolBuilder->buildPool($this->repositories, $request); + } + + /** + * Create a pool for dependency resolution from the packages in this repository set. + */ + public function createPoolWithAllPackages(): Pool + { + foreach ($this->repositories as $repo) { + if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { + throw new \LogicException('The pool can not accept packages from an installed repository'); + } + } + + $this->locked = true; + + $packages = []; + foreach ($this->repositories as $repository) { + foreach ($repository->getPackages() as $package) { + $packages[] = $package; + + if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { + $alias = $this->rootAliases[$package->getName()][$package->getVersion()]; + while ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + if ($package instanceof CompletePackage) { + $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']); + } else { + $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); + } + $aliasPackage->setRootPackageAlias(true); + $packages[] = $aliasPackage; + } + } + } + + return new Pool($packages); + } + + public function createPoolForPackage(string $packageName, ?LockArrayRepository $lockedRepo = null): Pool + { + // TODO unify this with above in some simpler version without "request"? + return $this->createPoolForPackages([$packageName], $lockedRepo); + } + + /** + * @param string[] $packageNames + */ + public function createPoolForPackages(array $packageNames, ?LockArrayRepository $lockedRepo = null): Pool + { + $request = new Request($lockedRepo); + + $allowedPackages = []; + foreach ($packageNames as $packageName) { + if (PlatformRepository::isPlatformPackage($packageName)) { + throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.'); + } + + $request->requireName($packageName); + $allowedPackages[] = strtolower($packageName); + } + + if (count($allowedPackages) > 0) { + $request->restrictPackages($allowedPackages); + } + + return $this->createPool($request, new NullIO()); + } + + /** + * @param array[] $aliases + * @phpstan-param list $aliases + * + * @return array> + */ + private static function getRootAliasesPerPackage(array $aliases): array + { + $normalizedAliases = []; + + foreach ($aliases as $alias) { + $normalizedAliases[$alias['package']][$alias['version']] = [ + 'alias' => $alias['alias'], + 'alias_normalized' => $alias['alias_normalized'], + ]; + } + + return $normalizedAliases; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php b/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php new file mode 100644 index 0000000..e6960c6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RepositoryUtils.php @@ -0,0 +1,83 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; + +/** + * @author Jordi Boggiano + * + * @see RepositorySet for ways to work with sets of repos + */ +class RepositoryUtils +{ + /** + * Find all of $packages which are required by $requirer, either directly or transitively + * + * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer + * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies + * are always ignored. + * + * @template T of PackageInterface + * @param array $packages + * @param list $bucket Do not pass this in, only used to avoid recursion with circular deps + * @return list + */ + public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array + { + $requires = $requirer->getRequires(); + if ($includeRequireDev) { + $requires = array_merge($requires, $requirer->getDevRequires()); + } + + foreach ($packages as $candidate) { + foreach ($candidate->getNames() as $name) { + if (isset($requires[$name])) { + if (!in_array($candidate, $bucket, true)) { + $bucket[] = $candidate; + $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket); + } + break; + } + } + } + + return $bucket; + } + + /** + * Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances + * + * @return RepositoryInterface[] + */ + public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array + { + // unwrap filter repos + if ($unwrapFilterRepos && $repo instanceof FilterRepository) { + $repo = $repo->getRepository(); + } + + if (!$repo instanceof CompositeRepository) { + return [$repo]; + } + + $repos = []; + foreach ($repo->getRepositories() as $r) { + foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) { + $repos[] = $r2; + } + } + + return $repos; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php b/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php new file mode 100644 index 0000000..2e60e3a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php @@ -0,0 +1,35 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\RootPackageInterface; + +/** + * Root package repository. + * + * This is used for serving the RootPackage inside an in-memory InstalledRepository + * + * @author Jordi Boggiano + */ +class RootPackageRepository extends ArrayRepository +{ + public function __construct(RootPackageInterface $package) + { + parent::__construct([$package]); + } + + public function getRepoName(): string + { + return 'root package repo'; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php new file mode 100644 index 0000000..8a30521 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php @@ -0,0 +1,248 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Config; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\IO\IOInterface; + +/** + * @author BohwaZ + */ +class FossilDriver extends VcsDriver +{ + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var ?string */ + protected $rootIdentifier = null; + /** @var ?string */ + protected $repoFile = null; + /** @var string */ + protected $checkoutDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + // Make sure fossil is installed and reachable. + $this->checkFossil(); + + // Ensure we are allowed to use this URL by config. + $this->config->prohibitUrlByConfig($this->url, $this->io); + + // Only if url points to a locally accessible directory, assume it's the checkout directory. + // Otherwise, it should be something fossil can clone from. + if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) { + $this->checkoutDir = $this->url; + } else { + if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url); + $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; + $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; + + $this->updateLocalRepo(); + } + + $this->getTags(); + $this->getBranches(); + } + + /** + * Check that fossil can be invoked via command line. + */ + protected function checkFossil(): void + { + if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) { + throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput()); + } + } + + /** + * Clone or update existing local fossil repository. + */ + protected function updateLocalRepo(): void + { + assert($this->repoFile !== null); + + $fs = new Filesystem(); + $fs->ensureDirectoryExists($this->checkoutDir); + + if (!is_writable(dirname($this->checkoutDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.'); + } + + // update the repo if it is a valid fossil repository + if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) { + if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) { + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + } + } else { + // clean up directory and do a fresh clone into it + $fs->removeDirectory($this->checkoutDir); + $fs->remove($this->repoFile); + + $fs->ensureDirectoryExists($this->checkoutDir); + + if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) { + $output = $this->process->getErrorOutput(); + + throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output); + } + + if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) { + $output = $this->process->getErrorOutput(); + + throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); + } + } + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->rootIdentifier = 'trunk'; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir); + + if ('' === trim($content)) { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir); + [, $date] = explode(' ', trim($output), 3); + + return new \DateTimeImmutable($date, new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir); + foreach ($this->process->splitLines($output) as $tag) { + $tags[$tag] = $tag; + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir); + foreach ($this->process->splitLines($output) as $branch) { + $branch = trim(Preg::replace('/^\*/', '', trim($branch))); + $branches[$branch] = $branch; + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) { + return true; + } + + if (Preg::isMatch('!/fossil/|\.fossil!', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a fossil repo in that path + if ($process->execute(['fossil', 'info'], $output, $url) === 0) { + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php new file mode 100644 index 0000000..ff9aeca --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -0,0 +1,522 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Cache; +use Composer\Downloader\TransportException; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\Bitbucket; +use Composer\Util\Http\Response; + +/** + * @author Per Bernhardt + */ +class GitBitbucketDriver extends VcsDriver +{ + /** @var string */ + protected $owner; + /** @var string */ + protected $repository; + /** @var bool */ + private $hasIssues = false; + /** @var ?string */ + private $rootIdentifier; + /** @var array Map of tag name to identifier */ + private $tags; + /** @var array Map of branch name to identifier */ + private $branches; + /** @var string */ + private $branchesUrl = ''; + /** @var string */ + private $tagsUrl = ''; + /** @var string */ + private $homeUrl = ''; + /** @var string */ + private $website = ''; + /** @var string */ + private $cloneHttpsUrl = ''; + /** @var array */ + private $repoData; + + /** + * @var ?VcsDriver + */ + protected $fallbackDriver = null; + /** @var string|null if set either git or hg */ + private $vcsType; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatchStrictGroups('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url)); + } + + $this->owner = $match[1]; + $this->repository = $match[2]; + $this->originUrl = 'bitbucket.org'; + $this->cache = new Cache( + $this->io, + implode('/', [ + $this->config->get('cache-repo-dir'), + $this->originUrl, + $this->owner, + $this->repository, + ]) + ); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getUrl(); + } + + return $this->cloneHttpsUrl; + } + + /** + * Attempts to fetch the repository data via the BitBucket API and + * sets some parameters which are used in other methods + * + * @phpstan-impure + */ + protected function getRepoData(): bool + { + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s?%s', + $this->owner, + $this->repository, + http_build_query( + ['fields' => '-project,-owner'], + '', + '&' + ) + ); + + $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson(); + if ($this->fallbackDriver) { + return false; + } + $this->parseCloneUrls($repoData['links']['clone']); + + $this->hasIssues = !empty($repoData['has_issues']); + $this->branchesUrl = $repoData['links']['branches']['href']; + $this->tagsUrl = $repoData['links']['tags']['href']; + $this->homeUrl = $repoData['links']['html']['href']; + $this->website = $repoData['website']; + $this->vcsType = $repoData['scm']; + + $this->repoData = $repoData; + + return true; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } + + if ($composer !== null) { + // specials for bitbucket + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source'])) { + $label = array_search( + $identifier, + $this->getTags() + ) ?: array_search( + $identifier, + $this->getBranches() + ) ?: $identifier; + + if (array_key_exists($label, $tags = $this->getTags())) { + $hash = $tags[$label]; + } elseif (array_key_exists($label, $branches = $this->getBranches())) { + $hash = $branches[$label]; + } + + if (!isset($hash)) { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src', + $this->originUrl, + $this->owner, + $this->repository + ); + } else { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src/%s/?at=%s', + $this->originUrl, + $this->owner, + $this->repository, + $hash, + $label + ); + } + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf( + 'https://%s/%s/%s/issues', + $this->originUrl, + $this->owner, + $this->repository + ); + } + if (!isset($composer['homepage'])) { + $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getFileContent($file, $identifier); + } + + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s', + $this->owner, + $this->repository, + $identifier, + $file + ); + + return $this->fetchWithOAuthCredentials($resource)->getBody(); + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getChangeDate($identifier); + } + + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', + $this->owner, + $this->repository, + $identifier + ); + $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + + return new \DateTimeImmutable($commit['date']); + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getSource($identifier); + } + + return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getDist($identifier); + } + + $url = sprintf( + 'https://bitbucket.org/%s/%s/get/%s.zip', + $this->owner, + $this->repository, + $identifier + ); + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getTags(); + } + + if (null === $this->tags) { + $tags = []; + $resource = sprintf( + '%s?%s', + $this->tagsUrl, + http_build_query( + [ + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,next', + 'sort' => '-target.date', + ], + '', + '&' + ) + ); + $hasNext = true; + while ($hasNext) { + $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + foreach ($tagsData['values'] as $data) { + $tags[$data['name']] = $data['target']['hash']; + } + if (empty($tagsData['next'])) { + $hasNext = false; + } else { + $resource = $tagsData['next']; + } + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getBranches(); + } + + if (null === $this->branches) { + $branches = []; + $resource = sprintf( + '%s?%s', + $this->branchesUrl, + http_build_query( + [ + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,values.heads,next', + 'sort' => '-target.date', + ], + '', + '&' + ) + ); + $hasNext = true; + while ($hasNext) { + $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); + foreach ($branchData['values'] as $data) { + $branches[$data['name']] = $data['target']['hash']; + } + if (empty($branchData['next'])) { + $hasNext = false; + } else { + $resource = $branchData['next']; + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * Get the remote content. + * + * @param string $url The URL of content + * + * @return Response The result + * + * @phpstan-impure + */ + protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = false): Response + { + try { + return parent::getContents($url); + } catch (TransportException $e) { + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); + + if (in_array($e->getCode(), [403, 404], true) || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) { + if (!$this->io->hasAuthentication($this->originUrl) + && $bitbucketUtil->authorizeOAuth($this->originUrl) + ) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + } + + throw $e; + } + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + try { + $this->setupFallbackDriver($this->generateSshUrl()); + + return true; + } catch (\RuntimeException $e) { + $this->fallbackDriver = null; + + $this->io->writeError( + 'Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' + . ' so that you can enter your Bitbucket OAuth consumer credentials' + ); + throw $e; + } + } + + protected function setupFallbackDriver(string $url): void + { + $this->fallbackDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->fallbackDriver->initialize(); + } + + /** + * @param array $cloneLinks + */ + protected function parseCloneUrls(array $cloneLinks): void + { + foreach ($cloneLinks as $cloneLink) { + if ($cloneLink['name'] === 'https') { + // Format: https://(user@)bitbucket.org/{user}/{repo} + // Strip username from URL (only present in clone URL's for private repositories) + $this->cloneHttpsUrl = Preg::replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); + } + } + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getRootIdentifier(); + } + + if (null === $this->rootIdentifier) { + if (!$this->getRepoData()) { + if (!$this->fallbackDriver) { + throw new \LogicException('A fallback driver should be setup if getRepoData returns false'); + } + + return $this->fallbackDriver->getRootIdentifier(); + } + + if ($this->vcsType !== 'git') { + throw new \RuntimeException( + $this->url.' does not appear to be a git repository, use '. + $this->cloneHttpsUrl.' but remember that Bitbucket no longer supports the mercurial repositories. '. + 'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket' + ); + } + + $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master'; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $url)) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php new file mode 100644 index 0000000..7be1fab --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php @@ -0,0 +1,253 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Git as GitUtil; +use Composer\IO\IOInterface; +use Composer\Cache; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class GitDriver extends VcsDriver +{ + /** @var array Map of tag name (can be turned to an int by php if it is a numeric name) to identifier */ + protected $tags; + /** @var array Map of branch name (can be turned to an int by php if it is a numeric name) to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var string */ + protected $repoDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (Filesystem::isLocalPath($this->url)) { + $this->url = Preg::replace('{[\\/]\.git/?$}', '', $this->url); + if (!is_dir($this->url)) { + throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist'); + } + $this->repoDir = $this->url; + $cacheUrl = realpath($this->url); + } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/'; + + GitUtil::cleanEnv(); + + $fs = new Filesystem(); + $fs->ensureDirectoryExists(dirname($this->repoDir)); + + if (!is_writable(dirname($this->repoDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); + } + + if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { + throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); + } + + $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); + if (!$gitUtil->syncMirror($this->url, $this->repoDir)) { + if (!is_dir($this->repoDir)) { + throw new \RuntimeException('Failed to clone '.$this->url.' to read package information from it'); + } + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated'); + } + + $cacheUrl = $this->url; + } + + $this->getTags(); + $this->getBranches(); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl))); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->rootIdentifier = 'master'; + + $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem()); + if (!Filesystem::isLocalPath($this->url)) { + $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, false); + if ($defaultBranch !== null) { + return $this->rootIdentifier = $defaultBranch; + } + } + + // select currently checked out branch as default branch + $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir); + $branches = $this->process->splitLines($output); + if (!in_array('* master', $branches)) { + foreach ($branches as $branch) { + if ($branch && Preg::isMatchStrictGroups('{^\* +(\S+)}', $branch, $match)) { + $this->rootIdentifier = $match[1]; + break; + } + } + } + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + + $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir); + + if (trim($content) === '') { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $this->process->execute(['git', '-c', 'log.showSignature=false', 'log', '-1', '--format=%at', $identifier], $output, $this->repoDir); + + return new \DateTimeImmutable('@'.trim($output), new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $this->tags = []; + + $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $tag) { + if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { + $this->tags[$match[2]] = $match[1]; + } + } + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { + if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a git repo in that path + if ($process->execute(['git', 'tag'], $output, $url) === 0) { + return true; + } + GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url); + } + + if (!$deep) { + return false; + } + + $gitUtil = new GitUtil($io, $config, new ProcessExecutor($io), new Filesystem()); + GitUtil::cleanEnv(); + + try { + $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir()); + } catch (\RuntimeException $e) { + return false; + } + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php new file mode 100644 index 0000000..803aa23 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php @@ -0,0 +1,649 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\Json\JsonFile; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\GitHub; +use Composer\Util\Http\Response; + +/** + * @author Jordi Boggiano + */ +class GitHubDriver extends VcsDriver +{ + /** @var string */ + protected $owner; + /** @var string */ + protected $repository; + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var mixed[] */ + protected $repoData; + /** @var bool */ + protected $hasIssues = false; + /** @var bool */ + protected $isPrivate = false; + /** @var bool */ + private $isArchived = false; + /** @var array|false|null */ + private $fundingInfo; + + /** + * Git Driver + * + * @var ?GitDriver + */ + protected $gitDriver = null; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); + } + + $this->owner = $match[3]; + $this->repository = $match[4]; + $this->originUrl = strtolower($match[1] ?? (string) $match[2]); + if ($this->originUrl === 'www.github.com') { + $this->originUrl = 'github.com'; + } + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + if ($this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'])) { + $this->setupGitDriver($this->url); + + return; + } + + $this->fetchRootIdentifier(); + } + + public function getRepositoryUrl(): string + { + return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + protected function getApiUrl(): string + { + if ('github.com' === $this->originUrl) { + $apiUrl = 'api.github.com'; + } else { + $apiUrl = $this->originUrl . '/api/v3'; + } + + return 'https://' . $apiUrl; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + if ($this->isPrivate) { + // Private GitHub repositories should be accessed using the + // SSH version of the URL. + $url = $this->generateSshUrl(); + } else { + $url = $this->getUrl(); + } + + return ['type' => 'git', 'url' => $url, 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier; + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } + + if ($composer !== null) { + // specials for github + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source'])) { + $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; + $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); + } + if (!isset($composer['abandoned']) && $this->isArchived) { + $composer['abandoned'] = true; + } + if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) { + $composer['funding'] = $funding; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @return array|false + */ + private function getFundingInfo() + { + if (null !== $this->fundingInfo) { + return $this->fundingInfo; + } + + if ($this->originUrl !== 'github.com') { + return $this->fundingInfo = false; + } + + foreach ([$this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml'] as $file) { + try { + $response = $this->httpDownloader->get($file, [ + 'retry-auth-failure' => false, + ])->decodeJson(); + } catch (TransportException $e) { + continue; + } + if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) { + continue; + } + break; + } + if (empty($funding)) { + return $this->fundingInfo = false; + } + + $result = []; + $key = null; + foreach (Preg::split('{\r?\n}', $funding) as $line) { + $line = trim($line); + if (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*(.+)$}', $line, $match)) { + if ($match[2] === '[') { + $key = $match[1]; + continue; + } + if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) { + foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) { + $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')]; + } + } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\s+#.*)?$}', $match[2], $match2)) { + $result[] = ['type' => $match[1], 'url' => trim($match2[1], '"\' ')]; + } + $key = null; + } elseif (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*#\s*$}', $line, $match)) { + $key = $match[1]; + } elseif ($key !== null && ( + Preg::isMatchStrictGroups('{^-\s*(.+)(?:\s+#.*)?$}', $line, $match) + || Preg::isMatchStrictGroups('{^(.+),(?:\s*#.*)?$}', $line, $match) + )) { + $result[] = ['type' => $key, 'url' => trim($match[1], '"\' ')]; + } elseif ($key !== null && $line === ']') { + $key = null; + } + } + + foreach ($result as $key => $item) { + switch ($item['type']) { + case 'community_bridge': + $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']); + break; + case 'github': + $result[$key]['url'] = 'https://github.com/' . basename($item['url']); + break; + case 'issuehunt': + $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url']; + break; + case 'ko_fi': + $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']); + break; + case 'liberapay': + $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']); + break; + case 'open_collective': + $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']); + break; + case 'patreon': + $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']); + break; + case 'tidelift': + $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url']; + break; + case 'polar': + $result[$key]['url'] = 'https://polar.sh/' . basename($item['url']); + break; + case 'buy_me_a_coffee': + $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']); + break; + case 'thanks_dev': + $result[$key]['url'] = 'https://thanks.dev/' . $item['url']; + break; + case 'otechie': + $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']); + break; + case 'custom': + $bits = parse_url($item['url']); + if ($bits === false) { + unset($result[$key]); + break; + } + + if (!array_key_exists('scheme', $bits) && !array_key_exists('host', $bits)) { + if (Preg::isMatch('{^[a-z0-9-]++\.[a-z]{2,3}$}', $item['url'])) { + $result[$key]['url'] = 'https://'.$item['url']; + break; + } + + $this->io->writeError('Funding URL '.$item['url'].' not in a supported format.'); + unset($result[$key]); + break; + } + break; + } + } + + return $this->fundingInfo = $result; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = $this->getContents($resource)->decodeJson(); + + // The GitHub contents API only returns files up to 1MB as base64 encoded files + // larger files either need be fetched with a raw accept header or by using the git blob endpoint + if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { + $resource = $this->getContents($resource['git_url'])->decodeJson(); + } + + if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); + $commit = $this->getContents($resource)->decodeJson(); + + return new \DateTimeImmutable($commit['commit']['committer']['date']); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + if (null === $this->tags) { + $tags = []; + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; + + do { + $response = $this->getContents($resource); + $tagsData = $response->decodeJson(); + foreach ($tagsData as $tag) { + $tags[$tag['name']] = $tag['commit']['sha']; + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + if (null === $this->branches) { + $branches = []; + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; + + do { + $response = $this->getContents($resource); + $branchData = $response->decodeJson(); + foreach ($branchData as $branch) { + $name = substr($branch['ref'], 11); + if ($name !== 'gh-pages') { + $branches[$name] = $branch['object']['sha']; + } + } + + $resource = $this->getNextPage($response); + } while ($resource); + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $url, $matches)) { + return false; + } + + $originUrl = $matches[2] ?? (string) $matches[3]; + if (!in_array(strtolower(Preg::replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + /** + * Gives back the loaded /repos// result + * + * @return mixed[]|null + */ + public function getRepoData(): ?array + { + $this->fetchRootIdentifier(); + + return $this->repoData; + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + if (false !== strpos($this->originUrl, ':')) { + return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + } + + /** + * @inheritDoc + */ + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + try { + return parent::getContents($url); + } catch (TransportException $e) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + $scopesIssued = []; + $scopesNeeded = []; + if ($headers = $e->getHeaders()) { + if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) { + $scopesIssued = explode(' ', $scopes); + } + if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { + $scopesNeeded = explode(' ', $scopes); + } + } + $scopesFailed = array_diff($scopesNeeded, $scopesIssued); + // non-authenticated requests get no scopesNeeded, so ask for credentials + // authenticated requests which failed some scopes should ask for new credentials too + if (!$headers || !count($scopesNeeded) || count($scopesFailed)) { + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); + } + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders()); + + if (!$this->io->hasAuthentication($this->originUrl)) { + if (!$this->io->isInteractive()) { + $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); + throw $e; + } + + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); + + return parent::getContents($url); + } + + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders()); + $this->io->writeError(sprintf( + 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', + $rateLimit['limit'], + $rateLimit['reset'] + )); + } + + throw $e; + + default: + throw $e; + } + } + } + + /** + * Fetch root identifier from GitHub + * + * @throws TransportException + */ + protected function fetchRootIdentifier(): void + { + if ($this->repoData) { + return; + } + + $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; + + try { + $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson(); + } catch (TransportException $e) { + if ($e->getCode() === 499) { + $this->attemptCloneFallback(); + } else { + throw $e; + } + } + if (null === $this->repoData && null !== $this->gitDriver) { + return; + } + + $this->owner = $this->repoData['owner']['login']; + $this->repository = $this->repoData['name']; + + $this->isPrivate = !empty($this->repoData['private']); + if (isset($this->repoData['default_branch'])) { + $this->rootIdentifier = $this->repoData['default_branch']; + } elseif (isset($this->repoData['master_branch'])) { + $this->rootIdentifier = $this->repoData['master_branch']; + } else { + $this->rootIdentifier = 'master'; + } + $this->hasIssues = !empty($this->repoData['has_issues']); + $this->isArchived = !empty($this->repoData['archived']); + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + $this->isPrivate = true; + + try { + // If this repository may be private (hard to say for sure, + // GitHub returns 404 for private repositories) and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($this->generateSshUrl()); + + return true; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); + throw $e; + } + } + + protected function setupGitDriver(string $url): void + { + $this->gitDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->gitDriver->initialize(); + } + + protected function getNextPage(Response $response): ?string + { + $header = $response->getHeader('link'); + if (!$header) { + return null; + } + + $links = explode(',', $header); + foreach ($links as $link) { + if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php new file mode 100644 index 0000000..09fb425 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php @@ -0,0 +1,643 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Util\GitLab; +use Composer\Util\Http\Response; + +/** + * Driver for GitLab API, use the Git driver for local checkouts. + * + * @author Henrik Bjørnskov + * @author Jérôme Tamarelle + */ +class GitLabDriver extends VcsDriver +{ + /** + * @var string + * @phpstan-var 'https'|'http' + */ + private $scheme; + /** @var string */ + private $namespace; + /** @var string */ + private $repository; + + /** + * @var mixed[] Project data returned by GitLab API + */ + private $project = null; + + /** + * @var array Keeps commits returned by GitLab API as commit id => info + */ + private $commits = []; + + /** @var array Map of tag name to identifier */ + private $tags; + + /** @var array Map of branch name to identifier */ + private $branches; + + /** + * Git Driver + * + * @var ?GitDriver + */ + protected $gitDriver = null; + + /** + * Protocol to force use of for repository URLs. + * + * @var string One of ssh, http + */ + protected $protocol; + + /** + * Defaults to true unless we can make sure it is public + * + * @var bool defines whether the repo is private or not + */ + private $isPrivate = true; + + /** + * @var bool true if the origin has a port number or a path component in it + */ + private $hasNonstandardOrigin = false; + + public const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; + + /** + * Extracts information from the repository url. + * + * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. + * + * @inheritDoc + */ + public function initialize(): void + { + if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); + } + + $guessedDomain = $match['domain'] ?? (string) $match['domain2']; + $configuredDomains = $this->config->get('gitlab-domains'); + $urlParts = explode('/', $match['parts']); + + $this->scheme = in_array($match['scheme'], ['https', 'http'], true) + ? $match['scheme'] + : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') + ; + $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); + if (false === $origin) { + throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.$this->url.')'); + } + $this->originUrl = $origin; + + if (is_string($protocol = $this->config->get('gitlab-protocol'))) { + // https treated as a synonym for http. + if (!in_array($protocol, ['git', 'http', 'https'], true)) { + throw new \RuntimeException('gitlab-protocol must be one of git, http.'); + } + $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; + } + + if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { + $this->hasNonstandardOrigin = true; + } + + $this->namespace = implode('/', $urlParts); + $this->repository = Preg::replace('#(\.git)$#', '', $match['repo']); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + $this->fetchProject(); + } + + /** + * Updates the HttpDownloader instance. + * Mainly useful for tests. + * + * @internal + */ + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + $this->httpDownloader = $httpDownloader; + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } + + if (null !== $composer) { + // specials for gitlab (this data is only available if authentication is provided) + if (isset($composer['support']) && !is_array($composer['support'])) { + $composer['support'] = []; + } + if (!isset($composer['support']['source']) && isset($this->project['web_url'])) { + $label = array_search($identifier, $this->getTags(), true) ?: array_search($identifier, $this->getBranches(), true) ?: $identifier; + $composer['support']['source'] = sprintf('%s/-/tree/%s', $this->project['web_url'], $label); + } + if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) { + $composer['support']['issues'] = sprintf('%s/-/issues', $this->project['web_url']); + } + if (!isset($composer['abandoned']) && !empty($this->project['archived'])) { + $composer['abandoned'] = true; + } + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + // Convert the root identifier to a cacheable commit id + if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier; + + try { + $content = $this->getContents($resource)->getBody(); + } catch (TransportException $e) { + if ($e->getCode() !== 404) { + throw $e; + } + + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + if (isset($this->commits[$identifier])) { + return new \DateTimeImmutable($this->commits[$identifier]['committed_date']); + } + + return null; + } + + public function getRepositoryUrl(): string + { + if ($this->protocol) { + return $this->project["{$this->protocol}_url_to_repo"]; + } + + return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return $this->project['web_url']; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier; + + return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + + return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->project['default_branch']; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + + if (null === $this->branches) { + $this->branches = $this->getReferences('branches'); + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + + if (null === $this->tags) { + $this->tags = $this->getReferences('tags'); + } + + return $this->tags; + } + + /** + * @return string Base URL for GitLab API v3 + */ + public function getApiUrl(): string + { + return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); + } + + /** + * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.` + */ + private function urlEncodeAll(string $string): string + { + $encoded = ''; + for ($i = 0; isset($string[$i]); $i++) { + $character = $string[$i]; + if (!ctype_alnum($character) && !in_array($character, ['-', '_'], true)) { + $character = '%' . sprintf('%02X', ord($character)); + } + $encoded .= $character; + } + + return $encoded; + } + + /** + * @return string[] where keys are named references like tags or branches and the value a sha + */ + protected function getReferences(string $type): array + { + $perPage = 100; + $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage; + + $references = []; + do { + $response = $this->getContents($resource); + $data = $response->decodeJson(); + + foreach ($data as $datum) { + $references[$datum['name']] = $datum['commit']['id']; + + // Keep the last commit date of a reference to avoid + // unnecessary API call when retrieving the composer file. + $this->commits[$datum['commit']['id']] = $datum['commit']; + } + + if (count($data) >= $perPage) { + $resource = $this->getNextPage($response); + } else { + $resource = false; + } + } while ($resource); + + return $references; + } + + protected function fetchProject(): void + { + if (!is_null($this->project)) { + return; + } + + // we need to fetch the default branch from the api + $resource = $this->getApiUrl(); + $this->project = $this->getContents($resource, true)->decodeJson(); + if (isset($this->project['visibility'])) { + $this->isPrivate = $this->project['visibility'] !== 'public'; + } else { + // client is not authenticated, therefore repository has to be public + $this->isPrivate = false; + } + } + + /** + * @phpstan-impure + * + * @return true + * @throws \RuntimeException + */ + protected function attemptCloneFallback(): bool + { + if ($this->isPrivate === false) { + $url = $this->generatePublicUrl(); + } else { + $url = $this->generateSshUrl(); + } + + try { + // If this repository may be private and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($url); + + return true; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials'); + throw $e; + } + } + + /** + * Generate an SSH URL + */ + protected function generateSshUrl(): string + { + if ($this->hasNonstandardOrigin) { + return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; + } + + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; + } + + protected function generatePublicUrl(): string + { + return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git'; + } + + protected function setupGitDriver(string $url): void + { + $this->gitDriver = new GitDriver( + ['url' => $url], + $this->io, + $this->config, + $this->httpDownloader, + $this->process + ); + $this->gitDriver->initialize(); + } + + /** + * @inheritDoc + */ + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + try { + $response = parent::getContents($url); + + if ($fetchingRepoData) { + $json = $response->decodeJson(); + + // Accessing the API with a token with Guest (10) access will return + // more data than unauthenticated access but no default_branch data + // accessing files via the API will then also fail + if (!isset($json['default_branch']) && isset($json['permissions'])) { + $this->isPrivate = $json['visibility'] !== 'public'; + + $moreThanGuestAccess = false; + // Check both access levels (e.g. project, group) + // - value will be null if no access is set + // - value will be array with key access_level if set + foreach ($json['permissions'] as $permission) { + if ($permission && $permission['access_level'] > 10) { + $moreThanGuestAccess = true; + } + } + + if (!$moreThanGuestAccess) { + $this->io->writeError('GitLab token with Guest only access detected'); + + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + } + + // force auth as the unauthenticated version of the API is broken + if (!isset($json['default_branch'])) { + // GitLab allows you to disable the repository inside a project to use a project only for issues and wiki + if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') { + throw new TransportException('The GitLab repository is disabled in the project', 400); + } + + if (!empty($json['id'])) { + $this->isPrivate = false; + } + + throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); + } + } + + return $response; + } catch (TransportException $e) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); + $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + $this->attemptCloneFallback(); + + return new Response(['url' => 'dummy'], 200, [], 'null'); + } + + throw $e; + + default: + throw $e; + } + } + } + + /** + * Uses the config `gitlab-domains` to see if the driver supports the url for the + * repository given. + * + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (!Preg::isMatch(self::URL_REGEX, $url, $match)) { + return false; + } + + $scheme = $match['scheme']; + $guessedDomain = $match['domain'] ?? (string) $match['domain2']; + $urlParts = explode('/', $match['parts']); + + if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { + return false; + } + + if ('https' === $scheme && !extension_loaded('openssl')) { + $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + /** + * Gives back the loaded /projects// result + * + * @return mixed[]|null + */ + public function getRepoData(): ?array + { + $this->fetchProject(); + + return $this->project; + } + + protected function getNextPage(Response $response): ?string + { + $header = $response->getHeader('link'); + + $links = explode(',', $header); + foreach ($links as $link) { + if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + + return null; + } + + /** + * @param array $configuredDomains + * @param array $urlParts + * @param string $portNumber + * + * @return string|false + */ + private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber) + { + $guessedDomain = strtolower($guessedDomain); + + if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { + if (null !== $portNumber) { + return $guessedDomain.':'.$portNumber; + } + + return $guessedDomain; + } + + if (null !== $portNumber) { + $guessedDomain .= ':'.$portNumber; + } + + while (null !== ($part = array_shift($urlParts))) { + $guessedDomain .= '/' . $part; + + if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array(Preg::replace('{:\d+}', '', $guessedDomain), $configuredDomains))) { + return $guessedDomain; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php new file mode 100644 index 0000000..625a2a1 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php @@ -0,0 +1,242 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\Pcre\Preg; +use Composer\Util\Hg as HgUtils; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\IO\IOInterface; +use Composer\Util\Url; + +/** + * @author Per Bernhardt + */ +class HgDriver extends VcsDriver +{ + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var string */ + protected $rootIdentifier; + /** @var string */ + protected $repoDir; + + /** + * @inheritDoc + */ + public function initialize(): void + { + if (Filesystem::isLocalPath($this->url)) { + $this->repoDir = $this->url; + } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $cacheDir = $this->config->get('cache-vcs-dir'); + $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/'; + + $fs = new Filesystem(); + $fs->ensureDirectoryExists($cacheDir); + + if (!is_writable(dirname($this->repoDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.'); + } + + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($this->url, $this->io); + + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + // update the repo if it is a valid hg repository + if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) { + if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) { + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + } + } else { + // clean up directory and do a fresh clone into it + $fs->removeDirectory($this->repoDir); + + $repoDir = $this->repoDir; + $command = static function ($url) use ($repoDir): array { + return ['hg', 'clone', '--noupdate', '--', $url, $repoDir]; + }; + + $hgUtils->runCommand($command, $this->url, null); + } + } + + $this->getTags(); + $this->getBranches(); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + if (null === $this->rootIdentifier) { + $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir); + $output = $this->process->splitLines($output); + $this->rootIdentifier = $output[0]; + } + + return $this->rootIdentifier; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + + $resource = ['hg', 'cat', '-r', $identifier, '--', $file]; + $this->process->execute($resource, $content, $this->repoDir); + + if (!trim($content)) { + return null; + } + + return $content; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $this->process->execute( + ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier], + $output, + $this->repoDir + ); + + return new \DateTimeImmutable(trim($output), new \DateTimeZone('UTC')); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + $this->process->execute(['hg', 'tags'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $tag) { + if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { + $tags[$match[1]] = $match[2]; + } + } + unset($tags['tip']); + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + $bookmarks = []; + + $this->process->execute(['hg', 'branches'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } + + $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { + $bookmarks[$match[1]] = $match[2]; + } + } + + // Branches will have preference over bookmarks + $this->branches = array_merge($bookmarks, $branches); + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { + return true; + } + + // local filesystem + if (Filesystem::isLocalPath($url)) { + $url = Filesystem::getPlatformPath($url); + if (!is_dir($url)) { + return false; + } + + $process = new ProcessExecutor($io); + // check whether there is a hg repo in that path + if ($process->execute(['hg', 'summary'], $output, $url) === 0) { + return true; + } + } + + if (!$deep) { + return false; + } + + $process = new ProcessExecutor($io); + $exit = $process->execute(['hg', 'identify', '--', $url], $ignored); + + return $exit === 0; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php new file mode 100644 index 0000000..a77c8c9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Perforce; +use Composer\Util\Http\Response; + +/** + * @author Matt Whittom + */ +class PerforceDriver extends VcsDriver +{ + /** @var string */ + protected $depot; + /** @var string */ + protected $branch; + /** @var ?Perforce */ + protected $perforce = null; + + /** + * @inheritDoc + */ + public function initialize(): void + { + $this->depot = $this->repoConfig['depot']; + $this->branch = ''; + if (!empty($this->repoConfig['branch'])) { + $this->branch = $this->repoConfig['branch']; + } + + $this->initPerforce($this->repoConfig); + $this->perforce->p4Login(); + $this->perforce->checkStream(); + + $this->perforce->writeP4ClientSpec(); + $this->perforce->connectClient(); + } + + /** + * @param array $repoConfig + */ + private function initPerforce(array $repoConfig): void + { + if (!empty($this->perforce)) { + return; + } + + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + + $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; + $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); + } + + /** + * @inheritDoc + */ + public function getFileContent(string $file, string $identifier): ?string + { + return $this->perforce->getFileContent($file, $identifier); + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + return null; + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + return $this->branch; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + return $this->perforce->getBranches(); + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + return $this->perforce->getTags(); + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return [ + 'type' => 'perforce', + 'url' => $this->repoConfig['url'], + 'reference' => $identifier, + 'p4user' => $this->perforce->getUser(), + ]; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function hasComposerFile(string $identifier): bool + { + $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); + + return !empty($composerInfo); + } + + /** + * @inheritDoc + */ + public function getContents(string $url): Response + { + throw new \BadMethodCallException('Not implemented/used in PerforceDriver'); + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + if ($deep || Preg::isMatch('#\b(perforce|p4)\b#i', $url)) { + return Perforce::checkServerExists($url, new ProcessExecutor($io)); + } + + return false; + } + + /** + * @inheritDoc + */ + public function cleanup(): void + { + $this->perforce->cleanupClientSpec(); + $this->perforce = null; + } + + public function getDepot(): string + { + return $this->depot; + } + + public function getBranch(): string + { + return $this->branch; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php new file mode 100644 index 0000000..9a303ee --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php @@ -0,0 +1,409 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Config; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; +use Composer\Util\Url; +use Composer\Util\Svn as SvnUtil; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; + +/** + * @author Jordi Boggiano + * @author Till Klampaeckel + */ +class SvnDriver extends VcsDriver +{ + /** @var string */ + protected $baseUrl; + /** @var array Map of tag name to identifier */ + protected $tags; + /** @var array Map of branch name to identifier */ + protected $branches; + /** @var ?string */ + protected $rootIdentifier; + + /** @var string|false */ + protected $trunkPath = 'trunk'; + /** @var string */ + protected $branchesPath = 'branches'; + /** @var string */ + protected $tagsPath = 'tags'; + /** @var string */ + protected $packagePath = ''; + /** @var bool */ + protected $cacheCredentials = true; + + /** + * @var \Composer\Util\Svn + */ + private $util; + + /** + * @inheritDoc + */ + public function initialize(): void + { + $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/'); + + SvnUtil::cleanEnv(); + + if (isset($this->repoConfig['trunk-path'])) { + $this->trunkPath = $this->repoConfig['trunk-path']; + } + if (isset($this->repoConfig['branches-path'])) { + $this->branchesPath = $this->repoConfig['branches-path']; + } + if (isset($this->repoConfig['tags-path'])) { + $this->tagsPath = $this->repoConfig['tags-path']; + } + if (array_key_exists('svn-cache-credentials', $this->repoConfig)) { + $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials']; + } + if (isset($this->repoConfig['package-path'])) { + $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/'); + } + + if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) { + $this->baseUrl = substr($this->url, 0, $pos); + } + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl))); + $this->cache->setReadOnly($this->config->get('cache-read-only')); + + $this->getBranches(); + $this->getTags(); + } + + /** + * @inheritDoc + */ + public function getRootIdentifier(): string + { + return $this->rootIdentifier ?: $this->trunkPath; + } + + /** + * @inheritDoc + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @inheritDoc + */ + public function getSource(string $identifier): array + { + return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier]; + } + + /** + * @inheritDoc + */ + public function getDist(string $identifier): ?array + { + return null; + } + + /** + * @inheritDoc + */ + protected function shouldCache(string $identifier): bool + { + return $this->cache && Preg::isMatch('{@\d+$}', $identifier); + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) { + // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array + // and fix outdated invalid cache files + if ($res === '""') { + $res = 'null'; + $this->cache->write($identifier.'.json', json_encode(null)); + } + + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + try { + $composer = $this->getBaseComposerInformation($identifier); + } catch (TransportException $e) { + $message = $e->getMessage(); + if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) { + throw $e; + } + // remember a not-existent composer.json + $composer = null; + } + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier.'.json', json_encode($composer)); + } + + $this->infoCache[$identifier] = $composer; + } + + // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array + if (!is_array($this->infoCache[$identifier])) { + return null; + } + + return $this->infoCache[$identifier]; + } + + public function getFileContent(string $file, string $identifier): ?string + { + $identifier = '/' . trim($identifier, '/') . '/'; + + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } + + try { + $resource = $path.$file; + $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev); + if ('' === trim($output)) { + return null; + } + } catch (\RuntimeException $e) { + throw new TransportException($e->getMessage()); + } + + return $output; + } + + /** + * @inheritDoc + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + $identifier = '/' . trim($identifier, '/') . '/'; + + if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } + + $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev); + foreach ($this->process->splitLines($output) as $line) { + if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { + return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function getTags(): array + { + if (null === $this->tags) { + $tags = []; + + if ($this->tagsPath !== false) { + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath); + if ($output !== '') { + $lastRev = 0; + foreach ($this->process->splitLines($output) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $tags[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->tagsPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); + } + } + } + } + } + + $this->tags = $tags; + } + + return $this->tags; + } + + /** + * @inheritDoc + */ + public function getBranches(): array + { + if (null === $this->branches) { + $branches = []; + + if (false === $this->trunkPath) { + $trunkParent = $this->baseUrl . '/'; + } else { + $trunkParent = $this->baseUrl . '/' . $this->trunkPath; + } + + $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent); + if ($output !== '') { + foreach ($this->process->splitLines($output) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $branches['trunk'] = $this->buildIdentifier( + '/' . $this->trunkPath, + (int) $match[1] + ); + $this->rootIdentifier = $branches['trunk']; + break; + } + } + } + } + unset($output); + + if ($this->branchesPath !== false) { + $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath); + if ($output !== '') { + $lastRev = 0; + foreach ($this->process->splitLines(trim($output)) as $line) { + $line = trim($line); + if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { + if ($match[2] === './') { + $lastRev = (int) $match[1]; + } else { + $branches[rtrim($match[2], '/')] = $this->buildIdentifier( + '/' . $this->branchesPath . '/' . $match[2], + max($lastRev, (int) $match[1]) + ); + } + } + } + } + } + + $this->branches = $branches; + } + + return $this->branches; + } + + /** + * @inheritDoc + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + $url = self::normalizeUrl($url); + if (Preg::isMatch('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) { + return true; + } + + // proceed with deep check for local urls since they are fast to process + if (!$deep && !Filesystem::isLocalPath($url)) { + return false; + } + + $process = new ProcessExecutor($io); + $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput); + + if ($exit === 0) { + // This is definitely a Subversion repository. + return true; + } + + // Subversion client 1.7 and older + if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) { + // This is likely a remote Subversion repository that requires + // authentication. We will handle actual authentication later. + return true; + } + + // Subversion client 1.8 and newer + if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) { + // This is likely a remote Subversion or newer repository that requires + // authentication. We will handle actual authentication later. + return true; + } + + return false; + } + + /** + * An absolute path (leading '/') is converted to a file:// url. + */ + protected static function normalizeUrl(string $url): string + { + $fs = new Filesystem(); + if ($fs->isAbsolutePath($url)) { + return 'file://' . strtr($url, '\\', '/'); + } + + return $url; + } + + /** + * Execute an SVN command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command The svn command to run. + * @param string $url The SVN URL. + * @throws \RuntimeException + */ + protected function execute(array $command, string $url): string + { + if (null === $this->util) { + $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); + $this->util->setCacheCredentials($this->cacheCredentials); + } + + try { + return $this->util->execute($command, $url); + } catch (\RuntimeException $e) { + if (null === $this->util->binaryVersion()) { + throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); + } + + throw new \RuntimeException( + 'Repository '.$this->url.' could not be processed, '.$e->getMessage() + ); + } + } + + /** + * Build the identifier respecting "package-path" config option + * + * @param string $baseDir The path to trunk/branch/tag + * @param int $revision The revision mark to add to identifier + */ + protected function buildIdentifier(string $baseDir, int $revision): string + { + return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php new file mode 100644 index 0000000..b780304 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php @@ -0,0 +1,179 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Cache; +use Composer\Downloader\TransportException; +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Filesystem; +use Composer\Util\Http\Response; + +/** + * A driver implementation for driver with authentication interaction. + * + * @author François Pluchino + */ +abstract class VcsDriver implements VcsDriverInterface +{ + /** @var string */ + protected $url; + /** @var string */ + protected $originUrl; + /** @var array */ + protected $repoConfig; + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var array */ + protected $infoCache = []; + /** @var ?Cache */ + protected $cache; + + /** + * Constructor. + * + * @param array{url: string}&array $repoConfig The repository configuration + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + * @param ProcessExecutor $process Process instance, injectable for mocking + */ + final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process) + { + if (Filesystem::isLocalPath($repoConfig['url'])) { + $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); + } + + $this->url = $repoConfig['url']; + $this->originUrl = $repoConfig['url']; + $this->repoConfig = $repoConfig; + $this->io = $io; + $this->config = $config; + $this->httpDownloader = $httpDownloader; + $this->process = $process; + } + + /** + * Returns whether or not the given $identifier should be cached or not. + */ + protected function shouldCache(string $identifier): bool + { + return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier); + } + + /** + * @inheritDoc + */ + public function getComposerInformation(string $identifier): ?array + { + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, JsonFile::encode($composer, 0)); + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * @return array|null + */ + protected function getBaseComposerInformation(string $identifier): ?array + { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); + + if ([] === $composer || !is_array($composer)) { + return null; + } + + if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) { + $composer['time'] = $changeDate->format(DATE_RFC3339); + } + + return $composer; + } + + /** + * @inheritDoc + */ + public function hasComposerFile(string $identifier): bool + { + try { + return null !== $this->getComposerInformation($identifier); + } catch (TransportException $e) { + } + + return false; + } + + /** + * Get the https or http protocol depending on SSL support. + * + * Call this only if you know that the server supports both. + * + * @return string The correct type of protocol + */ + protected function getScheme(): string + { + if (extension_loaded('openssl')) { + return 'https'; + } + + return 'http'; + } + + /** + * Get the remote content. + * + * @param string $url The URL of content + * + * @throws TransportException + */ + protected function getContents(string $url): Response + { + $options = $this->repoConfig['options'] ?? []; + + return $this->httpDownloader->get($url, $options); + } + + /** + * @inheritDoc + */ + public function cleanup(): void + { + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php new file mode 100644 index 0000000..96a4b8a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php @@ -0,0 +1,110 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\IO\IOInterface; + +/** + * @author Jordi Boggiano + * @internal + */ +interface VcsDriverInterface +{ + /** + * Initializes the driver (git clone, svn checkout, fetch info etc) + */ + public function initialize(): void; + + /** + * Return the composer.json file information + * + * @param string $identifier Any identifier to a specific branch/tag/commit + * @return mixed[]|null Array containing all infos from the composer.json file, or null to denote that no file was present + */ + public function getComposerInformation(string $identifier): ?array; + + /** + * Return the content of $file or null if the file does not exist. + */ + public function getFileContent(string $file, string $identifier): ?string; + + /** + * Get the changedate for $identifier. + */ + public function getChangeDate(string $identifier): ?\DateTimeImmutable; + + /** + * Return the root identifier (trunk, master, default/tip ..) + * + * @return string Identifier + */ + public function getRootIdentifier(): string; + + /** + * Return list of branches in the repository + * + * @return array Branch names as keys, identifiers as values + */ + public function getBranches(): array; + + /** + * Return list of tags in the repository + * + * @return array Tag names as keys, identifiers as values + */ + public function getTags(): array; + + /** + * @param string $identifier Any identifier to a specific branch/tag/commit + * + * @return array{type: string, url: string, reference: string, shasum: string}|null + */ + public function getDist(string $identifier): ?array; + + /** + * @param string $identifier Any identifier to a specific branch/tag/commit + * + * @return array{type: string, url: string, reference: string} + */ + public function getSource(string $identifier): array; + + /** + * Return the URL of the repository + */ + public function getUrl(): string; + + /** + * Return true if the repository has a composer file for a given identifier, + * false otherwise. + * + * @param string $identifier Any identifier to a specific branch/tag/commit + * @return bool Whether the repository has a composer file for a given identifier. + */ + public function hasComposerFile(string $identifier): bool; + + /** + * Performs any cleanup necessary as the driver is not longer needed + */ + public function cleanup(): void; + + /** + * Checks if this driver can handle a given url + * + * @param IOInterface $io IO instance + * @param Config $config current $config + * @param string $url URL to validate/check + * @param bool $deep unless true, only shallow checks (url matching typically) should be done + */ + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool; +} diff --git a/vendor/composer/composer/src/Composer/Repository/VcsRepository.php b/vendor/composer/composer/src/Composer/Repository/VcsRepository.php new file mode 100644 index 0000000..5aaea60 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/VcsRepository.php @@ -0,0 +1,535 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Repository\Vcs\VcsDriverInterface; +use Composer\Package\Version\VersionParser; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Package\Loader\InvalidPackageException; +use Composer\Package\Loader\LoaderInterface; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; +use Composer\Util\Url; +use Composer\Semver\Constraint\Constraint; +use Composer\IO\IOInterface; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface +{ + /** @var string */ + protected $url; + /** @var ?string */ + protected $packageName; + /** @var bool */ + protected $isVerbose; + /** @var bool */ + protected $isVeryVerbose; + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var VersionParser */ + protected $versionParser; + /** @var string */ + protected $type; + /** @var ?LoaderInterface */ + protected $loader; + /** @var array */ + protected $repoConfig; + /** @var HttpDownloader */ + protected $httpDownloader; + /** @var ProcessExecutor */ + protected $processExecutor; + /** @var bool */ + protected $branchErrorOccurred = false; + /** @var array> */ + private $drivers; + /** @var ?VcsDriverInterface */ + private $driver; + /** @var ?VersionCacheInterface */ + private $versionCache; + /** @var list */ + private $emptyReferences = []; + /** @var array<'tags'|'branches', array> */ + private $versionTransportExceptions = []; + + /** + * @param array{url: string, type?: string}&array $repoConfig + * @param array>|null $drivers + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null, ?array $drivers = null, ?VersionCacheInterface $versionCache = null) + { + parent::__construct(); + $this->drivers = $drivers ?: [ + 'github' => 'Composer\Repository\Vcs\GitHubDriver', + 'gitlab' => 'Composer\Repository\Vcs\GitLabDriver', + 'bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', + 'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', + 'git' => 'Composer\Repository\Vcs\GitDriver', + 'hg' => 'Composer\Repository\Vcs\HgDriver', + 'perforce' => 'Composer\Repository\Vcs\PerforceDriver', + 'fossil' => 'Composer\Repository\Vcs\FossilDriver', + // svn must be last because identifying a subversion server for sure is practically impossible + 'svn' => 'Composer\Repository\Vcs\SvnDriver', + ]; + + $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']); + $this->io = $io; + $this->type = $repoConfig['type'] ?? 'vcs'; + $this->isVerbose = $io->isVerbose(); + $this->isVeryVerbose = $io->isVeryVerbose(); + $this->config = $config; + $this->repoConfig = $repoConfig; + $this->versionCache = $versionCache; + $this->httpDownloader = $httpDownloader; + $this->processExecutor = $process ?? new ProcessExecutor($io); + } + + public function getRepoName() + { + $driverClass = get_class($this->getDriver()); + $driverType = array_search($driverClass, $this->drivers); + if (!$driverType) { + $driverType = $driverClass; + } + + return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')'; + } + + public function getRepoConfig() + { + return $this->repoConfig; + } + + public function setLoader(LoaderInterface $loader): void + { + $this->loader = $loader; + } + + public function getDriver(): ?VcsDriverInterface + { + if ($this->driver) { + return $this->driver; + } + + if (isset($this->drivers[$this->type])) { + $class = $this->drivers[$this->type]; + $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + + foreach ($this->drivers as $driver) { + if ($driver::supports($this->io, $this->config, $this->url)) { + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + } + + foreach ($this->drivers as $driver) { + if ($driver::supports($this->io, $this->config, $this->url, true)) { + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); + $this->driver->initialize(); + + return $this->driver; + } + } + + return null; + } + + public function hadInvalidBranches(): bool + { + return $this->branchErrorOccurred; + } + + /** + * @return list + */ + public function getEmptyReferences(): array + { + return $this->emptyReferences; + } + + /** + * @return array<'tags'|'branches', array> + */ + public function getVersionTransportExceptions(): array + { + return $this->versionTransportExceptions; + } + + protected function initialize() + { + parent::initialize(); + + $isVerbose = $this->isVerbose; + $isVeryVerbose = $this->isVeryVerbose; + + $driver = $this->getDriver(); + if (!$driver) { + throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url); + } + + $this->versionParser = new VersionParser; + if (!$this->loader) { + $this->loader = new ArrayLoader($this->versionParser); + } + + $hasRootIdentifierComposerJson = false; + try { + $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier()); + if ($hasRootIdentifierComposerJson) { + $data = $driver->getComposerInformation($driver->getRootIdentifier()); + $this->packageName = !empty($data['name']) ? $data['name'] : null; + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) { + throw $e; + } + + if ($isVeryVerbose) { + $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); + } + } + + foreach ($driver->getTags() as $tag => $identifier) { + $tag = (string) $tag; + $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; + + // strip the release- prefix from tags if present + $tag = str_replace('release-', '', $tag); + + $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + if ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + + continue; + } + + if (!$parsedTag = $this->validateTag($tag)) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); + } + continue; + } + + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + try { + $data = $driver->getComposerInformation($identifier); + if (null === $data) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', no composer file'); + } + $this->emptyReferences[] = $identifier; + continue; + } + + // manually versioned package + if (isset($data['version'])) { + $data['version_normalized'] = $this->versionParser->normalize($data['version']); + } else { + // auto-versioned package, read value from tag + $data['version'] = $tag; + $data['version_normalized'] = $parsedTag; + } + + // make sure tag packages have no -dev flag + $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']); + $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); + + // make sure tag do not contain the default-branch marker + unset($data['default-branch']); + + // broken package, version doesn't match tag + if ($data['version_normalized'] !== $parsedTag) { + if ($isVeryVerbose) { + if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) { + $this->io->writeError('Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes'); + } else { + $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); + } + } + continue; + } + + $tagPackageName = $this->packageName ?: ($data['name'] ?? ''); + if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); + } + continue; + } + + if ($isVeryVerbose) { + $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); + } + + $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); + } catch (\Exception $e) { + if ($e instanceof TransportException) { + $this->versionTransportExceptions['tags'][$tag] = $e; + if ($e->getCode() === 404) { + $this->emptyReferences[] = $identifier; + } + if ($this->shouldRethrowTransportException($e)) { + throw $e; + } + } + if ($isVeryVerbose) { + $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).''); + } + continue; + } + } + + if (!$isVeryVerbose) { + $this->io->overwriteError('', false); + } + + $branches = $driver->getBranches(); + // make sure the root identifier branch gets loaded first + if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) { + $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches; + } + + foreach ($branches as $branch => $identifier) { + $branch = (string) $branch; + $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + if (!$parsedBranch = $this->validateBranch($branch)) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', invalid name'); + } + continue; + } + + // make sure branch packages have a dev flag + if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) { + $version = 'dev-' . str_replace('#', '+', $branch); + $parsedBranch = str_replace('#', '+', $parsedBranch); + } else { + $prefix = strpos($branch, 'v') === 0 ? 'v' : ''; + $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch); + } + + $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + if ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + + continue; + } + + try { + $data = $driver->getComposerInformation($identifier); + if (null === $data) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', no composer file'); + } + $this->emptyReferences[] = $identifier; + continue; + } + + // branches are always auto-versioned, read value from branch name + $data['version'] = $version; + $data['version_normalized'] = $parsedBranch; + + unset($data['default-branch']); + if ($driver->getRootIdentifier() === $branch) { + $data['default-branch'] = true; + } + + if ($isVeryVerbose) { + $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); + } + + $packageData = $this->preProcess($driver, $data, $identifier); + $package = $this->loader->load($packageData); + if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) { + throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData); + } + $this->addPackage($package); + } catch (TransportException $e) { + $this->versionTransportExceptions['branches'][$branch] = $e; + if ($e->getCode() === 404) { + $this->emptyReferences[] = $identifier; + } + if ($this->shouldRethrowTransportException($e)) { + throw $e; + } + if ($isVeryVerbose) { + $this->io->writeError('Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)'); + } + continue; + } catch (\Exception $e) { + if (!$isVeryVerbose) { + $this->io->writeError(''); + } + $this->branchErrorOccurred = true; + $this->io->writeError('Skipped branch '.$branch.', '.$e->getMessage().''); + $this->io->writeError(''); + continue; + } + } + $driver->cleanup(); + + if (!$isVeryVerbose) { + $this->io->overwriteError('', false); + } + + if (!$this->getPackages()) { + throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.'); + } + } + + /** + * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data + * + * @return array{name: string|null, dist: array{type: string, url: string, reference: string, shasum: string}|null, source: array{type: string, url: string, reference: string}} + */ + protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier): array + { + // keep the name of the main identifier for all packages + // this ensures that a package can be renamed in one place and that all old tags + // will still be installable using that new name without requiring re-tagging + $dataPackageName = $data['name'] ?? null; + $data['name'] = $this->packageName ?: $dataPackageName; + + if (!isset($data['dist'])) { + $data['dist'] = $driver->getDist($identifier); + } + if (!isset($data['source'])) { + $data['source'] = $driver->getSource($identifier); + } + + // if custom dist info is provided but does not provide a reference, copy the source reference to it + if (is_array($data['dist']) && !isset($data['dist']['reference']) && isset($data['source']['reference'])) { + $data['dist']['reference'] = $data['source']['reference']; + } + + return $data; + } + + /** + * @return string|false + */ + private function validateBranch(string $branch) + { + try { + $normalizedBranch = $this->versionParser->normalizeBranch($branch); + + // validate that the branch name has no weird characters conflicting with constraints + $this->versionParser->parseConstraints($normalizedBranch); + + return $normalizedBranch; + } catch (\Exception $e) { + } + + return false; + } + + /** + * @return string|false + */ + private function validateTag(string $version) + { + try { + return $this->versionParser->normalize($version); + } catch (\Exception $e) { + } + + return false; + } + + /** + * @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached + */ + private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = false) + { + if (!$this->versionCache) { + return null; + } + + $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); + if ($cachedPackage === false) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); + } + + return false; + } + + if ($cachedPackage) { + $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; + if ($isVeryVerbose) { + $this->io->writeError($msg); + } elseif ($isVerbose) { + $this->io->overwriteError($msg, false); + } + + unset($cachedPackage['default-branch']); + if ($isDefaultBranch) { + $cachedPackage['default-branch'] = true; + } + + if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) { + if ($isVeryVerbose) { + $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); + } + $cachedPackage = null; + } + } + + if ($cachedPackage) { + return $this->loader->load($cachedPackage); + } + + return null; + } + + private function shouldRethrowTransportException(TransportException $e): bool + { + return in_array($e->getCode(), [401, 403, 429], true) || $e->getCode() >= 500; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php b/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php new file mode 100644 index 0000000..ac0c417 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php @@ -0,0 +1,21 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +interface VersionCacheInterface +{ + /** + * @return mixed[]|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier + */ + public function getVersionPackage(string $version, string $identifier); +} diff --git a/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php b/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php new file mode 100644 index 0000000..349c324 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php @@ -0,0 +1,73 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Installer\InstallationManager; + +/** + * Writable array repository. + * + * @author Jordi Boggiano + */ +class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface +{ + use CanonicalPackagesTrait; + + /** + * @var string[] + */ + protected $devPackageNames = []; + + /** @var bool|null */ + private $devMode = null; + + /** + * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown + */ + public function getDevMode() + { + return $this->devMode; + } + + /** + * @inheritDoc + */ + public function setDevPackageNames(array $devPackageNames) + { + $this->devPackageNames = $devPackageNames; + } + + /** + * @inheritDoc + */ + public function getDevPackageNames() + { + return $this->devPackageNames; + } + + /** + * @inheritDoc + */ + public function write(bool $devMode, InstallationManager $installationManager) + { + $this->devMode = $devMode; + } + + /** + * @inheritDoc + */ + public function reload() + { + $this->devMode = null; + } +} diff --git a/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php b/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php new file mode 100644 index 0000000..46a3c9e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php @@ -0,0 +1,73 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; +use Composer\Installer\InstallationManager; + +/** + * Writable repository interface. + * + * @author Konstantin Kudryashov + */ +interface WritableRepositoryInterface extends RepositoryInterface +{ + /** + * Writes repository (f.e. to the disc). + * + * @param bool $devMode Whether dev requirements were included or not in this installation + * @return void + */ + public function write(bool $devMode, InstallationManager $installationManager); + + /** + * Adds package to the repository. + * + * @param PackageInterface $package package instance + * @return void + */ + public function addPackage(PackageInterface $package); + + /** + * Removes package from the repository. + * + * @param PackageInterface $package package instance + * @return void + */ + public function removePackage(PackageInterface $package); + + /** + * Get unique packages (at most one package of each name), with aliases resolved and removed. + * + * @return PackageInterface[] + */ + public function getCanonicalPackages(); + + /** + * Forces a reload of all packages. + * + * @return void + */ + public function reload(); + + /** + * @param string[] $devPackageNames + * @return void + */ + public function setDevPackageNames(array $devPackageNames); + + /** + * @return string[] Names of dependencies installed through require-dev + */ + public function getDevPackageNames(); +} diff --git a/vendor/composer/composer/src/Composer/Script/Event.php b/vendor/composer/composer/src/Composer/Script/Event.php new file mode 100644 index 0000000..fc3386f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Script/Event.php @@ -0,0 +1,122 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\EventDispatcher\Event as BaseEvent; + +/** + * The script event class + * + * @author François Pluchino + * @author Nils Adermann + */ +class Event extends BaseEvent +{ + /** + * @var Composer The composer instance + */ + private $composer; + + /** + * @var IOInterface The IO instance + */ + private $io; + + /** + * @var bool Dev mode flag + */ + private $devMode; + + /** + * @var BaseEvent|null + */ + private $originatingEvent; + + /** + * Constructor. + * + * @param string $name The event name + * @param Composer $composer The composer object + * @param IOInterface $io The IOInterface object + * @param bool $devMode Whether or not we are in dev mode + * @param array $args Arguments passed by the user + * @param mixed[] $flags Optional flags to pass data not as argument + */ + public function __construct(string $name, Composer $composer, IOInterface $io, bool $devMode = false, array $args = [], array $flags = []) + { + parent::__construct($name, $args, $flags); + $this->composer = $composer; + $this->io = $io; + $this->devMode = $devMode; + } + + /** + * Returns the composer instance. + */ + public function getComposer(): Composer + { + return $this->composer; + } + + /** + * Returns the IO instance. + */ + public function getIO(): IOInterface + { + return $this->io; + } + + /** + * Return the dev mode flag + */ + public function isDevMode(): bool + { + return $this->devMode; + } + + /** + * Set the originating event. + * + * @return ?BaseEvent + */ + public function getOriginatingEvent(): ?BaseEvent + { + return $this->originatingEvent; + } + + /** + * Set the originating event. + * + * @return $this + */ + public function setOriginatingEvent(BaseEvent $event): self + { + $this->originatingEvent = $this->calculateOriginatingEvent($event); + + return $this; + } + + /** + * Returns the upper-most event in chain. + */ + private function calculateOriginatingEvent(BaseEvent $event): BaseEvent + { + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } + + return $event; + } +} diff --git a/vendor/composer/composer/src/Composer/Script/ScriptEvents.php b/vendor/composer/composer/src/Composer/Script/ScriptEvents.php new file mode 100644 index 0000000..1c40248 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Script/ScriptEvents.php @@ -0,0 +1,131 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +/** + * The Script Events. + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class ScriptEvents +{ + /** + * The PRE_INSTALL_CMD event occurs before the install command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_INSTALL_CMD = 'pre-install-cmd'; + + /** + * The POST_INSTALL_CMD event occurs after the install command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_INSTALL_CMD = 'post-install-cmd'; + + /** + * The PRE_UPDATE_CMD event occurs before the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_UPDATE_CMD = 'pre-update-cmd'; + + /** + * The POST_UPDATE_CMD event occurs after the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_UPDATE_CMD = 'post-update-cmd'; + + /** + * The PRE_STATUS_CMD event occurs before the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_STATUS_CMD = 'pre-status-cmd'; + + /** + * The POST_STATUS_CMD event occurs after the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_STATUS_CMD = 'post-status-cmd'; + + /** + * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump'; + + /** + * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_AUTOLOAD_DUMP = 'post-autoload-dump'; + + /** + * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install'; + + /** + * The POST_CREATE_PROJECT event occurs after the create-project command has been executed. + * Note: Event occurs after POST_INSTALL_CMD + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd'; + + /** + * The PRE_ARCHIVE_CMD event occurs before the update command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const PRE_ARCHIVE_CMD = 'pre-archive-cmd'; + + /** + * The POST_ARCHIVE_CMD event occurs after the status command is executed. + * + * The event listener method receives a Composer\Script\Event instance. + * + * @var string + */ + public const POST_ARCHIVE_CMD = 'post-archive-cmd'; +} diff --git a/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php b/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php new file mode 100644 index 0000000..595ffa7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/SelfUpdate/Keys.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\SelfUpdate; + +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Keys +{ + public static function fingerprint(string $path): string + { + $hash = strtoupper(hash('sha256', Preg::replace('{\s}', '', file_get_contents($path)))); + + return implode(' ', [ + substr($hash, 0, 8), + substr($hash, 8, 8), + substr($hash, 16, 8), + substr($hash, 24, 8), + '', // Extra space + substr($hash, 32, 8), + substr($hash, 40, 8), + substr($hash, 48, 8), + substr($hash, 56, 8), + ]); + } +} diff --git a/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php b/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php new file mode 100644 index 0000000..8cc7d45 --- /dev/null +++ b/vendor/composer/composer/src/Composer/SelfUpdate/Versions.php @@ -0,0 +1,117 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\SelfUpdate; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; +use Composer\Config; + +/** + * @author Jordi Boggiano + */ +class Versions +{ + /** + * @var string[] + * @deprecated use Versions::CHANNELS + */ + public static $channels = self::CHANNELS; + + public const CHANNELS = ['stable', 'preview', 'snapshot', '1', '2', '2.2']; + + /** @var HttpDownloader */ + private $httpDownloader; + /** @var Config */ + private $config; + /** @var string */ + private $channel; + /** @var array>|null */ + private $versionsData = null; + + public function __construct(Config $config, HttpDownloader $httpDownloader) + { + $this->httpDownloader = $httpDownloader; + $this->config = $config; + } + + public function getChannel(): string + { + if ($this->channel) { + return $this->channel; + } + + $channelFile = $this->config->get('home').'/update-channel'; + if (file_exists($channelFile)) { + $channel = trim(file_get_contents($channelFile)); + if (in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], true)) { + return $this->channel = $channel; + } + } + + return $this->channel = 'stable'; + } + + public function setChannel(string $channel, ?IOInterface $io = null): void + { + if (!in_array($channel, self::CHANNELS, true)) { + throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::CHANNELS)); + } + + $channelFile = $this->config->get('home').'/update-channel'; + $this->channel = $channel; + + // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned + $storedChannel = Preg::isMatch('{^\d+$}D', $channel) ? 'stable' : $channel; + $previouslyStored = file_exists($channelFile) ? trim((string) file_get_contents($channelFile)) : null; + file_put_contents($channelFile, $storedChannel.PHP_EOL); + + if ($io !== null && $previouslyStored !== $storedChannel) { + $io->writeError('Storing "'.$storedChannel.'" as default update channel for the next self-update run.'); + } + } + + /** + * @return array{path: string, version: string, min-php: int, eol?: true} + */ + public function getLatest(?string $channel = null): array + { + $versions = $this->getVersionsData(); + + foreach ($versions[$channel ?: $this->getChannel()] as $version) { + if ($version['min-php'] <= \PHP_VERSION_ID) { + return $version; + } + } + + throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); + } + + /** + * @return array> + */ + private function getVersionsData(): array + { + if (null === $this->versionsData) { + if ($this->config->get('disable-tls') === true) { + $protocol = 'http'; + } else { + $protocol = 'https'; + } + + $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson(); + } + + return $this->versionsData; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/AuthHelper.php b/vendor/composer/composer/src/Composer/Util/AuthHelper.php new file mode 100644 index 0000000..3f03126 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/AuthHelper.php @@ -0,0 +1,308 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class AuthHelper +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var array Map of origins to message displayed */ + private $displayedOriginAuthentications = []; + /** @var array Map of URLs and whether they already retried with authentication from Bitbucket */ + private $bitbucketRetry = []; + + public function __construct(IOInterface $io, Config $config) + { + $this->io = $io; + $this->config = $config; + } + + /** + * @param 'prompt'|bool $storeAuth + */ + public function storeAuth(string $origin, $storeAuth): void + { + $store = false; + $configSource = $this->config->getAuthConfigSource(); + if ($storeAuth === true) { + $store = $configSource; + } elseif ($storeAuth === 'prompt') { + $answer = $this->io->askAndValidate( + 'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ', + static function ($value): string { + $input = strtolower(substr(trim($value), 0, 1)); + if (in_array($input, ['y','n'])) { + return $input; + } + throw new \RuntimeException('Please answer (y)es or (n)o'); + }, + null, + 'y' + ); + + if ($answer === 'y') { + $store = $configSource; + } + } + if ($store) { + $store->addConfigSetting( + 'http-basic.'.$origin, + $this->io->getAuthentication($origin) + ); + } + } + + /** + * @param int $statusCode HTTP status code that triggered this call + * @param string|null $reason a message/description explaining why this was called + * @param string[] $headers + * @param int $retryCount the amount of retries already done on this URL + * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be + * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json + * @phpstan-return array{retry: bool, storeAuth: 'prompt'|bool} + */ + public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0): array + { + $storeAuth = false; + + if (in_array($origin, $this->config->get('github-domains'), true)) { + $gitHubUtil = new GitHub($this->io, $this->config, null); + $message = "\n"; + + $rateLimited = $gitHubUtil->isRateLimited($headers); + $requiresSso = $gitHubUtil->requiresSso($headers); + + if ($requiresSso) { + $ssoUrl = $gitHubUtil->getSsoUrl($headers); + $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . $ssoUrl . "\n"; + $this->io->writeError($message); + if (!$this->io->isInteractive()) { + throw new TransportException('Could not authenticate against ' . $origin, 403); + } + $this->io->ask('After authorizing your token, confirm that you would like to retry the request'); + + return ['retry' => true, 'storeAuth' => $storeAuth]; + } + + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($headers); + if ($this->io->hasAuthentication($origin)) { + $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; + } else { + $message = 'Create a GitHub OAuth token to go over the API rate limit.'; + } + + $message = sprintf( + 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.', + $rateLimit['limit'], + $rateLimit['reset'] + )."\n"; + } else { + $message .= 'Could not fetch '.$url.', please '; + if ($this->io->hasAuthentication($origin)) { + $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; + } else { + $message .= 'create a GitHub OAuth token to access private repos'; + } + } + + if (!$gitHubUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { + $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); + $gitLabUtil = new GitLab($this->io, $this->config, null); + + $auth = null; + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); + } + } + + if (!$gitLabUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + + if ($auth !== null && $this->io->hasAuthentication($origin)) { + if ($auth === $this->io->getAuthentication($origin)) { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); + } + } + } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') { + $askForOAuthToken = true; + $origin = 'bitbucket.org'; + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if ($auth['username'] !== 'x-token-auth') { + $bitbucketUtil = new Bitbucket($this->io, $this->config); + $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']); + if (!empty($accessToken)) { + $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); + $askForOAuthToken = false; + } + } elseif (!isset($this->bitbucketRetry[$url])) { + // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others + // reach the code path and without this fallback they would end up throwing below + // see https://github.com/composer/composer/pull/11464 for more details + $askForOAuthToken = false; + $this->bitbucketRetry[$url] = true; + } else { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + + if ($askForOAuthToken) { + $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit'); + $bitBucketUtil = new Bitbucket($this->io, $this->config); + if (!$bitBucketUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + } else { + // 404s are only handled for github + if ($statusCode === 404) { + return ['retry' => false, 'storeAuth' => false]; + } + + // fail if the console is not interactive + if (!$this->io->isInteractive()) { + if ($statusCode === 401) { + $message = "The '" . $url . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate"; + } elseif ($statusCode === 403) { + $message = "The '" . $url . "' URL could not be accessed (HTTP 403): " . $reason; + } else { + $message = "Unknown error code '" . $statusCode . "', reason: " . $reason; + } + + throw new TransportException($message, $statusCode); + } + + // fail if we already have auth + if ($this->io->hasAuthentication($origin)) { + // if two or more requests are started together for the same host, and the first + // received authentication already, we let the others retry before failing them + if ($retryCount === 0) { + return ['retry' => true, 'storeAuth' => false]; + } + + throw new TransportException("Invalid credentials (HTTP $statusCode) for '$url', aborting.", $statusCode); + } + + $this->io->writeError(' Authentication required ('.$origin.'):'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthentication($origin, $username, $password); + $storeAuth = $this->config->get('store-auths'); + } + + return ['retry' => true, 'storeAuth' => $storeAuth]; + } + + /** + * @param string[] $headers + * + * @return string[] updated headers array + */ + public function addAuthenticationHeader(array $headers, string $origin, string $url): array + { + if ($this->io->hasAuthentication($origin)) { + $authenticationDisplayMessage = null; + $auth = $this->io->getAuthentication($origin); + if ($auth['password'] === 'bearer') { + $headers[] = 'Authorization: Bearer '.$auth['username']; + } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { + // only add the access_token if it is actually a github API URL + if (Preg::isMatch('{^https?://api\.github\.com/}', $url)) { + $headers[] = 'Authorization: token '.$auth['username']; + $authenticationDisplayMessage = 'Using GitHub token authentication'; + } + } elseif ( + in_array($origin, $this->config->get('gitlab-domains'), true) + && in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], true) + ) { + if ($auth['password'] === 'oauth2') { + $headers[] = 'Authorization: Bearer '.$auth['username']; + $authenticationDisplayMessage = 'Using GitLab OAuth token authentication'; + } else { + $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; + $authenticationDisplayMessage = 'Using GitLab private token authentication'; + } + } elseif ( + 'bitbucket.org' === $origin + && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL + && 'x-token-auth' === $auth['username'] + ) { + if (!$this->isPublicBitBucketDownload($url)) { + $headers[] = 'Authorization: Bearer ' . $auth['password']; + $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication'; + } + } else { + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $headers[] = 'Authorization: Basic '.$authStr; + $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"'; + } + + if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) { + $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG); + $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage; + } + } elseif (in_array($origin, ['api.bitbucket.org', 'api.github.com'], true)) { + return $this->addAuthenticationHeader($headers, str_replace('api.', '', $origin), $url); + } + + return $headers; + } + + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + public function isPublicBitBucketDownload(string $urlToBitBucketFile): bool + { + $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); + if (strpos($domain, 'bitbucket.org') === false) { + // Bitbucket downloads are hosted on amazonaws. + // We do not need to authenticate there at all + return true; + } + + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + + return count($pathParts) >= 4 && $pathParts[3] === 'downloads'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Bitbucket.php b/vendor/composer/composer/src/Composer/Util/Bitbucket.php new file mode 100644 index 0000000..15743a7 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Bitbucket.php @@ -0,0 +1,257 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Downloader\TransportException; + +/** + * @author Paul Wenke + */ +class Bitbucket +{ + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var ProcessExecutor */ + private $process; + /** @var HttpDownloader */ + private $httpDownloader; + /** @var array{access_token: string, expires_in?: int}|null */ + private $token = null; + /** @var int|null */ + private $time; + + public const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + * @param int $time Timestamp, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null, ?int $time = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + $this->time = $time; + } + + public function getToken(): string + { + if (!isset($this->token['access_token'])) { + return ''; + } + + return $this->token['access_token']; + } + + /** + * Attempts to authorize a Bitbucket domain via OAuth + * + * @param string $originUrl The host this Bitbucket instance is located at + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + if ($originUrl !== 'bitbucket.org') { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output)); + + return true; + } + + return false; + } + + private function requestAccessToken(): bool + { + try { + $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'content' => 'grant_type=client_credentials', + ], + ]); + + $token = $response->decodeJson(); + if (!isset($token['expires_in']) || !isset($token['access_token'])) { + throw new \LogicException('Expected a token configured with expires_in and access_token present, got '.json_encode($token)); + } + + $this->token = $token; + } catch (TransportException $e) { + if ($e->getCode() === 400) { + $this->io->writeError('Invalid OAuth consumer provided.'); + $this->io->writeError('This can have three reasons:'); + $this->io->writeError('1. You are authenticating with a bitbucket username/password combination'); + $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'); + $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer'); + + return false; + } + if (in_array($e->getCode(), [403, 401])) { + $this->io->writeError('Invalid OAuth consumer provided.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + throw $e; + } + + return true; + } + + /** + * Authorizes a Bitbucket domain interactively via OAuth + * + * @param string $originUrl The host this Bitbucket instance is located at + * @param string $message The reason this authorization is required + * @throws \RuntimeException + * @throws TransportException|\Exception + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/'; + $this->io->writeError('Follow the instructions here:'); + $this->io->writeError($url); + $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $consumerKey = trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): ')); + + if (!$consumerKey) { + $this->io->writeError('No consumer key given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + $consumerSecret = trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): ')); + + if (!$consumerSecret) { + $this->io->writeError('No consumer secret given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); + + return false; + } + + $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); + + if (!$this->requestAccessToken()) { + return false; + } + + // store value in user config + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret); + + // Remove conflicting basic auth credentials (if available) + $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); + + $this->io->writeError('Consumer stored successfully.'); + + return true; + } + + /** + * Retrieves an access token from Bitbucket. + */ + public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret): string + { + if ($this->token !== null || $this->getTokenFromConfig($originUrl)) { + return $this->token['access_token']; + } + + $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); + if (!$this->requestAccessToken()) { + return ''; + } + + $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret); + + if (!isset($this->token['access_token'])) { + throw new \LogicException('Failed to initialize token above'); + } + + return $this->token['access_token']; + } + + /** + * Store the new/updated credentials to the configuration + */ + private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret): void + { + $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); + + if (null === $this->token || !isset($this->token['expires_in'])) { + throw new \LogicException('Expected a token configured with expires_in present, got '.json_encode($this->token)); + } + + $time = null === $this->time ? time() : $this->time; + $consumer = [ + "consumer-key" => $consumerKey, + "consumer-secret" => $consumerSecret, + "access-token" => $this->token['access_token'], + "access-token-expiration" => $time + $this->token['expires_in'], + ]; + + $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); + } + + /** + * @phpstan-assert-if-true array{access_token: string} $this->token + */ + private function getTokenFromConfig(string $originUrl): bool + { + $authConfig = $this->config->get('bitbucket-oauth'); + + if ( + !isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration']) + || time() > $authConfig[$originUrl]['access-token-expiration'] + ) { + return false; + } + + $this->token = [ + 'access_token' => $authConfig[$originUrl]['access-token'], + ]; + + return true; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ComposerMirror.php b/vendor/composer/composer/src/Composer/Util/ComposerMirror.php new file mode 100644 index 0000000..6be5396 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ComposerMirror.php @@ -0,0 +1,77 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; + +/** + * Composer mirror utilities + * + * @author Jordi Boggiano + */ +class ComposerMirror +{ + /** + * @param non-empty-string $mirrorUrl + * @return non-empty-string + */ + public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null): string + { + if ($reference) { + $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : hash('md5', $reference); + } + $version = strpos($version, '/') === false ? $version : hash('md5', $version); + + $from = ['%package%', '%version%', '%reference%', '%type%']; + $to = [$packageName, $version, $reference, $type]; + if (null !== $prettyVersion) { + $from[] = '%prettyVersion%'; + $to[] = $prettyVersion; + } + + $url = str_replace($from, $to, $mirrorUrl); + assert($url !== ''); + + return $url; + } + + /** + * @param non-empty-string $mirrorUrl + * @return string + */ + public static function processGitUrl(string $mirrorUrl, string $packageName, string $url, ?string $type): string + { + if (Preg::isMatch('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) { + $url = 'gh-'.$match[1].'/'.$match[2]; + } elseif (Preg::isMatch('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) { + $url = 'bb-'.$match[1].'/'.$match[2]; + } else { + $url = Preg::replace('{[^a-z0-9_.-]}i', '-', trim($url, '/')); + } + + return str_replace( + ['%package%', '%normalizedUrl%', '%type%'], + [$packageName, $url, $type], + $mirrorUrl + ); + } + + /** + * @param non-empty-string $mirrorUrl + * @return string + */ + public static function processHgUrl(string $mirrorUrl, string $packageName, string $url, string $type): string + { + return self::processGitUrl($mirrorUrl, $packageName, $url, $type); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ConfigValidator.php b/vendor/composer/composer/src/Composer/Util/ConfigValidator.php new file mode 100644 index 0000000..ac57199 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ConfigValidator.php @@ -0,0 +1,235 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\ValidatingArrayLoader; +use Composer\Package\Loader\InvalidPackageException; +use Composer\Json\JsonValidationException; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Spdx\SpdxLicenses; +use Seld\JsonLint\DuplicateKeyException; +use Seld\JsonLint\JsonParser; + +/** + * Validates a composer configuration. + * + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ConfigValidator +{ + public const CHECK_VERSION = 1; + + /** @var IOInterface */ + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * Validates the config, and returns the result. + * + * @param string $file The path to the file + * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation + * @param int $flags Flags for validation + * + * @return array{list, list, list} a triple containing the errors, publishable errors, and warnings + */ + public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION): array + { + $errors = []; + $publishErrors = []; + $warnings = []; + + // validate json schema + $laxValid = false; + $manifest = null; + try { + $json = new JsonFile($file, null, $this->io); + $manifest = $json->read(); + + $json->validateSchema(JsonFile::LAX_SCHEMA); + $laxValid = true; + $json->validateSchema(); + } catch (JsonValidationException $e) { + foreach ($e->getErrors() as $message) { + if ($laxValid) { + $publishErrors[] = $message; + } else { + $errors[] = $message; + } + } + } catch (\Exception $e) { + $errors[] = $e->getMessage(); + + return [$errors, $publishErrors, $warnings]; + } + + if (is_array($manifest)) { + $jsonParser = new JsonParser(); + try { + $jsonParser->parse((string) file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS); + } catch (DuplicateKeyException $e) { + $details = $e->getDetails(); + $warnings[] = 'Key '.$details['key'].' is a duplicate in '.$file.' at line '.$details['line']; + } + } + + // validate actual data + if (empty($manifest['license'])) { + $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; + } else { + $licenses = (array) $manifest['license']; + + // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer + foreach ($licenses as $key => $license) { + if ('proprietary' === $license) { + unset($licenses[$key]); + } + } + + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); + if ($spdxLicense && $spdxLicense[3]) { + if (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', + $license + ); + } elseif (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', + $license + ); + } else { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', + $license + ); + } + } + } + } + + if (($flags & self::CHECK_VERSION) && isset($manifest['version'])) { + $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; + } + + if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) { + $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']); + $suggestName = strtolower($suggestName); + + $publishErrors[] = sprintf( + 'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', + $manifest['name'], + $suggestName + ); + } + + if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') { + $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; + } + + // check for require-dev overrides + if (isset($manifest['require'], $manifest['require-dev'])) { + $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']); + + if (!empty($requireOverrides)) { + $plural = (count($requireOverrides) > 1) ? 'are' : 'is'; + $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; + } + } + + // check for meaningless provide/replace satisfying requirements + foreach (['provide', 'replace'] as $linkType) { + if (isset($manifest[$linkType])) { + foreach (['require', 'require-dev'] as $requireType) { + if (isset($manifest[$requireType])) { + foreach ($manifest[$linkType] as $provide => $constraint) { + if (isset($manifest[$requireType][$provide])) { + $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.'; + } + } + } + } + } + } + + // check for commit references + $require = $manifest['require'] ?? []; + $requireDev = $manifest['require-dev'] ?? []; + $packages = array_merge($require, $requireDev); + foreach ($packages as $package => $version) { + if (Preg::isMatch('/#/', $version)) { + $warnings[] = sprintf( + 'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', + $package + ); + } + } + + // report scripts-descriptions for non-existent scripts + $scriptsDescriptions = $manifest['scripts-descriptions'] ?? []; + $scripts = $manifest['scripts'] ?? []; + foreach ($scriptsDescriptions as $scriptName => $scriptDescription) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Description for non-existent script "%s" found in "scripts-descriptions"', + $scriptName + ); + } + } + + // report scripts-aliases for non-existent scripts + $scriptAliases = $manifest['scripts-aliases'] ?? []; + foreach ($scriptAliases as $scriptName => $scriptAlias) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Aliases for non-existent script "%s" found in "scripts-aliases"', + $scriptName + ); + } + } + + // check for empty psr-0/psr-4 namespace prefixes + if (isset($manifest['autoload']['psr-0'][''])) { + $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; + } + if (isset($manifest['autoload']['psr-4'][''])) { + $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; + } + + $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); + try { + if (!isset($manifest['version'])) { + $manifest['version'] = '1.0.0'; + } + if (!isset($manifest['name'])) { + $manifest['name'] = 'dummy/dummy'; + } + $loader->load($manifest); + } catch (InvalidPackageException $e) { + $errors = array_merge($errors, $e->getErrors()); + } + + $warnings = array_merge($warnings, $loader->getWarnings()); + + return [$errors, $publishErrors, $warnings]; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ErrorHandler.php b/vendor/composer/composer/src/Composer/Util/ErrorHandler.php new file mode 100644 index 0000000..b99479c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ErrorHandler.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; + +/** + * Convert PHP errors into exceptions + * + * @author Artem Lopata + */ +class ErrorHandler +{ + /** @var ?IOInterface */ + private static $io; + + /** @var int<0, 2> */ + private static $hasShownDeprecationNotice = 0; + + /** + * Error handler + * + * @param int $level Level of the error raised + * @param string $message Error message + * @param string $file Filename that the error was raised in + * @param int $line Line number the error was raised at + * + * @static + * @throws \ErrorException + */ + public static function handle(int $level, string $message, string $file, int $line): bool + { + $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED; + + // error code is not included in error_reporting + if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) { + return true; + } + + if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) { + $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be". + "\na legitimately suppressed error that you were not supposed to see."; + } + + if (!$isDeprecationNotice) { + // ignore some newly introduced warnings in new php versions until dependencies + // can be fixed as we do not want to abort execution for those + if (in_array($level, [E_WARNING, E_USER_WARNING], true) && str_contains($message, 'should either be used or intentionally ignored by casting it as (void)')) { + self::outputWarning('Ignored new PHP warning but it should be reported and fixed: '.$message.' in '.$file.':'.$line, true); + return true; + } + + throw new \ErrorException($message, 0, $level, $file, $line); + } + + if (self::$io !== null) { + if (self::$hasShownDeprecationNotice > 0 && !self::$io->isVerbose()) { + if (self::$hasShownDeprecationNotice === 1) { + self::$io->writeError('More deprecation notices were hidden, run again with `-v` to show them.'); + self::$hasShownDeprecationNotice = 2; + } + return true; + } + self::$hasShownDeprecationNotice = 1; + self::outputWarning('Deprecation Notice: '.$message.' in '.$file.':'.$line); + } + + return true; + } + + /** + * Register error handler. + */ + public static function register(?IOInterface $io = null): void + { + set_error_handler([__CLASS__, 'handle']); + error_reporting(E_ALL); + self::$io = $io; + } + + private static function outputWarning(string $message, bool $outputEvenWithoutIO = false): void + { + if (self::$io !== null) { + self::$io->writeError(''.$message.''); + if (self::$io->isVerbose()) { + self::$io->writeError('Stack trace:'); + self::$io->writeError(array_filter(array_map(static function ($a): ?string { + if (isset($a['line'], $a['file'])) { + return ' '.$a['file'].':'.$a['line'].''; + } + + return null; + }, array_slice(debug_backtrace(), 2)), function (?string $line) { + return $line !== null; + })); + } + + return; + } + + if ($outputEvenWithoutIO) { + if (defined('STDERR') && is_resource(STDERR)) { + fwrite(STDERR, 'Warning: '.$message.PHP_EOL); + } else { + echo 'Warning: '.$message.PHP_EOL; + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Filesystem.php b/vendor/composer/composer/src/Composer/Util/Filesystem.php new file mode 100644 index 0000000..57e4fd6 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Filesystem.php @@ -0,0 +1,968 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; +use ErrorException; +use React\Promise\PromiseInterface; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Finder\Finder; + +/** + * @author Jordi Boggiano + * @author Johannes M. Schmitt + */ +class Filesystem +{ + /** @var ?ProcessExecutor */ + private $processExecutor; + + public function __construct(?ProcessExecutor $executor = null) + { + $this->processExecutor = $executor; + } + + /** + * @return bool + */ + public function remove(string $file) + { + if (is_dir($file)) { + return $this->removeDirectory($file); + } + + if (file_exists($file)) { + return $this->unlink($file); + } + + return false; + } + + /** + * Checks if a directory is empty + * + * @return bool + */ + public function isDirEmpty(string $dir) + { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->depth(0) + ->in($dir); + + return \count($finder) === 0; + } + + /** + * @return void + */ + public function emptyDirectory(string $dir, bool $ensureDirectoryExists = true) + { + if (is_link($dir) && file_exists($dir)) { + $this->unlink($dir); + } + + if ($ensureDirectoryExists) { + $this->ensureDirectoryExists($dir); + } + + if (is_dir($dir)) { + $finder = Finder::create() + ->ignoreVCS(false) + ->ignoreDotFiles(false) + ->depth(0) + ->in($dir); + + foreach ($finder as $path) { + $this->remove((string) $path); + } + } + } + + /** + * Recursively remove a directory + * + * Uses the process component if proc_open is enabled on the PHP + * installation. + * + * @throws \RuntimeException + * @return bool + */ + public function removeDirectory(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory); + if ($edgeCaseResult !== null) { + return $edgeCaseResult; + } + + if (Platform::isWindows()) { + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; + } else { + $cmd = ['rm', '-rf', $directory]; + } + + $result = $this->getProcess()->execute($cmd, $output) === 0; + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if ($result && !is_dir($directory)) { + return true; + } + + return $this->removeDirectoryPhp($directory); + } + + /** + * Recursively remove a directory asynchronously + * + * Uses the process component if proc_open is enabled on the PHP + * installation. + * + * @throws \RuntimeException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function removeDirectoryAsync(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory); + if ($edgeCaseResult !== null) { + return \React\Promise\resolve($edgeCaseResult); + } + + if (Platform::isWindows()) { + $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; + } else { + $cmd = ['rm', '-rf', $directory]; + } + + $promise = $this->getProcess()->executeAsync($cmd); + + return $promise->then(function ($process) use ($directory) { + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if ($process->isSuccessful()) { + if (!is_dir($directory)) { + return \React\Promise\resolve(true); + } + } + + return \React\Promise\resolve($this->removeDirectoryPhp($directory)); + }); + } + + /** + * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful + */ + private function removeEdgeCases(string $directory, bool $fallbackToPhp = true): ?bool + { + if ($this->isSymlinkedDirectory($directory)) { + return $this->unlinkSymlinkedDirectory($directory); + } + + if ($this->isJunction($directory)) { + return $this->removeJunction($directory); + } + + if (is_link($directory)) { + return unlink($directory); + } + + if (!is_dir($directory) || !file_exists($directory)) { + return true; + } + + if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) { + throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.'); + } + + if (!\function_exists('proc_open') && $fallbackToPhp) { + return $this->removeDirectoryPhp($directory); + } + + return null; + } + + /** + * Recursively delete directory using PHP iterators. + * + * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files + * before directories, creating a single non-recursive loop + * to delete files/directories in the correct order. + * + * @return bool + */ + public function removeDirectoryPhp(string $directory) + { + $edgeCaseResult = $this->removeEdgeCases($directory, false); + if ($edgeCaseResult !== null) { + return $edgeCaseResult; + } + + try { + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + // re-try once after clearing the stat cache if it failed as it + // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009 + clearstatcache(); + usleep(100000); + if (!is_dir($directory)) { + return true; + } + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + } + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($ri as $file) { + if ($file->isDir()) { + $this->rmdir($file->getPathname()); + } else { + $this->unlink($file->getPathname()); + } + } + + // release locks on the directory, see https://github.com/composer/composer/issues/9945 + unset($ri, $it, $file); + + return $this->rmdir($directory); + } + + /** + * @return void + */ + public function ensureDirectoryExists(string $directory) + { + if (!is_dir($directory)) { + if (file_exists($directory)) { + throw new \RuntimeException( + $directory.' exists and is not a directory.' + ); + } + + if (is_link($directory) && !@$this->unlinkImplementation($directory)) { + throw new \RuntimeException('Could not delete symbolic link '.$directory.': '.(error_get_last()['message'] ?? '')); + } + + if (!@mkdir($directory, 0777, true)) { + $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? '')); + + // in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo + // but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we + // report the initial error we had with the original path, and ignore the normalized path exception + // see https://github.com/composer/composer/issues/11864 + $normalized = $this->normalizePath($directory); + if ($normalized !== $directory) { + try { + $this->ensureDirectoryExists($normalized); + return; + } catch (\Throwable $ignoredEx) {} + } + + throw $e; + } + } + } + + /** + * Attempts to unlink a file and in case of failure retries after 350ms on windows + * + * @throws \RuntimeException + * @return bool + */ + public function unlink(string $path) + { + $unlinked = @$this->unlinkImplementation($path); + if (!$unlinked) { + // retry after a bit on windows since it tends to be touchy with mass removals + if (Platform::isWindows()) { + usleep(350000); + $unlinked = @$this->unlinkImplementation($path); + } + + if (!$unlinked) { + $error = error_get_last(); + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); + if (Platform::isWindows()) { + $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; + } + + throw new \RuntimeException($message); + } + } + + return true; + } + + /** + * Attempts to rmdir a file and in case of failure retries after 350ms on windows + * + * @throws \RuntimeException + * @return bool + */ + public function rmdir(string $path) + { + $deleted = @rmdir($path); + if (!$deleted) { + // retry after a bit on windows since it tends to be touchy with mass removals + if (Platform::isWindows()) { + usleep(350000); + $deleted = @rmdir($path); + } + + if (!$deleted) { + $error = error_get_last(); + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); + if (Platform::isWindows()) { + $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; + } + + throw new \RuntimeException($message); + } + } + + return true; + } + + /** + * Copy then delete is a non-atomic version of {@link rename}. + * + * Some systems can't rename and also don't have proc_open, + * which requires this solution. + * + * @return void + */ + public function copyThenRemove(string $source, string $target) + { + $this->copy($source, $target); + if (!is_dir($source)) { + $this->unlink($source); + + return; + } + + $this->removeDirectoryPhp($source); + } + + /** + * Copies a file or directory from $source to $target. + * + * @return bool + */ + public function copy(string $source, string $target) + { + // refs https://github.com/composer/composer/issues/11864 + $target = $this->normalizePath($target); + + if (!is_dir($source)) { + try { + return copy($source, $target); + } catch (ErrorException $e) { + // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders + // see https://github.com/composer/composer/issues/12057 + if (str_contains($e->getMessage(), 'Bad address')) { + $sourceHandle = fopen($source, 'r'); + $targetHandle = fopen($target, 'w'); + if (false === $sourceHandle || false === $targetHandle) { + throw $e; + } + while (!feof($sourceHandle)) { + if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) { + throw $e; + } + } + fclose($sourceHandle); + fclose($targetHandle); + + return true; + } + throw $e; + } + } + + $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); + $this->ensureDirectoryExists($target); + + $result = true; + foreach ($ri as $file) { + $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); + if ($file->isDir()) { + $this->ensureDirectoryExists($targetPath); + } else { + $result = $result && copy($file->getPathname(), $targetPath); + } + } + + return $result; + } + + /** + * @return void + */ + public function rename(string $source, string $target) + { + if (true === @rename($source, $target)) { + return; + } + + if (!\function_exists('proc_open')) { + $this->copyThenRemove($source, $target); + + return; + } + + if (Platform::isWindows()) { + // Try to copy & delete - this is a workaround for random "Access denied" errors. + $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output); + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if (0 === $result) { + $this->remove($source); + + return; + } + } else { + // We do not use PHP's "rename" function here since it does not support + // the case where $source, and $target are located on different partitions. + $result = $this->getProcess()->execute(['mv', $source, $target], $output); + + // clear stat cache because external processes aren't tracked by the php stat cache + clearstatcache(); + + if (0 === $result) { + return; + } + } + + $this->copyThenRemove($source, $target); + } + + /** + * Returns the shortest path from $from to $to + * + * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer + * @throws \InvalidArgumentException + * @return string + */ + public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); + } + + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); + + if ($directories) { + $from = rtrim($from, '/') . '/dummy_file'; + } + + if (\dirname($from) === \dirname($to)) { + return './'.basename($to); + } + + $commonPath = $to; + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) { + $commonPath = strtr(\dirname($commonPath), '\\', '/'); + } + + // no commonality at all + if (0 !== strpos($from, $commonPath)) { + return $to; + } + + $commonPath = rtrim($commonPath, '/') . '/'; + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/'); + $commonPathCode = str_repeat('../', $sourcePathDepth); + + // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { + return $to; + } + + $result = $commonPathCode . substr($to, \strlen($commonPath)); + if (\strlen($result) === 0) { + return './'; + } + + return $result; + } + + /** + * Returns PHP code that, when executed in $from, will return the path to $to + * + * @param bool $directories if true, the source/target are considered to be directories + * @param bool $preferRelative if true, relative paths will be preferred even if longer + * @throws \InvalidArgumentException + * @return string + */ + public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); + } + + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); + + if ($from === $to) { + return $directories ? '__DIR__' : '__FILE__'; + } + + $commonPath = $to; + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) { + $commonPath = strtr(\dirname($commonPath), '\\', '/'); + } + + // no commonality at all + if (0 !== strpos($from, $commonPath) || '.' === $commonPath) { + return var_export($to, true); + } + + $commonPath = rtrim($commonPath, '/') . '/'; + if (str_starts_with($to, $from.'/')) { + return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true); + } + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories; + + // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups + if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { + return var_export($to, true); + } + + if ($staticCode) { + $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'"; + } else { + $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); + } + $relTarget = (string) substr($to, \strlen($commonPath)); + + return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : ''); + } + + /** + * Checks if the given path is absolute + * + * @return bool + */ + public function isAbsolutePath(string $path) + { + return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; + } + + /** + * Returns size of a file or directory specified by path. If a directory is + * given, its size will be computed recursively. + * + * @param string $path Path to the file or directory + * @throws \RuntimeException + * @return int + */ + public function size(string $path) + { + if (!file_exists($path)) { + throw new \RuntimeException("$path does not exist."); + } + if (is_dir($path)) { + return $this->directorySize($path); + } + + return (int) filesize($path); + } + + /** + * Normalize a path. This replaces backslashes with slashes, removes ending + * slash and collapses redundant separators and up-level references. + * + * @param string $path Path to the file or directory + * @return string + */ + public function normalizePath(string $path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = ''; + + // extract windows UNC paths e.g. \\foo\bar + if (strpos($path, '//') === 0 && \strlen($path) > 2) { + $absolute = '//'; + $path = substr($path, 2); + } + + // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive: + if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, \strlen($prefix)); + } + + if (strpos($path, '/') === 0) { + $absolute = '/'; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { + array_pop($parts); + $up = !(\count($parts) === 0 || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + // ensure c: is normalized to C: + $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) { + return strtoupper($m[0]); + }, $prefix); + + return $prefix.$absolute.implode('/', $parts); + } + + /** + * Remove trailing slashes if present to avoid issues with symlinks + * + * And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422 + * + * @return string + */ + public static function trimTrailingSlash(string $path) + { + if (!Preg::isMatch('{^[/\\\\]+$}', $path)) { + $path = rtrim($path, '/\\'); + } + + return $path; + } + + /** + * Return if the given path is local + * + * @return bool + */ + public static function isLocalPath(string $path) + { + // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe + // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path + if (Platform::isWindows()) { + return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + } + + return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); + } + + /** + * @return string + */ + public static function getPlatformPath(string $path) + { + if (Platform::isWindows()) { + $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); + } + + return Preg::replace('{^file://}i', '', $path); + } + + /** + * Cross-platform safe version of is_readable() + * + * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts + * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + * + * @return bool + */ + public static function isReadable(string $path) + { + if (is_readable($path)) { + return true; + } + + if (is_file($path)) { + return false !== Silencer::call('file_get_contents', $path, false, null, 0, 1); + } + + if (is_dir($path)) { + return false !== Silencer::call('opendir', $path); + } + + // assume false otherwise + return false; + } + + /** + * @return int + */ + protected function directorySize(string $directory) + { + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + $size = 0; + foreach ($ri as $file) { + if ($file->isFile()) { + $size += $file->getSize(); + } + } + + return $size; + } + + /** + * @return ProcessExecutor + */ + protected function getProcess() + { + if (null === $this->processExecutor) { + $this->processExecutor = new ProcessExecutor(); + } + + return $this->processExecutor; + } + + /** + * delete symbolic link implementation (commonly known as "unlink()") + * + * symbolic links on windows which link to directories need rmdir instead of unlink + */ + private function unlinkImplementation(string $path): bool + { + if (Platform::isWindows() && is_dir($path) && is_link($path)) { + return rmdir($path); + } + + return unlink($path); + } + + /** + * Creates a relative symlink from $link to $target + * + * @param string $target The path of the binary file to be symlinked + * @param string $link The path where the symlink should be created + * @return bool + */ + public function relativeSymlink(string $target, string $link) + { + if (!function_exists('symlink')) { + return false; + } + + $cwd = Platform::getCwd(); + + $relativePath = $this->findShortestPath($link, $target); + chdir(\dirname($link)); + $result = @symlink($relativePath, $link); + + chdir($cwd); + + return $result; + } + + /** + * return true if that directory is a symlink. + * + * @return bool + */ + public function isSymlinkedDirectory(string $directory) + { + if (!is_dir($directory)) { + return false; + } + + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return is_link($resolved); + } + + private function unlinkSymlinkedDirectory(string $directory): bool + { + $resolved = $this->resolveSymlinkedDirectorySymlink($directory); + + return $this->unlink($resolved); + } + + /** + * resolve pathname to symbolic link of a directory + * + * @param string $pathname directory path to resolve + * + * @return string resolved path to symbolic link or original pathname (unresolved) + */ + private function resolveSymlinkedDirectorySymlink(string $pathname): string + { + if (!is_dir($pathname)) { + return $pathname; + } + + $resolved = rtrim($pathname, '/'); + + if (0 === \strlen($resolved)) { + return $pathname; + } + + return $resolved; + } + + /** + * Creates an NTFS junction. + * + * @return void + */ + public function junction(string $target, string $junction) + { + if (!Platform::isWindows()) { + throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__)); + } + if (!is_dir($target)) { + throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); + } + + // Removing any previously junction to ensure clean execution. + if (!is_dir($junction) || $this->isJunction($junction)) { + @rmdir($junction); + } + + $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)]; + if ($this->getProcess()->execute($cmd, $output) !== 0) { + throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); + } + clearstatcache(true, $junction); + } + + /** + * Returns whether the target directory is a Windows NTFS Junction. + * + * We test if the path is a directory and not an ordinary link, then check + * that the mode value returned from lstat (which gives the status of the + * link itself) is not a directory, by replicating the POSIX S_ISDIR test. + * + * This logic works because PHP does not set the mode value for a junction, + * since there is no universal file type flag for it. Unfortunately an + * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a + * random value to be returned. See https://bugs.php.net/bug.php?id=77552 + * + * If this random value passes the S_ISDIR test, then a junction will not be + * detected and a recursive delete operation could lead to loss of data in + * the target directory. Note that Windows rmdir can handle this situation + * and will only delete the junction (from Windows 7 onwards). + * + * @param string $junction Path to check. + * @return bool + */ + public function isJunction(string $junction) + { + if (!Platform::isWindows()) { + return false; + } + + // Important to clear all caches first + clearstatcache(true, $junction); + + if (!is_dir($junction) || is_link($junction)) { + return false; + } + + $stat = lstat($junction); + + // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) + return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false; + } + + /** + * Removes a Windows NTFS junction. + * + * @return bool + */ + public function removeJunction(string $junction) + { + if (!Platform::isWindows()) { + return false; + } + $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR); + if (!$this->isJunction($junction)) { + throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); + } + + return $this->rmdir($junction); + } + + /** + * @return int|false + */ + public function filePutContentsIfModified(string $path, string $content) + { + $currentContent = Silencer::call('file_get_contents', $path); + if (false === $currentContent || $currentContent !== $content) { + return file_put_contents($path, $content); + } + + return 0; + } + + /** + * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 + */ + public function safeCopy(string $source, string $target): void + { + if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { + $sourceHandle = fopen($source, 'r'); + assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.'); + $targetHandle = fopen($target, 'w+'); + assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.'); + + stream_copy_to_stream($sourceHandle, $targetHandle); + fclose($sourceHandle); + fclose($targetHandle); + + touch($target, (int) filemtime($source), (int) fileatime($source)); + } + } + + /** + * compare 2 files + * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files + */ + private function filesAreEqual(string $a, string $b): bool + { + // Check if filesize is different + if (filesize($a) !== filesize($b)) { + return false; + } + + // Check if content is different + $aHandle = fopen($a, 'rb'); + assert($aHandle !== false, 'Could not open "'.$a.'" for reading.'); + $bHandle = fopen($b, 'rb'); + assert($bHandle !== false, 'Could not open "'.$b.'" for reading.'); + + $result = true; + while (!feof($aHandle)) { + if (fread($aHandle, 8192) !== fread($bHandle, 8192)) { + $result = false; + break; + } + } + + fclose($aHandle); + fclose($bHandle); + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Git.php b/vendor/composer/composer/src/Composer/Util/Git.php new file mode 100644 index 0000000..e340c1b --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Git.php @@ -0,0 +1,628 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Git +{ + /** @var string|false|null */ + private static $version = false; + + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var Filesystem */ + protected $filesystem; + /** @var HttpDownloader */ + protected $httpDownloader; + + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs) + { + $this->io = $io; + $this->config = $config; + $this->process = $process; + $this->filesystem = $fs; + } + + /** + * @param IOInterface|null $io If present, a warning is output there instead of throwing, so pass this in only for cases where this is a soft failure + */ + public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void + { + if (str_contains($output, 'fatal: detected dubious ownership')) { + $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output; + if ($io === null) { + throw new \RuntimeException($msg); + } + $io->writeError(''.$msg.''); + } + } + + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + $this->httpDownloader = $httpDownloader; + } + + /** + * Runs a set of commands using the $url or a variation of it (with auth, ssh, ..) + * + * Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job + * %sanitizedUrl% is also automatically replaced by the url without user/pass + * + * As soon as a single command fails it will halt, so assume the commands are run as && in bash + * + * @param non-empty-array> $commands + * @param mixed $commandOutput the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + */ + public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + { + $callables = []; + foreach ($commands as $cmd) { + $callables[] = static function (string $url) use ($cmd): array { + $map = [ + '%url%' => $url, + '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url), + ]; + + return array_map(static function ($value) use ($map): string { + return $map[$value] ?? $value; + }, $cmd); + }; + } + + // @phpstan-ignore method.deprecated + $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput); + } + + /** + * @param callable|array $commandCallable + * @param mixed $commandOutput the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + * @deprecated Use runCommands with placeholders instead of callbacks for simplicity + */ + public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void + { + $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable; + $lastCommand = ''; + + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + if ($initialClone) { + $origCwd = $cwd; + } + + $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) { + $collectOutputs = !is_callable($commandOutput); + $outputs = []; + + $status = 0; + $counter = 0; + foreach ($commandCallables as $callable) { + $lastCommand = $callable($url); + if ($collectOutputs) { + $outputs[] = ''; + $output = &$outputs[count($outputs) - 1]; + } else { + $output = &$commandOutput; + } + $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd); + if ($status !== 0) { + break; + } + $counter++; + } + + if ($collectOutputs) { + $commandOutput = implode('', $outputs); + } + + return $status; + }; + + if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { + throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); + } + + if (!$initialClone) { + // capture username/password from URL if there is one and we have no auth configured yet + $this->process->execute(['git', 'remote', '-v'], $output, $cwd); + if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) { + $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); + } + } + + $protocols = $this->config->get('github-protocols'); + // public github, autoswitch protocols + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { + $messages = []; + foreach ($protocols as $protocol) { + if ('ssh' === $protocol) { + $protoUrl = "git@" . $match[1] . ":" . $match[2]; + } else { + $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; + } + + if (0 === $runCommands($protoUrl)) { + return; + } + $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); + + if ($initialClone && isset($origCwd)) { + $this->filesystem->removeDirectory($origCwd); + } + } + + // failed to checkout, first check git accessibility + if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) { + $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); + } + } + + // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https + $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); + + $auth = null; + $credentials = []; + if ($bypassSshForGitHub || 0 !== $runCommands($url)) { + $errorMsg = $this->process->getErrorOutput(); + // private github repository without ssh key access, try https with auth + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match) + ) { + if (!$this->io->hasAuthentication($match[1])) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process); + $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; + + if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { + $gitHubUtil->authorizeOAuthInteractively($match[1], $message); + } + } + + if ($this->io->hasAuthentication($match[1])) { + $auth = $this->io->getAuthentication($match[1]); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + } elseif ( + Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match) + || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match) + ) { //bitbucket either through oauth or app password, with fallback to ssh. + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); + + $domain = $match[2]; + $repo_with_git_part = $match[3]; + if (!str_ends_with($repo_with_git_part, '.git')) { + $repo_with_git_part .= '.git'; + } + if (!$this->io->hasAuthentication($domain)) { + $message = 'Enter your Bitbucket credentials to access private repos'; + + if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) { + $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); + $accessToken = $bitbucketUtil->getToken(); + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); + } + } + + // First we try to authenticate with whatever we have stored. + // This will be successful if there is for example an app + // password in there. + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; + + if (0 === $runCommands($authUrl)) { + // Well if that succeeded on our first try, let's just + // take the win. + return; + } + + //We already have an access_token from a previous request. + if ($auth['username'] !== 'x-token-auth') { + $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']); + if (!empty($accessToken)) { + $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); + } + } + } + + if ($this->io->hasAuthentication($domain)) { + $auth = $this->io->getAuthentication($domain); + $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; + } + //Falling back to ssh + $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part; + $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); + if (0 === $runCommands($sshUrl)) { + return; + } + + $errorMsg = $this->process->getErrorOutput(); + } elseif ( + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match) + ) { + if ($match[1] === 'git') { + $match[1] = 'https'; + } + + if (!$this->io->hasAuthentication($match[2])) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process); + $message = 'Cloning failed, enter your GitLab credentials to access private repos'; + + if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) { + $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message); + } + } + + if ($this->io->hasAuthentication($match[2])) { + $auth = $this->io->getAuthentication($match[2]); + if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { + $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password + } else { + $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; + } + + if (0 === $runCommands($authUrl)) { + return; + } + + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate + if (str_contains($match[2], '@')) { + [$authParts, $match[2]] = explode('@', $match[2], 2); + } + + $storeAuth = false; + if ($this->io->hasAuthentication($match[2])) { + $auth = $this->io->getAuthentication($match[2]); + } elseif ($this->io->isInteractive()) { + $defaultUsername = null; + if (isset($authParts) && $authParts !== '') { + if (str_contains($authParts, ':')) { + [$defaultUsername, ] = explode(':', $authParts, 2); + } else { + $defaultUsername = $authParts; + } + } + + $this->io->writeError(' Authentication required (' . $match[2] . '):'); + $this->io->writeError('' . trim($errorMsg) . '', true, IOInterface::VERBOSE); + $auth = [ + 'username' => $this->io->ask(' Username: ', $defaultUsername), + 'password' => $this->io->askAndHideAnswer(' Password: '), + ]; + $storeAuth = $this->config->get('store-auths'); + } + + if (null !== $auth) { + $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; + + if (0 === $runCommands($authUrl)) { + $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); + $authHelper = new AuthHelper($this->io, $this->config); + $authHelper->storeAuth($match[2], $storeAuth); + + return; + } + + $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; + $errorMsg = $this->process->getErrorOutput(); + } + } + + if ($initialClone && isset($origCwd)) { + $this->filesystem->removeDirectory($origCwd); + } + + $lastCommand = implode(' ', $lastCommand); + if (count($credentials) > 0) { + $lastCommand = $this->maskCredentials($lastCommand, $credentials); + $errorMsg = $this->maskCredentials($errorMsg, $credentials); + } + $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url); + } + } + + public function syncMirror(string $url, string $dir): bool + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { + $this->io->writeError('Aborting git mirror sync of '.$url.' as network is disabled'); + + return false; + } + + // update the repo if it is a valid git repository + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { + try { + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'update', '--prune', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ['git', 'gc', '--auto'], + ]; + + $this->runCommands($commands, $url, $dir); + } catch (\Exception $e) { + $this->io->writeError('Sync mirror failed: ' . $e->getMessage() . '', true, IOInterface::DEBUG); + + return false; + } + + return true; + } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); + + // clean up directory and do a fresh clone into it + $this->filesystem->removeDirectory($dir); + + $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true); + + return true; + } + + public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool + { + if ($this->checkRefIsInMirror($dir, $ref)) { + if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) { + $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); + $branches = null; + $tags = null; + if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) { + $branches = $output; + } + if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) { + $tags = $output; + } + + // if the pretty version cannot be found as a branch (nor branch with 'v' in front of the branch as it may have been stripped when generating pretty name), + // nor as a tag, then we sync the mirror as otherwise it will likely fail during install. + // this can occur if a git tag gets created *after* the reference is already put into the cache, as the ref check above will then not sync the new tags + // see https://github.com/composer/composer/discussions/11002 + if (null !== $branches && !Preg::isMatch('{^[\s*]*v?'.preg_quote($branch).'$}m', $branches) + && null !== $tags && !Preg::isMatch('{^[\s*]*'.preg_quote($branch).'$}m', $tags) + ) { + $this->syncMirror($url, $dir); + } + } + + return true; + } + + if ($this->syncMirror($url, $dir)) { + return $this->checkRefIsInMirror($dir, $ref); + } + + return false; + } + + public static function getNoShowSignatureFlag(ProcessExecutor $process): string + { + $gitVersion = self::getVersion($process); + if ($gitVersion && version_compare($gitVersion, '2.10.0-rc0', '>=')) { + return ' --no-show-signature'; + } + + return ''; + } + + /** + * @return list + */ + public static function getNoShowSignatureFlags(ProcessExecutor $process): array + { + $flags = static::getNoShowSignatureFlag($process); + if ('' === $flags) { + return []; + } + + return explode(' ', substr($flags, 1)); + } + + private function checkRefIsInMirror(string $dir, string $ref): bool + { + if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { + $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir); + if ($exitCode === 0) { + return true; + } + } + self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); + + return false; + } + + /** + * @return array|null + */ + private function getAuthenticationFailure(string $url): ?array + { + if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { + return null; + } + + $authFailures = [ + 'fatal: Authentication failed', + 'remote error: Invalid username or password.', + 'error: 401 Unauthorized', + 'fatal: unable to access', + 'fatal: could not read Username', + ]; + + $errorOutput = $this->process->getErrorOutput(); + foreach ($authFailures as $authFailure) { + if (strpos($errorOutput, $authFailure) !== false) { + return $match; + } + } + + return null; + } + + public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string + { + if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { + return null; + } + + try { + if ($isLocalPathRepository) { + $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir); + } else { + $commands = [ + ['git', 'remote', 'set-url', 'origin', '--', '%url%'], + ['git', 'remote', 'show', 'origin'], + ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], + ]; + + $this->runCommands($commands, $url, $dir, false, $output); + } + + $lines = $this->process->splitLines($output); + foreach ($lines as $line) { + if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) { + return $matches[1]; + } + } + } catch (\Exception $e) { + $this->io->writeError('Failed to fetch root identifier from remote: ' . $e->getMessage() . '', true, IOInterface::DEBUG); + } + + return null; + } + + public static function cleanEnv(): void + { + // added in git 1.7.1, prevents prompting the user for username/password + if (Platform::getEnv('GIT_ASKPASS') !== 'echo') { + Platform::putEnv('GIT_ASKPASS', 'echo'); + } + + // clean up rogue git env vars in case this is running in a git hook + if (Platform::getEnv('GIT_DIR')) { + Platform::clearEnv('GIT_DIR'); + } + if (Platform::getEnv('GIT_WORK_TREE')) { + Platform::clearEnv('GIT_WORK_TREE'); + } + + // Run processes with predictable LANGUAGE + if (Platform::getEnv('LANGUAGE') !== 'C') { + Platform::putEnv('LANGUAGE', 'C'); + } + + // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 + Platform::clearEnv('DYLD_LIBRARY_PATH'); + } + + /** + * @return non-empty-string + */ + public static function getGitHubDomainsRegex(Config $config): string + { + return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')'; + } + + /** + * @return non-empty-string + */ + public static function getGitLabDomainsRegex(Config $config): string + { + return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; + } + + /** + * @param non-empty-string $message + * + * @return never + */ + private function throwException($message, string $url): void + { + // git might delete a directory when it fails and php will not know + clearstatcache(); + + if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) { + throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); + } + + throw new \RuntimeException(Url::sanitize($message)); + } + + /** + * Retrieves the current git version. + * + * @return string|null The git version number, if present. + */ + public static function getVersion(ProcessExecutor $process): ?string + { + if (false === self::$version) { + self::$version = null; + if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { + self::$version = $matches[1]; + } + } + + return self::$version; + } + + /** + * @param string[] $credentials + */ + private function maskCredentials(string $error, array $credentials): string + { + $maskedCredentials = []; + + foreach ($credentials as $credential) { + if (in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) { + $maskedCredentials[] = $credential; + } elseif (strlen($credential) > 6) { + $maskedCredentials[] = substr($credential, 0, 3) . '...' . substr($credential, -3); + } elseif (strlen($credential) > 3) { + $maskedCredentials[] = substr($credential, 0, 3) . '...'; + } else { + $maskedCredentials[] = 'XXX'; + } + } + + return str_replace($credentials, $maskedCredentials, $error); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/GitHub.php b/vendor/composer/composer/src/Composer/Util/GitHub.php new file mode 100644 index 0000000..64ee4f5 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/GitHub.php @@ -0,0 +1,236 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Factory; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class GitHub +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + } + + /** + * Attempts to authorize a GitHub domain via OAuth + * + * @param string $originUrl The host this GitHub instance is located at + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + if (!in_array($originUrl, $this->config->get('github-domains'))) { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic'); + + return true; + } + + return false; + } + + /** + * Authorizes a GitHub domain interactively via OAuth + * + * @param string $originUrl The host this GitHub instance is located at + * @param string $message The reason this authorization is required + * @throws \RuntimeException + * @throws TransportException|\Exception + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $note = 'Composer'; + if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) { + $note .= ' on ' . trim($output); + } + $note .= ' ' . date('Y-m-d Hi'); + + $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=&description=' . str_replace('%20', '+', rawurlencode($note)); + $this->io->writeError('When working with _public_ GitHub repositories only, head here to retrieve a token:'); + $this->io->writeError($url); + $this->io->writeError('This token will have read-only permission for public information only.'); + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)); + $this->io->writeError('When you need to access _private_ GitHub repositories as well, go to:'); + $this->io->writeError($url); + $this->io->writeError('Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.'); + $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); + + if ($token === '') { + $this->io->writeError('No token given, aborting.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); + + return false; + } + + $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic'); + + try { + $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/'; + + $this->httpDownloader->get('https://'. $apiUrl, [ + 'retry-auth-failure' => false, + ]); + } catch (TransportException $e) { + if (in_array($e->getCode(), [403, 401])) { + $this->io->writeError('Invalid token provided.'); + $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); + + return false; + } + + throw $e; + } + + // store value in local/user config + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl); + $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token); + + $this->io->writeError('Token stored successfully.'); + + return true; + } + + /** + * Extract rate limit from response. + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + * + * @return array{limit: int|'?', reset: string} + */ + public function getRateLimit(array $headers): array + { + $rateLimit = [ + 'limit' => '?', + 'reset' => '?', + ]; + + foreach ($headers as $header) { + $header = trim($header); + if (false === stripos($header, 'x-ratelimit-')) { + continue; + } + [$type, $value] = explode(':', $header, 2); + switch (strtolower($type)) { + case 'x-ratelimit-limit': + $rateLimit['limit'] = (int) trim($value); + break; + case 'x-ratelimit-reset': + $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); + break; + } + } + + return $rateLimit; + } + + /** + * Extract SSO URL from response. + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function getSsoUrl(array $headers): ?string + { + foreach ($headers as $header) { + $header = trim($header); + if (false === stripos($header, 'x-github-sso: required')) { + continue; + } + if (Preg::isMatch('{\burl=(?P[^\s;]+)}', $header, $match)) { + return $match['url']; + } + } + + return null; + } + + /** + * Finds whether a request failed due to rate limiting + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function isRateLimited(array $headers): bool + { + foreach ($headers as $header) { + if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', trim($header))) { + return true; + } + } + + return false; + } + + /** + * Finds whether a request failed due to lacking SSO authorization + * + * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso + * + * @param string[] $headers Headers from Composer\Downloader\TransportException. + */ + public function requiresSso(array $headers): bool + { + foreach ($headers as $header) { + if (Preg::isMatch('{^x-github-sso: required}i', trim($header))) { + return true; + } + } + + return false; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/GitLab.php b/vendor/composer/composer/src/Composer/Util/GitLab.php new file mode 100644 index 0000000..b727dd9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/GitLab.php @@ -0,0 +1,319 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Factory; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; + +/** + * @author Roshan Gautam + */ +class GitLab +{ + /** @var IOInterface */ + protected $io; + /** @var Config */ + protected $config; + /** @var ProcessExecutor */ + protected $process; + /** @var HttpDownloader */ + protected $httpDownloader; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The composer configuration + * @param ProcessExecutor $process Process instance, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking + */ + public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null) + { + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + } + + /** + * Attempts to authorize a GitLab domain via OAuth. + * + * @param string $originUrl The host this GitLab instance is located at + * + * @return bool true on success + */ + public function authorizeOAuth(string $originUrl): bool + { + // before composer 1.9, origin URLs had no port number in them + $bcOriginUrl = Preg::replace('{:\d+}', '', $originUrl); + + if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { + return false; + } + + // if available use token from git config + if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) { + $this->io->setAuthentication($originUrl, trim($output), 'oauth2'); + + return true; + } + + // if available use deploy token from git config + if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) { + $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword)); + + return true; + } + + // if available use token from composer config + $authTokens = $this->config->get('gitlab-token'); + + if (isset($authTokens[$originUrl])) { + $token = $authTokens[$originUrl]; + } + + if (isset($authTokens[$bcOriginUrl])) { + $token = $authTokens[$bcOriginUrl]; + } + + if (isset($token)) { + $username = is_array($token) ? $token["username"] : $token; + $password = is_array($token) ? $token["token"] : 'private-token'; + + // Composer expects the GitLab token to be stored as username and 'private-token' or 'gitlab-ci-token' to be stored as password + // Detect cases where this is reversed and resolve automatically resolve it + if (in_array($username, ['private-token', 'gitlab-ci-token', 'oauth2'], true)) { + $this->io->setAuthentication($originUrl, $password, $username); + } else { + $this->io->setAuthentication($originUrl, $username, $password); + } + + return true; + } + + return false; + } + + /** + * Authorizes a GitLab domain interactively via OAuth. + * + * @param string $scheme Scheme used in the origin URL + * @param string $originUrl The host this GitLab instance is located at + * @param string $message The reason this authorization is required + * + * @throws \RuntimeException + * @throws TransportException|\Exception + * + * @return bool true on success + */ + public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null): bool + { + if ($message) { + $this->io->writeError($message); + } + + $localAuthConfig = $this->config->getLocalAuthConfigSource(); + $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens'; + $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications'; + $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('To revoke access to this token you can visit:'); + $this->io->writeError($revokeLink); + $this->io->writeError('Alternatively you can setup an personal access token on:'); + $this->io->writeError($personalAccessTokenLink); + $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); + $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token'); + $this->io->writeError('for more details.'); + + $storeInLocalAuthConfig = false; + if ($localAuthConfig !== null) { + $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); + } + + $attemptCounter = 0; + + while ($attemptCounter++ < 5) { + try { + $response = $this->createToken($scheme, $originUrl); + } catch (TransportException $e) { + // 401 is bad credentials, + // 403 is max login attempts exceeded + if (in_array($e->getCode(), [403, 401])) { + if (401 === $e->getCode()) { + $response = json_decode($e->getResponse(), true); + if (isset($response['error']) && $response['error'] === 'invalid_grant') { + $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token'); + } else { + $this->io->writeError('Bad credentials.'); + } + } else { + $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); + } + + $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:'); + $this->io->writeError($personalAccessTokenLink); + $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' "'); + + continue; + } + + throw $e; + } + + $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); + + $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); + // store value in user config in auth file + if (isset($response['expires_in'])) { + $authConfigSource->addConfigSetting( + 'gitlab-oauth.'.$originUrl, + [ + 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), + 'refresh-token' => $response['refresh_token'], + 'token' => $response['access_token'], + ] + ); + } else { + $authConfigSource->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']); + } + + return true; + } + + throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); + } + + /** + * Authorizes a GitLab domain interactively via OAuth. + * + * @param string $scheme Scheme used in the origin URL + * @param string $originUrl The host this GitLab instance is located at + * + * @throws \RuntimeException + * @throws TransportException|\Exception + * + * @return bool true on success + */ + public function authorizeOAuthRefresh(string $scheme, string $originUrl): bool + { + try { + $response = $this->refreshToken($scheme, $originUrl); + } catch (TransportException $e) { + $this->io->writeError("Couldn't refresh access token: ".$e->getMessage()); + + return false; + } + + $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); + + // store value in user config in auth file + $this->config->getAuthConfigSource()->addConfigSetting( + 'gitlab-oauth.'.$originUrl, + [ + 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), + 'refresh-token' => $response['refresh_token'], + 'token' => $response['access_token'], + ] + ); + + return true; + } + + /** + * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in?: positive-int, created_at: positive-int} + * + * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow + */ + private function createToken(string $scheme, string $originUrl): array + { + $username = $this->io->ask('Username: '); + $password = $this->io->askAndHideAnswer('Password: '); + + $headers = ['Content-Type: application/x-www-form-urlencoded']; + + $apiUrl = $originUrl; + $data = http_build_query([ + 'username' => $username, + 'password' => $password, + 'grant_type' => 'password', + ], '', '&'); + $options = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => $headers, + 'content' => $data, + ], + ]; + + $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson(); + + $this->io->writeError('Token successfully created'); + + return $token; + } + + /** + * Is the OAuth access token expired? + * + * @return bool true on expired token, false if token is fresh or expiration date is not set + */ + public function isOAuthExpired(string $originUrl): bool + { + $authTokens = $this->config->get('gitlab-oauth'); + if (isset($authTokens[$originUrl]['expires-at'])) { + if ($authTokens[$originUrl]['expires-at'] < time()) { + return true; + } + } + + return false; + } + + /** + * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int, created_at: positive-int} + * + * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow + */ + private function refreshToken(string $scheme, string $originUrl): array + { + $authTokens = $this->config->get('gitlab-oauth'); + if (!isset($authTokens[$originUrl]['refresh-token'])) { + throw new \RuntimeException('No GitLab refresh token present for '.$originUrl.'.'); + } + + $refreshToken = $authTokens[$originUrl]['refresh-token']; + $headers = ['Content-Type: application/x-www-form-urlencoded']; + + $data = http_build_query([ + 'refresh_token' => $refreshToken, + 'grant_type' => 'refresh_token', + ], '', '&'); + $options = [ + 'retry-auth-failure' => false, + 'http' => [ + 'method' => 'POST', + 'header' => $headers, + 'content' => $data, + ], + ]; + + $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson(); + $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE); + $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE); + + return $token; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Hg.php b/vendor/composer/composer/src/Composer/Util/Hg.php new file mode 100644 index 0000000..34b4796 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Hg.php @@ -0,0 +1,121 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Jonas Renaudot + */ +class Hg +{ + /** @var string|false|null */ + private static $version = false; + + /** + * @var \Composer\IO\IOInterface + */ + private $io; + + /** + * @var \Composer\Config + */ + private $config; + + /** + * @var \Composer\Util\ProcessExecutor + */ + private $process; + + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process) + { + $this->io = $io; + $this->config = $config; + $this->process = $process; + } + + public function runCommand(callable $commandCallable, string $url, ?string $cwd): void + { + $this->config->prohibitUrlByConfig($url, $this->io); + + // Try as is + $command = $commandCallable($url); + + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { + return; + } + + // Try with the authentication information available + if ( + Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) + && $this->io->hasAuthentication($matches['host']) + ) { + if ($matches['proto'] === 'ssh') { + $user = ''; + if ($matches['user'] !== null) { + $user = rawurlencode($matches['user']) . '@'; + } + $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; + } else { + $auth = $this->io->getAuthentication($matches['host']); + $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path']; + } + $command = $commandCallable($authenticatedUrl); + + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { + return; + } + + $error = $this->process->getErrorOutput(); + } else { + $error = 'The given URL (' .$url. ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)'; + } + + $this->throwException("Failed to clone $url, \n\n" . $error, $url); + } + + /** + * @param non-empty-string $message + * + * @return never + */ + private function throwException($message, string $url): void + { + if (null === self::getVersion($this->process)) { + throw new \RuntimeException(Url::sanitize( + 'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput() + )); + } + + throw new \RuntimeException(Url::sanitize($message)); + } + + /** + * Retrieves the current hg version. + * + * @return string|null The hg version number, if present. + */ + public static function getVersion(ProcessExecutor $process): ?string + { + if (false === self::$version) { + self::$version = null; + if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) { + self::$version = $matches[1]; + } + } + + return self::$version; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php b/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php new file mode 100644 index 0000000..0216597 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php @@ -0,0 +1,690 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Platform; +use Composer\Util\StreamContextFactory; +use Composer\Util\AuthHelper; +use Composer\Util\Url; +use Composer\Util\HttpDownloader; +use React\Promise\Promise; +use Symfony\Component\HttpFoundation\IpUtils; + +/** + * @internal + * @author Jordi Boggiano + * @author Nicolas Grekas + * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} + * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string} + */ +class CurlDownloader +{ + /** + * Known libcurl's broken versions when proxy is in use with HTTP/2 + * multiplexing. + * + * @var list + */ + private const BAD_MULTIPLEXING_CURL_VERSIONS = ['7.87.0', '7.88.0', '7.88.1']; + + /** @var \CurlMultiHandle */ + private $multiHandle; + /** @var \CurlShareHandle */ + private $shareHandle; + /** @var Job[] */ + private $jobs = []; + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var AuthHelper */ + private $authHelper; + /** @var float */ + private $selectTimeout = 5.0; + /** @var int */ + private $maxRedirects = 20; + /** @var int */ + private $maxRetries = 3; + /** @var array */ + protected $multiErrors = [ + CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'], + CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."], + CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'], + CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'], + ]; + + /** @var mixed[] */ + private static $options = [ + 'http' => [ + 'method' => CURLOPT_CUSTOMREQUEST, + 'content' => CURLOPT_POSTFIELDS, + 'header' => CURLOPT_HTTPHEADER, + 'timeout' => CURLOPT_TIMEOUT, + ], + 'ssl' => [ + 'cafile' => CURLOPT_CAINFO, + 'capath' => CURLOPT_CAPATH, + 'verify_peer' => CURLOPT_SSL_VERIFYPEER, + 'verify_peer_name' => CURLOPT_SSL_VERIFYHOST, + 'local_cert' => CURLOPT_SSLCERT, + 'local_pk' => CURLOPT_SSLKEY, + 'passphrase' => CURLOPT_SSLKEYPASSWD, + ], + ]; + + /** @var array */ + private static $timeInfo = [ + 'total_time' => true, + 'namelookup_time' => true, + 'connect_time' => true, + 'pretransfer_time' => true, + 'starttransfer_time' => true, + 'redirect_time' => true, + ]; + + /** + * @param mixed[] $options + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) + { + $this->io = $io; + $this->config = $config; + + $this->multiHandle = $mh = curl_multi_init(); + if (function_exists('curl_multi_setopt')) { + if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) { + /** + * Disable HTTP/2 multiplexing for some broken versions of libcurl. + * + * In certain versions of libcurl when proxy is in use with HTTP/2 + * multiplexing, connections will continue stacking up. This was + * fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc + */ + curl_multi_setopt($mh, CURLMOPT_PIPELINING, /* CURLPIPE_NOTHING */ 0); + } else { + curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + } + if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) { + curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); + } + } + + if (function_exists('curl_share_init')) { + $this->shareHandle = $sh = curl_share_init(); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); + } + + $this->authHelper = new AuthHelper($io, $config); + } + + /** + * @param mixed[] $options + * @param non-empty-string $url + * + * @return int internal job id + */ + public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null): int + { + $attributes = []; + if (isset($options['retry-auth-failure'])) { + $attributes['retryAuthFailure'] = $options['retry-auth-failure']; + unset($options['retry-auth-failure']); + } + + return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes); + } + + /** + * @param mixed[] $options + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes + * @param non-empty-string $url + * + * @return int internal job id + */ + private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []): int + { + $attributes = array_merge([ + 'retryAuthFailure' => true, + 'redirects' => 0, + 'retries' => 0, + 'storeAuth' => false, + 'ipResolve' => null, + ], $attributes); + + if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') { + $attributes['ipResolve'] = 4; + } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') { + $attributes['ipResolve'] = 6; + } + + $originalOptions = $options; + + // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 + if (!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) { + $this->config->prohibitUrlByConfig($url, $this->io, $options); + } + + $curlHandle = curl_init(); + $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b'); + if (false === $headerHandle) { + throw new \RuntimeException('Failed to open a temp stream to store curl headers'); + } + + if ($copyTo !== null) { + $bodyTarget = $copyTo.'~'; + } else { + $bodyTarget = 'php://temp/maxmemory:524288'; + } + + $errorMessage = ''; + set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg); + + return true; + }); + $bodyHandle = fopen($bodyTarget, 'w+b'); + restore_error_handler(); + if (false === $bodyHandle) { + throw new TransportException('The "'.$url.'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage); + } + + curl_setopt($curlHandle, CURLOPT_URL, $url); + curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300)); + curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle); + curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle); + curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports + curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + + if ($attributes['ipResolve'] === 4) { + curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } elseif ($attributes['ipResolve'] === 6) { + curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + } + + if (function_exists('curl_share_init')) { + curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle); + } + + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + + $options['http']['header'] = array_diff($options['http']['header'], ['Connection: close']); + $options['http']['header'][] = 'Connection: keep-alive'; + + $version = curl_version(); + $features = $version['features']; + if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) { + curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + } + + // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response + // https://github.com/composer/composer/issues/11913 + if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) { + curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); + } + + $options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url); + $options = StreamContextFactory::initOptions($url, $options, true); + + foreach (self::$options as $type => $curlOptions) { + foreach ($curlOptions as $name => $curlOption) { + if (isset($options[$type][$name])) { + if ($type === 'ssl' && $name === 'verify_peer_name') { + curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]); + } else { + curl_setopt($curlHandle, $curlOption, $options[$type][$name]); + } + } + } + } + + $proxy = ProxyManager::getInstance()->getProxyForRequest($url); + curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? [])); + + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + + $this->jobs[(int) $curlHandle] = [ + 'url' => $url, + 'origin' => $origin, + 'attributes' => $attributes, + 'options' => $originalOptions, + 'progress' => $progress, + 'curlHandle' => $curlHandle, + 'filename' => $copyTo, + 'headerHandle' => $headerHandle, + 'bodyHandle' => $bodyHandle, + 'resolve' => $resolve, + 'reject' => $reject, + 'primaryIp' => '', + ]; + + $usingProxy = $proxy->getStatus(' using proxy (%s)'); + $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : ''; + if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) { + $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG); + } + + $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); + // TODO progress + + return (int) $curlHandle; + } + + public function abortRequest(int $id): void + { + if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) { + $job = $this->jobs[$id]; + curl_multi_remove_handle($this->multiHandle, $job['curlHandle']); + curl_close($job['curlHandle']); + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + unset($this->jobs[$id]); + } + } + + public function tick(): void + { + static $timeoutWarning = false; + + if (count($this->jobs) === 0) { + return; + } + + $active = true; + $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); + if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { + // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select + usleep(150); + } + + while ($progress = curl_multi_info_read($this->multiHandle)) { + $curlHandle = $progress['handle']; + $result = $progress['result']; + $i = (int) $curlHandle; + if (!isset($this->jobs[$i])) { + continue; + } + + $progress = curl_getinfo($curlHandle); + if (false === $progress) { + throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.$this->jobs[$i]['url'].')'); + } + $job = $this->jobs[$i]; + unset($this->jobs[$i]); + $error = curl_error($curlHandle); + $errno = curl_errno($curlHandle); + curl_multi_remove_handle($this->multiHandle, $curlHandle); + curl_close($curlHandle); + + $headers = null; + $statusCode = null; + $response = null; + try { + // TODO progress + if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) { + $errno = $errno ?: $result; + if (!$error && function_exists('curl_strerror')) { + $error = curl_strerror($errno); + } + $progress['error_code'] = $errno; + + if ( + (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') + && ( + in_array($errno, [7 /* CURLE_COULDNT_CONNECT */, 16 /* CURLE_HTTP2 */, 92 /* CURLE_HTTP2_STREAM */, 6 /* CURLE_COULDNT_RESOLVE_HOST */], true) + || (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer')) + ) && $job['attributes']['retries'] < $this->maxRetries + ) { + $attributes = ['retries' => $job['attributes']['retries'] + 1]; + if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected + $attributes['ipResolve'] = 4; + } + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], $attributes); + continue; + } + + // TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved + if ($errno === 55 /* CURLE_SEND_ERROR */) { + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); + continue; + } + + if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) { + $timeoutWarning = true; + $this->io->writeError('A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.'); + } + + throw new TransportException('curl error '.$errno.' while downloading '.Url::sanitize($progress['url']).': '.$error); + } + $statusCode = $progress['http_code']; + rewind($job['headerHandle']); + $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); + fclose($job['headerHandle']); + + if ($statusCode === 0) { + throw new \LogicException('Received unexpected http status code 0 without error for '.Url::sanitize($progress['url']).': headers '.var_export($headers, true).' curl info '.var_export($progress, true)); + } + + // prepare response object + if (null !== $job['filename']) { + $contents = $job['filename'].'~'; + if ($statusCode >= 300) { + rewind($job['bodyHandle']); + $contents = stream_get_contents($job['bodyHandle']); + } + $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); + $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); + } else { + $maxFileSize = $job['options']['max_file_size'] ?? null; + rewind($job['bodyHandle']); + if ($maxFileSize !== null) { + $contents = stream_get_contents($job['bodyHandle'], $maxFileSize); + // Gzipped responses with missing Content-Length header cannot be detected during the file download + // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size + if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' . $maxFileSize . ' bytes'); + } + } else { + $contents = stream_get_contents($job['bodyHandle']); + } + + $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); + $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); + } + fclose($job['bodyHandle']); + + if ($response->getStatusCode() >= 300 && $response->getHeader('content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true)); + } + + $result = $this->isAuthenticatedRetryNeeded($job, $response); + if ($result['retry']) { + $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]); + continue; + } + + // handle 3xx redirects, 304 Not Modified is excluded + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { + $location = $this->handleRedirect($job, $response); + if ($location) { + $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]); + continue; + } + } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode >= 400 && $statusCode <= 599) { + if ( + (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') + && in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], true) + && $job['attributes']['retries'] < $this->maxRetries + ) { + $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to status code '. $statusCode, true, IOInterface::DEBUG); + $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); + continue; + } + + throw $this->failResponse($job, $response, $response->getStatusMessage()); + } + + if ($job['attributes']['storeAuth'] !== false) { + $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); + } + + // resolve promise + if (null !== $job['filename']) { + rename($job['filename'].'~', $job['filename']); + $job['resolve']($response); + } else { + $job['resolve']($response); + } + } catch (\Exception $e) { + if ($e instanceof TransportException) { + if (null !== $headers) { + $e->setHeaders($headers); + $e->setStatusCode($statusCode); + } + if (null !== $response) { + $e->setResponse($response->getBody()); + } + $e->setResponseInfo($progress); + } + + $this->rejectJob($job, $e); + } + } + + foreach ($this->jobs as $i => $curlHandle) { + $curlHandle = $this->jobs[$i]['curlHandle']; + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + + if ($this->jobs[$i]['progress'] !== $progress) { + $this->jobs[$i]['progress'] = $progress; + + if (isset($this->jobs[$i]['options']['max_file_size'])) { + // Compare max_file_size with the content-length header this value will be -1 until the header is parsed + if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) { + $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); + } + + // Compare max_file_size with the download size in bytes + if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) { + $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); + } + } + + if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) { + if ( + isset($this->jobs[$i]['options']['prevent_ip_access_callable']) && + is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && + $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) + ) { + $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url']))); + } + + $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; + } + + // TODO progress + } + } + } + + /** + * @param Job $job + */ + private function handleRedirect(array $job, Response $response): string + { + if ($locationHeader = $response->getHeader('location')) { + if (parse_url($locationHeader, PHP_URL_SCHEME)) { + // Absolute URL; e.g. https://example.com/composer + $targetUrl = $locationHeader; + } elseif (parse_url($locationHeader, PHP_URL_HOST)) { + // Scheme relative; e.g. //example.com/foo + $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader; + } elseif ('/' === $locationHeader[0]) { + // Absolute path; e.g. /foo + $urlHost = parse_url($job['url'], PHP_URL_HOST); + + // Replace path using hostname as an anchor. + $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']); + } else { + // Relative path; e.g. foo + // This actually differs from PHP which seems to add duplicate slashes. + $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']); + } + } + + if (!empty($targetUrl)) { + $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); + + return $targetUrl; + } + + throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')'); + } + + /** + * @param Job $job + * @return array{retry: bool, storeAuth: 'prompt'|bool} + */ + private function isAuthenticatedRetryNeeded(array $job, Response $response): array + { + if (in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) { + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries']); + + if ($result['retry']) { + return $result; + } + } + + $locationHeader = $response->getHeader('location'); + $needsAuthRetry = false; + + // check for bitbucket login page asking to authenticate + if ( + $job['origin'] === 'bitbucket.org' + && !$this->authHelper->isPublicBitBucketDownload($job['url']) + && substr($job['url'], -4) === '.zip' + && (!$locationHeader || substr($locationHeader, -4) !== '.zip') + && Preg::isMatch('{^text/html\b}i', $response->getHeader('content-type')) + ) { + $needsAuthRetry = 'Bitbucket requires authentication and it was not provided'; + } + + // check for gitlab 404 when downloading archives + if ( + $response->getStatusCode() === 404 + && in_array($job['origin'], $this->config->get('gitlab-domains'), true) + && false !== strpos($job['url'], 'archive.zip') + ) { + $needsAuthRetry = 'GitLab requires authentication and it was not provided'; + } + + if ($needsAuthRetry) { + if ($job['attributes']['retryAuthFailure']) { + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']); + if ($result['retry']) { + return $result; + } + } + + throw $this->failResponse($job, $response, $needsAuthRetry); + } + + return ['retry' => false, 'storeAuth' => false]; + } + + /** + * @param Job $job + * @param non-empty-string $url + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes + */ + private function restartJob(array $job, string $url, array $attributes = []): void + { + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + + $attributes = array_merge($job['attributes'], $attributes); + $origin = Url::getOrigin($this->config, $url); + + $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); + } + + /** + * @param Job $job + * @param non-empty-string $url + * + * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes + */ + private function restartJobWithDelay(array $job, string $url, array $attributes): void + { + if ($attributes['retries'] >= 3) { + usleep(500000); // half a second delay for 3rd retry and beyond + } elseif ($attributes['retries'] >= 2) { + usleep(100000); // 100ms delay for 2nd retry + } // no sleep for the first retry + + $this->restartJob($job, $url, $attributes); + } + + /** + * @param Job $job + */ + private function failResponse(array $job, Response $response, string $errorMessage): TransportException + { + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + + $details = ''; + if (in_array(strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], true)) { + $details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : ''); + } + + return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode()); + } + + /** + * @param Job $job + */ + private function rejectJob(array $job, \Exception $e): void + { + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if (null !== $job['filename']) { + @unlink($job['filename'].'~'); + } + $job['reject']($e); + } + + private function checkCurlResult(int $code): void + { + if ($code !== CURLM_OK && $code !== CURLM_CALL_MULTI_PERFORM) { + throw new \RuntimeException( + isset($this->multiErrors[$code]) + ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" + : 'Unexpected cURL error: ' . $code + ); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php b/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php new file mode 100644 index 0000000..aca8f37 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +/** + * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed} + */ +class CurlResponse extends Response +{ + /** + * @see https://www.php.net/curl_getinfo + * @var array + * @phpstan-var CurlInfo + */ + private $curlInfo; + + /** + * @phpstan-param CurlInfo $curlInfo + */ + public function __construct(array $request, ?int $code, array $headers, ?string $body, array $curlInfo) + { + parent::__construct($request, $code, $headers, $body); + $this->curlInfo = $curlInfo; + } + + /** + * @phpstan-return CurlInfo + */ + public function getCurlInfo(): array + { + return $this->curlInfo; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php b/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php new file mode 100644 index 0000000..2839be9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/ProxyItem.php @@ -0,0 +1,119 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +/** + * @internal + * @author John Stevenson + */ +class ProxyItem +{ + /** @var non-empty-string */ + private $url; + /** @var non-empty-string */ + private $safeUrl; + /** @var ?non-empty-string */ + private $curlAuth; + /** @var string */ + private $optionsProxy; + /** @var ?non-empty-string */ + private $optionsAuth; + + /** + * @param string $proxyUrl The value from the environment + * @param string $envName The name of the environment variable + * @throws \RuntimeException If the proxy url is invalid + */ + public function __construct(string $proxyUrl, string $envName) + { + $syntaxError = sprintf('unsupported `%s` syntax', $envName); + + if (strpbrk($proxyUrl, "\r\n\t") !== false) { + throw new \RuntimeException($syntaxError); + } + if (false === ($proxy = parse_url($proxyUrl))) { + throw new \RuntimeException($syntaxError); + } + if (!isset($proxy['host'])) { + throw new \RuntimeException('unable to find proxy host in ' . $envName); + } + + $scheme = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : 'http://'; + $safe = ''; + + if (isset($proxy['user'])) { + $safe = '***'; + $user = $proxy['user']; + $auth = rawurldecode($proxy['user']); + + if (isset($proxy['pass'])) { + $safe .= ':***'; + $user .= ':' . $proxy['pass']; + $auth .= ':' . rawurldecode($proxy['pass']); + } + + $safe .= '@'; + + if (strlen($user) > 0) { + $this->curlAuth = $user; + $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth); + } + } + + $host = $proxy['host']; + $port = null; + + if (isset($proxy['port'])) { + $port = $proxy['port']; + } elseif ($scheme === 'http://') { + $port = 80; + } elseif ($scheme === 'https://') { + $port = 443; + } + + // We need a port because curl uses 1080 for http. Port 0 is reserved, + // but is considered valid depending on the PHP or Curl version. + if ($port === null) { + throw new \RuntimeException('unable to find proxy port in ' . $envName); + } + if ($port === 0) { + throw new \RuntimeException('port 0 is reserved in ' . $envName); + } + + $this->url = sprintf('%s%s:%d', $scheme, $host, $port); + $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port); + + $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme); + $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port); + } + + /** + * Returns a RequestProxy instance for the scheme of the request url + * + * @param string $scheme The scheme of the request url + */ + public function toRequestProxy(string $scheme): RequestProxy + { + $options = ['http' => ['proxy' => $this->optionsProxy]]; + + if ($this->optionsAuth !== null) { + $options['http']['header'] = $this->optionsAuth; + } + + if ($scheme === 'http') { + $options['http']['request_fulluri'] = true; + } + + return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php b/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php new file mode 100644 index 0000000..3747ced --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php @@ -0,0 +1,173 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Downloader\TransportException; +use Composer\Util\NoProxyPattern; + +/** + * @internal + * @author John Stevenson + */ +class ProxyManager +{ + /** @var ?string */ + private $error = null; + /** @var ?ProxyItem */ + private $httpProxy = null; + /** @var ?ProxyItem */ + private $httpsProxy = null; + /** @var ?NoProxyPattern */ + private $noProxyHandler = null; + + /** @var ?self */ + private static $instance = null; + + private function __construct() + { + try { + $this->getProxyData(); + } catch (\RuntimeException $e) { + $this->error = $e->getMessage(); + } + } + + public static function getInstance(): ProxyManager + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Clears the persistent instance + */ + public static function reset(): void + { + self::$instance = null; + } + + public function hasProxy(): bool + { + return $this->httpProxy !== null || $this->httpsProxy !== null; + } + + /** + * Returns a RequestProxy instance for the request url + * + * @param non-empty-string $requestUrl + */ + public function getProxyForRequest(string $requestUrl): RequestProxy + { + if ($this->error !== null) { + throw new TransportException('Unable to use a proxy: '.$this->error); + } + + $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME); + $proxy = $this->getProxyForScheme($scheme); + + if ($proxy === null) { + return RequestProxy::none(); + } + + if ($this->noProxy($requestUrl)) { + return RequestProxy::noProxy(); + } + + return $proxy->toRequestProxy($scheme); + } + + /** + * Returns a ProxyItem if one is set for the scheme, otherwise null + */ + private function getProxyForScheme(string $scheme): ?ProxyItem + { + if ($scheme === 'http') { + return $this->httpProxy; + } + + if ($scheme === 'https') { + return $this->httpsProxy; + } + + return null; + } + + /** + * Finds proxy values from the environment and sets class properties + */ + private function getProxyData(): void + { + // Handle http_proxy/HTTP_PROXY on CLI only for security reasons + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + [$env, $name] = $this->getProxyEnv('http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } + } + + // Handle cgi_http_proxy/CGI_HTTP_PROXY if needed + if ($this->httpProxy === null) { + [$env, $name] = $this->getProxyEnv('cgi_http_proxy'); + if ($env !== null) { + $this->httpProxy = new ProxyItem($env, $name); + } + } + + // Handle https_proxy/HTTPS_PROXY + [$env, $name] = $this->getProxyEnv('https_proxy'); + if ($env !== null) { + $this->httpsProxy = new ProxyItem($env, $name); + } + + // Handle no_proxy/NO_PROXY + [$env, $name] = $this->getProxyEnv('no_proxy'); + if ($env !== null) { + $this->noProxyHandler = new NoProxyPattern($env); + } + } + + /** + * Searches $_SERVER for case-sensitive values + * + * @return array{0: string|null, 1: string} value, name + */ + private function getProxyEnv(string $envName): array + { + $names = [strtolower($envName), strtoupper($envName)]; + + foreach ($names as $name) { + if (is_string($_SERVER[$name] ?? null)) { + if ($_SERVER[$name] !== '') { + return [$_SERVER[$name], $name]; + } + } + } + + return [null, '']; + } + + /** + * Returns true if a url matches no_proxy value + */ + private function noProxy(string $requestUrl): bool + { + if ($this->noProxyHandler === null) { + return false; + } + + return $this->noProxyHandler->test($requestUrl); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php b/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php new file mode 100644 index 0000000..d9df688 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php @@ -0,0 +1,168 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Downloader\TransportException; + +/** + * @internal + * @author John Stevenson + * + * @phpstan-type contextOptions array{http: array{proxy: string, header?: string, request_fulluri?: bool}} + */ +class RequestProxy +{ + /** @var ?contextOptions */ + private $contextOptions; + /** @var ?non-empty-string */ + private $status; + /** @var ?non-empty-string */ + private $url; + /** @var ?non-empty-string */ + private $auth; + + /** + * @param ?non-empty-string $url The proxy url, without authorization + * @param ?non-empty-string $auth Authorization for curl + * @param ?contextOptions $contextOptions + * @param ?non-empty-string $status + */ + public function __construct(?string $url, ?string $auth, ?array $contextOptions, ?string $status) + { + $this->url = $url; + $this->auth = $auth; + $this->contextOptions = $contextOptions; + $this->status = $status; + } + + public static function none(): RequestProxy + { + return new self(null, null, null, null); + } + + public static function noProxy(): RequestProxy + { + return new self(null, null, null, 'excluded by no_proxy'); + } + + /** + * Returns the context options to use for this request, otherwise null + * + * @return ?contextOptions + */ + public function getContextOptions(): ?array + { + return $this->contextOptions; + } + + /** + * Returns an array of curl proxy options + * + * @param array $sslOptions + * @return array + */ + public function getCurlOptions(array $sslOptions): array + { + if ($this->isSecure() && !$this->supportsSecureProxy()) { + throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.'); + } + + // Always set a proxy url, even an empty value, because it tells curl + // to ignore proxy environment variables + $options = [CURLOPT_PROXY => (string) $this->url]; + + // If using a proxy, tell curl to ignore no_proxy environment variables + if ($this->url !== null) { + $options[CURLOPT_NOPROXY] = ''; + } + + // Set any authorization + if ($this->auth !== null) { + $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; + $options[CURLOPT_PROXYUSERPWD] = $this->auth; + } + + if ($this->isSecure()) { + if (isset($sslOptions['cafile'])) { + $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile']; + } + if (isset($sslOptions['capath'])) { + $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath']; + } + } + + return $options; + } + + /** + * Returns proxy info associated with this request + * + * An empty return value means that the user has not set a proxy. + * A non-empty value will either be the sanitized proxy url if a proxy is + * required, or a message indicating that a no_proxy value has disabled the + * proxy. + * + * @param ?string $format Output format specifier + */ + public function getStatus(?string $format = null): string + { + if ($this->status === null) { + return ''; + } + + $format = $format ?? '%s'; + if (strpos($format, '%s') !== false) { + return sprintf($format, $this->status); + } + + throw new \InvalidArgumentException('String format specifier is missing'); + } + + /** + * Returns true if the request url has been excluded by a no_proxy value + * + * A false value can also mean that the user has not set a proxy. + */ + public function isExcludedByNoProxy(): bool + { + return $this->status !== null && $this->url === null; + } + + /** + * Returns true if this is a secure (HTTPS) proxy + * + * A false value means that this is either an HTTP proxy, or that a proxy + * is not required for this request, or that the user has not set a proxy. + */ + public function isSecure(): bool + { + return 0 === strpos((string) $this->url, 'https://'); + } + + /** + * Returns true if an HTTPS proxy can be used. + * + * This depends on PHP7.3+ for CURL_VERSION_HTTPS_PROXY + * and curl including the feature (from version 7.52.0) + */ + public function supportsSecureProxy(): bool + { + if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) { + return false; + } + + $features = $version['features']; + + return (bool) ($features & CURL_VERSION_HTTPS_PROXY); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Http/Response.php b/vendor/composer/composer/src/Composer/Util/Http/Response.php new file mode 100644 index 0000000..e355f25 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Http/Response.php @@ -0,0 +1,122 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Json\JsonFile; +use Composer\Pcre\Preg; +use Composer\Util\HttpDownloader; + +/** + * @phpstan-type Request array{url: non-empty-string, options?: mixed[], copyTo?: string|null} + */ +class Response +{ + /** @var Request */ + private $request; + /** @var int */ + private $code; + /** @var list */ + private $headers; + /** @var ?string */ + private $body; + + /** + * @param Request $request + * @param list $headers + */ + public function __construct(array $request, ?int $code, array $headers, ?string $body) + { + if (!isset($request['url'])) { + throw new \LogicException('url key missing from request array'); + } + $this->request = $request; + $this->code = (int) $code; + $this->headers = $headers; + $this->body = $body; + } + + public function getStatusCode(): int + { + return $this->code; + } + + public function getStatusMessage(): ?string + { + $value = null; + foreach ($this->headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, headers contain the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + + /** + * @return string[] + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @return ?string + */ + public function getHeader(string $name): ?string + { + return self::findHeaderValue($this->headers, $name); + } + + /** + * @return ?string + */ + public function getBody(): ?string + { + return $this->body; + } + + /** + * @return mixed + */ + public function decodeJson() + { + return JsonFile::parseJson($this->body, $this->request['url']); + } + + /** + * @phpstan-impure + */ + public function collect(): void + { + unset($this->request, $this->code, $this->headers, $this->body); + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @param string $name header name (case insensitive) + */ + public static function findHeaderValue(array $headers, string $name): ?string + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) { + $value = $match[1]; + } + } + + return $value; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/HttpDownloader.php b/vendor/composer/composer/src/Composer/Util/HttpDownloader.php new file mode 100644 index 0000000..723ff02 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/HttpDownloader.php @@ -0,0 +1,551 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Http\Response; +use Composer\Util\Http\CurlDownloader; +use Composer\Composer; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Exception\IrrecoverableDownloadException; +use React\Promise\Promise; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + * @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null} + * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable} + */ +class HttpDownloader +{ + private const STATUS_QUEUED = 1; + private const STATUS_STARTED = 2; + private const STATUS_COMPLETED = 3; + private const STATUS_FAILED = 4; + private const STATUS_ABORTED = 5; + + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var array */ + private $jobs = []; + /** @var mixed[] */ + private $options = []; + /** @var int */ + private $runningJobs = 0; + /** @var int */ + private $maxJobs = 12; + /** @var ?CurlDownloader */ + private $curl; + /** @var ?RemoteFilesystem */ + private $rfs; + /** @var int */ + private $idGen = 0; + /** @var bool */ + private $disabled; + /** @var bool */ + private $allowAsync = false; + + /** + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param mixed[] $options The options + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) + { + $this->io = $io; + + $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK'); + + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $this->options = StreamContextFactory::getTlsDefaults($options, $io); + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); + $this->config = $config; + + if (self::isCurlEnabled()) { + $this->curl = new CurlDownloader($io, $config, $options, $disableTls); + } + + $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); + + if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) { + $this->maxJobs = max(1, min(50, (int) $maxJobs)); + } + } + + /** + * Download a file synchronously + * + * @param string $url URL to download + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return Response + */ + public function get(string $url, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], true); + $promise->then(null, function (\Throwable $e) { + // suppress error as it is rethrown to the caller by getResponse() a few lines below + }); + $this->wait($job['id']); + + $response = $this->getResponse($job['id']); + + return $response; + } + + /** + * Create an async download operation + * + * @param string $url URL to download + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function add(string $url, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]); + + return $promise; + } + + /** + * Copy a file synchronously + * + * @param string $url URL to download + * @param string $to Path to copy to + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return Response + */ + public function copy(string $url, string $to, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [$job] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], true); + $this->wait($job['id']); + + return $this->getResponse($job['id']); + } + + /** + * Create an async copy operation + * + * @param string $url URL to download + * @param string $to Path to copy to + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * although not all options are supported when using the default curl downloader + * @throws TransportException + * @return PromiseInterface + * @phpstan-return PromiseInterface + */ + public function addCopy(string $url, string $to, array $options = []) + { + if ('' === $url) { + throw new \InvalidArgumentException('$url must not be an empty string'); + } + [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]); + + return $promise; + } + + /** + * Retrieve the options set in the constructor + * + * @return mixed[] Options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Merges new options + * + * @param mixed[] $options + * @return void + */ + public function setOptions(array $options) + { + $this->options = array_replace_recursive($this->options, $options); + } + + /** + * @phpstan-param Request $request + * @return array{Job, PromiseInterface} + * @phpstan-return array{Job, PromiseInterface} + */ + private function addJob(array $request, bool $sync = false): array + { + $request['options'] = array_replace_recursive($this->options, $request['options']); + + /** @var Job */ + $job = [ + 'id' => $this->idGen++, + 'status' => self::STATUS_QUEUED, + 'request' => $request, + 'sync' => $sync, + 'origin' => Url::getOrigin($this->config, $request['url']), + ]; + + if (!$sync && !$this->allowAsync) { + throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\Loop instance to be able to run async http requests'); + } + + // capture username/password from URL if there is one + if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { + $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2])); + } + + $rfs = $this->rfs; + + if ($this->canUseCurl($job)) { + $resolver = static function ($resolve, $reject) use (&$job): void { + $job['status'] = HttpDownloader::STATUS_QUEUED; + $job['resolve'] = $resolve; + $job['reject'] = $reject; + }; + } else { + $resolver = static function ($resolve, $reject) use (&$job, $rfs): void { + // start job + $url = $job['request']['url']; + $options = $job['request']['options']; + + $job['status'] = HttpDownloader::STATUS_STARTED; + + if ($job['request']['copyTo']) { + $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options); + + $headers = $rfs->getLastHeaders(); + $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~'); + + $resolve($response); + } else { + $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options); + $headers = $rfs->getLastHeaders(); + $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); + + $resolve($response); + } + }; + } + + $curl = $this->curl; + + $canceler = static function () use (&$job, $curl): void { + if ($job['status'] === HttpDownloader::STATUS_QUEUED) { + $job['status'] = HttpDownloader::STATUS_ABORTED; + } + if ($job['status'] !== HttpDownloader::STATUS_STARTED) { + return; + } + $job['status'] = HttpDownloader::STATUS_ABORTED; + if (isset($job['curl_id'])) { + $curl->abortRequest($job['curl_id']); + } + throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled'); + }; + + $promise = new Promise($resolver, $canceler); + $promise = $promise->then(function ($response) use (&$job) { + $job['status'] = HttpDownloader::STATUS_COMPLETED; + $job['response'] = $response; + + $this->markJobDone(); + + return $response; + }, function ($e) use (&$job): void { + $job['status'] = HttpDownloader::STATUS_FAILED; + $job['exception'] = $e; + + $this->markJobDone(); + + throw $e; + }); + $this->jobs[$job['id']] = &$job; + + if ($this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + + return [$job, $promise]; + } + + private function startJob(int $id): void + { + $job = &$this->jobs[$id]; + if ($job['status'] !== self::STATUS_QUEUED) { + return; + } + + // start job + $job['status'] = self::STATUS_STARTED; + $this->runningJobs++; + + assert(isset($job['resolve'])); + assert(isset($job['reject'])); + + $resolve = $job['resolve']; + $reject = $job['reject']; + $url = $job['request']['url']; + $options = $job['request']['options']; + $origin = $job['origin']; + + if ($this->disabled) { + if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { + $resolve(new Response(['url' => $url], 304, [], '')); + } else { + $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499); + $e->setStatusCode(499); + $reject($e); + } + + return; + } + + try { + if ($job['request']['copyTo']) { + $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); + } else { + $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options); + } + } catch (\Exception $exception) { + $reject($exception); + } + } + + private function markJobDone(): void + { + $this->runningJobs--; + } + + /** + * Wait for current async download jobs to complete + * + * @param int|null $index For internal use only, the job id + * + * @return void + */ + public function wait(?int $index = null) + { + do { + $jobCount = $this->countActiveJobs($index); + } while ($jobCount); + } + + /** + * @internal + */ + public function enableAsync(): void + { + $this->allowAsync = true; + } + + /** + * @internal + * + * @param int|null $index For internal use only, the job id + * @return int number of active (queued or started) jobs + */ + public function countActiveJobs(?int $index = null): int + { + if ($this->runningJobs < $this->maxJobs) { + foreach ($this->jobs as $job) { + if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + } + } + + if ($this->curl) { + $this->curl->tick(); + } + + if (null !== $index) { + return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; + } + + $active = 0; + foreach ($this->jobs as $job) { + if ($job['status'] < self::STATUS_COMPLETED) { + $active++; + } elseif (!$job['sync']) { + unset($this->jobs[$job['id']]); + } + } + + return $active; + } + + /** + * @param int $index Job id + */ + private function getResponse(int $index): Response + { + if (!isset($this->jobs[$index])) { + throw new \LogicException('Invalid request id'); + } + + if ($this->jobs[$index]['status'] === self::STATUS_FAILED) { + assert(isset($this->jobs[$index]['exception'])); + throw $this->jobs[$index]['exception']; + } + + if (!isset($this->jobs[$index]['response'])) { + throw new \LogicException('Response not available yet, call wait() first'); + } + + $resp = $this->jobs[$index]['response']; + + unset($this->jobs[$index]); + + return $resp; + } + + /** + * @internal + * + * @param array{warning?: string, info?: string, warning-versions?: string, info-versions?: string, warnings?: array, infos?: array} $data + */ + public static function outputWarnings(IOInterface $io, string $url, $data): void + { + $cleanMessage = static function ($msg) use ($io) { + if (!$io->isDecorated()) { + $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg); + } + + return $msg; + }; + + // legacy warning/info keys + foreach (['warning', 'info'] as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).''); + } + + // modern Composer 2.2+ format with support for multiple warning/info messages + foreach (['warnings', 'infos'] as $key) { + if (empty($data[$key])) { + continue; + } + + $versionParser = new VersionParser(); + foreach ($data[$key] as $spec) { + $type = substr($key, 0, -1); + $constraint = $versionParser->parseConstraints($spec['versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).''); + } + } + } + + /** + * @internal + * + * @return ?string[] + */ + public static function getExceptionHints(\Throwable $e): ?array + { + if (!$e instanceof TransportException) { + return null; + } + + if ( + false !== strpos($e->getMessage(), 'Resolving timed out') + || false !== strpos($e->getMessage(), 'Could not resolve host') + ) { + Silencer::suppress(); + $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([ + 'ssl' => ['verify_peer' => false], + 'http' => ['follow_location' => false, 'ignore_errors' => true], + ])); + Silencer::restore(); + if (false !== $testConnectivity) { + return [ + 'The following exception probably indicates you have misconfigured DNS resolver(s)', + ]; + } + + return [ + 'The following exception probably indicates you are offline or have misconfigured DNS resolver(s)', + ]; + } + + return null; + } + + /** + * @param Job $job + */ + private function canUseCurl(array $job): bool + { + if (!$this->curl) { + return false; + } + + if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) { + return false; + } + + if (!empty($job['request']['options']['ssl']['allow_self_signed'])) { + return false; + } + + return true; + } + + /** + * @internal + */ + public static function isCurlEnabled(): bool + { + return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init'); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/IniHelper.php b/vendor/composer/composer/src/Composer/Util/IniHelper.php new file mode 100644 index 0000000..c01a97d --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/IniHelper.php @@ -0,0 +1,62 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\XdebugHandler\XdebugHandler; + +/** + * Provides ini file location functions that work with and without a restart. + * When the process has restarted it uses a tmp ini and stores the original + * ini locations in an environment variable. + * + * @author John Stevenson + */ +class IniHelper +{ + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be empty. + * + * @return string[] + */ + public static function getAll(): array + { + return XdebugHandler::getAllIniFiles(); + } + + /** + * Describes the location of the loaded php.ini file(s) + */ + public static function getMessage(): string + { + $paths = self::getAll(); + + if (empty($paths[0])) { + array_shift($paths); + } + + $ini = array_shift($paths); + + if (empty($ini)) { + return 'A php.ini file does not exist. You will have to create one.'; + } + + if (!empty($paths)) { + return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.'; + } + + return 'The php.ini used by your command-line PHP is: '.$ini; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Loop.php b/vendor/composer/composer/src/Composer/Util/Loop.php new file mode 100644 index 0000000..ca24e69 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Loop.php @@ -0,0 +1,123 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use React\Promise\CancellablePromiseInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use React\Promise\PromiseInterface; + +/** + * @author Jordi Boggiano + */ +class Loop +{ + /** @var HttpDownloader */ + private $httpDownloader; + /** @var ProcessExecutor|null */ + private $processExecutor; + /** @var array>> */ + private $currentPromises = []; + /** @var int */ + private $waitIndex = 0; + + public function __construct(HttpDownloader $httpDownloader, ?ProcessExecutor $processExecutor = null) + { + $this->httpDownloader = $httpDownloader; + $this->httpDownloader->enableAsync(); + + $this->processExecutor = $processExecutor; + if ($this->processExecutor) { + $this->processExecutor->enableAsync(); + } + } + + public function getHttpDownloader(): HttpDownloader + { + return $this->httpDownloader; + } + + public function getProcessExecutor(): ?ProcessExecutor + { + return $this->processExecutor; + } + + /** + * @param array> $promises + * @param ProgressBar|null $progress + */ + public function wait(array $promises, ?ProgressBar $progress = null): void + { + $uncaught = null; + + \React\Promise\all($promises)->then( + static function (): void { + }, + static function (\Throwable $e) use (&$uncaught): void { + $uncaught = $e; + } + ); + + // keep track of every group of promises that is waited on, so abortJobs can + // cancel them all, even if wait() was called within a wait() + $waitIndex = $this->waitIndex++; + $this->currentPromises[$waitIndex] = $promises; + + if ($progress) { + $totalJobs = 0; + $totalJobs += $this->httpDownloader->countActiveJobs(); + if ($this->processExecutor) { + $totalJobs += $this->processExecutor->countActiveJobs(); + } + $progress->start($totalJobs); + } + + $lastUpdate = 0; + while (true) { + $activeJobs = 0; + + $activeJobs += $this->httpDownloader->countActiveJobs(); + if ($this->processExecutor) { + $activeJobs += $this->processExecutor->countActiveJobs(); + } + + if ($progress && microtime(true) - $lastUpdate > 0.1) { + $lastUpdate = microtime(true); + $progress->setProgress($progress->getMaxSteps() - $activeJobs); + } + + if (!$activeJobs) { + break; + } + } + + // as we skip progress updates if they are too quick, make sure we do one last one here at 100% + if ($progress) { + $progress->finish(); + } + + unset($this->currentPromises[$waitIndex]); + if (null !== $uncaught) { + throw $uncaught; + } + } + + public function abortJobs(): void + { + foreach ($this->currentPromises as $promiseGroup) { + foreach ($promiseGroup as $promise) { + // to support react/promise 2.x we wrap the promise in a resolve() call for safety + \React\Promise\resolve($promise)->cancel(); + } + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php b/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php new file mode 100644 index 0000000..ff93821 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/MetadataMinifier.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +@trigger_error('Composer\Util\MetadataMinifier is deprecated, use Composer\MetadataMinifier\MetadataMinifier from composer/metadata-minifier instead.', E_USER_DEPRECATED); + +/** + * @deprecated Use Composer\MetadataMinifier\MetadataMinifier instead + */ +class MetadataMinifier extends \Composer\MetadataMinifier\MetadataMinifier +{ +} diff --git a/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php b/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php new file mode 100644 index 0000000..45f4734 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/NoProxyPattern.php @@ -0,0 +1,412 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; +use stdClass; + +/** + * Tests URLs against NO_PROXY patterns + */ +class NoProxyPattern +{ + /** + * @var string[] + */ + protected $hostNames = []; + + /** + * @var (null|object)[] + */ + protected $rules = []; + + /** + * @var bool + */ + protected $noproxy; + + /** + * @param string $pattern NO_PROXY pattern + */ + public function __construct(string $pattern) + { + $this->hostNames = Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY); + $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0]; + } + + /** + * Returns true if a URL matches the NO_PROXY pattern + */ + public function test(string $url): bool + { + if ($this->noproxy) { + return true; + } + + if (!$urlData = $this->getUrlData($url)) { + return false; + } + + foreach ($this->hostNames as $index => $hostName) { + if ($this->match($index, $hostName, $urlData)) { + return true; + } + } + + return false; + } + + /** + * Returns false is the url cannot be parsed, otherwise a data object + * + * @return bool|stdClass + */ + protected function getUrlData(string $url) + { + if (!$host = parse_url($url, PHP_URL_HOST)) { + return false; + } + + $port = parse_url($url, PHP_URL_PORT); + + if (empty($port)) { + switch (parse_url($url, PHP_URL_SCHEME)) { + case 'http': + $port = 80; + break; + case 'https': + $port = 443; + break; + } + } + + $hostName = $host . ($port ? ':' . $port : ''); + [$host, $port, $err] = $this->splitHostPort($hostName); + + if ($err || !$this->ipCheckData($host, $ipdata)) { + return false; + } + + return $this->makeData($host, $port, $ipdata); + } + + /** + * Returns true if the url is matched by a rule + */ + protected function match(int $index, string $hostName, stdClass $url): bool + { + if (!$rule = $this->getRule($index, $hostName)) { + // Data must have been misformatted + return false; + } + + if ($rule->ipdata) { + // Match ipdata first + if (!$url->ipdata) { + return false; + } + + if ($rule->ipdata->netmask) { + return $this->matchRange($rule->ipdata, $url->ipdata); + } + + $match = $rule->ipdata->ip === $url->ipdata->ip; + } else { + // Match host and port + $haystack = substr($url->name, -strlen($rule->name)); + $match = stripos($haystack, $rule->name) === 0; + } + + if ($match && $rule->port) { + $match = $rule->port === $url->port; + } + + return $match; + } + + /** + * Returns true if the target ip is in the network range + */ + protected function matchRange(stdClass $network, stdClass $target): bool + { + $net = unpack('C*', $network->ip); + $mask = unpack('C*', $network->netmask); + $ip = unpack('C*', $target->ip); + if (false === $net) { + throw new \RuntimeException('Could not parse network IP '.$network->ip); + } + if (false === $mask) { + throw new \RuntimeException('Could not parse netmask '.$network->netmask); + } + if (false === $ip) { + throw new \RuntimeException('Could not parse target IP '.$target->ip); + } + + for ($i = 1; $i < 17; ++$i) { + if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) { + return false; + } + } + + return true; + } + + /** + * Finds or creates rule data for a hostname + * + * @return null|stdClass Null if the hostname is invalid + */ + private function getRule(int $index, string $hostName): ?stdClass + { + if (array_key_exists($index, $this->rules)) { + return $this->rules[$index]; + } + + $this->rules[$index] = null; + [$host, $port, $err] = $this->splitHostPort($hostName); + + if ($err || !$this->ipCheckData($host, $ipdata, true)) { + return null; + } + + $this->rules[$index] = $this->makeData($host, $port, $ipdata); + + return $this->rules[$index]; + } + + /** + * Creates an object containing IP data if the host is an IP address + * + * @param null|stdClass $ipdata Set by method if IP address found + * @param bool $allowPrefix Whether a CIDR prefix-length is expected + * + * @return bool False if the host contains invalid data + */ + private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = false): bool + { + $ipdata = null; + $netmask = null; + $prefix = null; + $modified = false; + + // Check for a CIDR prefix-length + if (strpos($host, '/') !== false) { + [$host, $prefix] = explode('/', $host); + + if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) { + return false; + } + $prefix = (int) $prefix; + $modified = true; + } + + // See if this is an ip address + if (!filter_var($host, FILTER_VALIDATE_IP)) { + return !$modified; + } + + [$ip, $size] = $this->ipGetAddr($host); + + if ($prefix !== null) { + // Check for a valid prefix + if ($prefix > $size * 8) { + return false; + } + + [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix); + } + + $ipdata = $this->makeIpData($ip, $size, $netmask); + + return true; + } + + /** + * Returns an array of the IP in_addr and its byte size + * + * IPv4 addresses are always mapped to IPv6, which simplifies handling + * and comparison. + * + * @return mixed[] in_addr, size + */ + private function ipGetAddr(string $host): array + { + $ip = inet_pton($host); + $size = strlen($ip); + $mapped = $this->ipMapTo6($ip, $size); + + return [$mapped, $size]; + } + + /** + * Returns the binary network mask mapped to IPv6 + * + * @param int $prefix CIDR prefix-length + * @param int $size Byte size of in_addr + */ + private function ipGetMask(int $prefix, int $size): string + { + $mask = ''; + + if ($ones = floor($prefix / 8)) { + $mask = str_repeat(chr(255), (int) $ones); + } + + if ($remainder = $prefix % 8) { + $mask .= chr(0xff ^ (0xff >> $remainder)); + } + + $mask = str_pad($mask, $size, chr(0)); + + return $this->ipMapTo6($mask, $size); + } + + /** + * Calculates and returns the network and mask + * + * @param string $rangeIp IP in_addr + * @param int $size Byte size of in_addr + * @param int $prefix CIDR prefix-length + * + * @return string[] network in_addr, binary mask + */ + private function ipGetNetwork(string $rangeIp, int $size, int $prefix): array + { + $netmask = $this->ipGetMask($prefix, $size); + + // Get the network from the address and mask + $mask = unpack('C*', $netmask); + $ip = unpack('C*', $rangeIp); + $net = ''; + if (false === $mask) { + throw new \RuntimeException('Could not parse netmask '.$netmask); + } + if (false === $ip) { + throw new \RuntimeException('Could not parse range IP '.$rangeIp); + } + + for ($i = 1; $i < 17; ++$i) { + $net .= chr($ip[$i] & $mask[$i]); + } + + return [$net, $netmask]; + } + + /** + * Maps an IPv4 address to IPv6 + * + * @param string $binary in_addr + * @param int $size Byte size of in_addr + * + * @return string Mapped or existing in_addr + */ + private function ipMapTo6(string $binary, int $size): string + { + if ($size === 4) { + $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2); + $binary = $prefix . $binary; + } + + return $binary; + } + + /** + * Creates a rule data object + */ + private function makeData(string $host, int $port, ?stdClass $ipdata): stdClass + { + return (object) [ + 'host' => $host, + 'name' => '.' . ltrim($host, '.'), + 'port' => $port, + 'ipdata' => $ipdata, + ]; + } + + /** + * Creates an ip data object + * + * @param string $ip in_addr + * @param int $size Byte size of in_addr + * @param null|string $netmask Network mask + */ + private function makeIpData(string $ip, int $size, ?string $netmask): stdClass + { + return (object) [ + 'ip' => $ip, + 'size' => $size, + 'netmask' => $netmask, + ]; + } + + /** + * Splits the hostname into host and port components + * + * @return mixed[] host, port, if there was error + */ + private function splitHostPort(string $hostName): array + { + // host, port, err + $error = ['', '', true]; + $port = 0; + $ip6 = ''; + + // Check for square-bracket notation + if ($hostName[0] === '[') { + $index = strpos($hostName, ']'); + + // The smallest ip6 address is :: + if (false === $index || $index < 3) { + return $error; + } + + $ip6 = substr($hostName, 1, $index - 1); + $hostName = substr($hostName, $index + 1); + + if (strpbrk($hostName, '[]') !== false || substr_count($hostName, ':') > 1) { + return $error; + } + } + + if (substr_count($hostName, ':') === 1) { + $index = strpos($hostName, ':'); + $port = substr($hostName, $index + 1); + $hostName = substr($hostName, 0, $index); + + if (!$this->validateInt($port, 1, 65535)) { + return $error; + } + + $port = (int) $port; + } + + $host = $ip6 . $hostName; + + return [$host, $port, false]; + } + + /** + * Wrapper around filter_var FILTER_VALIDATE_INT + */ + private function validateInt(string $int, int $min, int $max): bool + { + $options = [ + 'options' => [ + 'min_range' => $min, + 'max_range' => $max, + ], + ]; + + return false !== filter_var($int, FILTER_VALIDATE_INT, $options); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/PackageInfo.php b/vendor/composer/composer/src/Composer/Util/PackageInfo.php new file mode 100644 index 0000000..e93c584 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/PackageInfo.php @@ -0,0 +1,39 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\CompletePackageInterface; +use Composer\Package\PackageInterface; + +class PackageInfo +{ + public static function getViewSourceUrl(PackageInterface $package): ?string + { + if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) { + return $package->getSupport()['source']; + } + + return $package->getSourceUrl(); + } + + public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string + { + $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null); + + if ($url === '') { + return null; + } + + return $url; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/PackageSorter.php b/vendor/composer/composer/src/Composer/Util/PackageSorter.php new file mode 100644 index 0000000..80ea2cc --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/PackageSorter.php @@ -0,0 +1,140 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; + +class PackageSorter +{ + /** + * Returns the most recent version of a set of packages + * + * This is ideally the default branch version, or failing that it will return the package with the highest version + * + * @template T of PackageInterface + * @param array $packages + * @return ($packages is non-empty-array ? T : T|null) + */ + public static function getMostCurrentVersion(array $packages): ?PackageInterface + { + if (count($packages) === 0) { + return null; + } + + $highest = reset($packages); + foreach ($packages as $candidate) { + if ($candidate->isDefaultBranch()) { + return $candidate; + } + + if (version_compare($highest->getVersion(), $candidate->getVersion(), '<')) { + $highest = $candidate; + } + } + + return $highest; + } + + /** + * Sorts packages by name + * + * @template T of PackageInterface + * @param array $packages + * @return array + */ + public static function sortPackagesAlphabetically(array $packages): array + { + usort($packages, static function (PackageInterface $a, PackageInterface $b) { + return $a->getName() <=> $b->getName(); + }); + + return $packages; + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight are sorted alphabetically + * + * @param PackageInterface[] $packages + * @param array $weights Pre-set weights for some packages to give them more (negative number) or less (positive) weight offsets + * @return PackageInterface[] sorted array + */ + public static function sortPackages(array $packages, array $weights = []): array + { + $usageList = []; + + foreach ($packages as $package) { + $links = $package->getRequires(); + if ($package instanceof RootPackageInterface) { + $links = array_merge($links, $package->getDevRequires()); + } + foreach ($links as $link) { + $target = $link->getTarget(); + $usageList[$target][] = $package->getName(); + } + } + $computing = []; + $computed = []; + $computeImportance = static function ($name) use (&$computeImportance, &$computing, &$computed, $usageList, $weights) { + // reusing computed importance + if (isset($computed[$name])) { + return $computed[$name]; + } + + // canceling circular dependency + if (isset($computing[$name])) { + return 0; + } + + $computing[$name] = true; + $weight = $weights[$name] ?? 0; + + if (isset($usageList[$name])) { + foreach ($usageList[$name] as $user) { + $weight -= 1 - $computeImportance($user); + } + } + + unset($computing[$name]); + $computed[$name] = $weight; + + return $weight; + }; + + $weightedPackages = []; + + foreach ($packages as $index => $package) { + $name = $package->getName(); + $weight = $computeImportance($name); + $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index]; + } + + usort($weightedPackages, static function (array $a, array $b): int { + if ($a['weight'] !== $b['weight']) { + return $a['weight'] - $b['weight']; + } + + return strnatcasecmp($a['name'], $b['name']); + }); + + $sortedPackages = []; + + foreach ($weightedPackages as $pkg) { + $sortedPackages[] = $packages[$pkg['index']]; + } + + return $sortedPackages; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Perforce.php b/vendor/composer/composer/src/Composer/Util/Perforce.php new file mode 100644 index 0000000..bfed834 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Perforce.php @@ -0,0 +1,637 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; + +/** + * @author Matt Whittom + * + * @phpstan-type RepoConfig array{unique_perforce_client_name?: string, depot?: string, branch?: string, p4user?: string, p4password?: string} + */ +class Perforce +{ + /** @var string */ + protected $path; + /** @var ?string */ + protected $p4Depot; + /** @var ?string */ + protected $p4Client; + /** @var ?string */ + protected $p4User; + /** @var ?string */ + protected $p4Password; + /** @var string */ + protected $p4Port; + /** @var ?string */ + protected $p4Stream; + /** @var string */ + protected $p4ClientSpec; + /** @var ?string */ + protected $p4DepotType; + /** @var ?string */ + protected $p4Branch; + /** @var ProcessExecutor */ + protected $process; + /** @var string */ + protected $uniquePerforceClientName; + /** @var bool */ + protected $windowsFlag; + /** @var string */ + protected $commandResult; + + /** @var IOInterface */ + protected $io; + + /** @var ?Filesystem */ + protected $filesystem; + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public function __construct($repoConfig, string $port, string $path, ProcessExecutor $process, bool $isWindows, IOInterface $io) + { + $this->windowsFlag = $isWindows; + $this->p4Port = $port; + $this->initializePath($path); + $this->process = $process; + $this->initialize($repoConfig); + $this->io = $io; + } + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public static function create($repoConfig, string $port, string $path, ProcessExecutor $process, IOInterface $io): self + { + return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); + } + + public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool + { + return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput); + } + + /** + * @phpstan-param RepoConfig $repoConfig + */ + public function initialize($repoConfig): void + { + $this->uniquePerforceClientName = $this->generateUniquePerforceClientName(); + if (!$repoConfig) { + return; + } + if (isset($repoConfig['unique_perforce_client_name'])) { + $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name']; + } + + if (isset($repoConfig['depot'])) { + $this->p4Depot = $repoConfig['depot']; + } + if (isset($repoConfig['branch'])) { + $this->p4Branch = $repoConfig['branch']; + } + if (isset($repoConfig['p4user'])) { + $this->p4User = $repoConfig['p4user']; + } else { + $this->p4User = $this->getP4variable('P4USER'); + } + if (isset($repoConfig['p4password'])) { + $this->p4Password = $repoConfig['p4password']; + } + } + + public function initializeDepotAndBranch(?string $depot, ?string $branch): void + { + if (isset($depot)) { + $this->p4Depot = $depot; + } + if (isset($branch)) { + $this->p4Branch = $branch; + } + } + + /** + * @return non-empty-string + */ + public function generateUniquePerforceClientName(): string + { + return gethostname() . "_" . time(); + } + + public function cleanupClientSpec(): void + { + $client = $this->getClient(); + $task = 'client -d ' . ProcessExecutor::escape($client); + $useP4Client = false; + $command = $this->generateP4Command($task, $useP4Client); + $this->executeCommand($command); + $clientSpec = $this->getP4ClientSpec(); + $fileSystem = $this->getFilesystem(); + $fileSystem->remove($clientSpec); + } + + /** + * @param non-empty-string $command + */ + protected function executeCommand($command): int + { + $this->commandResult = ''; + + return $this->process->execute($command, $this->commandResult); + } + + public function getClient(): string + { + if (!isset($this->p4Client)) { + $cleanStreamName = str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream()); + $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName; + } + + return $this->p4Client; + } + + protected function getPath(): string + { + return $this->path; + } + + public function initializePath(string $path): void + { + $this->path = $path; + $fs = $this->getFilesystem(); + $fs->ensureDirectoryExists($path); + } + + protected function getPort(): string + { + return $this->p4Port; + } + + public function setStream(string $stream): void + { + $this->p4Stream = $stream; + $index = strrpos($stream, '/'); + //Stream format is //depot/stream, while non-streaming depot is //depot + if ($index > 2) { + $this->p4DepotType = 'stream'; + } + } + + public function isStream(): bool + { + return is_string($this->p4DepotType) && (strcmp($this->p4DepotType, 'stream') === 0); + } + + public function getStream(): string + { + if (!isset($this->p4Stream)) { + if ($this->isStream()) { + $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch; + } else { + $this->p4Stream = '//' . $this->p4Depot; + } + } + + return $this->p4Stream; + } + + public function getStreamWithoutLabel(string $stream): string + { + $index = strpos($stream, '@'); + if ($index === false) { + return $stream; + } + + return substr($stream, 0, $index); + } + + /** + * @return non-empty-string + */ + public function getP4ClientSpec(): string + { + return $this->path . '/' . $this->getClient() . '.p4.spec'; + } + + public function getUser(): ?string + { + return $this->p4User; + } + + public function setUser(?string $user): void + { + $this->p4User = $user; + } + + public function queryP4User(): void + { + $this->getUser(); + if (strlen((string) $this->p4User) > 0) { + return; + } + $this->p4User = $this->getP4variable('P4USER'); + if (strlen((string) $this->p4User) > 0) { + return; + } + $this->p4User = $this->io->ask('Enter P4 User:'); + if ($this->windowsFlag) { + $command = $this->getP4Executable().' set P4USER=' . $this->p4User; + } else { + $command = 'export P4USER=' . $this->p4User; + } + $this->executeCommand($command); + } + + /** + * @return ?string + */ + protected function getP4variable(string $name): ?string + { + if ($this->windowsFlag) { + $command = $this->getP4Executable().' set'; + $this->executeCommand($command); + $result = trim($this->commandResult); + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + $fields = explode('=', $line); + if (strcmp($name, $fields[0]) === 0) { + $index = strpos($fields[1], ' '); + if ($index === false) { + $value = $fields[1]; + } else { + $value = substr($fields[1], 0, $index); + } + $value = trim($value); + + return $value; + } + } + + return null; + } + + $command = 'echo $' . $name; + $this->executeCommand($command); + $result = trim($this->commandResult); + + return $result; + } + + public function queryP4Password(): ?string + { + if (isset($this->p4Password)) { + return $this->p4Password; + } + $password = $this->getP4variable('P4PASSWD'); + if (strlen((string) $password) <= 0) { + $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': '); + } + $this->p4Password = $password; + + return $password; + } + + /** + * @return non-empty-string + */ + public function generateP4Command(string $command, bool $useClient = true): string + { + $p4Command = $this->getP4Executable().' '; + $p4Command .= '-u ' . $this->getUser() . ' '; + if ($useClient) { + $p4Command .= '-c ' . $this->getClient() . ' '; + } + $p4Command .= '-p ' . $this->getPort() . ' ' . $command; + + return $p4Command; + } + + public function isLoggedIn(): bool + { + $command = $this->generateP4Command('login -s', false); + $exitCode = $this->executeCommand($command); + if ($exitCode) { + $errorOutput = $this->process->getErrorOutput(); + $index = strpos($errorOutput, $this->getUser()); + if ($index === false) { + $index = strpos($errorOutput, 'p4'); + if ($index === false) { + return false; + } + throw new \Exception('p4 command not found in path: ' . $errorOutput); + } + throw new \Exception('Invalid user name: ' . $this->getUser()); + } + + return true; + } + + public function connectClient(): void + { + $p4CreateClientCommand = $this->generateP4Command( + 'client -i < ' . ProcessExecutor::escape($this->getP4ClientSpec()) + ); + $this->executeCommand($p4CreateClientCommand); + } + + public function syncCodeBase(?string $sourceReference): void + { + $prevDir = Platform::getCwd(); + chdir($this->path); + $p4SyncCommand = $this->generateP4Command('sync -f '); + if (null !== $sourceReference) { + $p4SyncCommand .= '@' . $sourceReference; + } + $this->executeCommand($p4SyncCommand); + chdir($prevDir); + } + + /** + * @param resource|false $spec + */ + public function writeClientSpecToFile($spec): void + { + fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL); + fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL); + fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Description:' . PHP_EOL); + fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL); + fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL); + fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL); + fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL); + fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL); + if ($this->isStream()) { + fwrite($spec, 'Stream:' . PHP_EOL); + fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL); + } else { + fwrite( + $spec, + 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL + ); + } + } + + public function writeP4ClientSpec(): void + { + $clientSpec = $this->getP4ClientSpec(); + $spec = fopen($clientSpec, 'w'); + try { + $this->writeClientSpecToFile($spec); + } catch (\Exception $e) { + fclose($spec); + throw $e; + } + fclose($spec); + } + + /** + * @param resource $pipe + * @param mixed $name + */ + protected function read($pipe, $name): void + { + if (feof($pipe)) { + return; + } + $line = fgets($pipe); + while ($line !== false) { + $line = fgets($pipe); + } + } + + public function windowsLogin(?string $password): int + { + $command = $this->generateP4Command(' login -a'); + + $process = Process::fromShellCommandline($command, null, null, $password); + + return $process->run(); + } + + public function p4Login(): void + { + $this->queryP4User(); + if (!$this->isLoggedIn()) { + $password = $this->queryP4Password(); + if ($this->windowsFlag) { + $this->windowsLogin($password); + } else { + $command = 'echo ' . ProcessExecutor::escape($password) . ' | ' . $this->generateP4Command(' login -a', false); + $exitCode = $this->executeCommand($command); + if ($exitCode) { + throw new \Exception("Error logging in:" . $this->process->getErrorOutput()); + } + } + } + } + + /** + * @return mixed[]|null + */ + public function getComposerInformation(string $identifier): ?array + { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + return json_decode($composerFileContent, true); + } + + public function getFileContent(string $file, string $identifier): ?string + { + $path = $this->getFilePath($file, $identifier); + + $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path)); + $this->executeCommand($command); + $result = $this->commandResult; + + if (!trim($result)) { + return null; + } + + return $result; + } + + public function getFilePath(string $file, string $identifier): ?string + { + $index = strpos($identifier, '@'); + if ($index === false) { + return $identifier. '/' . $file; + } + + $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); + $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false); + $this->executeCommand($command); + $result = $this->commandResult; + $index2 = strpos($result, 'no such file(s).'); + if ($index2 === false) { + $index3 = strpos($result, 'change'); + if ($index3 !== false) { + $phrase = trim(substr($result, $index3)); + $fields = explode(' ', $phrase); + + return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; + } + } + + return null; + } + + /** + * @return array{master: string} + */ + public function getBranches(): array + { + $possibleBranches = []; + if (!$this->isStream()) { + $possibleBranches[$this->p4Branch] = $this->getStream(); + } else { + $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...')); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + $resBits = explode(' ', $line); + if (count($resBits) > 4) { + $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]); + $possibleBranches[$branch] = $resBits[1]; + } + } + } + $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + $lastCommit = $resArray[0]; + $lastCommitArr = explode(' ', $lastCommit); + $lastCommitNum = $lastCommitArr[1]; + + return ['master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum]; + } + + /** + * @return array + */ + public function getTags(): array + { + $command = $this->generateP4Command('labels'); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + $tags = []; + foreach ($resArray as $line) { + if (strpos($line, 'Label') !== false) { + $fields = explode(' ', $line); + $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; + } + } + + return $tags; + } + + public function checkStream(): bool + { + $command = $this->generateP4Command('depots', false); + $this->executeCommand($command); + $result = $this->commandResult; + $resArray = explode(PHP_EOL, $result); + foreach ($resArray as $line) { + if (strpos($line, 'Depot') !== false) { + $fields = explode(' ', $line); + if (strcmp($this->p4Depot, $fields[1]) === 0) { + $this->p4DepotType = $fields[3]; + + return $this->isStream(); + } + } + } + + return false; + } + + /** + * @return mixed|null + */ + protected function getChangeList(string $reference): mixed + { + $index = strpos($reference, '@'); + if ($index === false) { + return null; + } + $label = substr($reference, $index); + $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label)); + $this->executeCommand($command); + $changes = $this->commandResult; + if (strpos($changes, 'Change') !== 0) { + return null; + } + $fields = explode(' ', $changes); + + return $fields[1]; + } + + /** + * @return mixed|null + */ + public function getCommitLogs(string $fromReference, string $toReference): mixed + { + $fromChangeList = $this->getChangeList($fromReference); + if ($fromChangeList === null) { + return null; + } + $toChangeList = $this->getChangeList($toReference); + if ($toChangeList === null) { + return null; + } + $index = strpos($fromReference, '@'); + $main = substr($fromReference, 0, $index) . '/...'; + $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList)); + $this->executeCommand($command); + + return $this->commandResult; + } + + public function getFilesystem(): Filesystem + { + if (null === $this->filesystem) { + $this->filesystem = new Filesystem($this->process); + } + + return $this->filesystem; + } + + public function setFilesystem(Filesystem $fs): void + { + $this->filesystem = $fs; + } + + private function getP4Executable(): string + { + static $p4Executable; + + if ($p4Executable) { + return $p4Executable; + } + + $finder = new ExecutableFinder(); + + return $p4Executable = $finder->find('p4') ?? 'p4'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Platform.php b/vendor/composer/composer/src/Composer/Util/Platform.php new file mode 100644 index 0000000..85ab6d9 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Platform.php @@ -0,0 +1,351 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Pcre\Preg; + +/** + * Platform helper for uniform platform-specific tests. + * + * @author Niels Keurentjes + */ +class Platform +{ + /** @var ?bool */ + private static $isVirtualBoxGuest = null; + /** @var ?bool */ + private static $isWindowsSubsystemForLinux = null; + /** @var ?bool */ + private static $isDocker = null; + + /** + * getcwd() equivalent which always returns a string + * + * @throws \RuntimeException + */ + public static function getCwd(bool $allowEmpty = false): string + { + $cwd = getcwd(); + + // fallback to realpath('') just in case this works but odds are it would break as well if we are in a case where getcwd fails + if (false === $cwd) { + $cwd = realpath(''); + } + + // crappy state, assume '' and hopefully relative paths allow things to continue + if (false === $cwd) { + if ($allowEmpty) { + return ''; + } + + throw new \RuntimeException('Could not determine the current working directory'); + } + + return $cwd; + } + + /** + * Infallible realpath version that falls back on the given $path if realpath is not working + */ + public static function realpath(string $path): string + { + $realPath = realpath($path); + if ($realPath === false) { + return $path; + } + + return $realPath; + } + + /** + * getenv() equivalent but reads from the runtime global variables first + * + * @param non-empty-string $name + * + * @return string|false + */ + public static function getEnv(string $name) + { + if (array_key_exists($name, $_SERVER)) { + return (string) $_SERVER[$name]; + } + if (array_key_exists($name, $_ENV)) { + return (string) $_ENV[$name]; + } + + return getenv($name); + } + + /** + * putenv() equivalent but updates the runtime global variables too + */ + public static function putEnv(string $name, string $value): void + { + putenv($name . '=' . $value); + $_SERVER[$name] = $_ENV[$name] = $value; + } + + /** + * putenv('X') equivalent but updates the runtime global variables too + */ + public static function clearEnv(string $name): void + { + putenv($name); + unset($_SERVER[$name], $_ENV[$name]); + } + + /** + * Parses tildes and environment variables in paths. + */ + public static function expandPath(string $path): string + { + if (Preg::isMatch('#^~[\\/]#', $path)) { + return self::getUserDirectory() . substr($path, 1); + } + + return Preg::replaceCallback('#^(\$|(?P%))(?P\w++)(?(percent)%)(?P.*)#', static function ($matches): string { + // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons + if (Platform::isWindows() && $matches['var'] === 'HOME') { + if ((bool) Platform::getEnv('HOME')) { + return Platform::getEnv('HOME') . $matches['path']; + } + return Platform::getEnv('USERPROFILE') . $matches['path']; + } + + return Platform::getEnv($matches['var']) . $matches['path']; + }, $path); + } + + /** + * @throws \RuntimeException If the user home could not reliably be determined + * @return string The formal user home as detected from environment parameters + */ + public static function getUserDirectory(): string + { + if (false !== ($home = self::getEnv('HOME'))) { + return $home; + } + + if (self::isWindows() && false !== ($home = self::getEnv('USERPROFILE'))) { + return $home; + } + + if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) { + $info = posix_getpwuid(posix_getuid()); + + if (is_array($info)) { + return $info['dir']; + } + } + + throw new \RuntimeException('Could not determine user directory'); + } + + /** + * @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL) + */ + public static function isWindowsSubsystemForLinux(): bool + { + if (null === self::$isWindowsSubsystemForLinux) { + self::$isWindowsSubsystemForLinux = false; + + // while WSL will be hosted within windows, WSL itself cannot be windows based itself. + if (self::isWindows()) { + return self::$isWindowsSubsystemForLinux = false; + } + + if ( + !(bool) ini_get('open_basedir') + && is_readable('/proc/version') + && false !== stripos((string)Silencer::call('file_get_contents', '/proc/version'), 'microsoft') + && !self::isDocker() // Docker and Podman running inside WSL should not be seen as WSL + ) { + return self::$isWindowsSubsystemForLinux = true; + } + } + + return self::$isWindowsSubsystemForLinux; + } + + /** + * @return bool Whether the host machine is running a Windows OS + */ + public static function isWindows(): bool + { + return \defined('PHP_WINDOWS_VERSION_BUILD'); + } + + public static function isDocker(): bool + { + if (null !== self::$isDocker) { + return self::$isDocker; + } + + // cannot check so assume no + if ((bool) ini_get('open_basedir')) { + return self::$isDocker = false; + } + + // .dockerenv and .containerenv are present in some cases but not reliably + if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) { + return self::$isDocker = true; + } + + // see https://www.baeldung.com/linux/is-process-running-inside-container + $cgroups = [ + '/proc/self/mountinfo', // cgroup v2 + '/proc/1/cgroup', // cgroup v1 + ]; + foreach ($cgroups as $cgroup) { + if (!is_readable($cgroup)) { + continue; + } + // suppress errors as some environments have these files as readable but system restrictions prevent the read from succeeding + // see https://github.com/composer/composer/issues/12095 + try { + $data = @file_get_contents($cgroup); + } catch (\Throwable $e) { + break; + } + if (!is_string($data)) { + continue; + } + // detect default mount points created by Docker/containerd + if (str_contains($data, '/var/lib/docker/') || str_contains($data, '/io.containerd.snapshotter')) { + return self::$isDocker = true; + } + } + + return self::$isDocker = false; + } + + /** + * @return int return a guaranteed binary length of the string, regardless of silly mbstring configs + */ + public static function strlen(string $str): int + { + static $useMbString = null; + if (null === $useMbString) { + $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload'); + } + + if ($useMbString) { + return mb_strlen($str, '8bit'); + } + + return \strlen($str); + } + + /** + * @param ?resource $fd Open file descriptor or null to default to STDOUT + */ + public static function isTty($fd = null): bool + { + if ($fd === null) { + $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); + if ($fd === false) { + return false; + } + } + + // detect msysgit/mingw and assume this is a tty because detection + // does not work correctly, see https://github.com/composer/composer/issues/9690 + if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return true; + } + + // modern cross-platform function, includes the fstat + // fallback so if it is present we trust it + if (function_exists('stream_isatty')) { + return stream_isatty($fd); + } + + // only trusting this if it is positive, otherwise prefer fstat fallback + if (function_exists('posix_isatty') && posix_isatty($fd)) { + return true; + } + + $stat = @fstat($fd); + if ($stat === false) { + return false; + } + // Check if formatted mode is S_IFCHR + return 0020000 === ($stat['mode'] & 0170000); + } + + /** + * @return bool Whether the current command is for bash completion + */ + public static function isInputCompletionProcess(): bool + { + return '_complete' === ($_SERVER['argv'][1] ?? null); + } + + public static function workaroundFilesystemIssues(): void + { + if (self::isVirtualBoxGuest()) { + usleep(200000); + } + } + + /** + * Attempts detection of VirtualBox guest VMs + * + * This works based on the process' user being "vagrant", the COMPOSER_RUNTIME_ENV env var being set to "virtualbox", or lsmod showing the virtualbox guest additions are loaded + */ + private static function isVirtualBoxGuest(): bool + { + if (null === self::$isVirtualBoxGuest) { + self::$isVirtualBoxGuest = false; + if (self::isWindows()) { + return self::$isVirtualBoxGuest; + } + + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $processUser = posix_getpwuid(posix_geteuid()); + if (is_array($processUser) && $processUser['name'] === 'vagrant') { + return self::$isVirtualBoxGuest = true; + } + } + + if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') { + return self::$isVirtualBoxGuest = true; + } + + if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') { + $process = new ProcessExecutor(); + try { + if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) { + return self::$isVirtualBoxGuest = true; + } + } catch (\Exception $e) { + // noop + } + } + } + + return self::$isVirtualBoxGuest; + } + + /** + * @return 'NUL'|'/dev/null' + */ + public static function getDevNull(): string + { + if (self::isWindows()) { + return 'NUL'; + } + + return '/dev/null'; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php b/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php new file mode 100644 index 0000000..11db709 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/ProcessExecutor.php @@ -0,0 +1,600 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; +use Seld\Signal\SignalHandler; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; +use React\Promise\Promise; +use React\Promise\PromiseInterface; +use Symfony\Component\Process\ExecutableFinder; + +/** + * @author Robert Schönthal + * @author Jordi Boggiano + */ +class ProcessExecutor +{ + private const STATUS_QUEUED = 1; + private const STATUS_STARTED = 2; + private const STATUS_COMPLETED = 3; + private const STATUS_FAILED = 4; + private const STATUS_ABORTED = 5; + + private const BUILTIN_CMD_COMMANDS = [ + 'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date', + 'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto', + 'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause', + 'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set', + 'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol', + ]; + + private const GIT_CMDS_NEED_GIT_DIR = [ + ['show'], + ['log'], + ['branch'], + ['remote', 'set-url'] + ]; + + /** @var int */ + protected static $timeout = 300; + + /** @var bool */ + protected $captureOutput = false; + /** @var string */ + protected $errorOutput = ''; + /** @var ?IOInterface */ + protected $io; + + /** + * @phpstan-var array> + */ + private $jobs = []; + /** @var int */ + private $runningJobs = 0; + /** @var int */ + private $maxJobs = 10; + /** @var int */ + private $idGen = 0; + /** @var bool */ + private $allowAsync = false; + + /** @var array */ + private static $executables = []; + + public function __construct(?IOInterface $io = null) + { + $this->io = $io; + $this->resetMaxJobs(); + } + + /** + * runs a process on the commandline + * + * @param string|non-empty-list $command the command to execute + * @param mixed $output the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + * @param null|string $cwd the working directory + * @return int statuscode + */ + public function execute($command, &$output = null, ?string $cwd = null): int + { + if (func_num_args() > 1) { + return $this->doExecute($command, $cwd, false, $output); + } + + return $this->doExecute($command, $cwd, false); + } + + /** + * runs a process on the commandline in TTY mode + * + * @param string|non-empty-list $command the command to execute + * @param null|string $cwd the working directory + * @return int statuscode + */ + public function executeTty($command, ?string $cwd = null): int + { + if (Platform::isTty()) { + return $this->doExecute($command, $cwd, true); + } + + return $this->doExecute($command, $cwd, false); + } + + /** + * @param string|non-empty-list $command + * @param array|null $env + * @param mixed $output + */ + private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int + { + // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups + // in the current directory which could be untrusted. Instead we use the ExecutableFinder. + + if (is_string($command)) { + if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) { + $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1])); + } + + $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout()); + } else { + if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) { + $command[0] = self::getExecutable($command[0]); + } + + $process = new Process($command, $cwd, $env, null, static::getTimeout()); + } + + if (! Platform::isWindows() && $tty) { + try { + $process->setTty(true); + } catch (RuntimeException $e) { + // ignore TTY enabling errors + } + } + + $callback = is_callable($output) ? $output : function (string $type, string $buffer): void { + $this->outputHandler($type, $buffer); + }; + + $signalHandler = SignalHandler::create( + [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], + function (string $signal) { + if ($this->io !== null) { + $this->io->writeError( + 'Received '.$signal.', aborting when child process is done', + true, + IOInterface::DEBUG + ); + } + } + ); + + try { + $process->run($callback); + + if ($this->captureOutput && !is_callable($output)) { + $output = $process->getOutput(); + } + + $this->errorOutput = $process->getErrorOutput(); + } catch (ProcessSignaledException $e) { + if ($signalHandler->isTriggered()) { + // exiting as we were signaled and the child process exited too due to the signal + $signalHandler->exitWithLastSignal(); + } + } finally { + $signalHandler->unregister(); + } + + return $process->getExitCode(); + } + + /** + * @param string|non-empty-list $command + * @param mixed $output + */ + private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int + { + $this->outputCommandRun($command, $cwd, false); + + $this->captureOutput = func_num_args() > 3; + $this->errorOutput = ''; + + $env = null; + + $requiresGitDirEnv = $this->requiresGitDirEnv($command); + if ($cwd !== null && $requiresGitDirEnv) { + $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/'))); + if ($isBareRepository) { + $configValue = ''; + $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); + $configValue = trim($configValue); + if ($configValue === 'explicit') { + $env = ['GIT_DIR' => $cwd]; + } + } + } + + return $this->runProcess($command, $cwd, $env, $tty, $output); + } + + /** + * starts a process on the commandline in async mode + * + * @param string|list $command the command to execute + * @param string $cwd the working directory + * @phpstan-return PromiseInterface + */ + public function executeAsync($command, ?string $cwd = null): PromiseInterface + { + if (!$this->allowAsync) { + throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\Loop instance to be able to run async processes'); + } + + $job = [ + 'id' => $this->idGen++, + 'status' => self::STATUS_QUEUED, + 'command' => $command, + 'cwd' => $cwd, + ]; + + $resolver = static function ($resolve, $reject) use (&$job): void { + $job['status'] = ProcessExecutor::STATUS_QUEUED; + $job['resolve'] = $resolve; + $job['reject'] = $reject; + }; + + $canceler = static function () use (&$job): void { + if ($job['status'] === ProcessExecutor::STATUS_QUEUED) { + $job['status'] = ProcessExecutor::STATUS_ABORTED; + } + if ($job['status'] !== ProcessExecutor::STATUS_STARTED) { + return; + } + $job['status'] = ProcessExecutor::STATUS_ABORTED; + try { + if (defined('SIGINT')) { + $job['process']->signal(SIGINT); + } + } catch (\Exception $e) { + // signal can throw in various conditions, but we don't care if it fails + } + $job['process']->stop(1); + + throw new \RuntimeException('Aborted process'); + }; + + $promise = new Promise($resolver, $canceler); + $promise = $promise->then(function () use (&$job) { + if ($job['process']->isSuccessful()) { + $job['status'] = ProcessExecutor::STATUS_COMPLETED; + } else { + $job['status'] = ProcessExecutor::STATUS_FAILED; + } + + $this->markJobDone(); + + return $job['process']; + }, function ($e) use (&$job): void { + $job['status'] = ProcessExecutor::STATUS_FAILED; + + $this->markJobDone(); + + throw $e; + }); + $this->jobs[$job['id']] = &$job; + + if ($this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + + return $promise; + } + + protected function outputHandler(string $type, string $buffer): void + { + if ($this->captureOutput) { + return; + } + + if (null === $this->io) { + echo $buffer; + + return; + } + + if (Process::ERR === $type) { + $this->io->writeErrorRaw($buffer, false); + } else { + $this->io->writeRaw($buffer, false); + } + } + + private function startJob(int $id): void + { + $job = &$this->jobs[$id]; + if ($job['status'] !== self::STATUS_QUEUED) { + return; + } + + // start job + $job['status'] = self::STATUS_STARTED; + $this->runningJobs++; + + $command = $job['command']; + $cwd = $job['cwd']; + + $this->outputCommandRun($command, $cwd, true); + + try { + if (is_string($command)) { + $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); + } else { + $process = new Process($command, $cwd, null, null, static::getTimeout()); + } + } catch (\Throwable $e) { + $job['reject']($e); + + return; + } + + $job['process'] = $process; + + try { + $process->start(); + } catch (\Throwable $e) { + $job['reject']($e); + + return; + } + } + + public function setMaxJobs(int $maxJobs): void + { + $this->maxJobs = $maxJobs; + } + + public function resetMaxJobs(): void + { + if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_PROCESSES'))) { + $this->maxJobs = max(1, min(50, (int) $maxJobs)); + } else { + $this->maxJobs = 10; + } + } + + /** + * @param ?int $index job id + */ + public function wait($index = null): void + { + while (true) { + if (0 === $this->countActiveJobs($index)) { + return; + } + + usleep(1000); + } + } + + /** + * @internal + */ + public function enableAsync(): void + { + $this->allowAsync = true; + } + + /** + * @internal + * + * @param ?int $index job id + * @return int number of active (queued or started) jobs + */ + public function countActiveJobs($index = null): int + { + // tick + foreach ($this->jobs as $job) { + if ($job['status'] === self::STATUS_STARTED) { + if (!$job['process']->isRunning()) { + call_user_func($job['resolve'], $job['process']); + } + + $job['process']->checkTimeout(); + } + + if ($this->runningJobs < $this->maxJobs) { + if ($job['status'] === self::STATUS_QUEUED) { + $this->startJob($job['id']); + } + } + } + + if (null !== $index) { + return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; + } + + $active = 0; + foreach ($this->jobs as $job) { + if ($job['status'] < self::STATUS_COMPLETED) { + $active++; + } else { + unset($this->jobs[$job['id']]); + } + } + + return $active; + } + + private function markJobDone(): void + { + $this->runningJobs--; + } + + /** + * @return string[] + */ + public function splitLines(?string $output): array + { + $output = trim((string) $output); + + return $output === '' ? [] : Preg::split('{\r?\n}', $output); + } + + /** + * Get any error output from the last command + */ + public function getErrorOutput(): string + { + return $this->errorOutput; + } + + /** + * @return int the timeout in seconds + */ + public static function getTimeout(): int + { + return static::$timeout; + } + + /** + * @param int $timeout the timeout in seconds + */ + public static function setTimeout(int $timeout): void + { + static::$timeout = $timeout; + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string|false|null $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escape($argument): string + { + return self::escapeArgument($argument); + } + + /** + * @param string|list $command + */ + private function outputCommandRun($command, ?string $cwd, bool $async): void + { + if (null === $this->io || !$this->io->isDebug()) { + return; + } + + $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command)); + $safeCommand = Preg::replaceCallback('{://(?P[^:/\s]+):(?P[^@\s/]+)@}i', static function ($m): string { + // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that + if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) { + return '://***:***@'; + } + if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) { + return '://***:***@'; + } + + return '://'.$m['user'].':***@'; + }, $commandString); + $safeCommand = Preg::replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand); + $this->io->writeError('Executing'.($async ? ' async' : '').' command ('.($cwd ?: 'CWD').'): '.$safeCommand); + } + + /** + * Escapes a string to be used as a shell argument for Symfony Process. + * + * This method expects cmd.exe to be started with the /V:ON option, which + * enables delayed environment variable expansion using ! as the delimiter. + * If this is not the case, any escaped ^^!var^^! will be transformed to + * ^!var^! and introduce two unintended carets. + * + * Modified from https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string|false|null $argument + */ + private static function escapeArgument($argument): string + { + if ('' === ($argument = (string) $argument)) { + return escapeshellarg($argument); + } + + if (!Platform::isWindows()) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + + // New lines break cmd.exe command parsing + // and special chars like the fullwidth quote can be used to break out + // of parameter encoding via "Best Fit" encoding conversion + $argument = strtr($argument, [ + "\n" => ' ', + "\u{ff02}" => '"', + "\u{02ba}" => '"', + "\u{301d}" => '"', + "\u{301e}" => '"', + "\u{030e}" => '"', + "\u{ff1a}" => ':', + "\u{0589}" => ':', + "\u{2236}" => ':', + "\u{ff0f}" => '/', + "\u{2044}" => '/', + "\u{2215}" => '/', + "\u{00b4}" => '/', + ]); + + // In addition to whitespace, commas need quoting to preserve paths + $quote = strpbrk($argument, " \t,") !== false; + $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes); + $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument); + + if (!$meta && !$quote) { + $quote = strpbrk($argument, '^&|<>()') !== false; + } + + if ($quote) { + $argument = '"'.Preg::replace('/(\\\\*)$/', '$1$1', $argument).'"'; + } + + if ($meta) { + $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument); + $argument = Preg::replace('/(!)/', '^^$1', $argument); + } + + return $argument; + } + + /** + * @param string[]|string $command + */ + public function requiresGitDirEnv($command): bool + { + $cmd = !is_array($command) ? explode(' ', $command) : $command; + if ($cmd[0] !== 'git') { + return false; + } + + foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) { + if (array_intersect($cmd, $gitCmd) === $gitCmd) { + return true; + } + } + + return false; + } + + /** + * Resolves executable paths on Windows + */ + private static function getExecutable(string $name): string + { + if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) { + return $name; + } + + if (!isset(self::$executables[$name])) { + $path = (new ExecutableFinder())->find($name, $name); + if ($path !== null) { + self::$executables[$name] = $path; + } + } + + return self::$executables[$name] ?? $name; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php b/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php new file mode 100644 index 0000000..1e96301 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php @@ -0,0 +1,734 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\Pcre\Preg; +use Composer\Util\Http\Response; +use Composer\Util\Http\ProxyManager; + +/** + * @internal + * @author François Pluchino + * @author Jordi Boggiano + * @author Nils Adermann + */ +class RemoteFilesystem +{ + /** @var IOInterface */ + private $io; + /** @var Config */ + private $config; + /** @var string */ + private $scheme; + /** @var int */ + private $bytesMax; + /** @var string */ + private $originUrl; + /** @var non-empty-string */ + private $fileUrl; + /** @var ?string */ + private $fileName; + /** @var bool */ + private $retry = false; + /** @var bool */ + private $progress; + /** @var ?int */ + private $lastProgress; + /** @var mixed[] */ + private $options = []; + /** @var bool */ + private $disableTls = false; + /** @var list */ + private $lastHeaders; + /** @var bool */ + private $storeAuth = false; + /** @var AuthHelper */ + private $authHelper; + /** @var bool */ + private $degradedMode = false; + /** @var int */ + private $redirects; + /** @var int */ + private $maxRedirects = 20; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param mixed[] $options The options + * @param AuthHelper $authHelper + */ + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false, ?AuthHelper $authHelper = null) + { + $this->io = $io; + + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $this->options = StreamContextFactory::getTlsDefaults($options, $io); + } else { + $this->disableTls = true; + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); + $this->config = $config; + $this->authHelper = $authHelper ?? new AuthHelper($io, $config); + } + + /** + * Copy the remote file in local. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param string $fileName the local filename + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options + * + * @return bool true + */ + public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = true, array $options = []) + { + return $this->get($originUrl, $fileUrl, $options, $fileName, $progress); + } + + /** + * Get the content. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options + * + * @return bool|string The content + */ + public function getContents(string $originUrl, string $fileUrl, bool $progress = true, array $options = []) + { + return $this->get($originUrl, $fileUrl, $options, null, $progress); + } + + /** + * Retrieve the options set in the constructor + * + * @return mixed[] Options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Merges new options + * + * @param mixed[] $options + * @return void + */ + public function setOptions(array $options) + { + $this->options = array_replace_recursive($this->options, $options); + } + + /** + * Check is disable TLS. + * + * @return bool + */ + public function isTlsDisabled() + { + return $this->disableTls === true; + } + + /** + * Returns the headers of the last request + * + * @return list + */ + public function getLastHeaders() + { + return $this->lastHeaders; + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @return int|null + */ + public static function findStatusCode(array $headers) + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ (\d+)}i', $header, $match)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we can not return directly and need to keep iterating + $value = (int) $match[1]; + } + } + + return $value; + } + + /** + * @param string[] $headers array of returned headers like from getLastHeaders() + * @return string|null + */ + public function findStatusMessage(array $headers) + { + $value = null; + foreach ($headers as $header) { + if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + + /** + * Get file content or copy action. + * + * @param string $originUrl The origin URL + * @param non-empty-string $fileUrl The file URL + * @param mixed[] $additionalOptions context options + * @param string $fileName the local filename + * @param bool $progress Display the progression + * + * @throws TransportException|\Exception + * @throws TransportException When the file could not be downloaded + * + * @return bool|string + */ + protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = true) + { + $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME); + $this->bytesMax = 0; + $this->originUrl = $originUrl; + $this->fileUrl = $fileUrl; + $this->fileName = $fileName; + $this->progress = $progress; + $this->lastProgress = null; + $retryAuthFailure = true; + $this->lastHeaders = []; + $this->redirects = 1; // The first request counts. + + $tempAdditionalOptions = $additionalOptions; + if (isset($tempAdditionalOptions['retry-auth-failure'])) { + $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; + + unset($tempAdditionalOptions['retry-auth-failure']); + } + + $isRedirect = false; + if (isset($tempAdditionalOptions['redirects'])) { + $this->redirects = $tempAdditionalOptions['redirects']; + $isRedirect = true; + + unset($tempAdditionalOptions['redirects']); + } + + $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); + unset($tempAdditionalOptions); + + $origFileUrl = $fileUrl; + + if (isset($options['prevent_ip_access_callable'])) { + throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config."); + } + + if (isset($options['gitlab-token'])) { + $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; + unset($options['gitlab-token']); + } + + if (isset($options['http'])) { + $options['http']['ignore_errors'] = true; + } + + if ($this->degradedMode && strpos($fileUrl, 'http://repo.packagist.org/') === 0) { + // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol + $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20); + $degradedPackagist = true; + } + + $maxFileSize = null; + if (isset($options['max_file_size'])) { + $maxFileSize = $options['max_file_size']; + unset($options['max_file_size']); + } + + $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => [$this, 'callbackGet']]); + + $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl); + $usingProxy = $proxy->getStatus(' using proxy (%s)'); + $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG); + unset($origFileUrl, $proxy, $usingProxy); + + // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 + if ((!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) { + $this->config->prohibitUrlByConfig($fileUrl, $this->io); + } + + if ($this->progress && !$isRedirect) { + $this->io->writeError("Downloading (connecting...)", false); + } + + $errorMessage = ''; + $errorCode = 0; + $result = false; + set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }', '', $msg); + + return true; + }); + $http_response_header = []; + try { + $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize); + + if (!empty($http_response_header[0])) { + $statusCode = self::findStatusCode($http_response_header); + if ($statusCode >= 300 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true)); + } + + if (in_array($statusCode, [401, 403]) && $retryAuthFailure) { + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header); + } + } + + $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null; + if ($contentLength && Platform::strlen($result) < $contentLength) { + // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP + $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength); + $e->setHeaders($http_response_header); + $e->setStatusCode(self::findStatusCode($http_response_header)); + try { + $e->setResponse($this->decodeResult($result, $http_response_header)); + } catch (\Exception $discarded) { + $e->setResponse($this->normalizeResult($result)); + } + + $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG); + + throw $e; + } + } catch (\Exception $e) { + if ($e instanceof TransportException && !empty($http_response_header[0])) { + $e->setHeaders($http_response_header); + $e->setStatusCode(self::findStatusCode($http_response_header)); + } + if ($e instanceof TransportException && $result !== false) { + $e->setResponse($this->decodeResult($result, $http_response_header)); + } + $result = false; + } + if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { + $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')'; + } + restore_error_handler(); + if (isset($e) && !$this->retry) { + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(''); + $this->io->writeError([ + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + + throw $e; + } + + $statusCode = null; + $contentType = null; + $locationHeader = null; + if (!empty($http_response_header[0])) { + $statusCode = self::findStatusCode($http_response_header); + $contentType = Response::findHeaderValue($http_response_header, 'content-type'); + $locationHeader = Response::findHeaderValue($http_response_header, 'location'); + } + + // check for bitbucket login page asking to authenticate + if ($originUrl === 'bitbucket.org' + && !$this->authHelper->isPublicBitBucketDownload($fileUrl) + && substr($fileUrl, -4) === '.zip' + && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip') + && $contentType && Preg::isMatch('{^text/html\b}i', $contentType) + ) { + $result = false; + if ($retryAuthFailure) { + $this->promptAuthAndRetry(401); + } + } + + // check for gitlab 404 when downloading archives + if ($statusCode === 404 + && in_array($originUrl, $this->config->get('gitlab-domains'), true) + && false !== strpos($fileUrl, 'archive.zip') + ) { + $result = false; + if ($retryAuthFailure) { + $this->promptAuthAndRetry(401); + } + } + + // handle 3xx redirects, 304 Not Modified is excluded + $hasFollowedRedirect = false; + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { + $hasFollowedRedirect = true; + $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); + } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { + if (!$this->retry) { + if ($this->progress && !$isRedirect) { + $this->io->overwriteError("Downloading (failed)", false); + } + + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode); + $e->setHeaders($http_response_header); + $e->setResponse($this->decodeResult($result, $http_response_header)); + $e->setStatusCode($statusCode); + throw $e; + } + $result = false; + } + + if ($this->progress && !$this->retry && !$isRedirect) { + $this->io->overwriteError("Downloading (".($result === false ? 'failed' : '100%').")", false); + } + + // decode gzip + if ($result && extension_loaded('zlib') && strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) { + try { + $result = $this->decodeResult($result, $http_response_header); + } catch (\Exception $e) { + if ($this->degradedMode) { + throw $e; + } + + $this->degradedMode = true; + $this->io->writeError([ + '', + 'Failed to decode response: '.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + } + + // handle copy command if download was successful + if (false !== $result && null !== $fileName && !$isRedirect) { + if ('' === $result) { + throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response'); + } + + $errorMessage = ''; + set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= Preg::replace('{^file_put_contents\(.*?\): }', '', $msg); + + return true; + }); + $result = (bool) file_put_contents($fileName, $result); + restore_error_handler(); + if (false === $result) { + throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage); + } + } + + if ($this->retry) { + $this->retry = false; + + $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + + if ($this->storeAuth) { + $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); + $this->storeAuth = false; + } + + return $result; + } + + if (false === $result) { + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode); + if (!empty($http_response_header[0])) { + $e->setHeaders($http_response_header); + } + + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(''); + $this->io->writeError([ + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + ]); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + + throw $e; + } + + if (!empty($http_response_header[0])) { + $this->lastHeaders = $http_response_header; + } + + return $result; + } + + /** + * Get contents of remote URL. + * + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param resource $context The stream context + * @param string[] $responseHeaders + * @param int $maxFileSize The maximum allowed file size + * + * @return string|false The response contents or false on failure + * + * @param-out list $responseHeaders + */ + protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null) + { + $result = false; + + try { + $e = null; + if ($maxFileSize !== null) { + $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize); + } else { + // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes + $result = file_get_contents($fileUrl, false, $context); + } + } catch (\Throwable $e) { + } + + if ($result !== false && $maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes'); + } + + // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php + if (\PHP_VERSION_ID >= 80400) { + $responseHeaders = http_get_last_response_headers(); + http_clear_last_response_headers(); + } else { + $responseHeaders = $http_response_header ?? []; + } + + if (null !== $e) { + throw $e; + } + + return $result; + } + + /** + * Get notification action. + * + * @param int $notificationCode The notification code + * @param int $severity The severity level + * @param string $message The message + * @param int $messageCode The message code + * @param int $bytesTransferred The loaded size + * @param int $bytesMax The total size + * + * @return void + * + * @throws TransportException + */ + protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax) + { + switch ($notificationCode) { + case STREAM_NOTIFY_FAILURE: + if (400 === $messageCode) { + // This might happen if your host is secured by ssl client certificate authentication + // but you do not send an appropriate certificate + throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); + } + break; + + case STREAM_NOTIFY_FILE_SIZE_IS: + $this->bytesMax = $bytesMax; + break; + + case STREAM_NOTIFY_PROGRESS: + if ($this->bytesMax > 0 && $this->progress) { + $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100)); + + if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { + $this->lastProgress = $progression; + $this->io->overwriteError("Downloading ($progression%)", false); + } + } + break; + + default: + break; + } + } + + /** + * @param positive-int $httpStatus + * @param string[] $headers + * + * @return void + */ + protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = []) + { + $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 /** always pass 1 as RemoteFilesystem is single threaded there is no race condition possible */); + + $this->storeAuth = $result['storeAuth']; + $this->retry = $result['retry']; + + if ($this->retry) { + throw new TransportException('RETRY'); + } + } + + /** + * @param mixed[] $additionalOptions + * + * @return mixed[] + */ + protected function getOptionsForUrl(string $originUrl, array $additionalOptions) + { + $tlsOptions = []; + $headers = []; + + if (extension_loaded('zlib')) { + $headers[] = 'Accept-Encoding: gzip'; + } + + $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions); + if (!$this->degradedMode) { + // degraded mode disables HTTP/1.1 which causes issues with some bad + // proxies/software due to the use of chunked encoding + $options['http']['protocol_version'] = 1.1; + $headers[] = 'Connection: close'; + } + + $headers = $this->authHelper->addAuthenticationHeader($headers, $originUrl, $this->fileUrl); + + $options['http']['follow_location'] = 0; + + if (isset($options['http']['header']) && !is_array($options['http']['header'])) { + $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n")); + } + foreach ($headers as $header) { + $options['http']['header'][] = $header; + } + + return $options; + } + + /** + * @param string[] $http_response_header + * @param mixed[] $additionalOptions + * @param string|false $result + * + * @return bool|string + */ + private function handleRedirect(array $http_response_header, array $additionalOptions, $result) + { + if ($locationHeader = Response::findHeaderValue($http_response_header, 'location')) { + if (parse_url($locationHeader, PHP_URL_SCHEME)) { + // Absolute URL; e.g. https://example.com/composer + $targetUrl = $locationHeader; + } elseif (parse_url($locationHeader, PHP_URL_HOST)) { + // Scheme relative; e.g. //example.com/foo + $targetUrl = $this->scheme.':'.$locationHeader; + } elseif ('/' === $locationHeader[0]) { + // Absolute path; e.g. /foo + $urlHost = parse_url($this->fileUrl, PHP_URL_HOST); + + // Replace path using hostname as an anchor. + $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl); + } else { + // Relative path; e.g. foo + // This actually differs from PHP which seems to add duplicate slashes. + $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl); + } + } + + if (!empty($targetUrl)) { + $this->redirects++; + + $this->io->writeError('', true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); + + $additionalOptions['redirects'] = $this->redirects; + + return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress); + } + + if (!$this->retry) { + $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')'); + $e->setHeaders($http_response_header); + $e->setResponse($this->decodeResult($result, $http_response_header)); + + throw $e; + } + + return false; + } + + /** + * @param string|false $result + * @param string[] $http_response_header + */ + private function decodeResult($result, array $http_response_header): ?string + { + // decode gzip + if ($result && extension_loaded('zlib')) { + $contentEncoding = Response::findHeaderValue($http_response_header, 'content-encoding'); + $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding); + + if ($decode) { + $result = zlib_decode($result); + + if ($result === false) { + throw new TransportException('Failed to decode zlib stream'); + } + } + } + + return $this->normalizeResult($result); + } + + /** + * @param string|false $result + */ + private function normalizeResult($result): ?string + { + if ($result === false) { + return null; + } + + return $result; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Silencer.php b/vendor/composer/composer/src/Composer/Util/Silencer.php new file mode 100644 index 0000000..6dd0efb --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Silencer.php @@ -0,0 +1,77 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * Temporarily suppress PHP error reporting, usually warnings and below. + * + * @author Niels Keurentjes + */ +class Silencer +{ + /** + * @var int[] Unpop stack + */ + private static $stack = []; + + /** + * Suppresses given mask or errors. + * + * @param int|null $mask Error levels to suppress, default value NULL indicates all warnings and below. + * @return int The old error reporting level. + */ + public static function suppress(?int $mask = null): int + { + if (!isset($mask)) { + $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED; + } + $old = error_reporting(); + self::$stack[] = $old; + error_reporting($old & ~$mask); + + return $old; + } + + /** + * Restores a single state. + */ + public static function restore(): void + { + if (!empty(self::$stack)) { + error_reporting(array_pop(self::$stack)); + } + } + + /** + * Calls a specified function while silencing warnings and below. + * + * @param callable $callable Function to execute. + * @param mixed $parameters Function to execute. + * @throws \Exception Any exceptions from the callback are rethrown. + * @return mixed Return value of the callback. + */ + public static function call(callable $callable, ...$parameters) + { + try { + self::suppress(); + $result = $callable(...$parameters); + self::restore(); + + return $result; + } catch (\Exception $e) { + // Use a finally block for this when requirements are raised to PHP 5.5 + self::restore(); + throw $e; + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php b/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php new file mode 100644 index 0000000..be4c976 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/StreamContextFactory.php @@ -0,0 +1,255 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Composer; +use Composer\CaBundle\CaBundle; +use Composer\Downloader\TransportException; +use Composer\Repository\PlatformRepository; +use Composer\Util\Http\ProxyManager; +use Psr\Log\LoggerInterface; + +/** + * Allows the creation of a basic context supporting http proxy + * + * @author Jordan Alliot + * @author Markus Tacker + */ +final class StreamContextFactory +{ + /** + * Creates a context supporting HTTP proxies + * + * @param non-empty-string $url URL the context is to be used for + * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array}} $defaultOptions + * @param mixed[] $defaultOptions Options to merge with the default + * @param mixed[] $defaultParams Parameters to specify on the context + * @throws \RuntimeException if https proxy required and OpenSSL uninstalled + * @return resource Default context + */ + public static function getContext(string $url, array $defaultOptions = [], array $defaultParams = []) + { + $options = ['http' => [ + // specify defaults again to try and work better with curlwrappers enabled + 'follow_location' => 1, + 'max_redirects' => 20, + ]]; + + $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions)); + unset($defaultOptions['http']['header']); + $options = array_replace_recursive($options, $defaultOptions); + + if (isset($options['http']['header'])) { + $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); + } + + return stream_context_create($options, $defaultParams); + } + + /** + * @param non-empty-string $url + * @param mixed[] $options + * @param bool $forCurl When true, will not add proxy values as these are handled separately + * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl?: mixed[]} + * @return array formatted as a stream context array + */ + public static function initOptions(string $url, array $options, bool $forCurl = false): array + { + // Make sure the headers are in an array form + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + if (is_string($options['http']['header'])) { + $options['http']['header'] = explode("\r\n", $options['http']['header']); + } + + // Add stream proxy options if there is a proxy + if (!$forCurl) { + $proxy = ProxyManager::getInstance()->getProxyForRequest($url); + $proxyOptions = $proxy->getContextOptions(); + if ($proxyOptions !== null) { + $isHttpsRequest = 0 === strpos($url, 'https://'); + + if ($proxy->isSecure()) { + if (!extension_loaded('openssl')) { + throw new TransportException('You must enable the openssl extension to use a secure proxy.'); + } + if ($isHttpsRequest) { + throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.'); + } + } elseif ($isHttpsRequest && !extension_loaded('openssl')) { + throw new TransportException('You must enable the openssl extension to make https requests through a proxy.'); + } + + // Header will be a Proxy-Authorization string or not set + if (isset($proxyOptions['http']['header'])) { + $options['http']['header'][] = $proxyOptions['http']['header']; + unset($proxyOptions['http']['header']); + } + $options = array_replace_recursive($options, $proxyOptions); + } + } + + if (defined('HHVM_VERSION')) { + $phpVersion = 'HHVM ' . HHVM_VERSION; + } else { + $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + } + + if ($forCurl) { + $curl = curl_version(); + $httpVersion = 'cURL '.$curl['version']; + } else { + $httpVersion = 'streams'; + } + + if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) { + $platformPhpVersion = PlatformRepository::getPlatformPhpVersion(); + $options['http']['header'][] = sprintf( + 'User-Agent: Composer/%s (%s; %s; %s; %s%s%s)', + Composer::getVersion(), + function_exists('php_uname') ? php_uname('s') : 'Unknown', + function_exists('php_uname') ? php_uname('r') : 'Unknown', + $phpVersion, + $httpVersion, + $platformPhpVersion ? '; Platform-PHP '.$platformPhpVersion : '', + Platform::getEnv('CI') ? '; CI' : '' + ); + } + + return $options; + } + + /** + * @param mixed[] $options + * + * @return mixed[] + */ + public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null): array + { + $ciphers = implode(':', [ + 'ECDHE-RSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256', + 'DHE-DSS-AES128-GCM-SHA256', + 'kEDH+AESGCM', + 'ECDHE-RSA-AES128-SHA256', + 'ECDHE-ECDSA-AES128-SHA256', + 'ECDHE-RSA-AES128-SHA', + 'ECDHE-ECDSA-AES128-SHA', + 'ECDHE-RSA-AES256-SHA384', + 'ECDHE-ECDSA-AES256-SHA384', + 'ECDHE-RSA-AES256-SHA', + 'ECDHE-ECDSA-AES256-SHA', + 'DHE-RSA-AES128-SHA256', + 'DHE-RSA-AES128-SHA', + 'DHE-DSS-AES128-SHA256', + 'DHE-RSA-AES256-SHA256', + 'DHE-DSS-AES256-SHA', + 'DHE-RSA-AES256-SHA', + 'AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'AES128-SHA256', + 'AES256-SHA256', + 'AES128-SHA', + 'AES256-SHA', + 'AES', + 'CAMELLIA', + 'DES-CBC3-SHA', + '!aNULL', + '!eNULL', + '!EXPORT', + '!DES', + '!RC4', + '!MD5', + '!PSK', + '!aECDH', + '!EDH-DSS-DES-CBC3-SHA', + '!EDH-RSA-DES-CBC3-SHA', + '!KRB5-DES-CBC3-SHA', + ]); + + /** + * CN_match and SNI_server_name are only known once a URL is passed. + * They will be set in the getOptionsForUrl() method which receives a URL. + * + * cafile or capath can be overridden by passing in those options to constructor. + */ + $defaults = [ + 'ssl' => [ + 'ciphers' => $ciphers, + 'verify_peer' => true, + 'verify_depth' => 7, + 'SNI_enabled' => true, + 'capture_peer_cert' => true, + ], + ]; + + if (isset($options['ssl'])) { + $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); + } + + /** + * Attempt to find a local cafile or throw an exception if none pre-set + * The user may go download one if this occurs. + */ + if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { + $result = CaBundle::getSystemCaRootBundlePath($logger); + + if (is_dir($result)) { + $defaults['ssl']['capath'] = $result; + } else { + $defaults['ssl']['cafile'] = $result; + } + } + + if (isset($defaults['ssl']['cafile']) && (!Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) { + throw new TransportException('The configured cafile was not valid or could not be read.'); + } + + if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !Filesystem::isReadable($defaults['ssl']['capath']))) { + throw new TransportException('The configured capath was not valid or could not be read.'); + } + + /** + * Disable TLS compression to prevent CRIME attacks where supported. + */ + $defaults['ssl']['disable_compression'] = true; + + return $defaults; + } + + /** + * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and + * NOT at the end of the array + * + * This method fixes the array by moving the content-type header to the end + * + * @link https://bugs.php.net/bug.php?id=61548 + * @param string|string[] $header + * @return string[] + */ + private static function fixHttpHeaderField($header): array + { + if (!is_array($header)) { + $header = explode("\r\n", $header); + } + uasort($header, static function ($el): int { + return stripos($el, 'content-type') === 0 ? 1 : -1; + }); + + return $header; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Svn.php b/vendor/composer/composer/src/Composer/Util/Svn.php new file mode 100644 index 0000000..506a14e --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Svn.php @@ -0,0 +1,367 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Pcre\Preg; + +/** + * @author Till Klampaeckel + * @author Jordi Boggiano + */ +class Svn +{ + private const MAX_QTY_AUTH_TRIES = 5; + + /** + * @var ?array{username: string, password: string} + */ + protected $credentials; + + /** + * @var bool + */ + protected $hasAuth; + + /** + * @var \Composer\IO\IOInterface + */ + protected $io; + + /** + * @var string + */ + protected $url; + + /** + * @var bool + */ + protected $cacheCredentials = true; + + /** + * @var ProcessExecutor + */ + protected $process; + + /** + * @var int + */ + protected $qtyAuthTries = 0; + + /** + * @var \Composer\Config + */ + protected $config; + + /** + * @var string|null + */ + private static $version; + + /** + * @param ProcessExecutor $process + */ + public function __construct(string $url, IOInterface $io, Config $config, ?ProcessExecutor $process = null) + { + $this->url = $url; + $this->io = $io; + $this->config = $config; + $this->process = $process ?: new ProcessExecutor($io); + } + + public static function cleanEnv(): void + { + // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 + Platform::clearEnv('DYLD_LIBRARY_PATH'); + } + + /** + * Execute an SVN remote command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command SVN command to run + * @param string $url SVN url + * @param ?string $cwd Working directory + * @param ?string $path Target for a checkout + * @param bool $verbose Output all output to the user + * + * @throws \RuntimeException + */ + public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string + { + // Ensure we are allowed to use this URL by config + $this->config->prohibitUrlByConfig($url, $this->io); + + return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose); + } + + /** + * Execute an SVN local command and try to fix up the process with credentials + * if necessary. + * + * @param non-empty-list $command SVN command to run + * @param string $path Path argument passed thru to the command + * @param string $cwd Working directory + * @param bool $verbose Output all output to the user + * + * @throws \RuntimeException + */ + public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string + { + // A local command has no remote url + return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); + } + + /** + * @param non-empty-list $svnCommand + */ + private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string + { + // Regenerate the command at each try, to use the newly user-provided credentials + $command = $this->getCommand($svnCommand, $url, $path); + + $output = null; + $io = $this->io; + $handler = static function ($type, $buffer) use (&$output, $io, $verbose) { + if ($type !== 'out') { + return null; + } + if (strpos($buffer, 'Redirecting to URL ') === 0) { + return null; + } + $output .= $buffer; + if ($verbose) { + $io->writeError($buffer, false); + } + }; + $status = $this->process->execute($command, $handler, $cwd); + if (0 === $status) { + return $output; + } + + $errorOutput = $this->process->getErrorOutput(); + $fullOutput = trim(implode("\n", [$output, $errorOutput])); + + // the error is not auth-related + if (false === stripos($fullOutput, 'Could not authenticate to server:') + && false === stripos($fullOutput, 'authorization failed') + && false === stripos($fullOutput, 'svn: E170001:') + && false === stripos($fullOutput, 'svn: E215004:')) { + throw new \RuntimeException($fullOutput); + } + + if (!$this->hasAuth()) { + $this->doAuthDance(); + } + + // try to authenticate if maximum quantity of tries not reached + if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { + // restart the process + return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose); + } + + throw new \RuntimeException( + 'wrong credentials provided ('.$fullOutput.')' + ); + } + + public function setCacheCredentials(bool $cacheCredentials): void + { + $this->cacheCredentials = $cacheCredentials; + } + + /** + * Repositories requests credentials, let's put them in. + * + * @throws \RuntimeException + * @return \Composer\Util\Svn + */ + protected function doAuthDance(): Svn + { + // cannot ask for credentials in non interactive mode + if (!$this->io->isInteractive()) { + throw new \RuntimeException( + 'can not ask for authentication in non interactive mode' + ); + } + + $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); + + $this->hasAuth = true; + $this->credentials = [ + 'username' => (string) $this->io->ask("Username: ", ''), + 'password' => (string) $this->io->askAndHideAnswer("Password: "), + ]; + + $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) "); + + return $this; + } + + /** + * A method to create the svn commands run. + * + * @param non-empty-list $cmd Usually 'svn ls' or something like that. + * @param string $url Repo URL. + * @param string $path Target for a checkout + * + * @return non-empty-list + */ + protected function getCommand(array $cmd, string $url, ?string $path = null): array + { + $cmd = array_merge( + $cmd, + ['--non-interactive'], + $this->getCredentialArgs(), + ['--', $url] + ); + + if ($path !== null) { + $cmd[] = $path; + } + + return $cmd; + } + + /** + * Return the credential string for the svn command. + * + * Adds --no-auth-cache when credentials are present. + * + * @return list + */ + protected function getCredentialArgs(): array + { + if (!$this->hasAuth()) { + return []; + } + + return array_merge( + $this->getAuthCacheArgs(), + ['--username', $this->getUsername(), '--password', $this->getPassword()] + ); + } + + /** + * Get the password for the svn command. Can be empty. + * + * @throws \LogicException + */ + protected function getPassword(): string + { + if ($this->credentials === null) { + throw new \LogicException("No svn auth detected."); + } + + return $this->credentials['password']; + } + + /** + * Get the username for the svn command. + * + * @throws \LogicException + */ + protected function getUsername(): string + { + if ($this->credentials === null) { + throw new \LogicException("No svn auth detected."); + } + + return $this->credentials['username']; + } + + /** + * Detect Svn Auth. + */ + protected function hasAuth(): bool + { + if (null !== $this->hasAuth) { + return $this->hasAuth; + } + + if (false === $this->createAuthFromConfig()) { + $this->createAuthFromUrl(); + } + + return (bool) $this->hasAuth; + } + + /** + * Return the no-auth-cache switch. + * + * @return list + */ + protected function getAuthCacheArgs(): array + { + return $this->cacheCredentials ? [] : ['--no-auth-cache']; + } + + /** + * Create the auth params from the configuration file. + */ + private function createAuthFromConfig(): bool + { + if (!$this->config->has('http-basic')) { + return $this->hasAuth = false; + } + + $authConfig = $this->config->get('http-basic'); + + $host = parse_url($this->url, PHP_URL_HOST); + if (isset($authConfig[$host])) { + $this->credentials = [ + 'username' => $authConfig[$host]['username'], + 'password' => $authConfig[$host]['password'], + ]; + + return $this->hasAuth = true; + } + + return $this->hasAuth = false; + } + + /** + * Create the auth params from the url + */ + private function createAuthFromUrl(): bool + { + $uri = parse_url($this->url); + if (empty($uri['user'])) { + return $this->hasAuth = false; + } + + $this->credentials = [ + 'username' => $uri['user'], + 'password' => !empty($uri['pass']) ? $uri['pass'] : '', + ]; + + return $this->hasAuth = true; + } + + /** + * Returns the version of the svn binary contained in PATH + */ + public function binaryVersion(): ?string + { + if (!self::$version) { + if (0 === $this->process->execute(['svn', '--version'], $output)) { + if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) { + self::$version = $match[1]; + } + } + } + + return self::$version; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/SyncHelper.php b/vendor/composer/composer/src/Composer/Util/SyncHelper.php new file mode 100644 index 0000000..9a7398c --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/SyncHelper.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Downloader\DownloaderInterface; +use Composer\Downloader\DownloadManager; +use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; + +class SyncHelper +{ + /** + * Helps you download + install a single package in a synchronous way + * + * This executes all the required steps and waits for promises to complete + * + * @param Loop $loop Loop instance which you can get from $composer->getLoop() + * @param DownloaderInterface|DownloadManager $downloader DownloadManager instance or Downloader instance you can get from $composer->getDownloadManager()->getDownloader('zip') for example + * @param string $path The installation path for the package + * @param PackageInterface $package The package to install + * @param PackageInterface|null $prevPackage The previous package if this is an update and not an initial installation + */ + public static function downloadAndInstallPackageSync(Loop $loop, $downloader, string $path, PackageInterface $package, ?PackageInterface $prevPackage = null): void + { + assert($downloader instanceof DownloaderInterface || $downloader instanceof DownloadManager); + + $type = $prevPackage !== null ? 'update' : 'install'; + + try { + self::await($loop, $downloader->download($package, $path, $prevPackage)); + + self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage)); + + if ($type === 'update' && $prevPackage !== null) { + self::await($loop, $downloader->update($package, $prevPackage, $path)); + } else { + self::await($loop, $downloader->install($package, $path)); + } + } catch (\Exception $e) { + self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); + throw $e; + } + + self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); + } + + /** + * Waits for a promise to resolve + * + * @param Loop $loop Loop instance which you can get from $composer->getLoop() + * @phpstan-param PromiseInterface|null $promise + */ + public static function await(Loop $loop, ?PromiseInterface $promise = null): void + { + if ($promise !== null) { + $loop->wait([$promise]); + } + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Tar.php b/vendor/composer/composer/src/Composer/Util/Tar.php new file mode 100644 index 0000000..1fb608f --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Tar.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Wissem Riahi + */ +class Tar +{ + public static function getComposerJson(string $pathToArchive): ?string + { + $phar = new \PharData($pathToArchive); + + if (!$phar->valid()) { + return null; + } + + return self::extractComposerJsonFromFolder($phar); + } + + /** + * @throws \RuntimeException + */ + private static function extractComposerJsonFromFolder(\PharData $phar): string + { + if (isset($phar['composer.json'])) { + return $phar['composer.json']->getContent(); + } + + $topLevelPaths = []; + foreach ($phar as $folderFile) { + $name = $folderFile->getBasename(); + + if ($folderFile->isDir()) { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + } + } + + $composerJsonPath = key($topLevelPaths).'/composer.json'; + if (\count($topLevelPaths) > 0 && isset($phar[$composerJsonPath])) { + return $phar[$composerJsonPath]->getContent(); + } + + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); + } +} diff --git a/vendor/composer/composer/src/Composer/Util/TlsHelper.php b/vendor/composer/composer/src/Composer/Util/TlsHelper.php new file mode 100644 index 0000000..da0801a --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/TlsHelper.php @@ -0,0 +1,209 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\CaBundle\CaBundle; +use Composer\Pcre\Preg; + +/** + * @author Chris Smith + * @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 compatibility, this class will be removed in Composer 3.0 + */ +final class TlsHelper +{ + /** + * Match hostname against a certificate. + * + * @param mixed $certificate X.509 certificate + * @param string $hostname Hostname in the URL + * @param string $cn Set to the common name of the certificate iff match found + */ + public static function checkCertificateHost($certificate, string $hostname, ?string &$cn = null): bool + { + $names = self::getCertificateNames($certificate); + + if (empty($names)) { + return false; + } + + $combinedNames = array_merge($names['san'], [$names['cn']]); + $hostname = strtolower($hostname); + + foreach ($combinedNames as $certName) { + $matcher = self::certNameMatcher($certName); + + if ($matcher && $matcher($hostname)) { + $cn = $names['cn']; + + return true; + } + } + + return false; + } + + /** + * Extract DNS names out of an X.509 certificate. + * + * @param mixed $certificate X.509 certificate + * + * @return array{cn: string, san: string[]}|null + */ + public static function getCertificateNames($certificate): ?array + { + if (is_array($certificate)) { + $info = $certificate; + } elseif (CaBundle::isOpensslParseSafe()) { + $info = openssl_x509_parse($certificate, false); + } + + if (!isset($info['subject']['commonName'])) { + return null; + } + + $commonName = strtolower($info['subject']['commonName']); + $subjectAltNames = []; + + if (isset($info['extensions']['subjectAltName'])) { + $subjectAltNames = Preg::split('{\s*,\s*}', $info['extensions']['subjectAltName']); + $subjectAltNames = array_filter( + array_map(static function ($name): ?string { + if (0 === strpos($name, 'DNS:')) { + return strtolower(ltrim(substr($name, 4))); + } + + return null; + }, $subjectAltNames), + function (?string $san) { + return $san !== null; + } + ); + $subjectAltNames = array_values($subjectAltNames); + } + + return [ + 'cn' => $commonName, + 'san' => $subjectAltNames, + ]; + } + + /** + * Get the certificate pin. + * + * By Kevin McArthur of StormTide Digital Studios Inc. + * @KevinSMcArthur / https://github.com/StormTide + * + * See https://tools.ietf.org/html/draft-ietf-websec-key-pinning-02 + * + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + public static function getCertificateFingerprint(string $certificate): string + { + $pubkey = openssl_get_publickey($certificate); + if ($pubkey === false) { + throw new \RuntimeException('Failed to retrieve the public key from certificate'); + } + $pubkeydetails = openssl_pkey_get_details($pubkey); + $pubkeypem = $pubkeydetails['key']; + //Convert PEM to DER before SHA1'ing + $start = '-----BEGIN PUBLIC KEY-----'; + $end = '-----END PUBLIC KEY-----'; + $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); + $der = base64_decode($pemtrim); + + return hash('sha1', $der); + } + + /** + * Test if it is safe to use the PHP function openssl_x509_parse(). + * + * This checks if OpenSSL extensions is vulnerable to remote code execution + * via the exploit documented as CVE-2013-6420. + */ + public static function isOpensslParseSafe(): bool + { + return CaBundle::isOpensslParseSafe(); + } + + /** + * Convert certificate name into matching function. + * + * @param string $certName CN/SAN + */ + private static function certNameMatcher(string $certName): ?callable + { + $wildcards = substr_count($certName, '*'); + + if (0 === $wildcards) { + // Literal match. + return static function ($hostname) use ($certName): bool { + return $hostname === $certName; + }; + } + + if (1 === $wildcards) { + $components = explode('.', $certName); + + if (3 > count($components)) { + // Must have 3+ components + return null; + } + + $firstComponent = $components[0]; + + // Wildcard must be the last character. + if ('*' !== $firstComponent[strlen($firstComponent) - 1]) { + return null; + } + + $wildcardRegex = preg_quote($certName); + $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex); + $wildcardRegex = "{^{$wildcardRegex}$}"; + + return static function ($hostname) use ($wildcardRegex): bool { + return Preg::isMatch($wildcardRegex, $hostname); + }; + } + + return null; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Url.php b/vendor/composer/composer/src/Composer/Util/Url.php new file mode 100644 index 0000000..32f6134 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Url.php @@ -0,0 +1,123 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\Pcre\Preg; + +/** + * @author Jordi Boggiano + */ +class Url +{ + /** + * @param non-empty-string $url + * @return non-empty-string the updated URL + */ + public static function updateDistReference(Config $config, string $url, string $ref): string + { + $host = parse_url($url, PHP_URL_HOST); + + if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') { + if (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { + // update legacy github archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) { + // update current github web archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (Preg::isMatch('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { + // update api archives to the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } + } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') { + if (Preg::isMatch('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { + // update Bitbucket archives to the proper reference + $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4]; + } + } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') { + if (Preg::isMatch('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) { + // update Gitlab archives to the proper reference + $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref; + } + } elseif (in_array($host, $config->get('github-domains'), true)) { + $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url); + } elseif (in_array($host, $config->get('gitlab-domains'), true)) { + $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url); + } + + assert($url !== ''); + + return $url; + } + + /** + * @param non-empty-string $url + * @return non-empty-string + */ + public static function getOrigin(Config $config, string $url): string + { + if (0 === strpos($url, 'file://')) { + return $url; + } + + $origin = (string) parse_url($url, PHP_URL_HOST); + if ($port = parse_url($url, PHP_URL_PORT)) { + $origin .= ':'.$port; + } + + if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') { + return 'github.com'; + } + + if ($origin === 'repo.packagist.org') { + return 'packagist.org'; + } + + if ($origin === '') { + $origin = $url; + } + + // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl + // is the host without the path, so we look for the registered gitlab-domains matching the host here + if ( + false === strpos($origin, '/') + && !in_array($origin, $config->get('gitlab-domains'), true) + ) { + foreach ($config->get('gitlab-domains') as $gitlabDomain) { + if ($gitlabDomain !== '' && str_starts_with($gitlabDomain, $origin)) { + return $gitlabDomain; + } + } + } + + return $origin; + } + + public static function sanitize(string $url): string + { + // GitHub repository rename result in redirect locations containing the access_token as GET parameter + // e.g. https://api.github.com/repositories/9999999999?access_token=github_token + $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); + + $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string { + // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that + if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) { + return $m['prefix'].'***:***@'; + } + + return $m['prefix'].$m['user'].':***@'; + }, $url); + + return $url; + } +} diff --git a/vendor/composer/composer/src/Composer/Util/Zip.php b/vendor/composer/composer/src/Composer/Util/Zip.php new file mode 100644 index 0000000..9fd8f07 --- /dev/null +++ b/vendor/composer/composer/src/Composer/Util/Zip.php @@ -0,0 +1,101 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Andreas Schempp + */ +class Zip +{ + /** + * Gets content of the root composer.json inside a ZIP archive. + */ + public static function getComposerJson(string $pathToZip): ?string + { + if (!extension_loaded('zip')) { + throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); + } + + $zip = new \ZipArchive(); + if ($zip->open($pathToZip) !== true) { + return null; + } + + if (0 === $zip->numFiles) { + $zip->close(); + + return null; + } + + $foundFileIndex = self::locateFile($zip, 'composer.json'); + + $content = null; + $configurationFileName = $zip->getNameIndex($foundFileIndex); + $stream = $zip->getStream($configurationFileName); + + if (false !== $stream) { + $content = stream_get_contents($stream); + } + + $zip->close(); + + return $content; + } + + /** + * Find a file by name, returning the one that has the shortest path. + * + * @throws \RuntimeException + */ + private static function locateFile(\ZipArchive $zip, string $filename): int + { + // return root composer.json if it is there and is a file + if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + $topLevelPaths = []; + for ($i = 0; $i < $zip->numFiles; $i++) { + $name = $zip->getNameIndex($i); + $dirname = dirname($name); + + // ignore OSX specific resource fork folder + if (strpos($name, '__MACOSX') !== false) { + continue; + } + + // handle archives with proper TOC + if ($dirname === '.') { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + continue; + } + + // handle archives which do not have a TOC record for the directory itself + if (false === strpos($dirname, '\\') && false === strpos($dirname, '/')) { + $topLevelPaths[$dirname.'/'] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + } + } + + if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); + } +} diff --git a/vendor/composer/composer/src/bootstrap.php b/vendor/composer/composer/src/bootstrap.php new file mode 100644 index 0000000..886b4ff --- /dev/null +++ b/vendor/composer/composer/src/bootstrap.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Composer\Autoload\ClassLoader; + +function includeIfExists(string $file): ?ClassLoader +{ + return file_exists($file) ? include $file : null; +} + +if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) { + echo 'You must set up the project dependencies using `composer install`'.PHP_EOL. + 'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL; + exit(1); +} + +return $loader; diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 69367c5..7015d43 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1,32 +1,42 @@ { "packages": [ { - "name": "robertdevore/wpcom-check", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "name": "composer/ca-bundle", + "version": "1.5.7", + "version_normalized": "1.5.7.0", "source": { "type": "git", - "url": "https://github.com/robertdevore/wpcom-check.git", - "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058" + "url": "https://github.com/composer/ca-bundle.git", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robertdevore/wpcom-check/zipball/25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", - "reference": "25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82", "shasum": "" }, "require": { - "php": ">=7.4" + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" }, - "time": "2025-01-07T17:17:53+00:00", + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "time": "2025-05-26T15:08:54+00:00", "type": "library", "extra": { - "wordpress-plugin": true + "branch-alias": { + "dev-main": "1.x-dev" + } }, "installation-source": "dist", "autoload": { "psr-4": { - "RobertDevore\\WPComCheck\\": "src/" + "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -35,28 +45,5724 @@ ], "authors": [ { - "name": "Robert DeVore", - "email": "me@robertdevore.com", - "homepage": "https://github.com/robertdevore", - "role": "Developer" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "A utility to handle WordPress.com-specific plugin compatibility and auto-deactivation.", - "homepage": "https://github.com/robertdevore/wpcom-check", + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", "keywords": [ - "compatibility", - "deactivation", - "plugin", + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./ca-bundle" + }, + { + "name": "composer/class-map-generator", + "version": "1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" + }, + "time": "2025-03-24T13:50:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./class-map-generator" + }, + { + "name": "composer/composer", + "version": "2.8.9", + "version_normalized": "2.8.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.2 || ^3.2", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^6.3.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^2.11 || ^3.2", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "time": "2025-05-13T12:01:37+00:00", + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, + "branch-alias": { + "dev-main": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.8.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./composer" + }, + { + "name": "composer/installers", + "version": "v2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "time": "2024-06-24T20:46:46+00:00", + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", "wordpress", - "wordpress.com" + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./installers" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "time": "2021-04-07T13:37:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./metadata-minifier" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "time": "2024-11-12T16:29:46+00:00", + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/robertdevore/wpcom-check/issues", - "source": "https://github.com/robertdevore/wpcom-check/tree/1.0.1" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./pcre" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "version_normalized": "3.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "time": "2024-09-19T14:15:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./semver" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.9", + "version_normalized": "1.5.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "time": "2025-05-12T21:07:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } }, - "install-path": "../robertdevore/wpcom-check" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./spdx-licenses" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "version_normalized": "3.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "time": "2024-05-06T16:37:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "install-path": "./xdebug-handler" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "time": "2023-01-05T11:28:13+00:00", + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "install-path": "../dealerdirect/phpcodesniffer-composer-installer" + }, + { + "name": "eftec/bladeone", + "version": "3.52", + "version_normalized": "3.52.0.0", + "source": { + "type": "git", + "url": "https://github.com/EFTEC/BladeOne.git", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EFTEC/BladeOne/zipball/a19bf66917de0b29836983db87a455a4f6e32148", + "reference": "a19bf66917de0b29836983db87a455a4f6e32148", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16.1", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.5.4" + }, + "suggest": { + "eftec/bladeonehtml": "Extension to create forms", + "ext-mbstring": "This extension is used if it's active" + }, + "time": "2021-04-17T13:49:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "eftec\\bladeone\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jorge Patricio Castro Castillo", + "email": "jcastro@eftec.cl" + } + ], + "description": "The standalone version Blade Template Engine from Laravel in a single php file", + "homepage": "https://github.com/EFTEC/BladeOne", + "keywords": [ + "blade", + "php", + "template", + "templating", + "view" + ], + "support": { + "issues": "https://github.com/EFTEC/BladeOne/issues", + "source": "https://github.com/EFTEC/BladeOne/tree/3.52" + }, + "install-path": "../eftec/bladeone" + }, + { + "name": "gettext/gettext", + "version": "v4.8.12", + "version_normalized": "4.8.12.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/11af89ee6c087db3cf09ce2111a150bca5c46e12", + "reference": "11af89ee6c087db3cf09ce2111a150bca5c46e12", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/view": "^5.0.x-dev", + "phpunit/phpunit": "^4.8|^5.7|^6.5", + "squizlabs/php_codesniffer": "^3.0", + "symfony/yaml": "~2", + "twig/extensions": "*", + "twig/twig": "^1.31|^2.0" + }, + "suggest": { + "illuminate/view": "Is necessary if you want to use the Blade extractor", + "symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator", + "twig/extensions": "Is necessary if you want to use the Twig extractor", + "twig/twig": "Is necessary if you want to use the Twig extractor" + }, + "time": "2024-05-18T10:25:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/oscarotero/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/oscarotero/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v4.8.12" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "install-path": "../gettext/gettext" + }, + { + "name": "gettext/languages", + "version": "2.12.1", + "version_normalized": "2.12.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/0b0b0851c55168e1dfb14305735c64019732b5f1", + "reference": "0b0b0851c55168e1dfb14305735c64019732b5f1", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "time": "2025-03-19T11:14:02+00:00", + "bin": [ + "bin/export-plural-rules", + "bin/import-cldr-data" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.12.1" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "install-path": "../gettext/languages" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.4.2", + "version_normalized": "6.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "1.2.0", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "time": "2025-06-03T18:27:04+00:00", + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" + }, + "install-path": "../justinrainbow/json-schema" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.1", + "version_normalized": "4.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "time": "2024-11-28T04:54:44+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "install-path": "../marc-mabe/php-enum" + }, + { + "name": "mck89/peast", + "version": "v1.17.0", + "version_normalized": "1.17.0.0", + "source": { + "type": "git", + "url": "https://github.com/mck89/peast.git", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mck89/peast/zipball/3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "reference": "3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "time": "2025-03-07T19:44:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Peast\\": "lib/Peast/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marco Marchiò", + "email": "marco.mm89@gmail.com" + } + ], + "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.17.0" + }, + "install-path": "../mck89/peast" + }, + { + "name": "nb/oxymel", + "version": "v0.1.0", + "version_normalized": "0.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nb/oxymel.git", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nb/oxymel/zipball/cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "time": "2013-02-24T15:01:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Oxymel": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikolay Bachiyski", + "email": "nb@nikolay.bg", + "homepage": "http://extrapolate.me/" + } + ], + "description": "A sweet XML builder", + "homepage": "https://github.com/nb/oxymel", + "keywords": [ + "xml" + ], + "support": { + "issues": "https://github.com/nb/oxymel/issues", + "source": "https://github.com/nb/oxymel/tree/master" + }, + "install-path": "../nb/oxymel" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "version_normalized": "9.3.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "time": "2019-12-27T09:44:58+00:00", + "type": "phpcodesniffer-standard", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "install-path": "../phpcompatibility/php-compatibility" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.3", + "version_normalized": "1.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "time": "2024-04-24T21:30:46+00:00", + "type": "phpcodesniffer-standard", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "install-path": "../phpcompatibility/phpcompatibility-paragonie" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.7", + "version_normalized": "2.1.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "time": "2025-05-12T16:38:37+00:00", + "type": "phpcodesniffer-standard", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "install-path": "../phpcompatibility/phpcompatibility-wp" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/fa4b8d051e278072928e32d817456a7fdb57b6ca", + "reference": "fa4b8d051e278072928e32d817456a7fdb57b6ca", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "time": "2025-06-14T07:40:39+00:00", + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "install-path": "../phpcsstandards/phpcsextra" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "reference": "65355670ac17c34cd235cf9d3ceae1b9252c4dad", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.13.0 || ^4.0" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + }, + "time": "2025-06-12T04:32:33+00:00", + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "phpcs4", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "install-path": "../phpcsstandards/phpcsutils" + }, + { + "name": "psr/container", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:50:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-07-14T16:41:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "install-path": "../psr/log" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "time": "2024-05-24T10:39:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/promise" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "time": "2024-07-11T14:55:45+00:00", + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "install-path": "../seld/jsonlint" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2022-08-31T10:31:18+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "install-path": "../seld/phar-utils" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "time": "2023-09-03T09:24:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "install-path": "../seld/signal-handler" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.2", + "version_normalized": "3.13.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "time": "2025-06-17T22:17:01+00:00", + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "install-path": "../squizlabs/php_codesniffer" + }, + { + "name": "symfony/console", + "version": "v5.4.47", + "version_normalized": "5.4.47.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "time": "2024-11-06T11:30:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/console" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "version_normalized": "2.5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-09-25T14:11:13+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.45", + "version_normalized": "5.4.45.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" + }, + "time": "2024-10-22T13:05:35+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/filesystem" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "version_normalized": "5.4.45.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2024-09-28T13:32:08+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-12-23T08:48:59+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2025-01-02T08:10:11+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "version_normalized": "2.5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2024-09-25T14:11:13+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/string", + "version": "v5.4.47", + "version_normalized": "5.4.47.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "time": "2024-11-10T20:33:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "wp-cli/cache-command", + "version": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cache-command.git", + "reference": "14f76b0bc8f9fa0a680e9c70e18fcf627774d055" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cache-command/zipball/14f76b0bc8f9fa0a680e9c70e18fcf627774d055", + "reference": "14f76b0bc8f9fa0a680e9c70e18fcf627774d055", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-05-06T01:43:20+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "cache", + "cache add", + "cache decr", + "cache delete", + "cache flush", + "cache flush-group", + "cache get", + "cache incr", + "cache patch", + "cache pluck", + "cache replace", + "cache set", + "cache supports", + "cache type", + "transient", + "transient delete", + "transient get", + "transient list", + "transient patch", + "transient pluck", + "transient set", + "transient type" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "cache-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manages object and transient caches.", + "homepage": "https://github.com/wp-cli/cache-command", + "support": { + "issues": "https://github.com/wp-cli/cache-command/issues", + "source": "https://github.com/wp-cli/cache-command/tree/v2.2.0" + }, + "install-path": "../wp-cli/cache-command" + }, + { + "name": "wp-cli/checksum-command", + "version": "v2.3.1", + "version_normalized": "2.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/checksum-command.git", + "reference": "39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe", + "reference": "39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-10T11:02:20+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "core verify-checksums", + "plugin verify-checksums" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "checksum-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Verifies file integrity by comparing to published checksums.", + "homepage": "https://github.com/wp-cli/checksum-command", + "support": { + "issues": "https://github.com/wp-cli/checksum-command/issues", + "source": "https://github.com/wp-cli/checksum-command/tree/v2.3.1" + }, + "install-path": "../wp-cli/checksum-command" + }, + { + "name": "wp-cli/config-command", + "version": "v2.3.8", + "version_normalized": "2.3.8.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/config-command.git", + "reference": "994b3dc9e8284fc978366920d5c5ae0dde3a004e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/config-command/zipball/994b3dc9e8284fc978366920d5c5ae0dde3a004e", + "reference": "994b3dc9e8284fc978366920d5c5ae0dde3a004e", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12", + "wp-cli/wp-config-transformer": "^1.4.0" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4.2.8" + }, + "time": "2025-04-11T09:37:43+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "config", + "config edit", + "config delete", + "config create", + "config get", + "config has", + "config is-true", + "config list", + "config path", + "config set", + "config shuffle-salts" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "config-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + }, + { + "name": "Alain Schlesser", + "email": "alain.schlesser@gmail.com", + "homepage": "https://www.alainschlesser.com" + } + ], + "description": "Generates and reads the wp-config.php file.", + "homepage": "https://github.com/wp-cli/config-command", + "support": { + "issues": "https://github.com/wp-cli/config-command/issues", + "source": "https://github.com/wp-cli/config-command/tree/v2.3.8" + }, + "install-path": "../wp-cli/config-command" + }, + { + "name": "wp-cli/core-command", + "version": "v2.1.20", + "version_normalized": "2.1.20.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/core-command.git", + "reference": "83e4692784a815bb7f5df10b72204f237b5224b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/core-command/zipball/83e4692784a815bb7f5df10b72204f237b5224b9", + "reference": "83e4692784a815bb7f5df10b72204f237b5224b9", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4 || ^2 || ^3", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/checksum-command": "^1 || ^2", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-16T11:23:00+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "core", + "core check-update", + "core download", + "core install", + "core is-installed", + "core multisite-convert", + "core multisite-install", + "core update", + "core update-db", + "core version" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "core-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Downloads, installs, updates, and manages a WordPress installation.", + "homepage": "https://github.com/wp-cli/core-command", + "support": { + "issues": "https://github.com/wp-cli/core-command/issues", + "source": "https://github.com/wp-cli/core-command/tree/v2.1.20" + }, + "install-path": "../wp-cli/core-command" + }, + { + "name": "wp-cli/cron-command", + "version": "v2.3.2", + "version_normalized": "2.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cron-command.git", + "reference": "6f450028a75ebd275f12cad62959a0709bf3e7c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cron-command/zipball/6f450028a75ebd275f12cad62959a0709bf3e7c1", + "reference": "6f450028a75ebd275f12cad62959a0709bf3e7c1", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/eval-command": "^2.0", + "wp-cli/server-command": "^2.0", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T11:55:20+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "cron", + "cron test", + "cron event", + "cron event delete", + "cron event list", + "cron event run", + "cron event schedule", + "cron schedule", + "cron schedule list", + "cron event unschedule" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "cron-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules.", + "homepage": "https://github.com/wp-cli/cron-command", + "support": { + "issues": "https://github.com/wp-cli/cron-command/issues", + "source": "https://github.com/wp-cli/cron-command/tree/v2.3.2" + }, + "install-path": "../wp-cli/cron-command" + }, + { + "name": "wp-cli/db-command", + "version": "v2.1.3", + "version_normalized": "2.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/db-command.git", + "reference": "f857c91454d7092fa672bc388512a51752d9264a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/f857c91454d7092fa672bc388512a51752d9264a", + "reference": "f857c91454d7092fa672bc388512a51752d9264a", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-10T11:02:04+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "db", + "db clean", + "db create", + "db drop", + "db reset", + "db check", + "db optimize", + "db prefix", + "db repair", + "db cli", + "db query", + "db export", + "db import", + "db search", + "db tables", + "db size", + "db columns" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "db-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Performs basic database operations using credentials stored in wp-config.php.", + "homepage": "https://github.com/wp-cli/db-command", + "support": { + "issues": "https://github.com/wp-cli/db-command/issues", + "source": "https://github.com/wp-cli/db-command/tree/v2.1.3" + }, + "install-path": "../wp-cli/db-command" + }, + { + "name": "wp-cli/embed-command", + "version": "v2.0.18", + "version_normalized": "2.0.18.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/embed-command.git", + "reference": "52f59a1dacf1d4a1c68fd685f27909e1f493816b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/embed-command/zipball/52f59a1dacf1d4a1c68fd685f27909e1f493816b", + "reference": "52f59a1dacf1d4a1c68fd685f27909e1f493816b", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-10T11:01:32+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "embed", + "embed fetch", + "embed provider", + "embed provider list", + "embed provider match", + "embed handler", + "embed handler list", + "embed cache", + "embed cache clear", + "embed cache find", + "embed cache trigger" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "embed-command.php" + ], + "psr-4": { + "WP_CLI\\Embeds\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Birchler", + "homepage": "https://pascalbirchler.com/" + } + ], + "description": "Inspects oEmbed providers, clears embed cache, and more.", + "homepage": "https://github.com/wp-cli/embed-command", + "support": { + "issues": "https://github.com/wp-cli/embed-command/issues", + "source": "https://github.com/wp-cli/embed-command/tree/v2.0.18" + }, + "install-path": "../wp-cli/embed-command" + }, + { + "name": "wp-cli/entity-command", + "version": "v2.8.4", + "version_normalized": "2.8.4.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/entity-command.git", + "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/213611f8ab619ca137d983e9b987f7fbf1ac21d4", + "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/cache-command": "^1 || ^2", + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/media-command": "^1.1 || ^2", + "wp-cli/super-admin-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-05-06T16:12:49+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "comment", + "comment approve", + "comment count", + "comment create", + "comment delete", + "comment exists", + "comment generate", + "comment get", + "comment list", + "comment meta", + "comment meta add", + "comment meta delete", + "comment meta get", + "comment meta list", + "comment meta patch", + "comment meta pluck", + "comment meta update", + "comment recount", + "comment spam", + "comment status", + "comment trash", + "comment unapprove", + "comment unspam", + "comment untrash", + "comment update", + "menu", + "menu create", + "menu delete", + "menu item", + "menu item add-custom", + "menu item add-post", + "menu item add-term", + "menu item delete", + "menu item list", + "menu item update", + "menu list", + "menu location", + "menu location assign", + "menu location list", + "menu location remove", + "network meta", + "network meta add", + "network meta delete", + "network meta get", + "network meta list", + "network meta patch", + "network meta pluck", + "network meta update", + "option", + "option add", + "option delete", + "option get", + "option list", + "option patch", + "option pluck", + "option update", + "option set-autoload", + "option get-autoload", + "post", + "post create", + "post delete", + "post edit", + "post exists", + "post generate", + "post get", + "post list", + "post meta", + "post meta add", + "post meta clean-duplicates", + "post meta delete", + "post meta get", + "post meta list", + "post meta patch", + "post meta pluck", + "post meta update", + "post term", + "post term add", + "post term list", + "post term remove", + "post term set", + "post update", + "post url-to-id", + "post-type", + "post-type get", + "post-type list", + "site", + "site activate", + "site archive", + "site create", + "site generate", + "site deactivate", + "site delete", + "site empty", + "site list", + "site mature", + "site meta", + "site meta add", + "site meta delete", + "site meta get", + "site meta list", + "site meta patch", + "site meta pluck", + "site meta update", + "site option", + "site private", + "site public", + "site spam", + "site unarchive", + "site unmature", + "site unspam", + "taxonomy", + "taxonomy get", + "taxonomy list", + "term", + "term create", + "term delete", + "term generate", + "term get", + "term list", + "term meta", + "term meta add", + "term meta delete", + "term meta get", + "term meta list", + "term meta patch", + "term meta pluck", + "term meta update", + "term recount", + "term update", + "user", + "user add-cap", + "user add-role", + "user application-password", + "user application-password create", + "user application-password delete", + "user application-password exists", + "user application-password get", + "user application-password list", + "user application-password record-usage", + "user application-password update", + "user create", + "user delete", + "user exists", + "user generate", + "user get", + "user import-csv", + "user list", + "user list-caps", + "user meta", + "user meta add", + "user meta delete", + "user meta get", + "user meta list", + "user meta patch", + "user meta pluck", + "user meta update", + "user remove-cap", + "user remove-role", + "user reset-password", + "user session", + "user session destroy", + "user session list", + "user set-role", + "user signup", + "user signup activate", + "user signup delete", + "user signup get", + "user signup list", + "user spam", + "user term", + "user term add", + "user term list", + "user term remove", + "user term set", + "user unspam", + "user update" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "entity-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manage WordPress comments, menus, options, posts, sites, terms, and users.", + "homepage": "https://github.com/wp-cli/entity-command", + "support": { + "issues": "https://github.com/wp-cli/entity-command/issues", + "source": "https://github.com/wp-cli/entity-command/tree/v2.8.4" + }, + "install-path": "../wp-cli/entity-command" + }, + { + "name": "wp-cli/eval-command", + "version": "v2.2.6", + "version_normalized": "2.2.6.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/eval-command.git", + "reference": "20ec428a7b9bc604fab0bd33ee8fa20662650455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/eval-command/zipball/20ec428a7b9bc604fab0bd33ee8fa20662650455", + "reference": "20ec428a7b9bc604fab0bd33ee8fa20662650455", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2024-11-24T17:28:06+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "eval", + "eval-file" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "eval-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Executes arbitrary PHP code or files.", + "homepage": "https://github.com/wp-cli/eval-command", + "support": { + "issues": "https://github.com/wp-cli/eval-command/issues", + "source": "https://github.com/wp-cli/eval-command/tree/v2.2.6" + }, + "install-path": "../wp-cli/eval-command" + }, + { + "name": "wp-cli/export-command", + "version": "v2.1.14", + "version_normalized": "2.1.14.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/export-command.git", + "reference": "2af32bf12c1bccd6561a215dbbafc2f272647ee8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/export-command/zipball/2af32bf12c1bccd6561a215dbbafc2f272647ee8", + "reference": "2af32bf12c1bccd6561a215dbbafc2f272647ee8", + "shasum": "" + }, + "require": { + "nb/oxymel": "~0.1.0", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/import-command": "^1 || ^2", + "wp-cli/media-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T15:29:08+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "export" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "export-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Exports WordPress content to a WXR file.", + "homepage": "https://github.com/wp-cli/export-command", + "support": { + "issues": "https://github.com/wp-cli/export-command/issues", + "source": "https://github.com/wp-cli/export-command/tree/v2.1.14" + }, + "install-path": "../wp-cli/export-command" + }, + { + "name": "wp-cli/extension-command", + "version": "v2.1.24", + "version_normalized": "2.1.24.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/extension-command.git", + "reference": "d21a2f504ac43a86b6b08697669b5b0844748133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/d21a2f504ac43a86b6b08697669b5b0844748133", + "reference": "d21a2f504ac43a86b6b08697669b5b0844748133", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4 || ^2 || ^3", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/cache-command": "^2.0", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/language-command": "^2.0", + "wp-cli/scaffold-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4.3.7" + }, + "time": "2025-05-06T19:17:53+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "plugin", + "plugin activate", + "plugin deactivate", + "plugin delete", + "plugin get", + "plugin install", + "plugin is-installed", + "plugin list", + "plugin path", + "plugin search", + "plugin status", + "plugin toggle", + "plugin uninstall", + "plugin update", + "theme", + "theme activate", + "theme delete", + "theme disable", + "theme enable", + "theme get", + "theme install", + "theme is-installed", + "theme list", + "theme mod", + "theme mod get", + "theme mod set", + "theme mod remove", + "theme path", + "theme search", + "theme status", + "theme update", + "theme mod list" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "extension-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + }, + { + "name": "Alain Schlesser", + "email": "alain.schlesser@gmail.com", + "homepage": "https://www.alainschlesser.com" + } + ], + "description": "Manages plugins and themes, including installs, activations, and updates.", + "homepage": "https://github.com/wp-cli/extension-command", + "support": { + "issues": "https://github.com/wp-cli/extension-command/issues", + "source": "https://github.com/wp-cli/extension-command/tree/v2.1.24" + }, + "install-path": "../wp-cli/extension-command" + }, + { + "name": "wp-cli/i18n-command", + "version": "v2.6.5", + "version_normalized": "2.6.5.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/i18n-command.git", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/5e73d417398993625331a9f69f6c2ef60f234070", + "reference": "5e73d417398993625331a9f69f6c2ef60f234070", + "shasum": "" + }, + "require": { + "eftec/bladeone": "3.52", + "gettext/gettext": "^4.8", + "mck89/peast": "^1.13.11", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/scaffold-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4.3.9" + }, + "suggest": { + "ext-json": "Used for reading and generating JSON translation files", + "ext-mbstring": "Used for calculating include/exclude matches in code extraction" + }, + "time": "2025-04-25T21:49:29+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "i18n", + "i18n make-pot", + "i18n make-json", + "i18n make-mo", + "i18n make-php", + "i18n update-po" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "i18n-command.php" + ], + "psr-4": { + "WP_CLI\\I18n\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Birchler", + "homepage": "https://pascalbirchler.com/" + } + ], + "description": "Provides internationalization tools for WordPress projects.", + "homepage": "https://github.com/wp-cli/i18n-command", + "support": { + "issues": "https://github.com/wp-cli/i18n-command/issues", + "source": "https://github.com/wp-cli/i18n-command/tree/v2.6.5" + }, + "install-path": "../wp-cli/i18n-command" + }, + { + "name": "wp-cli/import-command", + "version": "v2.0.14", + "version_normalized": "2.0.14.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/import-command.git", + "reference": "b2c48f3e51683e825738df62bf8ccc7004c5f0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/import-command/zipball/b2c48f3e51683e825738df62bf8ccc7004c5f0f9", + "reference": "b2c48f3e51683e825738df62bf8ccc7004c5f0f9", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/export-command": "^1 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T16:47:25+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "import" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "import-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Imports content from a given WXR file.", + "homepage": "https://github.com/wp-cli/import-command", + "support": { + "issues": "https://github.com/wp-cli/import-command/issues", + "source": "https://github.com/wp-cli/import-command/tree/v2.0.14" + }, + "install-path": "../wp-cli/import-command" + }, + { + "name": "wp-cli/language-command", + "version": "v2.0.23", + "version_normalized": "2.0.23.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/language-command.git", + "reference": "7221cc39d2b14fd39e55aa7884889f26eec2f822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/language-command/zipball/7221cc39d2b14fd39e55aa7884889f26eec2f822", + "reference": "7221cc39d2b14fd39e55aa7884889f26eec2f822", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-10T11:09:04+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "language", + "language core", + "language core activate", + "language core is-installed", + "language core install", + "language core list", + "language core uninstall", + "language core update", + "language plugin", + "language plugin is-installed", + "language plugin install", + "language plugin list", + "language plugin uninstall", + "language plugin update", + "language theme", + "language theme is-installed", + "language theme install", + "language theme list", + "language theme uninstall", + "language theme update", + "site switch-language" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "language-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Installs, activates, and manages language packs.", + "homepage": "https://github.com/wp-cli/language-command", + "support": { + "issues": "https://github.com/wp-cli/language-command/issues", + "source": "https://github.com/wp-cli/language-command/tree/v2.0.23" + }, + "install-path": "../wp-cli/language-command" + }, + { + "name": "wp-cli/maintenance-mode-command", + "version": "v2.1.3", + "version_normalized": "2.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/maintenance-mode-command.git", + "reference": "b947e094e00b7b68c6376ec9bd03303515864062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/b947e094e00b7b68c6376ec9bd03303515864062", + "reference": "b947e094e00b7b68c6376ec9bd03303515864062", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2024-11-24T17:26:30+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "maintenance-mode", + "maintenance-mode activate", + "maintenance-mode deactivate", + "maintenance-mode status", + "maintenance-mode is-active" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "maintenance-mode-command.php" + ], + "psr-4": { + "WP_CLI\\MaintenanceMode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thrijith Thankachan", + "email": "thrijith13@gmail.com", + "homepage": "https://thrijith.com" + } + ], + "description": "Activates, deactivates or checks the status of the maintenance mode of a site.", + "homepage": "https://github.com/wp-cli/maintenance-mode-command", + "support": { + "issues": "https://github.com/wp-cli/maintenance-mode-command/issues", + "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.1.3" + }, + "install-path": "../wp-cli/maintenance-mode-command" + }, + { + "name": "wp-cli/media-command", + "version": "v2.2.2", + "version_normalized": "2.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/media-command.git", + "reference": "a810ea0e68473fce6a234e67c6c5f19bb820a753" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/media-command/zipball/a810ea0e68473fce6a234e67c6c5f19bb820a753", + "reference": "a810ea0e68473fce6a234e67c6c5f19bb820a753", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^2.0", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-11T09:28:29+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "media", + "media import", + "media regenerate", + "media image-size" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "media-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Imports files as attachments, regenerates thumbnails, or lists registered image sizes.", + "homepage": "https://github.com/wp-cli/media-command", + "support": { + "issues": "https://github.com/wp-cli/media-command/issues", + "source": "https://github.com/wp-cli/media-command/tree/v2.2.2" + }, + "install-path": "../wp-cli/media-command" + }, + { + "name": "wp-cli/mustache", + "version": "v2.14.99", + "version_normalized": "2.14.99.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/mustache.php.git", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/mustache.php/zipball/ca23b97ac35fbe01c160549eb634396183d04a59", + "reference": "ca23b97ac35fbe01c160549eb634396183d04a59", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "replace": { + "mustache/mustache": "^2.14.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.19.3", + "yoast/phpunit-polyfills": "^2.0" + }, + "time": "2025-05-06T16:15:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "source": "https://github.com/wp-cli/mustache.php/tree/v2.14.99" + }, + "install-path": "../wp-cli/mustache" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "version_normalized": "0.6.3.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "time": "2017-04-25T11:26:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "support": { + "source": "https://github.com/wp-cli/spyc/tree/autoload" + }, + "install-path": "../wp-cli/mustangostang-spyc" + }, + { + "name": "wp-cli/package-command", + "version": "v2.6.0", + "version_normalized": "2.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/package-command.git", + "reference": "682d8c6bb30c782c3b09c015478c7cbe1cc727a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/package-command/zipball/682d8c6bb30c782c3b09c015478c7cbe1cc727a9", + "reference": "682d8c6bb30c782c3b09c015478c7cbe1cc727a9", + "shasum": "" + }, + "require": { + "composer/composer": "^2.2.25", + "ext-json": "*", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/scaffold-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-11T09:28:45+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "package", + "package browse", + "package install", + "package list", + "package update", + "package uninstall" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "package-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists, installs, and removes WP-CLI packages.", + "homepage": "https://github.com/wp-cli/package-command", + "support": { + "issues": "https://github.com/wp-cli/package-command/issues", + "source": "https://github.com/wp-cli/package-command/tree/v2.6.0" + }, + "install-path": "../wp-cli/package-command" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.12.5", + "version_normalized": "0.12.5.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "shasum": "" + }, + "require": { + "php": ">= 5.6.0" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-03-26T16:13:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.11.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "support": { + "issues": "https://github.com/wp-cli/php-cli-tools/issues", + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.12.5" + }, + "install-path": "../wp-cli/php-cli-tools" + }, + { + "name": "wp-cli/process", + "version": "v5.9.99", + "version_normalized": "5.9.99.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/process.git", + "reference": "f0aec5ca26a702d3157e3a19982b662521ac2b81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/process/zipball/f0aec5ca26a702d3157e3a19982b662521ac2b81", + "reference": "f0aec5ca26a702d3157e3a19982b662521ac2b81", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "replace": { + "symfony/process": "^5.4.47" + }, + "time": "2025-05-06T21:26:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/wp-cli/process/tree/v5.9.99" + }, + "install-path": "../wp-cli/process" + }, + { + "name": "wp-cli/rewrite-command", + "version": "v2.0.15", + "version_normalized": "2.0.15.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/rewrite-command.git", + "reference": "277ec689b7c268680ff429f52558508622c9b34c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/rewrite-command/zipball/277ec689b7c268680ff429f52558508622c9b34c", + "reference": "277ec689b7c268680ff429f52558508622c9b34c", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T12:09:09+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "rewrite", + "rewrite flush", + "rewrite list", + "rewrite structure" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "rewrite-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists or flushes the site's rewrite rules, updates the permalink structure.", + "homepage": "https://github.com/wp-cli/rewrite-command", + "support": { + "issues": "https://github.com/wp-cli/rewrite-command/issues", + "source": "https://github.com/wp-cli/rewrite-command/tree/v2.0.15" + }, + "install-path": "../wp-cli/rewrite-command" + }, + { + "name": "wp-cli/role-command", + "version": "v2.0.16", + "version_normalized": "2.0.16.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/role-command.git", + "reference": "ed57fb5436b4d47954b07e56c734d19deb4fc491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/role-command/zipball/ed57fb5436b4d47954b07e56c734d19deb4fc491", + "reference": "ed57fb5436b4d47954b07e56c734d19deb4fc491", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T12:24:15+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "role", + "role create", + "role delete", + "role exists", + "role list", + "role reset", + "cap", + "cap add", + "cap list", + "cap remove" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "role-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Adds, removes, lists, and resets roles and capabilities.", + "homepage": "https://github.com/wp-cli/role-command", + "support": { + "issues": "https://github.com/wp-cli/role-command/issues", + "source": "https://github.com/wp-cli/role-command/tree/v2.0.16" + }, + "install-path": "../wp-cli/role-command" + }, + { + "name": "wp-cli/scaffold-command", + "version": "v2.5.0", + "version_normalized": "2.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/scaffold-command.git", + "reference": "b4238ea12e768b3f15d10339a53a8642f82e1d2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/b4238ea12e768b3f15d10339a53a8642f82e1d2b", + "reference": "b4238ea12e768b3f15d10339a53a8642f82e1d2b", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-11T09:29:34+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "scaffold", + "scaffold underscores", + "scaffold block", + "scaffold child-theme", + "scaffold plugin", + "scaffold plugin-tests", + "scaffold post-type", + "scaffold taxonomy", + "scaffold theme-tests" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "scaffold-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Generates code for post types, taxonomies, blocks, plugins, child themes, etc.", + "homepage": "https://github.com/wp-cli/scaffold-command", + "support": { + "issues": "https://github.com/wp-cli/scaffold-command/issues", + "source": "https://github.com/wp-cli/scaffold-command/tree/v2.5.0" + }, + "install-path": "../wp-cli/scaffold-command" + }, + { + "name": "wp-cli/search-replace-command", + "version": "v2.1.8", + "version_normalized": "2.1.8.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/search-replace-command.git", + "reference": "65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057", + "reference": "65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T13:07:50+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "search-replace" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "search-replace-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Searches/replaces strings in the database.", + "homepage": "https://github.com/wp-cli/search-replace-command", + "support": { + "issues": "https://github.com/wp-cli/search-replace-command/issues", + "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.8" + }, + "install-path": "../wp-cli/search-replace-command" + }, + { + "name": "wp-cli/server-command", + "version": "v2.0.15", + "version_normalized": "2.0.15.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/server-command.git", + "reference": "80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/server-command/zipball/80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71", + "reference": "80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-10T11:03:13+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "server" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "server-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Launches PHP's built-in web server for a specific WordPress installation.", + "homepage": "https://github.com/wp-cli/server-command", + "support": { + "issues": "https://github.com/wp-cli/server-command/issues", + "source": "https://github.com/wp-cli/server-command/tree/v2.0.15" + }, + "install-path": "../wp-cli/server-command" + }, + { + "name": "wp-cli/shell-command", + "version": "v2.0.16", + "version_normalized": "2.0.16.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/shell-command.git", + "reference": "3af53a9f4b240e03e77e815b2ee10f229f1aa591" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/shell-command/zipball/3af53a9f4b240e03e77e815b2ee10f229f1aa591", + "reference": "3af53a9f4b240e03e77e815b2ee10f229f1aa591", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-11T09:39:33+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "shell" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "shell-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Opens an interactive PHP console for running and testing PHP code.", + "homepage": "https://github.com/wp-cli/shell-command", + "support": { + "issues": "https://github.com/wp-cli/shell-command/issues", + "source": "https://github.com/wp-cli/shell-command/tree/v2.0.16" + }, + "install-path": "../wp-cli/shell-command" + }, + { + "name": "wp-cli/super-admin-command", + "version": "v2.0.16", + "version_normalized": "2.0.16.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/super-admin-command.git", + "reference": "54ac063c384743ee414806d42cb8c61c6aa1fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/super-admin-command/zipball/54ac063c384743ee414806d42cb8c61c6aa1fa8e", + "reference": "54ac063c384743ee414806d42cb8c61c6aa1fa8e", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/entity-command": "^1.3 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-02T13:07:32+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "super-admin", + "super-admin add", + "super-admin list", + "super-admin remove" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "super-admin-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Lists, adds, or removes super admin users on a multisite installation.", + "homepage": "https://github.com/wp-cli/super-admin-command", + "support": { + "issues": "https://github.com/wp-cli/super-admin-command/issues", + "source": "https://github.com/wp-cli/super-admin-command/tree/v2.0.16" + }, + "install-path": "../wp-cli/super-admin-command" + }, + { + "name": "wp-cli/widget-command", + "version": "v2.1.12", + "version_normalized": "2.1.12.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/widget-command.git", + "reference": "73084053f7b32d92583e44d870b81f287beea6a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/widget-command/zipball/73084053f7b32d92583e44d870b81f287beea6a9", + "reference": "73084053f7b32d92583e44d870b81f287beea6a9", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "wp-cli/extension-command": "^1.2 || ^2", + "wp-cli/wp-cli-tests": "^4" + }, + "time": "2025-04-11T09:29:37+00:00", + "type": "wp-cli-package", + "extra": { + "bundled": true, + "commands": [ + "widget", + "widget add", + "widget deactivate", + "widget delete", + "widget list", + "widget move", + "widget reset", + "widget update", + "sidebar", + "sidebar list" + ], + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "widget-command.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Adds, moves, and removes widgets; lists sidebars.", + "homepage": "https://github.com/wp-cli/widget-command", + "support": { + "issues": "https://github.com/wp-cli/widget-command/issues", + "source": "https://github.com/wp-cli/widget-command/tree/v2.1.12" + }, + "install-path": "../wp-cli/widget-command" + }, + { + "name": "wp-cli/wp-cli", + "version": "v2.12.0", + "version_normalized": "2.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/03d30d4138d12b4bffd8b507b82e56e129e0523f", + "reference": "03d30d4138d12b4bffd8b507b82e56e129e0523f", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.6 || ^7.0 || ^8.0", + "symfony/finder": ">2.7", + "wp-cli/mustache": "^2.14.99", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/php-cli-tools": "~0.12.4" + }, + "require-dev": { + "wp-cli/db-command": "^1.3 || ^2", + "wp-cli/entity-command": "^1.2 || ^2", + "wp-cli/extension-command": "^1.1 || ^2", + "wp-cli/package-command": "^1 || ^2", + "wp-cli/wp-cli-tests": "^4.3.10" + }, + "suggest": { + "ext-readline": "Include for a better --prompt implementation", + "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" + }, + "time": "2025-05-07T01:16:12+00:00", + "bin": [ + "bin/wp", + "bin/wp.bat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.12.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "WP_CLI\\": "php/" + }, + "classmap": [ + "php/class-wp-cli.php", + "php/class-wp-cli-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI framework", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli/issues", + "source": "https://github.com/wp-cli/wp-cli" + }, + "install-path": "../wp-cli/wp-cli" + }, + { + "name": "wp-cli/wp-cli-bundle", + "version": "v2.12.0", + "version_normalized": "2.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli-bundle.git", + "reference": "d639a3dab65f4b935b21c61ea3662bf3258a03a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/d639a3dab65f4b935b21c61ea3662bf3258a03a5", + "reference": "d639a3dab65f4b935b21c61ea3662bf3258a03a5", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "wp-cli/cache-command": "^2", + "wp-cli/checksum-command": "^2.1", + "wp-cli/config-command": "^2.1", + "wp-cli/core-command": "^2.1", + "wp-cli/cron-command": "^2", + "wp-cli/db-command": "^2", + "wp-cli/embed-command": "^2", + "wp-cli/entity-command": "^2", + "wp-cli/eval-command": "^2", + "wp-cli/export-command": "^2", + "wp-cli/extension-command": "^2.1", + "wp-cli/i18n-command": "^2", + "wp-cli/import-command": "^2", + "wp-cli/language-command": "^2", + "wp-cli/maintenance-mode-command": "^2", + "wp-cli/media-command": "^2", + "wp-cli/package-command": "^2.1", + "wp-cli/process": "5.9.99", + "wp-cli/rewrite-command": "^2", + "wp-cli/role-command": "^2", + "wp-cli/scaffold-command": "^2", + "wp-cli/search-replace-command": "^2", + "wp-cli/server-command": "^2", + "wp-cli/shell-command": "^2", + "wp-cli/super-admin-command": "^2", + "wp-cli/widget-command": "^2", + "wp-cli/wp-cli": "^2.12" + }, + "require-dev": { + "roave/security-advisories": "dev-latest", + "wp-cli/wp-cli-tests": "^4" + }, + "suggest": { + "psy/psysh": "Enhanced `wp shell` functionality" + }, + "time": "2025-05-07T02:15:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.12.x-dev" + } + }, + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WP-CLI bundle package with default commands.", + "homepage": "https://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli-bundle/issues", + "source": "https://github.com/wp-cli/wp-cli-bundle" + }, + "install-path": "../wp-cli/wp-cli-bundle" + }, + { + "name": "wp-cli/wp-config-transformer", + "version": "v1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-config-transformer.git", + "reference": "b78cab1159b43eb5ee097e2cfafe5eab573d2a8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-config-transformer/zipball/b78cab1159b43eb5ee097e2cfafe5eab573d2a8a", + "reference": "b78cab1159b43eb5ee097e2cfafe5eab573d2a8a", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "wp-cli/wp-cli-tests": "^4.0" + }, + "time": "2025-03-31T08:37:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/WPConfigTransformer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frankie Jarrett", + "email": "fjarrett@gmail.com" + } + ], + "description": "Programmatically edit a wp-config.php file.", + "homepage": "https://github.com/wp-cli/wp-config-transformer", + "support": { + "issues": "https://github.com/wp-cli/wp-config-transformer/issues", + "source": "https://github.com/wp-cli/wp-config-transformer/tree/v1.4.2" + }, + "install-path": "../wp-cli/wp-config-transformer" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "version_normalized": "3.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "time": "2024-03-25T16:39:00+00:00", + "type": "phpcodesniffer-standard", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "install-path": "../wp-coding-standards/wpcs" } ], "dev": true, - "dev-package-names": [] + "dev-package-names": [ + "composer/ca-bundle", + "composer/class-map-generator", + "composer/composer", + "composer/metadata-minifier", + "composer/pcre", + "composer/semver", + "composer/spdx-licenses", + "composer/xdebug-handler", + "dealerdirect/phpcodesniffer-composer-installer", + "eftec/bladeone", + "gettext/gettext", + "gettext/languages", + "justinrainbow/json-schema", + "marc-mabe/php-enum", + "mck89/peast", + "nb/oxymel", + "phpcompatibility/php-compatibility", + "phpcompatibility/phpcompatibility-paragonie", + "phpcompatibility/phpcompatibility-wp", + "phpcsstandards/phpcsextra", + "phpcsstandards/phpcsutils", + "psr/container", + "psr/log", + "react/promise", + "seld/jsonlint", + "seld/phar-utils", + "seld/signal-handler", + "squizlabs/php_codesniffer", + "symfony/console", + "symfony/deprecation-contracts", + "symfony/filesystem", + "symfony/finder", + "symfony/polyfill-ctype", + "symfony/polyfill-intl-grapheme", + "symfony/polyfill-intl-normalizer", + "symfony/polyfill-mbstring", + "symfony/polyfill-php73", + "symfony/polyfill-php80", + "symfony/polyfill-php81", + "symfony/service-contracts", + "symfony/string", + "wp-cli/cache-command", + "wp-cli/checksum-command", + "wp-cli/config-command", + "wp-cli/core-command", + "wp-cli/cron-command", + "wp-cli/db-command", + "wp-cli/embed-command", + "wp-cli/entity-command", + "wp-cli/eval-command", + "wp-cli/export-command", + "wp-cli/extension-command", + "wp-cli/i18n-command", + "wp-cli/import-command", + "wp-cli/language-command", + "wp-cli/maintenance-mode-command", + "wp-cli/media-command", + "wp-cli/mustache", + "wp-cli/mustangostang-spyc", + "wp-cli/package-command", + "wp-cli/php-cli-tools", + "wp-cli/process", + "wp-cli/rewrite-command", + "wp-cli/role-command", + "wp-cli/scaffold-command", + "wp-cli/search-replace-command", + "wp-cli/server-command", + "wp-cli/shell-command", + "wp-cli/super-admin-command", + "wp-cli/widget-command", + "wp-cli/wp-cli", + "wp-cli/wp-cli-bundle", + "wp-cli/wp-config-transformer", + "wp-coding-standards/wpcs" + ] } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index bad4ac2..e3d5d16 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,32 +1,716 @@ array( - 'name' => '__root__', + 'name' => 'webdevstudios/bluesky-feed-for-wordpress', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '62f913eb8fb823bc9cf570ff9441af3ebcd398a5', - 'type' => 'library', + 'reference' => '92e57580a823f6e1c2691e7311831689f7aaa05b', + 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => true, ), 'versions' => array( - '__root__' => array( + 'composer/ca-bundle' => array( + 'pretty_version' => '1.5.7', + 'version' => '1.5.7.0', + 'reference' => 'd665d22c417056996c59019579f1967dfe5c1e82', + 'type' => 'library', + 'install_path' => __DIR__ . '/./ca-bundle', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/class-map-generator' => array( + 'pretty_version' => '1.6.1', + 'version' => '1.6.1.0', + 'reference' => '134b705ddb0025d397d8318a75825fe3c9d1da34', + 'type' => 'library', + 'install_path' => __DIR__ . '/./class-map-generator', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/composer' => array( + 'pretty_version' => '2.8.9', + 'version' => '2.8.9.0', + 'reference' => 'b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d', + 'type' => 'library', + 'install_path' => __DIR__ . '/./composer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/installers' => array( + 'pretty_version' => 'v2.3.0', + 'version' => '2.3.0.0', + 'reference' => '12fb2dfe5e16183de69e784a7b84046c43d97e8e', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/./installers', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'composer/metadata-minifier' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207', + 'type' => 'library', + 'install_path' => __DIR__ . '/./metadata-minifier', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/pcre' => array( + 'pretty_version' => '3.3.2', + 'version' => '3.3.2.0', + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', + 'type' => 'library', + 'install_path' => __DIR__ . '/./pcre', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/semver' => array( + 'pretty_version' => '3.4.3', + 'version' => '3.4.3.0', + 'reference' => '4313d26ada5e0c4edfbd1dc481a92ff7bff91f12', + 'type' => 'library', + 'install_path' => __DIR__ . '/./semver', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/spdx-licenses' => array( + 'pretty_version' => '1.5.9', + 'version' => '1.5.9.0', + 'reference' => 'edf364cefe8c43501e21e88110aac10b284c3c9f', + 'type' => 'library', + 'install_path' => __DIR__ . '/./spdx-licenses', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'composer/xdebug-handler' => array( + 'pretty_version' => '3.0.5', + 'version' => '3.0.5.0', + 'reference' => '6c1925561632e83d60a44492e0b344cf48ab85ef', + 'type' => 'library', + 'install_path' => __DIR__ . '/./xdebug-handler', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'dealerdirect/phpcodesniffer-composer-installer' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'reference' => '4be43904336affa5c2f70744a348312336afd0da', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'eftec/bladeone' => array( + 'pretty_version' => '3.52', + 'version' => '3.52.0.0', + 'reference' => 'a19bf66917de0b29836983db87a455a4f6e32148', + 'type' => 'library', + 'install_path' => __DIR__ . '/../eftec/bladeone', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'gettext/gettext' => array( + 'pretty_version' => 'v4.8.12', + 'version' => '4.8.12.0', + 'reference' => '11af89ee6c087db3cf09ce2111a150bca5c46e12', + 'type' => 'library', + 'install_path' => __DIR__ . '/../gettext/gettext', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'gettext/languages' => array( + 'pretty_version' => '2.12.1', + 'version' => '2.12.1.0', + 'reference' => '0b0b0851c55168e1dfb14305735c64019732b5f1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../gettext/languages', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'justinrainbow/json-schema' => array( + 'pretty_version' => '6.4.2', + 'version' => '6.4.2.0', + 'reference' => 'ce1fd2d47799bb60668643bc6220f6278a4c1d02', + 'type' => 'library', + 'install_path' => __DIR__ . '/../justinrainbow/json-schema', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'marc-mabe/php-enum' => array( + 'pretty_version' => 'v4.7.1', + 'version' => '4.7.1.0', + 'reference' => '7159809e5cfa041dca28e61f7f7ae58063aae8ed', + 'type' => 'library', + 'install_path' => __DIR__ . '/../marc-mabe/php-enum', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'mck89/peast' => array( + 'pretty_version' => 'v1.17.0', + 'version' => '1.17.0.0', + 'reference' => '3a752d39bd7d8dc1e19bcf424f3d5ac1a1ca6ad5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../mck89/peast', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'mustache/mustache' => array( + 'dev_requirement' => true, + 'replaced' => array( + 0 => '^2.14.2', + ), + ), + 'nb/oxymel' => array( + 'pretty_version' => 'v0.1.0', + 'version' => '0.1.0.0', + 'reference' => 'cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nb/oxymel', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcompatibility/php-compatibility' => array( + 'pretty_version' => '9.3.5', + 'version' => '9.3.5.0', + 'reference' => '9fb324479acf6f39452e0655d2429cc0d3914243', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcompatibility/php-compatibility', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcompatibility/phpcompatibility-paragonie' => array( + 'pretty_version' => '1.3.3', + 'version' => '1.3.3.0', + 'reference' => '293975b465e0e709b571cbf0c957c6c0a7b9a2ac', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-paragonie', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcompatibility/phpcompatibility-wp' => array( + 'pretty_version' => '2.1.7', + 'version' => '2.1.7.0', + 'reference' => '5bfbbfbabb3df2b9a83e601de9153e4a7111962c', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-wp', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcsstandards/phpcsextra' => array( + 'pretty_version' => '1.4.0', + 'version' => '1.4.0.0', + 'reference' => 'fa4b8d051e278072928e32d817456a7fdb57b6ca', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcsstandards/phpcsextra', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpcsstandards/phpcsutils' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '65355670ac17c34cd235cf9d3ceae1b9252c4dad', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../phpcsstandards/phpcsutils', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/container' => array( + 'pretty_version' => '1.1.2', + 'version' => '1.1.2.0', + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/log' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => 'ef29f6d262798707a9edd554e2b82517ef3a9376', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => true, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), + 'react/promise' => array( + 'pretty_version' => 'v3.2.0', + 'version' => '3.2.0.0', + 'reference' => '8a164643313c71354582dc850b42b33fa12a4b63', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/promise', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/jsonlint' => array( + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', + 'reference' => '1748aaf847fc731cfad7725aec413ee46f0cc3a2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/jsonlint', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/phar-utils' => array( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/phar-utils', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'seld/signal-handler' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/signal-handler', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'squizlabs/php_codesniffer' => array( + 'pretty_version' => '3.13.2', + 'version' => '3.13.2.0', + 'reference' => '5b5e3821314f947dd040c70f7992a64eac89025c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../squizlabs/php_codesniffer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/console' => array( + 'pretty_version' => 'v5.4.47', + 'version' => '5.4.47.0', + 'reference' => 'c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/console', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.4', + 'version' => '2.5.4.0', + 'reference' => '605389f2a7e5625f273b53960dc46aeaf9c62918', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/filesystem' => array( + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'reference' => '57c8294ed37d4a055b77057827c67f9558c95c54', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/filesystem', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/finder' => array( + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'reference' => '63741784cd7b9967975eec610b256eed3ede022b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => 'b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/process' => array( + 'dev_requirement' => true, + 'replaced' => array( + 0 => '^5.4.47', + ), + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v2.5.4', + 'version' => '2.5.4.0', + 'reference' => 'f37b419f7aea2e9abf10abd261832cace12e3300', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'symfony/string' => array( + 'pretty_version' => 'v5.4.47', + 'version' => '5.4.47.0', + 'reference' => '136ca7d72f72b599f2631aca474a4f8e26719799', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'webdevstudios/bluesky-feed-for-wordpress' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '62f913eb8fb823bc9cf570ff9441af3ebcd398a5', - 'type' => 'library', + 'reference' => '92e57580a823f6e1c2691e7311831689f7aaa05b', + 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), - 'robertdevore/wpcom-check' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => '25eb61d7e0fbd4b2a87a81c3fa7dca722f8d8058', + 'wp-cli/cache-command' => array( + 'pretty_version' => 'v2.2.0', + 'version' => '2.2.0.0', + 'reference' => '14f76b0bc8f9fa0a680e9c70e18fcf627774d055', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/cache-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/checksum-command' => array( + 'pretty_version' => 'v2.3.1', + 'version' => '2.3.1.0', + 'reference' => '39992dbd66835f8d5c2cc5bfeacf9d2c450cbafe', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/checksum-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/config-command' => array( + 'pretty_version' => 'v2.3.8', + 'version' => '2.3.8.0', + 'reference' => '994b3dc9e8284fc978366920d5c5ae0dde3a004e', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/config-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/core-command' => array( + 'pretty_version' => 'v2.1.20', + 'version' => '2.1.20.0', + 'reference' => '83e4692784a815bb7f5df10b72204f237b5224b9', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/core-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/cron-command' => array( + 'pretty_version' => 'v2.3.2', + 'version' => '2.3.2.0', + 'reference' => '6f450028a75ebd275f12cad62959a0709bf3e7c1', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/cron-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/db-command' => array( + 'pretty_version' => 'v2.1.3', + 'version' => '2.1.3.0', + 'reference' => 'f857c91454d7092fa672bc388512a51752d9264a', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/db-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/embed-command' => array( + 'pretty_version' => 'v2.0.18', + 'version' => '2.0.18.0', + 'reference' => '52f59a1dacf1d4a1c68fd685f27909e1f493816b', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/embed-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/entity-command' => array( + 'pretty_version' => 'v2.8.4', + 'version' => '2.8.4.0', + 'reference' => '213611f8ab619ca137d983e9b987f7fbf1ac21d4', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/entity-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/eval-command' => array( + 'pretty_version' => 'v2.2.6', + 'version' => '2.2.6.0', + 'reference' => '20ec428a7b9bc604fab0bd33ee8fa20662650455', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/eval-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/export-command' => array( + 'pretty_version' => 'v2.1.14', + 'version' => '2.1.14.0', + 'reference' => '2af32bf12c1bccd6561a215dbbafc2f272647ee8', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/export-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/extension-command' => array( + 'pretty_version' => 'v2.1.24', + 'version' => '2.1.24.0', + 'reference' => 'd21a2f504ac43a86b6b08697669b5b0844748133', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/extension-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/i18n-command' => array( + 'pretty_version' => 'v2.6.5', + 'version' => '2.6.5.0', + 'reference' => '5e73d417398993625331a9f69f6c2ef60f234070', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/i18n-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/import-command' => array( + 'pretty_version' => 'v2.0.14', + 'version' => '2.0.14.0', + 'reference' => 'b2c48f3e51683e825738df62bf8ccc7004c5f0f9', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/import-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/language-command' => array( + 'pretty_version' => 'v2.0.23', + 'version' => '2.0.23.0', + 'reference' => '7221cc39d2b14fd39e55aa7884889f26eec2f822', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/language-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/maintenance-mode-command' => array( + 'pretty_version' => 'v2.1.3', + 'version' => '2.1.3.0', + 'reference' => 'b947e094e00b7b68c6376ec9bd03303515864062', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/maintenance-mode-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/media-command' => array( + 'pretty_version' => 'v2.2.2', + 'version' => '2.2.2.0', + 'reference' => 'a810ea0e68473fce6a234e67c6c5f19bb820a753', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/media-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/mustache' => array( + 'pretty_version' => 'v2.14.99', + 'version' => '2.14.99.0', + 'reference' => 'ca23b97ac35fbe01c160549eb634396183d04a59', 'type' => 'library', - 'install_path' => __DIR__ . '/../robertdevore/wpcom-check', + 'install_path' => __DIR__ . '/../wp-cli/mustache', 'aliases' => array(), - 'dev_requirement' => false, + 'dev_requirement' => true, + ), + 'wp-cli/mustangostang-spyc' => array( + 'pretty_version' => '0.6.3', + 'version' => '0.6.3.0', + 'reference' => '6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/mustangostang-spyc', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/package-command' => array( + 'pretty_version' => 'v2.6.0', + 'version' => '2.6.0.0', + 'reference' => '682d8c6bb30c782c3b09c015478c7cbe1cc727a9', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/package-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/php-cli-tools' => array( + 'pretty_version' => 'v0.12.5', + 'version' => '0.12.5.0', + 'reference' => '34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/php-cli-tools', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/process' => array( + 'pretty_version' => 'v5.9.99', + 'version' => '5.9.99.0', + 'reference' => 'f0aec5ca26a702d3157e3a19982b662521ac2b81', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/process', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/rewrite-command' => array( + 'pretty_version' => 'v2.0.15', + 'version' => '2.0.15.0', + 'reference' => '277ec689b7c268680ff429f52558508622c9b34c', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/rewrite-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/role-command' => array( + 'pretty_version' => 'v2.0.16', + 'version' => '2.0.16.0', + 'reference' => 'ed57fb5436b4d47954b07e56c734d19deb4fc491', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/role-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/scaffold-command' => array( + 'pretty_version' => 'v2.5.0', + 'version' => '2.5.0.0', + 'reference' => 'b4238ea12e768b3f15d10339a53a8642f82e1d2b', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/scaffold-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/search-replace-command' => array( + 'pretty_version' => 'v2.1.8', + 'version' => '2.1.8.0', + 'reference' => '65397a7bfdd5ba2cff26f3ab03ef0bcb916c0057', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/search-replace-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/server-command' => array( + 'pretty_version' => 'v2.0.15', + 'version' => '2.0.15.0', + 'reference' => '80a9243f94e0ac073f9bfdb516d2ac7e1fa01a71', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/server-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/shell-command' => array( + 'pretty_version' => 'v2.0.16', + 'version' => '2.0.16.0', + 'reference' => '3af53a9f4b240e03e77e815b2ee10f229f1aa591', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/shell-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/super-admin-command' => array( + 'pretty_version' => 'v2.0.16', + 'version' => '2.0.16.0', + 'reference' => '54ac063c384743ee414806d42cb8c61c6aa1fa8e', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/super-admin-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/widget-command' => array( + 'pretty_version' => 'v2.1.12', + 'version' => '2.1.12.0', + 'reference' => '73084053f7b32d92583e44d870b81f287beea6a9', + 'type' => 'wp-cli-package', + 'install_path' => __DIR__ . '/../wp-cli/widget-command', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/wp-cli' => array( + 'pretty_version' => 'v2.12.0', + 'version' => '2.12.0.0', + 'reference' => '03d30d4138d12b4bffd8b507b82e56e129e0523f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/wp-cli', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/wp-cli-bundle' => array( + 'pretty_version' => 'v2.12.0', + 'version' => '2.12.0.0', + 'reference' => 'd639a3dab65f4b935b21c61ea3662bf3258a03a5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/wp-cli-bundle', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-cli/wp-config-transformer' => array( + 'pretty_version' => 'v1.4.2', + 'version' => '1.4.2.0', + 'reference' => 'b78cab1159b43eb5ee097e2cfafe5eab573d2a8a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../wp-cli/wp-config-transformer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'wp-coding-standards/wpcs' => array( + 'pretty_version' => '3.1.0', + 'version' => '3.1.0.0', + 'reference' => '9333efcbff231f10dfd9c56bb7b65818b4733ca7', + 'type' => 'phpcodesniffer-standard', + 'install_path' => __DIR__ . '/../wp-coding-standards/wpcs', + 'aliases' => array(), + 'dev_requirement' => true, ), ), ); diff --git a/vendor/composer/installers/.github/workflows/continuous-integration.yml b/vendor/composer/installers/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..2ecae13 --- /dev/null +++ b/vendor/composer/installers/.github/workflows/continuous-integration.yml @@ -0,0 +1,65 @@ +name: "Continuous Integration" + +on: + - push + - pull_request + +env: + COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist" + SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT: "1" + +permissions: + contents: read + +jobs: + tests: + name: "CI" + + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + dependencies: [locked] + include: + - php-version: "7.2" + dependencies: lowest + - php-version: "8.1" + dependencies: lowest + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + tools: composer:snapshot + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: "Handle lowest dependencies update" + if: "contains(matrix.dependencies, 'lowest')" + run: "echo \"COMPOSER_FLAGS=$COMPOSER_FLAGS --prefer-lowest\" >> $GITHUB_ENV" + + - name: "Install latest dependencies" + run: "composer update ${{ env.COMPOSER_FLAGS }}" + + - name: "Run tests" + run: "vendor/bin/simple-phpunit --verbose" diff --git a/vendor/composer/installers/.github/workflows/lint.yml b/vendor/composer/installers/.github/workflows/lint.yml new file mode 100644 index 0000000..61b5633 --- /dev/null +++ b/vendor/composer/installers/.github/workflows/lint.yml @@ -0,0 +1,33 @@ +name: "PHP Lint" + +on: + - push + - pull_request + +permissions: + contents: read + +jobs: + tests: + name: "Lint" + + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - "7.2" + - "latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + + - name: "Lint PHP files" + run: "find src/ -type f -name '*.php' -print0 | xargs -0 -L1 -P4 -- php -l -f" diff --git a/vendor/composer/installers/.github/workflows/phpstan.yml b/vendor/composer/installers/.github/workflows/phpstan.yml new file mode 100644 index 0000000..c638b44 --- /dev/null +++ b/vendor/composer/installers/.github/workflows/phpstan.yml @@ -0,0 +1,52 @@ +name: "PHPStan" + +on: + - push + - pull_request + +env: + COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist" + SYMFONY_PHPUNIT_VERSION: "" + +permissions: + contents: read + +jobs: + tests: + name: "PHPStan" + + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - "8.0" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: "Install latest dependencies" + run: "composer update ${{ env.COMPOSER_FLAGS }}" + + - name: Run PHPStan + run: | + composer require --dev phpunit/phpunit:^8.5.18 --with-all-dependencies ${{ env.COMPOSER_FLAGS }} + vendor/bin/phpstan analyse diff --git a/vendor/composer/installers/LICENSE b/vendor/composer/installers/LICENSE new file mode 100644 index 0000000..85f97fc --- /dev/null +++ b/vendor/composer/installers/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Kyle Robinson Young + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/composer/installers/composer.json b/vendor/composer/installers/composer.json new file mode 100644 index 0000000..9103484 --- /dev/null +++ b/vendor/composer/installers/composer.json @@ -0,0 +1,117 @@ +{ + "name": "composer/installers", + "type": "composer-plugin", + "license": "MIT", + "description": "A multi-framework Composer library installer", + "keywords": [ + "installer", + "AGL", + "AnnotateCms", + "Attogram", + "Bitrix", + "CakePHP", + "Chef", + "Cockpit", + "CodeIgniter", + "concrete5", + "ConcreteCMS", + "Croogo", + "DokuWiki", + "Dolibarr", + "Drupal", + "Elgg", + "Eliasis", + "ExpressionEngine", + "eZ Platform", + "FuelPHP", + "Grav", + "Hurad", + "ImageCMS", + "iTop", + "Kanboard", + "Known", + "Kohana", + "Lan Management System", + "Laravel", + "Lavalite", + "Lithium", + "Magento", + "majima", + "Mako", + "MantisBT", + "Matomo", + "Mautic", + "Maya", + "MODX", + "MODX Evo", + "MediaWiki", + "Miaoxing", + "OXID", + "osclass", + "MODULEWork", + "Moodle", + "Pantheon", + "Piwik", + "pxcms", + "phpBB", + "Plentymarkets", + "PPI", + "Puppet", + "Porto", + "ProcessWire", + "RadPHP", + "ReIndex", + "Roundcube", + "shopware", + "SilverStripe", + "SMF", + "Starbug", + "SyDES", + "Sylius", + "TastyIgniter", + "Thelia", + "WHMCS", + "WolfCMS", + "WordPress", + "YAWIK", + "Zend", + "Zikula" + ], + "homepage": "https://composer.github.io/installers/", + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "autoload": { + "psr-4": { "Composer\\Installers\\": "src/Composer/Installers" } + }, + "autoload-dev": { + "psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" } + }, + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "require": { + "php": "^7.2 || ^8.0", + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "symfony/phpunit-bridge": "^7.1.1", + "phpstan/phpstan": "^1.11", + "symfony/process": "^5 || ^6 || ^7", + "phpstan/phpstan-phpunit": "^1" + }, + "scripts": { + "test": "@php vendor/bin/simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/AglInstaller.php b/vendor/composer/installers/src/Composer/Installers/AglInstaller.php new file mode 100644 index 0000000..b0996a6 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/AglInstaller.php @@ -0,0 +1,29 @@ + */ + protected $locations = array( + 'module' => 'More/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $name = preg_replace_callback('/(?:^|_|-)(.?)/', function ($matches) { + return strtoupper($matches[1]); + }, $vars['name']); + + if (null === $name) { + throw new \RuntimeException('Failed to run preg_replace_callback: '.preg_last_error()); + } + + $vars['name'] = $name; + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/AkauntingInstaller.php b/vendor/composer/installers/src/Composer/Installers/AkauntingInstaller.php new file mode 100644 index 0000000..c504c70 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/AkauntingInstaller.php @@ -0,0 +1,23 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php b/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php new file mode 100644 index 0000000..58a0f66 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'module' => 'addons/modules/{$name}/', + 'component' => 'addons/components/{$name}/', + 'service' => 'addons/services/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php b/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php new file mode 100644 index 0000000..f01b399 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php @@ -0,0 +1,58 @@ + */ + protected $locations = array( + 'module' => 'Modules/{$name}/', + 'theme' => 'Themes/{$name}/' + ); + + /** + * Format package name. + * + * For package type asgard-module, cut off a trailing '-plugin' if present. + * + * For package type asgard-theme, cut off a trailing '-theme' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'asgard-module') { + return $this->inflectPluginVars($vars); + } + + if ($vars['type'] === 'asgard-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectPluginVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-module$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php b/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php new file mode 100644 index 0000000..bd7dd8d --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php b/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php new file mode 100644 index 0000000..663ec2a --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php @@ -0,0 +1,137 @@ + */ + protected $locations = array(); + /** @var Composer */ + protected $composer; + /** @var PackageInterface */ + protected $package; + /** @var IOInterface */ + protected $io; + + /** + * Initializes base installer. + */ + public function __construct(PackageInterface $package, Composer $composer, IOInterface $io) + { + $this->composer = $composer; + $this->package = $package; + $this->io = $io; + } + + /** + * Return the install path based on package type. + */ + public function getInstallPath(PackageInterface $package, string $frameworkType = ''): string + { + $type = $this->package->getType(); + + $prettyName = $this->package->getPrettyName(); + if (strpos($prettyName, '/') !== false) { + list($vendor, $name) = explode('/', $prettyName); + } else { + $vendor = ''; + $name = $prettyName; + } + + $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type')); + + $extra = $package->getExtra(); + if (!empty($extra['installer-name'])) { + $availableVars['name'] = $extra['installer-name']; + } + + $extra = $this->composer->getPackage()->getExtra(); + if (!empty($extra['installer-paths'])) { + $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor); + if ($customPath !== false) { + return $this->templatePath($customPath, $availableVars); + } + } + + $packageType = substr($type, strlen($frameworkType) + 1); + $locations = $this->getLocations($frameworkType); + if (!isset($locations[$packageType])) { + throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type)); + } + + return $this->templatePath($locations[$packageType], $availableVars); + } + + /** + * For an installer to override to modify the vars per installer. + * + * @param array $vars This will normally receive array{name: string, vendor: string, type: string} + * @return array + */ + public function inflectPackageVars(array $vars): array + { + return $vars; + } + + /** + * Gets the installer's locations + * + * @return array map of package types => install path + */ + public function getLocations(string $frameworkType) + { + return $this->locations; + } + + /** + * Replace vars in a path + * + * @param array $vars + */ + protected function templatePath(string $path, array $vars = array()): string + { + if (strpos($path, '{') !== false) { + extract($vars); + preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $var) { + $path = str_replace('{$' . $var . '}', $$var, $path); + } + } + } + + return $path; + } + + /** + * Search through a passed paths array for a custom install path. + * + * @param array $paths + * @return string|false + */ + protected function mapCustomInstallPaths(array $paths, string $name, string $type, ?string $vendor = null) + { + foreach ($paths as $path => $names) { + $names = (array) $names; + if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) { + return $path; + } + } + + return false; + } + + protected function pregReplace(string $pattern, string $replacement, string $subject): string + { + $result = preg_replace($pattern, $replacement, $subject); + if (null === $result) { + throw new \RuntimeException('Failed to run preg_replace with '.$pattern.': '.preg_last_error()); + } + + return $result; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php b/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php new file mode 100644 index 0000000..705ecb4 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php @@ -0,0 +1,123 @@ +.`. + * - `bitrix-d7-component` — copy the component to directory `bitrix/components//`. + * - `bitrix-d7-template` — copy the template to directory `bitrix/templates/_`. + * + * You can set custom path to directory with Bitrix kernel in `composer.json`: + * + * ```json + * { + * "extra": { + * "bitrix-dir": "s1/bitrix" + * } + * } + * ``` + * + * @author Nik Samokhvalov + * @author Denis Kulichkin + */ +class BitrixInstaller extends BaseInstaller +{ + /** @var array */ + protected $locations = array( + 'module' => '{$bitrix_dir}/modules/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) + 'component' => '{$bitrix_dir}/components/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) + 'theme' => '{$bitrix_dir}/templates/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) + 'd7-module' => '{$bitrix_dir}/modules/{$vendor}.{$name}/', + 'd7-component' => '{$bitrix_dir}/components/{$vendor}/{$name}/', + 'd7-template' => '{$bitrix_dir}/templates/{$vendor}_{$name}/', + ); + + /** + * @var string[] Storage for informations about duplicates at all the time of installation packages. + */ + private static $checkedDuplicates = array(); + + public function inflectPackageVars(array $vars): array + { + /** @phpstan-ignore-next-line */ + if ($this->composer->getPackage()) { + $extra = $this->composer->getPackage()->getExtra(); + + if (isset($extra['bitrix-dir'])) { + $vars['bitrix_dir'] = $extra['bitrix-dir']; + } + } + + if (!isset($vars['bitrix_dir'])) { + $vars['bitrix_dir'] = 'bitrix'; + } + + return parent::inflectPackageVars($vars); + } + + /** + * {@inheritdoc} + */ + protected function templatePath(string $path, array $vars = array()): string + { + $templatePath = parent::templatePath($path, $vars); + $this->checkDuplicates($templatePath, $vars); + + return $templatePath; + } + + /** + * Duplicates search packages. + * + * @param array $vars + */ + protected function checkDuplicates(string $path, array $vars = array()): void + { + $packageType = substr($vars['type'], strlen('bitrix') + 1); + $localDir = explode('/', $vars['bitrix_dir']); + array_pop($localDir); + $localDir[] = 'local'; + $localDir = implode('/', $localDir); + + $oldPath = str_replace( + array('{$bitrix_dir}', '{$name}'), + array($localDir, $vars['name']), + $this->locations[$packageType] + ); + + if (in_array($oldPath, static::$checkedDuplicates)) { + return; + } + + if ($oldPath !== $path && file_exists($oldPath) && $this->io->isInteractive()) { + $this->io->writeError(' Duplication of packages:'); + $this->io->writeError(' Package ' . $oldPath . ' will be called instead package ' . $path . ''); + + while (true) { + switch ($this->io->ask(' Delete ' . $oldPath . ' [y,n,?]? ', '?')) { + case 'y': + $fs = new Filesystem(); + $fs->removeDirectory($oldPath); + break 2; + + case 'n': + break 2; + + case '?': + default: + $this->io->writeError(array( + ' y - delete package ' . $oldPath . ' and to continue with the installation', + ' n - don\'t delete and to continue with the installation', + )); + $this->io->writeError(' ? - print help'); + break; + } + } + } + + static::$checkedDuplicates[] = $oldPath; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php b/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php new file mode 100644 index 0000000..ab022d9 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'package' => 'Packages/{$vendor}/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/BotbleInstaller.php b/vendor/composer/installers/src/Composer/Installers/BotbleInstaller.php new file mode 100644 index 0000000..35e1cb8 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/BotbleInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'plugin' => 'platform/plugins/{$name}/', + 'theme' => 'platform/themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php b/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php new file mode 100644 index 0000000..12b4ed4 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php @@ -0,0 +1,67 @@ + */ + protected $locations = array( + 'plugin' => 'Plugin/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + if ($this->matchesCakeVersion('>=', '3.0.0')) { + return $vars; + } + + $nameParts = explode('/', $vars['name']); + foreach ($nameParts as &$value) { + $value = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $value)); + $value = str_replace(array('-', '_'), ' ', $value); + $value = str_replace(' ', '', ucwords($value)); + } + $vars['name'] = implode('/', $nameParts); + + return $vars; + } + + /** + * Change the default plugin location when cakephp >= 3.0 + */ + public function getLocations(string $frameworkType): array + { + if ($this->matchesCakeVersion('>=', '3.0.0')) { + $this->locations['plugin'] = $this->composer->getConfig()->get('vendor-dir') . '/{$vendor}/{$name}/'; + } + return $this->locations; + } + + /** + * Check if CakePHP version matches against a version + * + * @phpstan-param '='|'=='|'<'|'<='|'>'|'>='|'<>'|'!=' $matcher + */ + protected function matchesCakeVersion(string $matcher, string $version): bool + { + $repositoryManager = $this->composer->getRepositoryManager(); + /** @phpstan-ignore-next-line */ + if (!$repositoryManager) { + return false; + } + + $repos = $repositoryManager->getLocalRepository(); + /** @phpstan-ignore-next-line */ + if (!$repos) { + return false; + } + + return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php b/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php new file mode 100644 index 0000000..b0d3c5f --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'cookbook' => 'Chef/{$vendor}/{$name}/', + 'role' => 'Chef/roles/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php b/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php new file mode 100644 index 0000000..1c52e0c --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'ext' => 'ext/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php b/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php new file mode 100644 index 0000000..2c943b2 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'ship' => 'CCF/orbit/{$name}/', + 'theme' => 'CCF/app/themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php b/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php new file mode 100644 index 0000000..d3fcdf7 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php @@ -0,0 +1,36 @@ + */ + protected $locations = array( + 'module' => 'cockpit/modules/addons/{$name}/', + ); + + /** + * Format module name. + * + * Strip `module-` prefix from package name. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] == 'cockpit-module') { + return $this->inflectModuleVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + public function inflectModuleVars(array $vars): array + { + $vars['name'] = ucfirst($this->pregReplace('/cockpit-/i', '', $vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php b/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php new file mode 100644 index 0000000..a183e07 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'library' => 'application/libraries/{$name}/', + 'third-party' => 'application/third_party/{$name}/', + 'module' => 'application/modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php b/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php new file mode 100644 index 0000000..2f5fecb --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php @@ -0,0 +1,15 @@ + */ + protected $locations = array( + 'core' => 'concrete/', + 'block' => 'application/blocks/{$name}/', + 'package' => 'packages/{$name}/', + 'theme' => 'application/themes/{$name}/', + 'update' => 'updates/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ConcreteCMSInstaller.php b/vendor/composer/installers/src/Composer/Installers/ConcreteCMSInstaller.php new file mode 100644 index 0000000..b6e7f00 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ConcreteCMSInstaller.php @@ -0,0 +1,15 @@ + */ + protected $locations = array( + 'core' => 'concrete/', + 'block' => 'application/blocks/{$name}/', + 'package' => 'packages/{$name}/', + 'theme' => 'application/themes/{$name}/', + 'update' => 'updates/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php b/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php new file mode 100644 index 0000000..31d4939 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php @@ -0,0 +1,23 @@ + */ + protected $locations = array( + 'plugin' => 'Plugin/{$name}/', + 'theme' => 'View/Themed/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower(str_replace(array('-', '_'), ' ', $vars['name'])); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php b/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php new file mode 100644 index 0000000..88f53f7 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'app' => 'app/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php b/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php new file mode 100644 index 0000000..196f60e --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'module' => 'modules/{$vendor}/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php b/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php new file mode 100644 index 0000000..aa3a2e6 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php @@ -0,0 +1,57 @@ + */ + protected $locations = array( + 'plugin' => 'lib/plugins/{$name}/', + 'template' => 'lib/tpl/{$name}/', + ); + + /** + * Format package name. + * + * For package type dokuwiki-plugin, cut off a trailing '-plugin', + * or leading dokuwiki_ if present. + * + * For package type dokuwiki-template, cut off a trailing '-template' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'dokuwiki-plugin') { + return $this->inflectPluginVars($vars); + } + + if ($vars['type'] === 'dokuwiki-template') { + return $this->inflectTemplateVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectPluginVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-plugin$/', '', $vars['name']); + $vars['name'] = $this->pregReplace('/^dokuwiki_?-?/', '', $vars['name']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectTemplateVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-template$/', '', $vars['name']); + $vars['name'] = $this->pregReplace('/^dokuwiki_?-?/', '', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php b/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php new file mode 100644 index 0000000..c583619 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php @@ -0,0 +1,18 @@ + + */ +class DolibarrInstaller extends BaseInstaller +{ + //TODO: Add support for scripts and themes + /** @var array */ + protected $locations = array( + 'module' => 'htdocs/custom/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php b/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php new file mode 100644 index 0000000..65a3a91 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php @@ -0,0 +1,25 @@ + */ + protected $locations = array( + 'core' => 'core/', + 'module' => 'modules/{$name}/', + 'theme' => 'themes/{$name}/', + 'library' => 'libraries/{$name}/', + 'profile' => 'profiles/{$name}/', + 'database-driver' => 'drivers/lib/Drupal/Driver/Database/{$name}/', + 'drush' => 'drush/{$name}/', + 'custom-theme' => 'themes/custom/{$name}/', + 'custom-module' => 'modules/custom/{$name}/', + 'custom-profile' => 'profiles/custom/{$name}/', + 'drupal-multisite' => 'sites/{$name}/', + 'console' => 'console/{$name}/', + 'console-language' => 'console/language/{$name}/', + 'config' => 'config/sync/', + 'recipe' => 'recipes/{$name}', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php b/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php new file mode 100644 index 0000000..48ef2ec --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'plugin' => 'mod/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php b/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php new file mode 100644 index 0000000..d7dd9a9 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'component' => 'components/{$name}/', + 'module' => 'modules/{$name}/', + 'plugin' => 'plugins/{$name}/', + 'template' => 'templates/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php b/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php new file mode 100644 index 0000000..fe1d468 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php @@ -0,0 +1,31 @@ + */ + private $ee2Locations = array( + 'addon' => 'system/expressionengine/third_party/{$name}/', + 'theme' => 'themes/third_party/{$name}/', + ); + + /** @var array */ + private $ee3Locations = array( + 'addon' => 'system/user/addons/{$name}/', + 'theme' => 'themes/user/{$name}/', + ); + + public function getLocations(string $frameworkType): array + { + if ($frameworkType === 'ee2') { + $this->locations = $this->ee2Locations; + } else { + $this->locations = $this->ee3Locations; + } + + return $this->locations; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php b/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php new file mode 100644 index 0000000..1f5b84e --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'meta-assets' => 'web/assets/ezplatform/', + 'assets' => 'web/assets/ezplatform/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ForkCMSInstaller.php b/vendor/composer/installers/src/Composer/Installers/ForkCMSInstaller.php new file mode 100644 index 0000000..cf62926 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ForkCMSInstaller.php @@ -0,0 +1,58 @@ + */ + protected $locations = [ + 'module' => 'src/Modules/{$name}/', + 'theme' => 'src/Themes/{$name}/' + ]; + + /** + * Format package name. + * + * For package type fork-cms-module, cut off a trailing '-plugin' if present. + * + * For package type fork-cms-theme, cut off a trailing '-theme' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'fork-cms-module') { + return $this->inflectModuleVars($vars); + } + + if ($vars['type'] === 'fork-cms-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^fork-cms-|-module|ForkCMS|ForkCms|Forkcms|forkcms|Module$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); // replace hyphens with spaces + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); // make module name camelcased + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^fork-cms-|-theme|ForkCMS|ForkCms|Forkcms|forkcms|Theme$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); // replace hyphens with spaces + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); // make theme name camelcased + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php b/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php new file mode 100644 index 0000000..5948572 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'module' => 'fuel/app/modules/{$name}/', + 'package' => 'fuel/packages/{$name}/', + 'theme' => 'fuel/app/themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php b/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php new file mode 100644 index 0000000..b4d80ed --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'component' => 'components/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/GravInstaller.php b/vendor/composer/installers/src/Composer/Installers/GravInstaller.php new file mode 100644 index 0000000..f5792e3 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/GravInstaller.php @@ -0,0 +1,29 @@ + */ + protected $locations = array( + 'plugin' => 'user/plugins/{$name}/', + 'theme' => 'user/themes/{$name}/', + ); + + /** + * Format package name + */ + public function inflectPackageVars(array $vars): array + { + $restrictedWords = implode('|', array_keys($this->locations)); + + $vars['name'] = strtolower($vars['name']); + $vars['name'] = $this->pregReplace( + '/^(?:grav-)?(?:(?:'.$restrictedWords.')-)?(.*?)(?:-(?:'.$restrictedWords.'))?$/ui', + '$1', + $vars['name'] + ); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php b/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php new file mode 100644 index 0000000..dd76c5b --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php @@ -0,0 +1,27 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + 'theme' => 'plugins/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $nameParts = explode('/', $vars['name']); + foreach ($nameParts as &$value) { + $value = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $value)); + $value = str_replace(array('-', '_'), ' ', $value); + $value = str_replace(' ', '', ucwords($value)); + } + $vars['name'] = implode('/', $nameParts); + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php b/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php new file mode 100644 index 0000000..4157cec --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'template' => 'templates/{$name}/', + 'module' => 'application/modules/{$name}/', + 'library' => 'application/libraries/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/Installer.php b/vendor/composer/installers/src/Composer/Installers/Installer.php new file mode 100644 index 0000000..862d8ae --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/Installer.php @@ -0,0 +1,288 @@ + + */ + private $supportedTypes = array( + 'akaunting' => 'AkauntingInstaller', + 'asgard' => 'AsgardInstaller', + 'attogram' => 'AttogramInstaller', + 'agl' => 'AglInstaller', + 'annotatecms' => 'AnnotateCmsInstaller', + 'bitrix' => 'BitrixInstaller', + 'botble' => 'BotbleInstaller', + 'bonefish' => 'BonefishInstaller', + 'cakephp' => 'CakePHPInstaller', + 'chef' => 'ChefInstaller', + 'civicrm' => 'CiviCrmInstaller', + 'ccframework' => 'ClanCatsFrameworkInstaller', + 'cockpit' => 'CockpitInstaller', + 'codeigniter' => 'CodeIgniterInstaller', + 'concrete5' => 'Concrete5Installer', + 'concretecms' => 'ConcreteCMSInstaller', + 'croogo' => 'CroogoInstaller', + 'dframe' => 'DframeInstaller', + 'dokuwiki' => 'DokuWikiInstaller', + 'dolibarr' => 'DolibarrInstaller', + 'decibel' => 'DecibelInstaller', + 'drupal' => 'DrupalInstaller', + 'elgg' => 'ElggInstaller', + 'eliasis' => 'EliasisInstaller', + 'ee3' => 'ExpressionEngineInstaller', + 'ee2' => 'ExpressionEngineInstaller', + 'ezplatform' => 'EzPlatformInstaller', + 'fork' => 'ForkCMSInstaller', + 'fuel' => 'FuelInstaller', + 'fuelphp' => 'FuelphpInstaller', + 'grav' => 'GravInstaller', + 'hurad' => 'HuradInstaller', + 'tastyigniter' => 'TastyIgniterInstaller', + 'imagecms' => 'ImageCMSInstaller', + 'itop' => 'ItopInstaller', + 'kanboard' => 'KanboardInstaller', + 'known' => 'KnownInstaller', + 'kodicms' => 'KodiCMSInstaller', + 'kohana' => 'KohanaInstaller', + 'lms' => 'LanManagementSystemInstaller', + 'laravel' => 'LaravelInstaller', + 'lavalite' => 'LavaLiteInstaller', + 'lithium' => 'LithiumInstaller', + 'magento' => 'MagentoInstaller', + 'majima' => 'MajimaInstaller', + 'mantisbt' => 'MantisBTInstaller', + 'mako' => 'MakoInstaller', + 'matomo' => 'MatomoInstaller', + 'maya' => 'MayaInstaller', + 'mautic' => 'MauticInstaller', + 'mediawiki' => 'MediaWikiInstaller', + 'miaoxing' => 'MiaoxingInstaller', + 'microweber' => 'MicroweberInstaller', + 'modulework' => 'MODULEWorkInstaller', + 'modx' => 'ModxInstaller', + 'modxevo' => 'MODXEvoInstaller', + 'moodle' => 'MoodleInstaller', + 'october' => 'OctoberInstaller', + 'ontowiki' => 'OntoWikiInstaller', + 'oxid' => 'OxidInstaller', + 'osclass' => 'OsclassInstaller', + 'pxcms' => 'PxcmsInstaller', + 'phpbb' => 'PhpBBInstaller', + 'piwik' => 'PiwikInstaller', + 'plentymarkets'=> 'PlentymarketsInstaller', + 'ppi' => 'PPIInstaller', + 'puppet' => 'PuppetInstaller', + 'radphp' => 'RadPHPInstaller', + 'phifty' => 'PhiftyInstaller', + 'porto' => 'PortoInstaller', + 'processwire' => 'ProcessWireInstaller', + 'quicksilver' => 'PantheonInstaller', + 'redaxo' => 'RedaxoInstaller', + 'redaxo5' => 'Redaxo5Installer', + 'reindex' => 'ReIndexInstaller', + 'roundcube' => 'RoundcubeInstaller', + 'shopware' => 'ShopwareInstaller', + 'sitedirect' => 'SiteDirectInstaller', + 'silverstripe' => 'SilverStripeInstaller', + 'smf' => 'SMFInstaller', + 'starbug' => 'StarbugInstaller', + 'sydes' => 'SyDESInstaller', + 'sylius' => 'SyliusInstaller', + 'tao' => 'TaoInstaller', + 'thelia' => 'TheliaInstaller', + 'tusk' => 'TuskInstaller', + 'userfrosting' => 'UserFrostingInstaller', + 'vanilla' => 'VanillaInstaller', + 'whmcs' => 'WHMCSInstaller', + 'winter' => 'WinterInstaller', + 'wolfcms' => 'WolfCMSInstaller', + 'wordpress' => 'WordPressInstaller', + 'yawik' => 'YawikInstaller', + 'zend' => 'ZendInstaller', + 'zikula' => 'ZikulaInstaller', + 'prestashop' => 'PrestashopInstaller' + ); + + /** + * Disables installers specified in main composer extra installer-disable + * list + */ + public function __construct( + IOInterface $io, + Composer $composer, + string $type = 'library', + ?Filesystem $filesystem = null, + ?BinaryInstaller $binaryInstaller = null + ) { + parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller); + $this->removeDisabledInstallers(); + } + + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + $type = $package->getType(); + $frameworkType = $this->findFrameworkType($type); + + if ($frameworkType === false) { + throw new \InvalidArgumentException( + 'Sorry the package type of this package is not yet supported.' + ); + } + + $class = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType]; + /** + * @var BaseInstaller + */ + $installer = new $class($package, $this->composer, $this->getIO()); + + $path = $installer->getInstallPath($package, $frameworkType); + if (!$this->filesystem->isAbsolutePath($path)) { + $path = getcwd() . '/' . $path; + } + + return $path; + } + + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $installPath = $this->getPackageBasePath($package); + $io = $this->io; + $outputStatus = function () use ($io, $installPath) { + $io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? 'deleted' : 'not deleted')); + }; + + $promise = parent::uninstall($repo, $package); + + // Composer v2 might return a promise here + if ($promise instanceof PromiseInterface) { + return $promise->then($outputStatus); + } + + // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async) + $outputStatus(); + + return null; + } + + /** + * {@inheritDoc} + * + * @param string $packageType + */ + public function supports($packageType) + { + $frameworkType = $this->findFrameworkType($packageType); + + if ($frameworkType === false) { + return false; + } + + $locationPattern = $this->getLocationPattern($frameworkType); + + return preg_match('#' . $frameworkType . '-' . $locationPattern . '#', $packageType, $matches) === 1; + } + + /** + * Finds a supported framework type if it exists and returns it + * + * @return string|false + */ + protected function findFrameworkType(string $type) + { + krsort($this->supportedTypes); + + foreach ($this->supportedTypes as $key => $val) { + if ($key === substr($type, 0, strlen($key))) { + return substr($type, 0, strlen($key)); + } + } + + return false; + } + + /** + * Get the second part of the regular expression to check for support of a + * package type + */ + protected function getLocationPattern(string $frameworkType): string + { + $pattern = null; + if (!empty($this->supportedTypes[$frameworkType])) { + $frameworkClass = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType]; + /** @var BaseInstaller $framework */ + $framework = new $frameworkClass(new Package('dummy/pkg', '1.0.0.0', '1.0.0'), $this->composer, $this->getIO()); + $locations = array_keys($framework->getLocations($frameworkType)); + if ($locations) { + $pattern = '(' . implode('|', $locations) . ')'; + } + } + + return $pattern ?: '(\w+)'; + } + + private function getIO(): IOInterface + { + return $this->io; + } + + /** + * Look for installers set to be disabled in composer's extra config and + * remove them from the list of supported installers. + * + * Globals: + * - true, "all", and "*" - disable all installers. + * - false - enable all installers (useful with + * wikimedia/composer-merge-plugin or similar) + */ + protected function removeDisabledInstallers(): void + { + $extra = $this->composer->getPackage()->getExtra(); + + if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) { + // No installers are disabled + return; + } + + // Get installers to disable + $disable = $extra['installer-disable']; + + // Ensure $disabled is an array + if (!is_array($disable)) { + $disable = array($disable); + } + + // Check which installers should be disabled + $all = array(true, "all", "*"); + $intersect = array_intersect($all, $disable); + if (!empty($intersect)) { + // Disable all installers + $this->supportedTypes = array(); + return; + } + + // Disable specified installers + foreach ($disable as $key => $installer) { + if (is_string($installer) && key_exists($installer, $this->supportedTypes)) { + unset($this->supportedTypes[$installer]); + } + } + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php b/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php new file mode 100644 index 0000000..06af068 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'extension' => 'extensions/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php b/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php new file mode 100644 index 0000000..bca954b --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php @@ -0,0 +1,20 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php b/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php new file mode 100644 index 0000000..61910a8 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'plugin' => 'IdnoPlugins/{$name}/', + 'theme' => 'Themes/{$name}/', + 'console' => 'ConsolePlugins/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php b/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php new file mode 100644 index 0000000..2505ac6 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'plugin' => 'cms/plugins/{$name}/', + 'media' => 'cms/media/vendor/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php b/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php new file mode 100644 index 0000000..b6aa809 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php b/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php new file mode 100644 index 0000000..7fe9d9b --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php @@ -0,0 +1,27 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + 'template' => 'templates/{$name}/', + 'document-template' => 'documents/templates/{$name}/', + 'userpanel-module' => 'userpanel/modules/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php b/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php new file mode 100644 index 0000000..a69dc88 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'library' => 'libraries/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php b/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php new file mode 100644 index 0000000..e4a7c7d --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'package' => 'packages/{$vendor}/{$name}/', + 'theme' => 'public/themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php b/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php new file mode 100644 index 0000000..b24bea2 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'library' => 'libraries/{$name}/', + 'source' => 'libraries/_source/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php b/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php new file mode 100644 index 0000000..369e8b4 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php b/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php new file mode 100644 index 0000000..062a839 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php @@ -0,0 +1,18 @@ + */ + protected $locations = array( + 'snippet' => 'assets/snippets/{$name}/', + 'plugin' => 'assets/plugins/{$name}/', + 'module' => 'assets/modules/{$name}/', + 'template' => 'assets/templates/{$name}/', + 'lib' => 'assets/lib/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php b/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php new file mode 100644 index 0000000..ec07cd6 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'theme' => 'app/design/frontend/{$name}/', + 'skin' => 'skin/frontend/default/{$name}/', + 'library' => 'lib/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php b/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php new file mode 100644 index 0000000..6fc3089 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php @@ -0,0 +1,46 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); + + /** + * Transforms the names + * + * @param array $vars + * @return array + */ + public function inflectPackageVars(array $vars): array + { + return $this->correctPluginName($vars); + } + + /** + * Change hyphenated names to camelcase + * + * @param array $vars + * @return array + */ + private function correctPluginName(array $vars): array + { + $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) { + return strtoupper($matches[0][1]); + }, $vars['name']); + + if (null === $camelCasedName) { + throw new \RuntimeException('Failed to run preg_replace_callback: '.preg_last_error()); + } + + $vars['name'] = ucfirst($camelCasedName); + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php b/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php new file mode 100644 index 0000000..cbe3760 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'package' => 'app/packages/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php b/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php new file mode 100644 index 0000000..98e230f --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php @@ -0,0 +1,25 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MatomoInstaller.php b/vendor/composer/installers/src/Composer/Installers/MatomoInstaller.php new file mode 100644 index 0000000..57fdb03 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MatomoInstaller.php @@ -0,0 +1,28 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php b/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php new file mode 100644 index 0000000..e48c133 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php @@ -0,0 +1,43 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + 'theme' => 'themes/{$name}/', + 'core' => 'app/', + ); + + private function getDirectoryName(): string + { + $extra = $this->package->getExtra(); + if (!empty($extra['install-directory-name'])) { + return $extra['install-directory-name']; + } + + return $this->toCamelCase($this->package->getPrettyName()); + } + + private function toCamelCase(string $packageName): string + { + return str_replace(' ', '', ucwords(str_replace('-', ' ', basename($packageName)))); + } + + /** + * Format package name of mautic-plugins to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] == 'mautic-plugin' || $vars['type'] == 'mautic-theme') { + $directoryName = $this->getDirectoryName(); + $vars['name'] = $directoryName; + } + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php b/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php new file mode 100644 index 0000000..df486da --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php @@ -0,0 +1,38 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); + + /** + * Format package name. + * + * For package type maya-module, cut off a trailing '-module' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'maya-module') { + return $this->inflectModuleVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-module$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php b/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php new file mode 100644 index 0000000..8e9d771 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php @@ -0,0 +1,58 @@ + */ + protected $locations = array( + 'core' => 'core/', + 'extension' => 'extensions/{$name}/', + 'skin' => 'skins/{$name}/', + ); + + /** + * Format package name. + * + * For package type mediawiki-extension, cut off a trailing '-extension' if present and transform + * to CamelCase keeping existing uppercase chars. + * + * For package type mediawiki-skin, cut off a trailing '-skin' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'mediawiki-extension') { + return $this->inflectExtensionVars($vars); + } + + if ($vars['type'] === 'mediawiki-skin') { + return $this->inflectSkinVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectExtensionVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-extension$/', '', $vars['name']); + $vars['name'] = str_replace('-', ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectSkinVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-skin$/', '', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php b/vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php new file mode 100644 index 0000000..0254177 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php b/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php new file mode 100644 index 0000000..a4d97ab --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php @@ -0,0 +1,145 @@ + */ + protected $locations = array( + 'module' => 'userfiles/modules/{$install_item_dir}/', + 'module-skin' => 'userfiles/modules/{$install_item_dir}/templates/', + 'template' => 'userfiles/templates/{$install_item_dir}/', + 'element' => 'userfiles/elements/{$install_item_dir}/', + 'vendor' => 'vendor/{$install_item_dir}/', + 'components' => 'components/{$install_item_dir}/' + ); + + /** + * Format package name. + * + * For package type microweber-module, cut off a trailing '-module' if present + * + * For package type microweber-template, cut off a trailing '-template' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($this->package->getTargetDir() !== null && $this->package->getTargetDir() !== '') { + $vars['install_item_dir'] = $this->package->getTargetDir(); + } else { + $vars['install_item_dir'] = $vars['name']; + if ($vars['type'] === 'microweber-template') { + return $this->inflectTemplateVars($vars); + } + if ($vars['type'] === 'microweber-templates') { + return $this->inflectTemplatesVars($vars); + } + if ($vars['type'] === 'microweber-core') { + return $this->inflectCoreVars($vars); + } + if ($vars['type'] === 'microweber-adapter') { + return $this->inflectCoreVars($vars); + } + if ($vars['type'] === 'microweber-module') { + return $this->inflectModuleVars($vars); + } + if ($vars['type'] === 'microweber-modules') { + return $this->inflectModulesVars($vars); + } + if ($vars['type'] === 'microweber-skin') { + return $this->inflectSkinVars($vars); + } + if ($vars['type'] === 'microweber-element' or $vars['type'] === 'microweber-elements') { + return $this->inflectElementVars($vars); + } + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectTemplateVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-template$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/template-$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectTemplatesVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-templates$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/templates-$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectCoreVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-providers$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/-provider$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/-adapter$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-module$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/module-$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModulesVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-modules$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/modules-$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectSkinVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-skin$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/skin-$/', '', $vars['install_item_dir']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectElementVars(array $vars): array + { + $vars['install_item_dir'] = $this->pregReplace('/-elements$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/elements-$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/-element$/', '', $vars['install_item_dir']); + $vars['install_item_dir'] = $this->pregReplace('/element-$/', '', $vars['install_item_dir']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php b/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php new file mode 100644 index 0000000..e2dddec --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'extra' => 'core/packages/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php b/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php new file mode 100644 index 0000000..eb2b8ac --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php @@ -0,0 +1,73 @@ + */ + protected $locations = array( + 'mod' => 'mod/{$name}/', + 'admin_report' => 'admin/report/{$name}/', + 'atto' => 'lib/editor/atto/plugins/{$name}/', + 'tool' => 'admin/tool/{$name}/', + 'assignment' => 'mod/assignment/type/{$name}/', + 'assignsubmission' => 'mod/assign/submission/{$name}/', + 'assignfeedback' => 'mod/assign/feedback/{$name}/', + 'antivirus' => 'lib/antivirus/{$name}/', + 'auth' => 'auth/{$name}/', + 'availability' => 'availability/condition/{$name}/', + 'block' => 'blocks/{$name}/', + 'booktool' => 'mod/book/tool/{$name}/', + 'cachestore' => 'cache/stores/{$name}/', + 'cachelock' => 'cache/locks/{$name}/', + 'calendartype' => 'calendar/type/{$name}/', + 'communication' => 'communication/provider/{$name}/', + 'customfield' => 'customfield/field/{$name}/', + 'fileconverter' => 'files/converter/{$name}/', + 'format' => 'course/format/{$name}/', + 'coursereport' => 'course/report/{$name}/', + 'contenttype' => 'contentbank/contenttype/{$name}/', + 'customcertelement' => 'mod/customcert/element/{$name}/', + 'datafield' => 'mod/data/field/{$name}/', + 'dataformat' => 'dataformat/{$name}/', + 'datapreset' => 'mod/data/preset/{$name}/', + 'editor' => 'lib/editor/{$name}/', + 'enrol' => 'enrol/{$name}/', + 'filter' => 'filter/{$name}/', + 'forumreport' => 'mod/forum/report/{$name}/', + 'gradeexport' => 'grade/export/{$name}/', + 'gradeimport' => 'grade/import/{$name}/', + 'gradereport' => 'grade/report/{$name}/', + 'gradingform' => 'grade/grading/form/{$name}/', + 'h5plib' => 'h5p/h5plib/{$name}/', + 'local' => 'local/{$name}/', + 'logstore' => 'admin/tool/log/store/{$name}/', + 'ltisource' => 'mod/lti/source/{$name}/', + 'ltiservice' => 'mod/lti/service/{$name}/', + 'media' => 'media/player/{$name}/', + 'message' => 'message/output/{$name}/', + 'mlbackend' => 'lib/mlbackend/{$name}/', + 'mnetservice' => 'mnet/service/{$name}/', + 'paygw' => 'payment/gateway/{$name}/', + 'plagiarism' => 'plagiarism/{$name}/', + 'portfolio' => 'portfolio/{$name}/', + 'qbank' => 'question/bank/{$name}/', + 'qbehaviour' => 'question/behaviour/{$name}/', + 'qformat' => 'question/format/{$name}/', + 'qtype' => 'question/type/{$name}/', + 'quizaccess' => 'mod/quiz/accessrule/{$name}/', + 'quiz' => 'mod/quiz/report/{$name}/', + 'report' => 'report/{$name}/', + 'repository' => 'repository/{$name}/', + 'scormreport' => 'mod/scorm/report/{$name}/', + 'search' => 'search/engine/{$name}/', + 'theme' => 'theme/{$name}/', + 'tiny' => 'lib/editor/tiny/plugins/{$name}/', + 'tinymce' => 'lib/editor/tinymce/plugins/{$name}/', + 'profilefield' => 'user/profile/field/{$name}/', + 'webservice' => 'webservice/{$name}/', + 'workshopallocation' => 'mod/workshop/allocation/{$name}/', + 'workshopeval' => 'mod/workshop/eval/{$name}/', + 'workshopform' => 'mod/workshop/form/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php b/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php new file mode 100644 index 0000000..524f17d --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php @@ -0,0 +1,57 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + 'plugin' => 'plugins/{$vendor}/{$name}/', + 'theme' => 'themes/{$vendor}-{$name}/' + ); + + /** + * Format package name. + * + * For package type october-plugin, cut off a trailing '-plugin' if present. + * + * For package type october-theme, cut off a trailing '-theme' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'october-plugin') { + return $this->inflectPluginVars($vars); + } + + if ($vars['type'] === 'october-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectPluginVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^oc-|-plugin$/', '', $vars['name']); + $vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^oc-|-theme$/', '', $vars['name']); + $vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php b/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php new file mode 100644 index 0000000..fd20c1a --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php @@ -0,0 +1,26 @@ + */ + protected $locations = array( + 'extension' => 'extensions/{$name}/', + 'theme' => 'extensions/themes/{$name}/', + 'translation' => 'extensions/translations/{$name}/', + ); + + /** + * Format package name to lower case and remove ".ontowiki" suffix + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($vars['name']); + $vars['name'] = $this->pregReplace('/.ontowiki$/', '', $vars['name']); + $vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']); + $vars['name'] = $this->pregReplace('/-translation$/', '', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php b/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php new file mode 100644 index 0000000..e61d61f --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'plugin' => 'oc-content/plugins/{$name}/', + 'theme' => 'oc-content/themes/{$name}/', + 'language' => 'oc-content/languages/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php b/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php new file mode 100644 index 0000000..6e1e862 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php @@ -0,0 +1,49 @@ +.+)\/.+/'; + + /** @var array */ + protected $locations = array( + 'module' => 'modules/{$name}/', + 'theme' => 'application/views/{$name}/', + 'out' => 'out/{$name}/', + ); + + public function getInstallPath(PackageInterface $package, string $frameworkType = ''): string + { + $installPath = parent::getInstallPath($package, $frameworkType); + $type = $this->package->getType(); + if ($type === 'oxid-module') { + $this->prepareVendorDirectory($installPath); + } + return $installPath; + } + + /** + * Makes sure there is a vendormetadata.php file inside + * the vendor folder if there is a vendor folder. + */ + protected function prepareVendorDirectory(string $installPath): void + { + $matches = ''; + $hasVendorDirectory = preg_match(self::VENDOR_PATTERN, $installPath, $matches); + if (!$hasVendorDirectory) { + return; + } + + $vendorDirectory = $matches['vendor']; + $vendorPath = getcwd() . '/modules/' . $vendorDirectory; + if (!file_exists($vendorPath)) { + mkdir($vendorPath, 0755, true); + } + + $vendorMetaDataPath = $vendorPath . '/vendormetadata.php'; + touch($vendorMetaDataPath); + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php b/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php new file mode 100644 index 0000000..714c467 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PantheonInstaller.php b/vendor/composer/installers/src/Composer/Installers/PantheonInstaller.php new file mode 100644 index 0000000..439f61a --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PantheonInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'script' => 'web/private/scripts/quicksilver/{$name}', + 'module' => 'web/private/scripts/quicksilver/{$name}', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php b/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php new file mode 100644 index 0000000..3c970e2 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'bundle' => 'bundles/{$name}/', + 'library' => 'libraries/{$name}/', + 'framework' => 'frameworks/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php b/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php new file mode 100644 index 0000000..d53ee4f --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'extension' => 'ext/{$vendor}/{$name}/', + 'language' => 'language/{$name}/', + 'style' => 'styles/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php b/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php new file mode 100644 index 0000000..b2faf44 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php @@ -0,0 +1,28 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php b/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php new file mode 100644 index 0000000..0c06359 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php @@ -0,0 +1,28 @@ + */ + protected $locations = array( + 'plugin' => '{$name}/' + ); + + /** + * Remove hyphen, "plugin" and format to camelcase + */ + public function inflectPackageVars(array $vars): array + { + $nameBits = explode("-", $vars['name']); + foreach ($nameBits as $key => $name) { + $nameBits[$key] = ucfirst($name); + if (strcasecmp($name, "Plugin") == 0) { + unset($nameBits[$key]); + } + } + $vars['name'] = implode('', $nameBits); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/Plugin.php b/vendor/composer/installers/src/Composer/Installers/Plugin.php new file mode 100644 index 0000000..437a949 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/Plugin.php @@ -0,0 +1,28 @@ +installer = new Installer($io, $composer); + $composer->getInstallationManager()->addInstaller($this->installer); + } + + public function deactivate(Composer $composer, IOInterface $io): void + { + $composer->getInstallationManager()->removeInstaller($this->installer); + } + + public function uninstall(Composer $composer, IOInterface $io): void + { + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php b/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php new file mode 100644 index 0000000..a01d7a0 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'container' => 'app/Containers/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php b/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php new file mode 100644 index 0000000..23f156f --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + 'theme' => 'themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php b/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php new file mode 100644 index 0000000..a7eb1ee --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php @@ -0,0 +1,23 @@ + */ + protected $locations = array( + 'module' => 'site/modules/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php b/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php new file mode 100644 index 0000000..1a0a8a3 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php b/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php new file mode 100644 index 0000000..fc58b8a --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php @@ -0,0 +1,62 @@ + */ + protected $locations = array( + 'module' => 'app/Modules/{$name}/', + 'theme' => 'themes/{$name}/', + ); + + /** + * Format package name. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'pxcms-module') { + return $this->inflectModuleVars($vars); + } + + if ($vars['type'] === 'pxcms-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * For package type pxcms-module, cut off a trailing '-plugin' if present. + * + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy) + $vars['name'] = str_replace('module-', '', $vars['name']); // strip out module- + $vars['name'] = $this->pregReplace('/-module$/', '', $vars['name']); // strip out -module + $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s + $vars['name'] = ucwords($vars['name']); // make module name camelcased + + return $vars; + } + + /** + * For package type pxcms-module, cut off a trailing '-plugin' if present. + * + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy) + $vars['name'] = str_replace('theme-', '', $vars['name']); // strip out theme- + $vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']); // strip out -theme + $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s + $vars['name'] = ucwords($vars['name']); // make module name camelcased + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php b/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php new file mode 100644 index 0000000..4caae51 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php @@ -0,0 +1,26 @@ + */ + protected $locations = array( + 'bundle' => 'src/{$name}/' + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $nameParts = explode('/', $vars['name']); + foreach ($nameParts as &$value) { + $value = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $value)); + $value = str_replace(array('-', '_'), ' ', $value); + $value = str_replace(' ', '', ucwords($value)); + } + $vars['name'] = implode('/', $nameParts); + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php b/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php new file mode 100644 index 0000000..a19eaaf --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'theme' => 'themes/{$name}/', + 'plugin' => 'plugins/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php b/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php new file mode 100644 index 0000000..b62c926 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'addon' => 'redaxo/src/addons/{$name}/', + 'bestyle-plugin' => 'redaxo/src/addons/be_style/plugins/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php b/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php new file mode 100644 index 0000000..26b3aa8 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'addon' => 'redaxo/include/addons/{$name}/', + 'bestyle-plugin' => 'redaxo/include/addons/be_style/plugins/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php b/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php new file mode 100644 index 0000000..7e71674 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php @@ -0,0 +1,21 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + ); + + /** + * Lowercase name and changes the name to a underscores + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower(str_replace('-', '_', $vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php b/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php new file mode 100644 index 0000000..7321046 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'module' => 'Sources/{$name}/', + 'theme' => 'Themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php b/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php new file mode 100644 index 0000000..82b8e28 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php @@ -0,0 +1,66 @@ + */ + protected $locations = array( + 'backend-plugin' => 'engine/Shopware/Plugins/Local/Backend/{$name}/', + 'core-plugin' => 'engine/Shopware/Plugins/Local/Core/{$name}/', + 'frontend-plugin' => 'engine/Shopware/Plugins/Local/Frontend/{$name}/', + 'theme' => 'templates/{$name}/', + 'plugin' => 'custom/plugins/{$name}/', + 'frontend-theme' => 'themes/Frontend/{$name}/', + ); + + /** + * Transforms the names + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'shopware-theme') { + return $this->correctThemeName($vars); + } + + return $this->correctPluginName($vars); + } + + /** + * Changes the name to a camelcased combination of vendor and name + * + * @param array $vars + * @return array + */ + private function correctPluginName(array $vars): array + { + $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) { + return strtoupper($matches[0][1]); + }, $vars['name']); + + if (null === $camelCasedName) { + throw new \RuntimeException('Failed to run preg_replace_callback: '.preg_last_error()); + } + + $vars['name'] = ucfirst($vars['vendor']) . ucfirst($camelCasedName); + + return $vars; + } + + /** + * Changes the name to a underscore separated name + * + * @param array $vars + * @return array + */ + private function correctThemeName(array $vars): array + { + $vars['name'] = str_replace('-', '_', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php b/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php new file mode 100644 index 0000000..aa2de21 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php @@ -0,0 +1,33 @@ + */ + protected $locations = array( + 'module' => '{$name}/', + 'theme' => 'themes/{$name}/', + ); + + /** + * Return the install path based on package type. + * + * Relies on built-in BaseInstaller behaviour with one exception: silverstripe/framework + * must be installed to 'sapphire' and not 'framework' if the version is <3.0.0 + */ + public function getInstallPath(PackageInterface $package, string $frameworkType = ''): string + { + if ( + $package->getName() == 'silverstripe/framework' + && preg_match('/^\d+\.\d+\.\d+/', $package->getVersion()) + && version_compare($package->getVersion(), '2.999.999') < 0 + ) { + return $this->templatePath($this->locations['module'], array('name' => 'sapphire')); + } + + return parent::getInstallPath($package, $frameworkType); + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php b/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php new file mode 100644 index 0000000..0af3239 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php @@ -0,0 +1,34 @@ + */ + protected $locations = array( + 'module' => 'modules/{$vendor}/{$name}/', + 'plugin' => 'plugins/{$vendor}/{$name}/' + ); + + /** + * @param array $vars + * @return array + */ + public function inflectPackageVars(array $vars): array + { + return $this->parseVars($vars); + } + + /** + * @param array $vars + * @return array + */ + protected function parseVars(array $vars): array + { + $vars['vendor'] = strtolower($vars['vendor']) == 'sitedirect' ? 'SiteDirect' : $vars['vendor']; + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php b/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php new file mode 100644 index 0000000..72afa08 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + 'theme' => 'themes/{$name}/', + 'custom-module' => 'app/modules/{$name}/', + 'custom-theme' => 'app/themes/{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php b/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php new file mode 100644 index 0000000..24673d2 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php @@ -0,0 +1,55 @@ + */ + protected $locations = array( + 'module' => 'app/modules/{$name}/', + 'theme' => 'themes/{$name}/', + ); + + /** + * Format module name. + * + * Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] == 'sydes-module') { + return $this->inflectModuleVars($vars); + } + + if ($vars['type'] === 'sydes-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + public function inflectModuleVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/(^sydes-|-module$)/i', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/(^sydes-|-theme$)/', '', $vars['name']); + $vars['name'] = strtolower($vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php b/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php new file mode 100644 index 0000000..c82bd85 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'theme' => 'themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php b/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php new file mode 100644 index 0000000..8c1d814 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php @@ -0,0 +1,32 @@ + */ + protected $locations = array( + 'extension' => '{$name}' + ); + + public function inflectPackageVars(array $vars): array + { + $extra = $this->package->getExtra(); + + if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) { + $vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME]; + return $vars; + } + + $vars['name'] = str_replace('extension-', '', $vars['name']); + $vars['name'] = str_replace('-', ' ', $vars['name']); + $vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name']))); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php b/vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php new file mode 100644 index 0000000..39ceae0 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php @@ -0,0 +1,85 @@ + */ + protected $locations = [ + 'module' => 'app/{$name}/', + 'extension' => 'extensions/{$vendor}/{$name}/', + 'theme' => 'themes/{$name}/', + ]; + + /** + * Format package name. + * + * Cut off leading 'ti-ext-' or 'ti-theme-' if present. + * Strip vendor name of characters that is not alphanumeric or an underscore + * + */ + public function inflectPackageVars(array $vars): array + { + $extra = $this->package->getExtra(); + + if ($vars['type'] === 'tastyigniter-module') { + return $this->inflectModuleVars($vars); + } + + if ($vars['type'] === 'tastyigniter-extension') { + return $this->inflectExtensionVars($vars, $extra); + } + + if ($vars['type'] === 'tastyigniter-theme') { + return $this->inflectThemeVars($vars, $extra); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^ti-module-/', '', $vars['name']); + + return $vars; + } + + /** + * @param array $vars + * @param array $extra + * @return array + */ + protected function inflectExtensionVars(array $vars, array $extra): array + { + if (!empty($extra['tastyigniter-extension']['code'])) { + $parts = explode('.', $extra['tastyigniter-extension']['code']); + $vars['vendor'] = (string)$parts[0]; + $vars['name'] = (string)($parts[1] ?? ''); + } + + $vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']); + $vars['name'] = $this->pregReplace('/^ti-ext-/', '', $vars['name']); + + return $vars; + } + + /** + * @param array $vars + * @param array $extra + * @return array + */ + protected function inflectThemeVars(array $vars, array $extra): array + { + if (!empty($extra['tastyigniter-theme']['code'])) { + $vars['name'] = $extra['tastyigniter-theme']['code']; + } + + $vars['name'] = $this->pregReplace('/^ti-theme-/', '', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php b/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php new file mode 100644 index 0000000..896bed5 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'module' => 'local/modules/{$name}/', + 'frontoffice-template' => 'templates/frontOffice/{$name}/', + 'backoffice-template' => 'templates/backOffice/{$name}/', + 'email-template' => 'templates/email/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php b/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php new file mode 100644 index 0000000..3b5f142 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php @@ -0,0 +1,17 @@ + + */ +class TuskInstaller extends BaseInstaller +{ + /** @var array */ + protected $locations = array( + 'task' => '.tusk/tasks/{$name}/', + 'command' => '.tusk/commands/{$name}/', + 'asset' => 'assets/tusk/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php b/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php new file mode 100644 index 0000000..a646c5b --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'sprinkle' => 'app/sprinkles/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php b/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php new file mode 100644 index 0000000..06d5db3 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'plugin' => 'plugins/{$name}/', + 'theme' => 'themes/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php b/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php new file mode 100644 index 0000000..cf094dd --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php @@ -0,0 +1,59 @@ + */ + protected $locations = array( + 'bundle' => 'src/{$vendor}/{$name}/', + 'theme' => 'themes/{$name}/' + ); + + /** + * Format package name. + * + * For package type vgmcp-bundle, cut off a trailing '-bundle' if present. + * + * For package type vgmcp-theme, cut off a trailing '-theme' if present. + * + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'vgmcp-bundle') { + return $this->inflectPluginVars($vars); + } + + if ($vars['type'] === 'vgmcp-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectPluginVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-bundle$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/-theme$/', '', $vars['name']); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php b/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php new file mode 100644 index 0000000..91b19fd --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php @@ -0,0 +1,22 @@ + */ + protected $locations = array( + 'addons' => 'modules/addons/{$vendor}_{$name}/', + 'fraud' => 'modules/fraud/{$vendor}_{$name}/', + 'gateways' => 'modules/gateways/{$vendor}_{$name}/', + 'notifications' => 'modules/notifications/{$vendor}_{$name}/', + 'registrars' => 'modules/registrars/{$vendor}_{$name}/', + 'reports' => 'modules/reports/{$vendor}_{$name}/', + 'security' => 'modules/security/{$vendor}_{$name}/', + 'servers' => 'modules/servers/{$vendor}_{$name}/', + 'social' => 'modules/social/{$vendor}_{$name}/', + 'support' => 'modules/support/{$vendor}_{$name}/', + 'templates' => 'templates/{$vendor}_{$name}/', + 'includes' => 'includes/{$vendor}_{$name}/' + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/WinterInstaller.php b/vendor/composer/installers/src/Composer/Installers/WinterInstaller.php new file mode 100644 index 0000000..f75a681 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/WinterInstaller.php @@ -0,0 +1,71 @@ + */ + protected $locations = array( + 'module' => 'modules/{$name}/', + 'plugin' => 'plugins/{$vendor}/{$name}/', + 'theme' => 'themes/{$name}/' + ); + + /** + * Format package name. + * + * For package type winter-plugin, cut off a trailing '-plugin' if present. + * + * For package type winter-theme, cut off a trailing '-theme' if present. + */ + public function inflectPackageVars(array $vars): array + { + if ($vars['type'] === 'winter-module') { + return $this->inflectModuleVars($vars); + } + + if ($vars['type'] === 'winter-plugin') { + return $this->inflectPluginVars($vars); + } + + if ($vars['type'] === 'winter-theme') { + return $this->inflectThemeVars($vars); + } + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectModuleVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^wn-|-module$/', '', $vars['name']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectPluginVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^wn-|-plugin$/', '', $vars['name']); + $vars['vendor'] = $this->pregReplace('/[^a-z0-9_]/i', '', $vars['vendor']); + + return $vars; + } + + /** + * @param array $vars + * @return array + */ + protected function inflectThemeVars(array $vars): array + { + $vars['name'] = $this->pregReplace('/^wn-|-theme$/', '', $vars['name']); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php b/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php new file mode 100644 index 0000000..58a9587 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php @@ -0,0 +1,11 @@ + */ + protected $locations = array( + 'plugin' => 'wolf/plugins/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php b/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php new file mode 100644 index 0000000..d46d5ab --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php @@ -0,0 +1,14 @@ + */ + protected $locations = array( + 'plugin' => 'wp-content/plugins/{$name}/', + 'theme' => 'wp-content/themes/{$name}/', + 'muplugin' => 'wp-content/mu-plugins/{$name}/', + 'dropin' => 'wp-content/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php b/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php new file mode 100644 index 0000000..d609dea --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php @@ -0,0 +1,23 @@ + */ + protected $locations = array( + 'module' => 'module/{$name}/', + ); + + /** + * Format package name to CamelCase + */ + public function inflectPackageVars(array $vars): array + { + $vars['name'] = strtolower($this->pregReplace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); + $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); + $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); + + return $vars; + } +} diff --git a/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php b/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php new file mode 100644 index 0000000..ccfcd4a --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php @@ -0,0 +1,13 @@ + */ + protected $locations = array( + 'library' => 'library/{$name}/', + 'extra' => 'extras/library/{$name}/', + 'module' => 'module/{$name}/', + ); +} diff --git a/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php b/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php new file mode 100644 index 0000000..d1fd1d7 --- /dev/null +++ b/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php @@ -0,0 +1,12 @@ + */ + protected $locations = array( + 'module' => 'modules/{$vendor}-{$name}/', + 'theme' => 'themes/{$vendor}-{$name}/' + ); +} diff --git a/vendor/composer/installers/src/bootstrap.php b/vendor/composer/installers/src/bootstrap.php new file mode 100644 index 0000000..a5bb9ad --- /dev/null +++ b/vendor/composer/installers/src/bootstrap.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\MetadataMinifier; + +class MetadataMinifier +{ + /** + * Expands an array of minified versions back to their original format + * + * @param array[] $versions A list of minified version arrays + * @return array[] A list of version arrays + */ + public static function expand(array $versions) + { + $expanded = array(); + $expandedVersion = null; + foreach ($versions as $versionData) { + if (!$expandedVersion) { + $expandedVersion = $versionData; + $expanded[] = $expandedVersion; + continue; + } + + // add any changes from the previous version to the expanded one + foreach ($versionData as $key => $val) { + if ($val === '__unset') { + unset($expandedVersion[$key]); + } else { + $expandedVersion[$key] = $val; + } + } + + $expanded[] = $expandedVersion; + } + + return $expanded; + } + + /** + * Minifies an array of versions into a set of version diffs + * + * @param array[] $versions A list of version arrays + * @return array[] A list of versions minified with each array only containing the differences to the previous one + */ + public static function minify(array $versions) + { + $minifiedVersions = array(); + + $lastKnownVersionData = null; + foreach ($versions as $version) { + if (!$lastKnownVersionData) { + $lastKnownVersionData = $version; + $minifiedVersions[] = $version; + continue; + } + + $minifiedVersion = array(); + + // add any changes from the previous version + foreach ($version as $key => $val) { + if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) { + $minifiedVersion[$key] = $val; + $lastKnownVersionData[$key] = $val; + } + } + + // store any deletions from the previous version for keys missing in current one + foreach ($lastKnownVersionData as $key => $val) { + if (!isset($version[$key])) { + $minifiedVersion[$key] = "__unset"; + unset($lastKnownVersionData[$key]); + } + } + + $minifiedVersions[] = $minifiedVersion; + } + + return $minifiedVersions; + } +} diff --git a/vendor/composer/pcre/LICENSE b/vendor/composer/pcre/LICENSE new file mode 100644 index 0000000..c5a282f --- /dev/null +++ b/vendor/composer/pcre/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2021 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/pcre/README.md b/vendor/composer/pcre/README.md new file mode 100644 index 0000000..4906514 --- /dev/null +++ b/vendor/composer/pcre/README.md @@ -0,0 +1,189 @@ +composer/pcre +============= + +PCRE wrapping library that offers type-safe `preg_*` replacements. + +This library gives you a way to ensure `preg_*` functions do not fail silently, returning +unexpected `null`s that may not be handled. + +As of 3.0 this library enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage +for all matching and replaceCallback functions, [read more below](#preg_unmatched_as_null) +to understand the implications. + +It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it +simplifies and reduces the possible return values from all the `preg_*` functions which +are quite packed with edge cases. As of v2.2.0 / v3.2.0 the library also comes with a +[PHPStan extension](#phpstan-extension) for parsing regular expressions and giving you even better output types. + +This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations). +If you are looking for a richer API to handle regular expressions have a look at +[rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead. + +[![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions) + + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/pcre +``` + + +Requirements +------------ + +* PHP 7.4.0 is required for 3.x versions +* PHP 7.2.0 is required for 2.x versions +* PHP 5.3.2 is required for 1.x versions + + +Basic usage +----------- + +Instead of: + +```php +if (preg_match('{fo+}', $string, $matches)) { ... } +if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... } +if (preg_match_all('{fo+}', $string, $matches)) { ... } +$newString = preg_replace('{fo+}', 'bar', $string); +$newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = preg_grep('{[a-z]}', $elements); +$array = preg_split('{[a-z]+}', $string); +``` + +You can now call these on the `Preg` class: + +```php +use Composer\Pcre\Preg; + +if (Preg::match('{fo+}', $string, $matches)) { ... } +if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... } +if (Preg::matchAll('{fo+}', $string, $matches)) { ... } +$newString = Preg::replace('{fo+}', 'bar', $string); +$newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string); +$newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string); +$filtered = Preg::grep('{[a-z]}', $elements); +$array = Preg::split('{[a-z]+}', $string); +``` + +The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException` +instead of returning `null` (or false in some cases), so you can now use the return values safely relying on +the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split). + +Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety +when the number of pattern matches is not useful: + +```php +use Composer\Pcre\Preg; + +if (Preg::isMatch('{fo+}', $string, $matches)) // bool +if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool +``` + +Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups +are always present and thus non-nullable, making it easier to write type-safe code: + +```php +use Composer\Pcre\Preg; + +// $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw +if (Preg::matchStrictGroups('{fo+}', $string, $matches)) +if (Preg::matchAllStrictGroups('{fo+}', $string, $matches)) +``` + +**Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?` +or `(something)*` or branches with a `|` that result in some groups not being matched at all). +A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an +empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in +matches so it is also not a problem to use these with `*StrictGroups` methods. + +If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class: + +```php +use Composer\Pcre\Regex; + +// this is useful when you are just interested in knowing if something matched +// as it returns a bool instead of int(1/0) for match +$bool = Regex::isMatch('{fo+}', $string); + +$result = Regex::match('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchWithOffsets('{fo+}', $string); +if ($result->matched) { something($result->matches); } + +$result = Regex::matchAll('{fo+}', $string); +if ($result->matched && $result->count > 3) { something($result->matches); } + +$newString = Regex::replace('{fo+}', 'bar', $string)->result; +$newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result; +$newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result; +``` + +Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have +complex return types warranting a specific result object. + +See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php), +[MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details. + +Restrictions / Limitations +-------------------------- + +Due to type safety requirements a few restrictions are in place. + +- matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`. + You cannot pass the flag to `match`/`matchAll`. +- `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets` + instead. +- `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There + is no alternative provided as you can fairly easily code around it. +- `preg_filter` is not supported as it has a rather crazy API, most likely you should rather + use `Preg::grep` in combination with some loop and `Preg::replace`. +- `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`, + only simple strings. +- As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much + saner/more predictable results](#preg_unmatched_as_null). As of 3.0 the flag is also set for + `replaceCallback` and `replaceCallbackArray`. + +#### PREG_UNMATCHED_AS_NULL + +As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*` +functions. As of 3.0 it is also done for `replaceCallback` and `replaceCallbackArray`. + +This means your matches will always contain all matching groups, either as null if unmatched +or as string if it matched. + +The advantages in clarity and predictability are clearer if you compare the two outputs of +running this with and without PREG_UNMATCHED_AS_NULL in $flags: + +```php +preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags); +``` + +| no flag | PREG_UNMATCHED_AS_NULL | +| --- | --- | +| array (size=4) | array (size=5) | +| 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) | +| 1 => string 'a' (length=1) | 1 => string 'a' (length=1) | +| 2 => string '' (length=0) | 2 => null | +| 3 => string 'c' (length=1) | 3 => string 'c' (length=1) | +| | 4 => null | +| group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for | +| group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` | + +PHPStan Extension +----------------- + +To use the PHPStan extension if you do not use `phpstan/extension-installer` you can include `vendor/composer/pcre/extension.neon` in your PHPStan config. + +The extension provides much better type information for $matches as well as regex validation where possible. + +License +------- + +composer/pcre is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/pcre/composer.json b/vendor/composer/pcre/composer.json new file mode 100644 index 0000000..d3a7e67 --- /dev/null +++ b/vendor/composer/pcre/composer.json @@ -0,0 +1,54 @@ +{ + "name": "composer/pcre", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "type": "library", + "license": "MIT", + "keywords": [ + "pcre", + "regex", + "preg", + "regular expression" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8 || ^9", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Pcre\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php phpstan analyse" + } +} diff --git a/vendor/composer/pcre/extension.neon b/vendor/composer/pcre/extension.neon new file mode 100644 index 0000000..b9cea11 --- /dev/null +++ b/vendor/composer/pcre/extension.neon @@ -0,0 +1,22 @@ +# composer/pcre PHPStan extensions +# +# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon' +# in your phpstan config + +services: + - + class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension + tags: + - phpstan.staticMethodParameterOutTypeExtension + - + class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension + tags: + - phpstan.staticMethodParameterClosureTypeExtension + +rules: + - Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule + - Composer\Pcre\PHPStan\InvalidRegexPatternRule diff --git a/vendor/composer/pcre/src/MatchAllResult.php b/vendor/composer/pcre/src/MatchAllResult.php new file mode 100644 index 0000000..b22b52d --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php new file mode 100644 index 0000000..b7ec397 --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllStrictGroupsResult.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllStrictGroupsResult +{ + /** + * An array of match group => list of matched strings + * + * @readonly + * @var array> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php new file mode 100644 index 0000000..032a02c --- /dev/null +++ b/vendor/composer/pcre/src/MatchAllWithOffsetsResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchAllWithOffsetsResult +{ + /** + * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array> + * @phpstan-var array}>> + */ + public $matches; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array> $matches + * @phpstan-param array}>> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + $this->count = $count; + } +} diff --git a/vendor/composer/pcre/src/MatchResult.php b/vendor/composer/pcre/src/MatchResult.php new file mode 100644 index 0000000..e951a5e --- /dev/null +++ b/vendor/composer/pcre/src/MatchResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchStrictGroupsResult.php b/vendor/composer/pcre/src/MatchStrictGroupsResult.php new file mode 100644 index 0000000..126ee62 --- /dev/null +++ b/vendor/composer/pcre/src/MatchStrictGroupsResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchStrictGroupsResult +{ + /** + * An array of match group => string matched + * + * @readonly + * @var array + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/MatchWithOffsetsResult.php b/vendor/composer/pcre/src/MatchWithOffsetsResult.php new file mode 100644 index 0000000..ba4d4bc --- /dev/null +++ b/vendor/composer/pcre/src/MatchWithOffsetsResult.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class MatchWithOffsetsResult +{ + /** + * An array of match group => pair of string matched + offset in bytes (or -1 if no match) + * + * @readonly + * @var array + * @phpstan-var array}> + */ + public $matches; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + * @param array $matches + * @phpstan-param array}> $matches + */ + public function __construct(int $count, array $matches) + { + $this->matches = $matches; + $this->matched = (bool) $count; + } +} diff --git a/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php b/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php new file mode 100644 index 0000000..8a05fb2 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php @@ -0,0 +1,142 @@ + + */ +class InvalidRegexPatternRule implements Rule +{ + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $patterns = $this->extractPatterns($node, $scope); + + $errors = []; + foreach ($patterns as $pattern) { + $errorMessage = $this->validatePattern($pattern); + if ($errorMessage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build(); + } + + return $errors; + } + + /** + * @return string[] + */ + private function extractPatterns(StaticCall $node, Scope $scope): array + { + if (!$node->class instanceof FullyQualified) { + return []; + } + $isRegex = $node->class->toString() === Regex::class; + $isPreg = $node->class->toString() === Preg::class; + if (!$isRegex && !$isPreg) { + return []; + } + if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) { + return []; + } + + $functionName = $node->name->name; + if (!isset($node->getArgs()[0])) { + return []; + } + + $patternNode = $node->getArgs()[0]->value; + $patternType = $scope->getType($patternNode); + + $patternStrings = []; + + foreach ($patternType->getConstantStrings() as $constantStringType) { + if ($functionName === 'replaceCallbackArray') { + continue; + } + + $patternStrings[] = $constantStringType->getValue(); + } + + foreach ($patternType->getConstantArrays() as $constantArrayType) { + if ( + in_array($functionName, [ + 'replace', + 'replaceCallback', + ], true) + ) { + foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { + foreach ($arrayKeyType->getConstantStrings() as $constantString) { + $patternStrings[] = $constantString->getValue(); + } + } + } + + if ($functionName !== 'replaceCallbackArray') { + continue; + } + + foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { + foreach ($arrayKeyType->getConstantStrings() as $constantString) { + $patternStrings[] = $constantString->getValue(); + } + } + } + + return $patternStrings; + } + + private function validatePattern(string $pattern): ?string + { + try { + $msg = null; + $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool { + $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message); + + return true; + }); + + if ($pattern === '') { + return 'Empty string is not a valid regular expression'; + } + + Preg::match($pattern, ''); + if ($msg !== null) { + return $msg; + } + } catch (PcreException $e) { + if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) { + return $msg; + } + + return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage()); + } finally { + restore_error_handler(); + } + + return null; + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php b/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php new file mode 100644 index 0000000..aa30ab3 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchFlags.php @@ -0,0 +1,70 @@ +getType($flagsArg->value); + + $constantScalars = $flagsType->getConstantScalarValues(); + if ($constantScalars === []) { + return null; + } + + $internalFlagsTypes = []; + foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) { + if (!is_int($constantScalarValue)) { + return null; + } + + $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL); + } + return TypeCombinator::union(...$internalFlagsTypes); + } + + static public function removeNullFromMatches(Type $matchesType): Type + { + return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + + if ($type instanceof ConstantArrayType) { + return new ConstantArrayType( + $type->getKeyTypes(), + array_map(static function (Type $valueType) use ($traverse): Type { + return $traverse($valueType); + }, $type->getValueTypes()), + $type->getNextAutoIndexes(), + [], + $type->isList() + ); + } + + if ($type instanceof ArrayType) { + return new ArrayType($type->getKeyType(), $traverse($type->getItemType())); + } + + return TypeCombinator::removeNull($type); + }); + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php b/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php new file mode 100644 index 0000000..e0d6020 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php @@ -0,0 +1,65 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return + $methodReflection->getDeclaringClass()->getName() === Preg::class + && in_array($methodReflection->getName(), [ + 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', + 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' + ], true) + && $parameter->getName() === 'matches'; + } + + public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ( + $patternArg === null || $matchesArg === null + ) { + return null; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return null; + } + + if (stripos($methodReflection->getName(), 'matchAll') !== false) { + return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } + + return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); + } + +} diff --git a/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php b/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php new file mode 100644 index 0000000..3db0ce0 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php @@ -0,0 +1,119 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return Preg::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool + { + return in_array($methodReflection->getName(), [ + 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', + 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' + ], true) + && !$context->null(); + } + + public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $args = $node->getArgs(); + $patternArg = $args[0] ?? null; + $matchesArg = $args[2] ?? null; + $flagsArg = $args[3] ?? null; + + if ( + $patternArg === null || $matchesArg === null + ) { + return new SpecifiedTypes(); + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return new SpecifiedTypes(); + } + + if (stripos($methodReflection->getName(), 'matchAll') !== false) { + $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + } else { + $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + } + + if ($matchedType === null) { + return new SpecifiedTypes(); + } + + if ( + in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true) + ) { + $matchedType = PregMatchFlags::removeNullFromMatches($matchedType); + } + + $overwrite = false; + if ($context->false()) { + $overwrite = true; + $context = $context->negate(); + } + + // @phpstan-ignore function.alreadyNarrowedType + if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) { + $typeSpecifier = $this->typeSpecifier->create( + $matchesArg->value, + $matchedType, + $context, + $scope + )->setRootExpr($node); + + return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier; + } + + // @phpstan-ignore arguments.count + return $this->typeSpecifier->create( + $matchesArg->value, + $matchedType, + $context, + // @phpstan-ignore argument.type + $overwrite, + $scope, + $node + ); + } +} diff --git a/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php b/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php new file mode 100644 index 0000000..7b95367 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php @@ -0,0 +1,91 @@ +regexShapeMatcher = $regexShapeMatcher; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool + { + return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true) + && in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true) + && $parameter->getName() === 'replacement'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + $patternArg = $args[0] ?? null; + $flagsArg = $args[5] ?? null; + + if ( + $patternArg === null + ) { + return null; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + + $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); + if ($matchesType === null) { + return null; + } + + if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) { + $matchesType = $matchesType->getConstantArrays()[0]; + $matchesType = new ConstantArrayType( + $matchesType->getKeyTypes(), + array_map(static function (Type $valueType): Type { + if (count($valueType->getConstantArrays()) === 1) { + $valueTypeArray = $valueType->getConstantArrays()[0]; + return new ConstantArrayType( + $valueTypeArray->getKeyTypes(), + array_map(static function (Type $valueType): Type { + return TypeCombinator::removeNull($valueType); + }, $valueTypeArray->getValueTypes()), + $valueTypeArray->getNextAutoIndexes(), + [], + $valueTypeArray->isList() + ); + } + return TypeCombinator::removeNull($valueType); + }, $matchesType->getValueTypes()), + $matchesType->getNextAutoIndexes(), + [], + $matchesType->isList() + ); + } + + return new ClosureType( + [ + new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()), + ], + new StringType() + ); + } +} diff --git a/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php b/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php new file mode 100644 index 0000000..5bced50 --- /dev/null +++ b/vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php @@ -0,0 +1,112 @@ + + */ +final class UnsafeStrictGroupsCallRule implements Rule +{ + /** + * @var RegexArrayShapeMatcher + */ + private $regexShapeMatcher; + + public function __construct(RegexArrayShapeMatcher $regexShapeMatcher) + { + $this->regexShapeMatcher = $regexShapeMatcher; + } + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->class instanceof FullyQualified) { + return []; + } + $isRegex = $node->class->toString() === Regex::class; + $isPreg = $node->class->toString() === Preg::class; + if (!$isRegex && !$isPreg) { + return []; + } + if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) { + return []; + } + + $args = $node->getArgs(); + if (!isset($args[0])) { + return []; + } + + $patternArg = $args[0] ?? null; + if ($isPreg) { + if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway + return []; + } + $flagsArg = $args[3] ?? null; + } else { + $flagsArg = $args[2] ?? null; + } + + if ($patternArg === null) { + return []; + } + + $flagsType = PregMatchFlags::getType($flagsArg, $scope); + if ($flagsType === null) { + return []; + } + + $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); + if ($matchedType === null) { + return [ + RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name)) + ->identifier('composerPcre.maybeUnsafeStrictGroups') + ->build(), + ]; + } + + if (count($matchedType->getConstantArrays()) === 1) { + $matchedType = $matchedType->getConstantArrays()[0]; + $nullableGroups = []; + foreach ($matchedType->getValueTypes() as $index => $type) { + if (TypeCombinator::containsNull($type)) { + $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue(); + } + } + + if (\count($nullableGroups) > 0) { + return [ + RuleErrorBuilder::message(sprintf( + 'The %s call is unsafe as match group%s "%s" %s optional and may be null.', + $node->name->name, + \count($nullableGroups) > 1 ? 's' : '', + implode('", "', $nullableGroups), + \count($nullableGroups) > 1 ? 'are' : 'is' + ))->identifier('composerPcre.unsafeStrictGroups')->build(), + ]; + } + } + + return []; + } +} diff --git a/vendor/composer/pcre/src/PcreException.php b/vendor/composer/pcre/src/PcreException.php new file mode 100644 index 0000000..23d9327 --- /dev/null +++ b/vendor/composer/pcre/src/PcreException.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class PcreException extends \RuntimeException +{ + /** + * @param string $function + * @param string|string[] $pattern + * @return self + */ + public static function fromFunction($function, $pattern) + { + $code = preg_last_error(); + + if (is_array($pattern)) { + $pattern = implode(', ', $pattern); + } + + return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code); + } + + /** + * @param int $code + * @return string + */ + private static function pcreLastErrorMessage($code) + { + if (function_exists('preg_last_error_msg')) { + return preg_last_error_msg(); + } + + $constants = get_defined_constants(true); + if (!isset($constants['pcre']) || !is_array($constants['pcre'])) { + return 'UNDEFINED_ERROR'; + } + + foreach ($constants['pcre'] as $const => $val) { + if ($val === $code && substr($const, -6) === '_ERROR') { + return $const; + } + } + + return 'UNDEFINED_ERROR'; + } +} diff --git a/vendor/composer/pcre/src/Preg.php b/vendor/composer/pcre/src/Preg.php new file mode 100644 index 0000000..400abbf --- /dev/null +++ b/vendor/composer/pcre/src/Preg.php @@ -0,0 +1,430 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Preg +{ + /** @internal */ + public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.'; + /** @internal */ + public const INVALID_TYPE_MSG = '$subject must be a string, %s given.'; + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * + * @param-out array $matches + */ + public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|1 + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match'); + + return $result; + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported + * @return 0|1 + * + * @param-out array}> $matches + */ + public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if ($result === false) { + throw PcreException::fromFunction('preg_match', $pattern); + } + + return $result; + } + + /** + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array> $matches + */ + public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * Variant of `match()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @return 0|positive-int + * @throws UnexpectedNullMatchException + * + * @param-out array> $matches + */ + public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + { + $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset); + $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll'); + + return $result; + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + * @return 0|positive-int + * + * @param-out array}>> $matches + */ + public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int + { + self::checkSetOrder($flags); + + $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset); + if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false + throw PcreException::fromFunction('preg_match_all', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + * @param int $count Set by method + * + * @param-out int<0, max> $count + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace($pattern, $replacement, $subject, $limit, $count); + if ($result === null) { + throw PcreException::fromFunction('preg_replace', $pattern); + } + + return $result; + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + throw PcreException::fromFunction('preg_replace_callback', $pattern); + } + + return $result; + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) { + return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback')); + }, $subject, $limit, $count, $flags); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int $count Set by method + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + * + * @param-out int<0, max> $count + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string + { + if (!is_scalar($subject)) { + if (is_array($subject)) { + throw new \InvalidArgumentException(static::ARRAY_MSG); + } + + throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); + } + + $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL); + if ($result === null) { + $pattern = array_keys($pattern); + throw PcreException::fromFunction('preg_replace_callback_array', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE + * @return list + */ + public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); + } + + $result = preg_split($pattern, $subject, $limit, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @param int-mask $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set + * @return list + * @phpstan-return list}> + */ + public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array + { + $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE); + if ($result === false) { + throw PcreException::fromFunction('preg_split', $pattern); + } + + return $result; + } + + /** + * @template T of string|\Stringable + * @param string $pattern + * @param array $array + * @param int-mask $flags PREG_GREP_INVERT + * @return array + */ + public static function grep(string $pattern, array $array, int $flags = 0): array + { + $result = preg_grep($pattern, $array, $flags); + if ($result === false) { + throw PcreException::fromFunction('preg_grep', $pattern); + } + + return $result; + } + + /** + * Variant of match() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array $matches + */ + public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::match($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatch()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + * + * @param-out array $matches + */ + public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAll() which returns a bool instead of int + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of `isMatchAll()` which outputs non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array> $matches + */ + public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool + { + return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchWithOffsets() which returns a bool instead of int + * + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}> $matches + */ + public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + /** + * Variant of matchAllWithOffsets() which returns a bool instead of int + * + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param array $matches Set by method + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * + * @param-out array}>> $matches + */ + public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool + { + return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); + } + } + + /** + * @param array $matches + * @return array + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $match) { + if (is_string($match) || (is_array($match) && is_string($match[0]))) { + continue; + } + + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + + /** @var array */ + return $matches; + } + + /** + * @param array> $matches + * @return array> + * @throws UnexpectedNullMatchException + */ + private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) + { + foreach ($matches as $group => $groupMatches) { + foreach ($groupMatches as $match) { + if (null === $match) { + throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); + } + } + } + + /** @var array> */ + return $matches; + } +} diff --git a/vendor/composer/pcre/src/Regex.php b/vendor/composer/pcre/src/Regex.php new file mode 100644 index 0000000..038cf06 --- /dev/null +++ b/vendor/composer/pcre/src/Regex.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class Regex +{ + /** + * @param non-empty-string $pattern + */ + public static function isMatch(string $pattern, string $subject, int $offset = 0): bool + { + return (bool) Preg::match($pattern, $subject, $matches, 0, $offset); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult + { + self::checkOffsetCapture($flags, 'matchWithOffsets'); + + $count = Preg::match($pattern, $subject, $matches, $flags, $offset); + + return new MatchResult($count, $matches); + } + + /** + * Variant of `match()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult + { + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult + { + $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchWithOffsetsResult($count, $matches); + } + + /** + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + */ + public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllResult($count, $matches); + } + + /** + * Variant of `matchAll()` which returns non-null matches (or throws) + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported + * @throws UnexpectedNullMatchException + */ + public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult + { + self::checkOffsetCapture($flags, 'matchAllWithOffsets'); + self::checkSetOrder($flags); + + // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups + $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllStrictGroupsResult($count, $matches); + } + + /** + * Runs preg_match_all with PREG_OFFSET_CAPTURE + * + * @param non-empty-string $pattern + * @param int-mask $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported + */ + public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult + { + self::checkSetOrder($flags); + + $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); + + return new MatchAllWithOffsetsResult($count, $matches); + } + /** + * @param string|string[] $pattern + * @param string|string[] $replacement + * @param string $subject + */ + public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult + { + $result = Preg::replace($pattern, $replacement, $subject, $limit, $count); + + return new ReplaceResult($count, $result); + } + + /** + * @param string|string[] $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * Variant of `replaceCallback()` which outputs non-null matches (or throws) + * + * @param string $pattern + * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array}>): string) : callable(array): string) $replacement + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + /** + * @param ($flags is PREG_OFFSET_CAPTURE ? (array}>): string>) : array): string>) $pattern + * @param string $subject + * @param int-mask $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set + */ + public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult + { + $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags); + + return new ReplaceResult($count, $result); + } + + private static function checkOffsetCapture(int $flags, string $useFunctionName): void + { + if (($flags & PREG_OFFSET_CAPTURE) !== 0) { + throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead'); + } + } + + private static function checkSetOrder(int $flags): void + { + if (($flags & PREG_SET_ORDER) !== 0) { + throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type'); + } + } +} diff --git a/vendor/composer/pcre/src/ReplaceResult.php b/vendor/composer/pcre/src/ReplaceResult.php new file mode 100644 index 0000000..3384771 --- /dev/null +++ b/vendor/composer/pcre/src/ReplaceResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +final class ReplaceResult +{ + /** + * @readonly + * @var string + */ + public $result; + + /** + * @readonly + * @var 0|positive-int + */ + public $count; + + /** + * @readonly + * @var bool + */ + public $matched; + + /** + * @param 0|positive-int $count + */ + public function __construct(int $count, string $result) + { + $this->count = $count; + $this->matched = (bool) $count; + $this->result = $result; + } +} diff --git a/vendor/composer/pcre/src/UnexpectedNullMatchException.php b/vendor/composer/pcre/src/UnexpectedNullMatchException.php new file mode 100644 index 0000000..f123828 --- /dev/null +++ b/vendor/composer/pcre/src/UnexpectedNullMatchException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Pcre; + +class UnexpectedNullMatchException extends PcreException +{ + public static function fromFunction($function, $pattern) + { + throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class); + } +} diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index 580fa96..adfb472 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 70400)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 80000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/vendor/composer/semver/CHANGELOG.md b/vendor/composer/semver/CHANGELOG.md new file mode 100644 index 0000000..bad46cd --- /dev/null +++ b/vendor/composer/semver/CHANGELOG.md @@ -0,0 +1,229 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +### [3.4.3] 2024-09-19 + + * Fixed some type annotations + +### [3.4.2] 2024-07-12 + + * Fixed PHP 5.3 syntax error + +### [3.4.1] 2024-07-12 + + * Fixed normalizeStability's return type to enforce valid stabilities + +### [3.4.0] 2023-08-31 + + * Support larger major version numbers (#149) + +### [3.3.2] 2022-04-01 + + * Fixed handling of non-string values (#134) + +### [3.3.1] 2022-03-16 + + * Fixed possible cache key clash in the CompilingMatcher memoization (#132) + +### [3.3.0] 2022-03-15 + + * Improved performance of CompilingMatcher by memoizing more (#131) + * Added CompilingMatcher::clear to clear all memoization caches + +### [3.2.9] 2022-02-04 + + * Revert #129 (Fixed MultiConstraint with MatchAllConstraint) which caused regressions + +### [3.2.8] 2022-02-04 + + * Updates to latest phpstan / CI by @Seldaek in https://github.com/composer/semver/pull/130 + * Fixed MultiConstraint with MatchAllConstraint by @Toflar in https://github.com/composer/semver/pull/129 + +### [3.2.7] 2022-01-04 + + * Fixed: typo in type definition of Intervals class causing issues with Psalm scanning vendors + +### [3.2.6] 2021-10-25 + + * Fixed: type improvements to parseStability + +### [3.2.5] 2021-05-24 + + * Fixed: issue comparing disjunctive MultiConstraints to conjunctive ones (#127) + * Fixed: added complete type information using phpstan annotations + +### [3.2.4] 2020-11-13 + + * Fixed: code clean-up + +### [3.2.3] 2020-11-12 + + * Fixed: constraints in the form of `X || Y, >=Y.1` and other such complex constructs were in some cases being optimized into a more restrictive constraint + +### [3.2.2] 2020-10-14 + + * Fixed: internal code cleanups + +### [3.2.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [3.2.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [3.1.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 3.0.1 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [3.0.1] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + +### [3.0.0] 2020-05-26 + + * Break: Renamed `EmptyConstraint`, replace it with `MatchAllConstraint` + * Break: Unlikely to affect anyone but strictly speaking a breaking change, `*.*` and such variants will not match all `dev-*` versions anymore, only `*` does + * Break: ConstraintInterface is now considered internal/private and not meant to be implemented by third parties anymore + * Added `Intervals` class to check if a constraint is a subsets of another one, and allow compacting complex MultiConstraints into simpler ones + * Added `CompilingMatcher` class to speed up constraint matching against simple Constraint instances + * Added `MatchAllConstraint` and `MatchNoneConstraint` which match everything and nothing + * Added more advanced optimization of contiguous constraints inside MultiConstraint + * Added tentative support for PHP 8 + * Fixed ConstraintInterface::matches to be commutative in all cases + +### [2.0.0] 2020-04-21 + + * Break: `dev-master`, `dev-trunk` and `dev-default` now normalize to `dev-master`, `dev-trunk` and `dev-default` instead of `9999999-dev` in 1.x + * Break: Removed the deprecated `AbstractConstraint` + * Added `getUpperBound` and `getLowerBound` to ConstraintInterface. They return `Composer\Semver\Constraint\Bound` instances + * Added `MultiConstraint::create` to create the most-optimal form of ConstraintInterface from an array of constraint strings + +### [1.7.2] 2020-12-03 + + * Fixed: Allow installing on php 8 + +### [1.7.1] 2020-09-27 + + * Fixed: accidental validation of broken constraints combining ^/~ and wildcards, and -dev suffix allowing weird cases + * Fixed: normalization of beta0 and such which was dropping the 0 + +### [1.7.0] 2020-09-09 + + * Added: support for `x || @dev`, not very useful but seen in the wild and failed to validate with 1.5.2/1.6.0 + * Added: support for `foobar-dev` being equal to `dev-foobar`, dev-foobar is the official way to write it but we need to support the other for BC and convenience + +### [1.6.0] 2020-09-08 + + * Added: support for constraints like `^2.x-dev` and `~2.x-dev`, not very useful but seen in the wild and failed to validate with 1.5.2 + * Fixed: invalid aliases will no longer throw, unless explicitly validated by Composer in the root package + +### [1.5.2] 2020-09-08 + + * Fixed: handling of some invalid -dev versions which were seen as valid + * Fixed: some doctypes + +### [1.5.1] 2020-01-13 + + * Fixed: Parsing of aliased version was not validating the alias to be a valid version + +### [1.5.0] 2019-03-19 + + * Added: some support for date versions (e.g. 201903) in `~` operator + * Fixed: support for stabilities in `~` operator was inconsistent + +### [1.4.2] 2016-08-30 + + * Fixed: collapsing of complex constraints lead to buggy constraints + +### [1.4.1] 2016-06-02 + + * Changed: branch-like requirements no longer strip build metadata - [composer/semver#38](https://github.com/composer/semver/pull/38). + +### [1.4.0] 2016-03-30 + + * Added: getters on MultiConstraint - [composer/semver#35](https://github.com/composer/semver/pull/35). + +### [1.3.0] 2016-02-25 + + * Fixed: stability parsing - [composer/composer#1234](https://github.com/composer/composer/issues/4889). + * Changed: collapse contiguous constraints when possible. + +### [1.2.0] 2015-11-10 + + * Changed: allow multiple numerical identifiers in 'pre-release' version part. + * Changed: add more 'v' prefix support. + +### [1.1.0] 2015-11-03 + + * Changed: dropped redundant `test` namespace. + * Changed: minor adjustment in datetime parsing normalization. + * Changed: `ConstraintInterface` relaxed, setPrettyString is not required anymore. + * Changed: `AbstractConstraint` marked deprecated, will be removed in 2.0. + * Changed: `Constraint` is now extensible. + +### [1.0.0] 2015-09-21 + + * Break: `VersionConstraint` renamed to `Constraint`. + * Break: `SpecificConstraint` renamed to `AbstractConstraint`. + * Break: `LinkConstraintInterface` renamed to `ConstraintInterface`. + * Break: `VersionParser::parseNameVersionPairs` was removed. + * Changed: `VersionParser::parseConstraints` allows (but ignores) build metadata now. + * Changed: `VersionParser::parseConstraints` allows (but ignores) prefixing numeric versions with a 'v' now. + * Changed: Fixed namespace(s) of test files. + * Changed: `Comparator::compare` no longer throws `InvalidArgumentException`. + * Changed: `Constraint` now throws `InvalidArgumentException`. + +### [0.1.0] 2015-07-23 + + * Added: `Composer\Semver\Comparator`, various methods to compare versions. + * Added: various documents such as README.md, LICENSE, etc. + * Added: configuration files for Git, Travis, php-cs-fixer, phpunit. + * Break: the following namespaces were renamed: + - Namespace: `Composer\Package\Version` -> `Composer\Semver` + - Namespace: `Composer\Package\LinkConstraint` -> `Composer\Semver\Constraint` + - Namespace: `Composer\Test\Package\Version` -> `Composer\Test\Semver` + - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` + * Changed: code style using php-cs-fixer. + +[3.4.3]: https://github.com/composer/semver/compare/3.4.2...3.4.3 +[3.4.2]: https://github.com/composer/semver/compare/3.4.1...3.4.2 +[3.4.1]: https://github.com/composer/semver/compare/3.4.0...3.4.1 +[3.4.0]: https://github.com/composer/semver/compare/3.3.2...3.4.0 +[3.3.2]: https://github.com/composer/semver/compare/3.3.1...3.3.2 +[3.3.1]: https://github.com/composer/semver/compare/3.3.0...3.3.1 +[3.3.0]: https://github.com/composer/semver/compare/3.2.9...3.3.0 +[3.2.9]: https://github.com/composer/semver/compare/3.2.8...3.2.9 +[3.2.8]: https://github.com/composer/semver/compare/3.2.7...3.2.8 +[3.2.7]: https://github.com/composer/semver/compare/3.2.6...3.2.7 +[3.2.6]: https://github.com/composer/semver/compare/3.2.5...3.2.6 +[3.2.5]: https://github.com/composer/semver/compare/3.2.4...3.2.5 +[3.2.4]: https://github.com/composer/semver/compare/3.2.3...3.2.4 +[3.2.3]: https://github.com/composer/semver/compare/3.2.2...3.2.3 +[3.2.2]: https://github.com/composer/semver/compare/3.2.1...3.2.2 +[3.2.1]: https://github.com/composer/semver/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/composer/semver/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/composer/semver/compare/3.0.1...3.1.0 +[3.0.1]: https://github.com/composer/semver/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/semver/compare/2.0.0...3.0.0 +[2.0.0]: https://github.com/composer/semver/compare/1.5.1...2.0.0 +[1.7.2]: https://github.com/composer/semver/compare/1.7.1...1.7.2 +[1.7.1]: https://github.com/composer/semver/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/composer/semver/compare/1.6.0...1.7.0 +[1.6.0]: https://github.com/composer/semver/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/composer/semver/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/composer/semver/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/composer/semver/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/composer/semver/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/semver/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/semver/compare/1.3.0...1.4.0 +[1.3.0]: https://github.com/composer/semver/compare/1.2.0...1.3.0 +[1.2.0]: https://github.com/composer/semver/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/semver/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/semver/compare/0.1.0...1.0.0 +[0.1.0]: https://github.com/composer/semver/compare/5e0b9a4da...0.1.0 diff --git a/vendor/composer/semver/LICENSE b/vendor/composer/semver/LICENSE new file mode 100644 index 0000000..4669758 --- /dev/null +++ b/vendor/composer/semver/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/semver/README.md b/vendor/composer/semver/README.md new file mode 100644 index 0000000..7677849 --- /dev/null +++ b/vendor/composer/semver/README.md @@ -0,0 +1,99 @@ +composer/semver +=============== + +Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +[![Continuous Integration](https://github.com/composer/semver/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/continuous-integration.yml) +[![PHP Lint](https://github.com/composer/semver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/lint.yml) +[![PHPStan](https://github.com/composer/semver/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/phpstan.yml) + +Installation +------------ + +Install the latest version with: + +```bash +composer require composer/semver +``` + + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + + +Version Comparison +------------------ + +For details on how versions are compared, refer to the [Versions](https://getcomposer.org/doc/articles/versions.md) +article in the documentation section of the [getcomposer.org](https://getcomposer.org) website. + + +Basic usage +----------- + +### Comparator + +The [`Composer\Semver\Comparator`](https://github.com/composer/semver/blob/main/src/Comparator.php) class provides the following methods for comparing versions: + +* greaterThan($v1, $v2) +* greaterThanOrEqualTo($v1, $v2) +* lessThan($v1, $v2) +* lessThanOrEqualTo($v1, $v2) +* equalTo($v1, $v2) +* notEqualTo($v1, $v2) + +Each function takes two version strings as arguments and returns a boolean. For example: + +```php +use Composer\Semver\Comparator; + +Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0 +``` + +### Semver + +The [`Composer\Semver\Semver`](https://github.com/composer/semver/blob/main/src/Semver.php) class provides the following methods: + +* satisfies($version, $constraints) +* satisfiedBy(array $versions, $constraint) +* sort($versions) +* rsort($versions) + +### Intervals + +The [`Composer\Semver\Intervals`](https://github.com/composer/semver/blob/main/src/Intervals.php) static class provides +a few utilities to work with complex constraints or read version intervals from a constraint: + +```php +use Composer\Semver\Intervals; + +// Checks whether $candidate is a subset of $constraint +Intervals::isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint); + +// Checks whether $a and $b have any intersection, equivalent to $a->matches($b) +Intervals::haveIntersections(ConstraintInterface $a, ConstraintInterface $b); + +// Optimizes a complex multi constraint by merging all intervals down to the smallest +// possible multi constraint. The drawbacks are this is not very fast, and the resulting +// multi constraint will have no human readable prettyConstraint configured on it +Intervals::compactConstraint(ConstraintInterface $constraint); + +// Creates an array of numeric intervals and branch constraints representing a given constraint +Intervals::get(ConstraintInterface $constraint); + +// Clears the memoization cache when you are done processing constraints +Intervals::clear() +``` + +See the class docblocks for more details. + + +License +------- + +composer/semver is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/semver/composer.json b/vendor/composer/semver/composer.json new file mode 100644 index 0000000..1fad9e5 --- /dev/null +++ b/vendor/composer/semver/composer.json @@ -0,0 +1,59 @@ +{ + "name": "composer/semver", + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "type": "library", + "license": "MIT", + "keywords": [ + "semver", + "semantic", + "versioning", + "validation" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^7", + "phpstan/phpstan": "^1.11" + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Semver\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/semver/src/Comparator.php b/vendor/composer/semver/src/Comparator.php new file mode 100644 index 0000000..38f483a --- /dev/null +++ b/vendor/composer/semver/src/Comparator.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Comparator +{ + /** + * Evaluates the expression: $version1 > $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThan($version1, $version2) + { + return self::compare($version1, '>', $version2); + } + + /** + * Evaluates the expression: $version1 >= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function greaterThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '>=', $version2); + } + + /** + * Evaluates the expression: $version1 < $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThan($version1, $version2) + { + return self::compare($version1, '<', $version2); + } + + /** + * Evaluates the expression: $version1 <= $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function lessThanOrEqualTo($version1, $version2) + { + return self::compare($version1, '<=', $version2); + } + + /** + * Evaluates the expression: $version1 == $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function equalTo($version1, $version2) + { + return self::compare($version1, '==', $version2); + } + + /** + * Evaluates the expression: $version1 != $version2. + * + * @param string $version1 + * @param string $version2 + * + * @return bool + */ + public static function notEqualTo($version1, $version2) + { + return self::compare($version1, '!=', $version2); + } + + /** + * Evaluates the expression: $version1 $operator $version2. + * + * @param string $version1 + * @param string $operator + * @param string $version2 + * + * @return bool + * + * @phpstan-param Constraint::STR_OP_* $operator + */ + public static function compare($version1, $operator, $version2) + { + $constraint = new Constraint($operator, $version2); + + return $constraint->matchSpecific(new Constraint('==', $version1), true); + } +} diff --git a/vendor/composer/semver/src/CompilingMatcher.php b/vendor/composer/semver/src/CompilingMatcher.php new file mode 100644 index 0000000..aea1d3b --- /dev/null +++ b/vendor/composer/semver/src/CompilingMatcher.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * Helper class to evaluate constraint by compiling and reusing the code to evaluate + */ +class CompilingMatcher +{ + /** + * @var array + * @phpstan-var array + */ + private static $compiledCheckerCache = array(); + /** + * @var array + * @phpstan-var array + */ + private static $resultCache = array(); + + /** @var bool */ + private static $enabled; + + /** + * @phpstan-var array + */ + private static $transOpInt = array( + Constraint::OP_EQ => Constraint::STR_OP_EQ, + Constraint::OP_LT => Constraint::STR_OP_LT, + Constraint::OP_LE => Constraint::STR_OP_LE, + Constraint::OP_GT => Constraint::STR_OP_GT, + Constraint::OP_GE => Constraint::STR_OP_GE, + Constraint::OP_NE => Constraint::STR_OP_NE, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$resultCache = array(); + self::$compiledCheckerCache = array(); + } + + /** + * Evaluates the expression: $constraint match $operator $version + * + * @param ConstraintInterface $constraint + * @param int $operator + * @phpstan-param Constraint::OP_* $operator + * @param string $version + * + * @return bool + */ + public static function match(ConstraintInterface $constraint, $operator, $version) + { + $resultCacheKey = $operator.$constraint.';'.$version; + + if (isset(self::$resultCache[$resultCacheKey])) { + return self::$resultCache[$resultCacheKey]; + } + + if (self::$enabled === null) { + self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true); + } + if (!self::$enabled) { + return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version)); + } + + $cacheKey = $operator.$constraint; + if (!isset(self::$compiledCheckerCache[$cacheKey])) { + $code = $constraint->compile($operator); + self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};'); + } else { + $function = self::$compiledCheckerCache[$cacheKey]; + } + + return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0); + } +} diff --git a/vendor/composer/semver/src/Constraint/Bound.php b/vendor/composer/semver/src/Constraint/Bound.php new file mode 100644 index 0000000..7effb11 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Bound.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +class Bound +{ + /** + * @var string + */ + private $version; + + /** + * @var bool + */ + private $isInclusive; + + /** + * @param string $version + * @param bool $isInclusive + */ + public function __construct($version, $isInclusive) + { + $this->version = $version; + $this->isInclusive = $isInclusive; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return bool + */ + public function isInclusive() + { + return $this->isInclusive; + } + + /** + * @return bool + */ + public function isZero() + { + return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive(); + } + + /** + * @return bool + */ + public function isPositiveInfinity() + { + return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive(); + } + + /** + * Compares a bound to another with a given operator. + * + * @param Bound $other + * @param string $operator + * + * @return bool + */ + public function compareTo(Bound $other, $operator) + { + if (!\in_array($operator, array('<', '>'), true)) { + throw new \InvalidArgumentException('Does not support any other operator other than > or <.'); + } + + // If they are the same it doesn't matter + if ($this == $other) { + return false; + } + + $compareResult = version_compare($this->getVersion(), $other->getVersion()); + + // Not the same version means we don't need to check if the bounds are inclusive or not + if (0 !== $compareResult) { + return (('>' === $operator) ? 1 : -1) === $compareResult; + } + + // Question we're answering here is "am I higher than $other?" + return '>' === $operator ? $other->isInclusive() : !$other->isInclusive(); + } + + public function __toString() + { + return sprintf( + '%s [%s]', + $this->getVersion(), + $this->isInclusive() ? 'inclusive' : 'exclusive' + ); + } + + /** + * @return self + */ + public static function zero() + { + return new Bound('0.0.0.0-dev', true); + } + + /** + * @return self + */ + public static function positiveInfinity() + { + return new Bound(PHP_INT_MAX.'.0.0.0', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/Constraint.php b/vendor/composer/semver/src/Constraint/Constraint.php new file mode 100644 index 0000000..dc39482 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/Constraint.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a constraint. + */ +class Constraint implements ConstraintInterface +{ + /* operator integer values */ + const OP_EQ = 0; + const OP_LT = 1; + const OP_LE = 2; + const OP_GT = 3; + const OP_GE = 4; + const OP_NE = 5; + + /* operator string values */ + const STR_OP_EQ = '=='; + const STR_OP_EQ_ALT = '='; + const STR_OP_LT = '<'; + const STR_OP_LE = '<='; + const STR_OP_GT = '>'; + const STR_OP_GE = '>='; + const STR_OP_NE = '!='; + const STR_OP_NE_ALT = '<>'; + + /** + * Operator to integer translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpStr = array( + '=' => self::OP_EQ, + '==' => self::OP_EQ, + '<' => self::OP_LT, + '<=' => self::OP_LE, + '>' => self::OP_GT, + '>=' => self::OP_GE, + '<>' => self::OP_NE, + '!=' => self::OP_NE, + ); + + /** + * Integer to operator translation table. + * + * @var array + * @phpstan-var array + */ + private static $transOpInt = array( + self::OP_EQ => '==', + self::OP_LT => '<', + self::OP_LE => '<=', + self::OP_GT => '>', + self::OP_GE => '>=', + self::OP_NE => '!=', + ); + + /** + * @var int + * @phpstan-var self::OP_* + */ + protected $operator; + + /** @var string */ + protected $version; + + /** @var string|null */ + protected $prettyString; + + /** @var Bound */ + protected $lowerBound; + + /** @var Bound */ + protected $upperBound; + + /** + * Sets operator and version to compare with. + * + * @param string $operator + * @param string $version + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @phpstan-param self::STR_OP_* $operator + */ + public function __construct($operator, $version) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $this->operator = self::$transOpStr[$operator]; + $this->version = $version; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @return string + * + * @phpstan-return self::STR_OP_* + */ + public function getOperator() + { + return self::$transOpInt[$this->operator]; + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if ($provider instanceof self) { + return $this->matchSpecific($provider); + } + + // turn matching around to find a match + return $provider->matches($this); + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return $this->__toString(); + } + + /** + * Get all supported comparison operators. + * + * @return array + * + * @phpstan-return list + */ + public static function getSupportedOperators() + { + return array_keys(self::$transOpStr); + } + + /** + * @param string $operator + * @return int + * + * @phpstan-param self::STR_OP_* $operator + * @phpstan-return self::OP_* + */ + public static function getOperatorConstant($operator) + { + return self::$transOpStr[$operator]; + } + + /** + * @param string $a + * @param string $b + * @param string $operator + * @param bool $compareBranches + * + * @throws \InvalidArgumentException if invalid operator is given. + * + * @return bool + * + * @phpstan-param self::STR_OP_* $operator + */ + public function versionCompare($a, $b, $operator, $compareBranches = false) + { + if (!isset(self::$transOpStr[$operator])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid operator "%s" given, expected one of: %s', + $operator, + implode(', ', self::getSupportedOperators()) + )); + } + + $aIsBranch = strpos($a, 'dev-') === 0; + $bIsBranch = strpos($b, 'dev-') === 0; + + if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { + return $a !== $b; + } + + if ($aIsBranch && $bIsBranch) { + return $operator === '==' && $a === $b; + } + + // when branches are not comparable, we make sure dev branches never match anything + if (!$compareBranches && ($aIsBranch || $bIsBranch)) { + return false; + } + + return \version_compare($a, $b, $operator); + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + if (strpos($this->version, 'dev-') === 0) { + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b && $v === %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + return 'false'; + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('!$b || $v !== %s', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + return 'false'; + } + + if (self::OP_EQ === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true)); + } + if (self::OP_NE === $otherOperator) { + return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true)); + } + + return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]); + } + + if (self::OP_NE === $this->operator) { + if (self::OP_EQ === $otherOperator) { + return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true)); + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + return '!$b'; + } + + if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { + if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { + return '!$b'; + } + } else { // $this->operator must be self::OP_GT || self::OP_GE here + if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { + return '!$b'; + } + } + + if (self::OP_NE === $otherOperator) { + return 'true'; + } + + $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]); + if ($this->operator === self::OP_LE) { + if ($otherOperator === self::OP_GT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } elseif ($this->operator === self::OP_GE) { + if ($otherOperator === self::OP_LT) { + return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; + } + } + + return sprintf('!$b && %s', $codeComparison); + } + + /** + * @param Constraint $provider + * @param bool $compareBranches + * + * @return bool + */ + public function matchSpecific(Constraint $provider, $compareBranches = false) + { + $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); + $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); + + $isEqualOp = self::OP_EQ === $this->operator; + $isNonEqualOp = self::OP_NE === $this->operator; + $isProviderEqualOp = self::OP_EQ === $provider->operator; + $isProviderNonEqualOp = self::OP_NE === $provider->operator; + + // '!=' operator is match when other operator is not '==' operator or version is not match + // these kinds of comparisons always have a solution + if ($isNonEqualOp || $isProviderNonEqualOp) { + if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) { + return false; + } + + if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) { + return false; + } + + if (!$isEqualOp && !$isProviderEqualOp) { + return true; + } + return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); + } + + // an example for the condition is <= 2.0 & < 1.0 + // these kinds of comparisons always have a solution + if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { + return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0); + } + + $version1 = $isEqualOp ? $this->version : $provider->version; + $version2 = $isEqualOp ? $provider->version : $this->version; + $operator = $isEqualOp ? $provider->operator : $this->operator; + + if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { + // special case, e.g. require >= 1.0 and provide < 1.0 + // 1.0 >= 1.0 but 1.0 is outside of the provided interval + + return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp + && self::$transOpInt[$this->operator] !== $noEqualOp + && \version_compare($provider->version, $this->version, '==')); + } + + return false; + } + + /** + * @return string + */ + public function __toString() + { + return self::$transOpInt[$this->operator] . ' ' . $this->version; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + return $this->upperBound; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + // Branches + if (strpos($this->version, 'dev-') === 0) { + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + + return; + } + + switch ($this->operator) { + case self::OP_EQ: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_LT: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, false); + break; + case self::OP_LE: + $this->lowerBound = Bound::zero(); + $this->upperBound = new Bound($this->version, true); + break; + case self::OP_GT: + $this->lowerBound = new Bound($this->version, false); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_GE: + $this->lowerBound = new Bound($this->version, true); + $this->upperBound = Bound::positiveInfinity(); + break; + case self::OP_NE: + $this->lowerBound = Bound::zero(); + $this->upperBound = Bound::positiveInfinity(); + break; + } + } +} diff --git a/vendor/composer/semver/src/Constraint/ConstraintInterface.php b/vendor/composer/semver/src/Constraint/ConstraintInterface.php new file mode 100644 index 0000000..389b935 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/ConstraintInterface.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint + * in libraries relying on composer/semver but creating your own constraint class + * that implements this interface is not a supported use case and will cause the + * composer/semver components to return unexpected results. + */ +interface ConstraintInterface +{ + /** + * Checks whether the given constraint intersects in any way with this constraint + * + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider); + + /** + * Provides a compiled version of the constraint for the given operator + * The compiled version must be a PHP expression. + * Executor of compile version must provide 2 variables: + * - $v = the string version to compare with + * - $b = whether or not the version is a non-comparable branch (starts with "dev-") + * + * @see Constraint::OP_* for the list of available operators. + * @example return '!$b && version_compare($v, '1.0', '>')'; + * + * @param int $otherOperator one Constraint::OP_* + * + * @return string + * + * @phpstan-param Constraint::OP_* $otherOperator + */ + public function compile($otherOperator); + + /** + * @return Bound + */ + public function getUpperBound(); + + /** + * @return Bound + */ + public function getLowerBound(); + + /** + * @return string + */ + public function getPrettyString(); + + /** + * @param string|null $prettyString + * + * @return void + */ + public function setPrettyString($prettyString); + + /** + * @return string + */ + public function __toString(); +} diff --git a/vendor/composer/semver/src/Constraint/MatchAllConstraint.php b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php new file mode 100644 index 0000000..5e51af9 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchAllConstraint.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines the absence of a constraint. + * + * This constraint matches everything. + */ +class MatchAllConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'true'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '*'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return Bound::positiveInfinity(); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return Bound::zero(); + } +} diff --git a/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php new file mode 100644 index 0000000..dadcf62 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Blackhole of constraints, nothing escapes it + */ +class MatchNoneConstraint implements ConstraintInterface +{ + /** @var string|null */ + protected $prettyString; + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + return 'false'; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return '[]'; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + return new Bound('0.0.0.0-dev', false); + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + return new Bound('0.0.0.0-dev', false); + } +} diff --git a/vendor/composer/semver/src/Constraint/MultiConstraint.php b/vendor/composer/semver/src/Constraint/MultiConstraint.php new file mode 100644 index 0000000..1f4c006 --- /dev/null +++ b/vendor/composer/semver/src/Constraint/MultiConstraint.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver\Constraint; + +/** + * Defines a conjunctive or disjunctive set of constraints. + */ +class MultiConstraint implements ConstraintInterface +{ + /** + * @var ConstraintInterface[] + * @phpstan-var non-empty-array + */ + protected $constraints; + + /** @var string|null */ + protected $prettyString; + + /** @var string|null */ + protected $string; + + /** @var bool */ + protected $conjunctive; + + /** @var Bound|null */ + protected $lowerBound; + + /** @var Bound|null */ + protected $upperBound; + + /** + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @throws \InvalidArgumentException If less than 2 constraints are passed + */ + public function __construct(array $constraints, $conjunctive = true) + { + if (\count($constraints) < 2) { + throw new \InvalidArgumentException( + 'Must provide at least two constraints for a MultiConstraint. Use '. + 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use '. + 'MultiConstraint::create() which optimizes and handles those cases automatically.' + ); + } + + $this->constraints = $constraints; + $this->conjunctive = $conjunctive; + } + + /** + * @return ConstraintInterface[] + */ + public function getConstraints() + { + return $this->constraints; + } + + /** + * @return bool + */ + public function isConjunctive() + { + return $this->conjunctive; + } + + /** + * @return bool + */ + public function isDisjunctive() + { + return !$this->conjunctive; + } + + /** + * {@inheritDoc} + */ + public function compile($otherOperator) + { + $parts = array(); + foreach ($this->constraints as $constraint) { + $code = $constraint->compile($otherOperator); + if ($code === 'true') { + if (!$this->conjunctive) { + return 'true'; + } + } elseif ($code === 'false') { + if ($this->conjunctive) { + return 'false'; + } + } else { + $parts[] = '('.$code.')'; + } + } + + if (!$parts) { + return $this->conjunctive ? 'true' : 'false'; + } + + return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts); + } + + /** + * @param ConstraintInterface $provider + * + * @return bool + */ + public function matches(ConstraintInterface $provider) + { + if (false === $this->conjunctive) { + foreach ($this->constraints as $constraint) { + if ($provider->matches($constraint)) { + return true; + } + } + + return false; + } + + // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one + // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one + // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect + if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) { + return $provider->matches($this); + } + + foreach ($this->constraints as $constraint) { + if (!$provider->matches($constraint)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function setPrettyString($prettyString) + { + $this->prettyString = $prettyString; + } + + /** + * {@inheritDoc} + */ + public function getPrettyString() + { + if ($this->prettyString) { + return $this->prettyString; + } + + return (string) $this; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + if ($this->string !== null) { + return $this->string; + } + + $constraints = array(); + foreach ($this->constraints as $constraint) { + $constraints[] = (string) $constraint; + } + + return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; + } + + /** + * {@inheritDoc} + */ + public function getLowerBound() + { + $this->extractBounds(); + + if (null === $this->lowerBound) { + throw new \LogicException('extractBounds should have populated the lowerBound property'); + } + + return $this->lowerBound; + } + + /** + * {@inheritDoc} + */ + public function getUpperBound() + { + $this->extractBounds(); + + if (null === $this->upperBound) { + throw new \LogicException('extractBounds should have populated the upperBound property'); + } + + return $this->upperBound; + } + + /** + * Tries to optimize the constraints as much as possible, meaning + * reducing/collapsing congruent constraints etc. + * Does not necessarily return a MultiConstraint instance if + * things can be reduced to a simple constraint + * + * @param ConstraintInterface[] $constraints A set of constraints + * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive + * + * @return ConstraintInterface + */ + public static function create(array $constraints, $conjunctive = true) + { + if (0 === \count($constraints)) { + return new MatchAllConstraint(); + } + + if (1 === \count($constraints)) { + return $constraints[0]; + } + + $optimized = self::optimizeConstraints($constraints, $conjunctive); + if ($optimized !== null) { + list($constraints, $conjunctive) = $optimized; + if (\count($constraints) === 1) { + return $constraints[0]; + } + } + + return new self($constraints, $conjunctive); + } + + /** + * @param ConstraintInterface[] $constraints + * @param bool $conjunctive + * @return ?array + * + * @phpstan-return array{0: list, 1: bool}|null + */ + private static function optimizeConstraints(array $constraints, $conjunctive) + { + // parse the two OR groups and if they are contiguous we collapse + // them into one constraint + // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4] + if (!$conjunctive) { + $left = $constraints[0]; + $mergedConstraints = array(); + $optimized = false; + for ($i = 1, $l = \count($constraints); $i < $l; $i++) { + $right = $constraints[$i]; + if ( + $left instanceof self + && $left->conjunctive + && $right instanceof self + && $right->conjunctive + && \count($left->constraints) === 2 + && \count($right->constraints) === 2 + && ($left0 = (string) $left->constraints[0]) + && $left0[0] === '>' && $left0[1] === '=' + && ($left1 = (string) $left->constraints[1]) + && $left1[0] === '<' + && ($right0 = (string) $right->constraints[0]) + && $right0[0] === '>' && $right0[1] === '=' + && ($right1 = (string) $right->constraints[1]) + && $right1[0] === '<' + && substr($left1, 2) === substr($right0, 3) + ) { + $optimized = true; + $left = new MultiConstraint( + array( + $left->constraints[0], + $right->constraints[1], + ), + true); + } else { + $mergedConstraints[] = $left; + $left = $right; + } + } + if ($optimized) { + $mergedConstraints[] = $left; + return array($mergedConstraints, false); + } + } + + // TODO: Here's the place to put more optimizations + + return null; + } + + /** + * @return void + */ + private function extractBounds() + { + if (null !== $this->lowerBound) { + return; + } + + foreach ($this->constraints as $constraint) { + if (null === $this->lowerBound || null === $this->upperBound) { + $this->lowerBound = $constraint->getLowerBound(); + $this->upperBound = $constraint->getUpperBound(); + continue; + } + + if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { + $this->lowerBound = $constraint->getLowerBound(); + } + + if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { + $this->upperBound = $constraint->getUpperBound(); + } + } + } +} diff --git a/vendor/composer/semver/src/Interval.php b/vendor/composer/semver/src/Interval.php new file mode 100644 index 0000000..43d5a4f --- /dev/null +++ b/vendor/composer/semver/src/Interval.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Interval +{ + /** @var Constraint */ + private $start; + /** @var Constraint */ + private $end; + + public function __construct(Constraint $start, Constraint $end) + { + $this->start = $start; + $this->end = $end; + } + + /** + * @return Constraint + */ + public function getStart() + { + return $this->start; + } + + /** + * @return Constraint + */ + public function getEnd() + { + return $this->end; + } + + /** + * @return Constraint + */ + public static function fromZero() + { + static $zero; + + if (null === $zero) { + $zero = new Constraint('>=', '0.0.0.0-dev'); + } + + return $zero; + } + + /** + * @return Constraint + */ + public static function untilPositiveInfinity() + { + static $positiveInfinity; + + if (null === $positiveInfinity) { + $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0'); + } + + return $positiveInfinity; + } + + /** + * @return self + */ + public static function any() + { + return new self(self::fromZero(), self::untilPositiveInfinity()); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function anyDev() + { + // any == exclude nothing + return array('names' => array(), 'exclude' => true); + } + + /** + * @return array{'names': string[], 'exclude': bool} + */ + public static function noDev() + { + // nothing == no names included + return array('names' => array(), 'exclude' => false); + } +} diff --git a/vendor/composer/semver/src/Intervals.php b/vendor/composer/semver/src/Intervals.php new file mode 100644 index 0000000..d889d0a --- /dev/null +++ b/vendor/composer/semver/src/Intervals.php @@ -0,0 +1,478 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MatchNoneConstraint; +use Composer\Semver\Constraint\MultiConstraint; + +/** + * Helper class generating intervals from constraints + * + * This contains utilities for: + * + * - compacting an existing constraint which can be used to combine several into one + * by creating a MultiConstraint out of the many constraints you have. + * + * - checking whether one subset is a subset of another. + * + * Note: You should call clear to free memoization memory usage when you are done using this class + */ +class Intervals +{ + /** + * @phpstan-var array + */ + private static $intervalsCache = array(); + + /** + * @phpstan-var array + */ + private static $opSortOrder = array( + '>=' => -3, + '<' => -2, + '>' => 2, + '<=' => 3, + ); + + /** + * Clears the memoization cache once you are done + * + * @return void + */ + public static function clear() + { + self::$intervalsCache = array(); + } + + /** + * Checks whether $candidate is a subset of $constraint + * + * @return bool + */ + public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint) + { + if ($constraint instanceof MatchAllConstraint) { + return true; + } + + if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true)); + $candidateIntervals = self::get($candidate); + if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) { + return false; + } + + foreach ($intersectionIntervals['numeric'] as $index => $interval) { + if (!isset($candidateIntervals['numeric'][$index])) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) { + return false; + } + + if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) { + return false; + } + } + + if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) { + return false; + } + if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) { + return false; + } + foreach ($intersectionIntervals['branches']['names'] as $index => $name) { + if ($name !== $candidateIntervals['branches']['names'][$index]) { + return false; + } + } + + return true; + } + + /** + * Checks whether $a and $b have any intersection, equivalent to $a->matches($b) + * + * @return bool + */ + public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b) + { + if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) { + return true; + } + + if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) { + return false; + } + + $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true); + + return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0; + } + + /** + * Attempts to optimize a MultiConstraint + * + * When merging MultiConstraints together they can get very large, this will + * compact it by looking at the real intervals covered by all the constraints + * and then creates a new constraint containing only the smallest amount of rules + * to match the same intervals. + * + * @return ConstraintInterface + */ + public static function compactConstraint(ConstraintInterface $constraint) + { + if (!$constraint instanceof MultiConstraint) { + return $constraint; + } + + $intervals = self::generateIntervals($constraint); + $constraints = array(); + $hasNumericMatchAll = false; + + if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $intervals['numeric'][0]->getStart(); + $hasNumericMatchAll = true; + } else { + $unEqualConstraints = array(); + for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { + $interval = $intervals['numeric'][$i]; + + // if current interval ends with < N and next interval begins with > N we can swap this out for != N + // but this needs to happen as a conjunctive expression together with the start of the current interval + // and end of next interval, so [>=M, N, [>=M, !=N, getEnd()->getOperator() === '<' && $i+1 < $count) { + $nextInterval = $intervals['numeric'][$i+1]; + if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { + // only add a start if we didn't already do so, can be skipped if we're looking at second + // interval in [>=M, N, P, =M, !=N] already and we only want to add !=P right now + if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) { + $unEqualConstraints[] = $interval->getStart(); + } + $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion()); + continue; + } + } + + if (\count($unEqualConstraints) > 0) { + // this is where the end of the following interval of a != constraint is added as explained above + if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) { + $unEqualConstraints[] = $interval->getEnd(); + } + + // count is 1 if entire constraint is just one != expression + if (\count($unEqualConstraints) > 1) { + $constraints[] = new MultiConstraint($unEqualConstraints, true); + } else { + $constraints[] = $unEqualConstraints[0]; + } + + $unEqualConstraints = array(); + continue; + } + + // convert back >= x - <= x intervals to == x + if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') { + $constraints[] = new Constraint('==', $interval->getStart()->getVersion()); + continue; + } + + if ((string) $interval->getStart() === (string) Interval::fromZero()) { + $constraints[] = $interval->getEnd(); + } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) { + $constraints[] = $interval->getStart(); + } else { + $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true); + } + } + } + + $devConstraints = array(); + + if (0 === \count($intervals['branches']['names'])) { + if ($intervals['branches']['exclude']) { + if ($hasNumericMatchAll) { + return new MatchAllConstraint; + } + // otherwise constraint should contain a != operator and already cover this + } + } else { + foreach ($intervals['branches']['names'] as $branchName) { + if ($intervals['branches']['exclude']) { + $devConstraints[] = new Constraint('!=', $branchName); + } else { + $devConstraints[] = new Constraint('==', $branchName); + } + } + + // excluded branches, e.g. != dev-foo are conjunctive with the interval, so + // > 2.0 != dev-foo must return a conjunctive constraint + if ($intervals['branches']['exclude']) { + if (\count($constraints) > 1) { + return new MultiConstraint(array_merge( + array(new MultiConstraint($constraints, false)), + $devConstraints + ), true); + } + + if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) { + if (\count($devConstraints) > 1) { + return new MultiConstraint($devConstraints, true); + } + return $devConstraints[0]; + } + + return new MultiConstraint(array_merge($constraints, $devConstraints), true); + } + + // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the + // rest of the constraint + $constraints = array_merge($constraints, $devConstraints); + } + + if (\count($constraints) > 1) { + return new MultiConstraint($constraints, false); + } + + if (\count($constraints) === 1) { + return $constraints[0]; + } + + return new MatchNoneConstraint; + } + + /** + * Creates an array of numeric intervals and branch constraints representing a given constraint + * + * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf) + * if the returned branches array is empty it means no dev-* versions are matched + * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev() + * + * @return array + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + public static function get(ConstraintInterface $constraint) + { + $key = (string) $constraint; + + if (!isset(self::$intervalsCache[$key])) { + self::$intervalsCache[$key] = self::generateIntervals($constraint); + } + + return self::$intervalsCache[$key]; + } + + /** + * @param bool $stopOnFirstValidInterval + * + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false) + { + if ($constraint instanceof MatchAllConstraint) { + return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev()); + } + + if ($constraint instanceof MatchNoneConstraint) { + return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false)); + } + + if ($constraint instanceof Constraint) { + return self::generateSingleConstraintIntervals($constraint); + } + + if (!$constraint instanceof MultiConstraint) { + throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.'); + } + + $constraints = $constraint->getConstraints(); + + $numericGroups = array(); + $constraintBranches = array(); + foreach ($constraints as $c) { + $res = self::get($c); + $numericGroups[] = $res['numeric']; + $constraintBranches[] = $res['branches']; + } + + if ($constraint->isDisjunctive()) { + $branches = Interval::noDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // disjunctive constraint, so only exclude what's excluded in all constraints + // !=a,!=b || !=b,!=c => !=b + $branches['names'] = array_intersect($branches['names'], $b['names']); + } else { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // (==b || ==c) || !=a,!=b => !=a + $branches['exclude'] = true; + $branches['names'] = array_diff($b['names'], $branches['names']); + } + } else { + if ($branches['exclude']) { + // disjunctive constraint so exclude all names which are not explicitly included in the alternative + // !=a,!=b || (==b || ==c) => !=a + $branches['names'] = array_diff($branches['names'], $b['names']); + } else { + // disjunctive constraint, so just add all the other branches + // (==a || ==b) || ==c => ==a || ==b || ==c + $branches['names'] = array_merge($branches['names'], $b['names']); + } + } + } + } else { + $branches = Interval::anyDev(); + foreach ($constraintBranches as $b) { + if ($b['exclude']) { + if ($branches['exclude']) { + // conjunctive, so just add all branch names to be excluded + // !=a && !=b => !=a,!=b + $branches['names'] = array_merge($branches['names'], $b['names']); + } else { + // conjunctive, so only keep included names which are not excluded + // (==a||==c) && !=a,!=b => ==c + $branches['names'] = array_diff($branches['names'], $b['names']); + } + } else { + if ($branches['exclude']) { + // conjunctive, so only keep included names which are not excluded + // !=a,!=b && (==a||==c) => ==c + $branches['names'] = array_diff($b['names'], $branches['names']); + $branches['exclude'] = false; + } else { + // conjunctive, so only keep names that are included in both + // (==a||==b) && (==a||==c) => ==a + $branches['names'] = array_intersect($branches['names'], $b['names']); + } + } + } + } + + $branches['names'] = array_unique($branches['names']); + + if (\count($numericGroups) === 1) { + return array('numeric' => $numericGroups[0], 'branches' => $branches); + } + + $borders = array(); + foreach ($numericGroups as $group) { + foreach ($group as $interval) { + $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start'); + $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end'); + } + } + + $opSortOrder = self::$opSortOrder; + usort($borders, function ($a, $b) use ($opSortOrder) { + $order = version_compare($a['version'], $b['version']); + if ($order === 0) { + return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']]; + } + + return $order; + }); + + $activeIntervals = 0; + $intervals = array(); + $index = 0; + $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1; + $start = null; + foreach ($borders as $border) { + if ($border['side'] === 'start') { + $activeIntervals++; + } else { + $activeIntervals--; + } + if (!$start && $activeIntervals >= $activationThreshold) { + $start = new Constraint($border['operator'], $border['version']); + } elseif ($start && $activeIntervals < $activationThreshold) { + // filter out invalid intervals like > x - <= x, or >= x - < x + if ( + version_compare($start->getVersion(), $border['version'], '=') + && ( + ($start->getOperator() === '>' && $border['operator'] === '<=') + || ($start->getOperator() === '>=' && $border['operator'] === '<') + ) + ) { + unset($intervals[$index]); + } else { + $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version'])); + $index++; + + if ($stopOnFirstValidInterval) { + break; + } + } + + $start = null; + } + } + + return array('numeric' => $intervals, 'branches' => $branches); + } + + /** + * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}} + */ + private static function generateSingleConstraintIntervals(Constraint $constraint) + { + $op = $constraint->getOperator(); + + // handle branch constraints first + if (strpos($constraint->getVersion(), 'dev-') === 0) { + $intervals = array(); + $branches = array('names' => array(), 'exclude' => false); + + // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches + if ($op === '!=') { + $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity()); + $branches = array('names' => array($constraint->getVersion()), 'exclude' => true); + } elseif ($op === '==') { + $branches['names'][] = $constraint->getVersion(); + } + + return array( + 'numeric' => $intervals, + 'branches' => $branches, + ); + } + + if ($op[0] === '>') { // > & >= + return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev()); + } + if ($op[0] === '<') { // < & <= + return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev()); + } + if ($op === '!=') { + // convert !=x to intervals of 0 - x - +inf + dev* + return array('numeric' => array( + new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), + new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()), + ), 'branches' => Interval::anyDev()); + } + + // convert ==x to an interval of >=x - <=x + return array('numeric' => array( + new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())), + ), 'branches' => Interval::noDev()); + } +} diff --git a/vendor/composer/semver/src/Semver.php b/vendor/composer/semver/src/Semver.php new file mode 100644 index 0000000..4d6de3c --- /dev/null +++ b/vendor/composer/semver/src/Semver.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\Constraint; + +class Semver +{ + const SORT_ASC = 1; + const SORT_DESC = -1; + + /** @var VersionParser */ + private static $versionParser; + + /** + * Determine if given version satisfies given constraints. + * + * @param string $version + * @param string $constraints + * + * @return bool + */ + public static function satisfies($version, $constraints) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $provider = new Constraint('==', $versionParser->normalize($version)); + $parsedConstraints = $versionParser->parseConstraints($constraints); + + return $parsedConstraints->matches($provider); + } + + /** + * Return all versions that satisfy given constraints. + * + * @param string[] $versions + * @param string $constraints + * + * @return string[] + */ + public static function satisfiedBy(array $versions, $constraints) + { + $versions = array_filter($versions, function ($version) use ($constraints) { + return Semver::satisfies($version, $constraints); + }); + + return array_values($versions); + } + + /** + * Sort given array of versions. + * + * @param string[] $versions + * + * @return string[] + */ + public static function sort(array $versions) + { + return self::usort($versions, self::SORT_ASC); + } + + /** + * Sort given array of versions in reverse. + * + * @param string[] $versions + * + * @return string[] + */ + public static function rsort(array $versions) + { + return self::usort($versions, self::SORT_DESC); + } + + /** + * @param string[] $versions + * @param int $direction + * + * @return string[] + */ + private static function usort(array $versions, $direction) + { + if (null === self::$versionParser) { + self::$versionParser = new VersionParser(); + } + + $versionParser = self::$versionParser; + $normalized = array(); + + // Normalize outside of usort() scope for minor performance increase. + // Creates an array of arrays: [[normalized, key], ...] + foreach ($versions as $key => $version) { + $normalizedVersion = $versionParser->normalize($version); + $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); + $normalized[] = array($normalizedVersion, $key); + } + + usort($normalized, function (array $left, array $right) use ($direction) { + if ($left[0] === $right[0]) { + return 0; + } + + if (Comparator::lessThan($left[0], $right[0])) { + return -$direction; + } + + return $direction; + }); + + // Recreate input array, using the original indexes which are now in sorted order. + $sorted = array(); + foreach ($normalized as $item) { + $sorted[] = $versions[$item[1]]; + } + + return $sorted; + } +} diff --git a/vendor/composer/semver/src/VersionParser.php b/vendor/composer/semver/src/VersionParser.php new file mode 100644 index 0000000..305a0fa --- /dev/null +++ b/vendor/composer/semver/src/VersionParser.php @@ -0,0 +1,591 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Semver; + +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Constraint\Constraint; + +/** + * Version parser. + * + * @author Jordi Boggiano + */ +class VersionParser +{ + /** + * Regex to match pre-release data (sort of). + * + * Due to backwards compatibility: + * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. + * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. + * - Numerical-only pre-release identifiers are not supported, see tests. + * + * |--------------| + * [major].[minor].[patch] -[pre-release] +[build-metadata] + * + * @var string + */ + private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; + + /** @var string */ + private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev'; + + /** + * Returns the stability of a version. + * + * @param string $version + * + * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public static function parseStability($version) + { + $version = (string) preg_replace('{#.+$}', '', (string) $version); + + if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) { + return 'dev'; + } + + preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); + + if (!empty($match[3])) { + return 'dev'; + } + + if (!empty($match[1])) { + if ('beta' === $match[1] || 'b' === $match[1]) { + return 'beta'; + } + if ('alpha' === $match[1] || 'a' === $match[1]) { + return 'alpha'; + } + if ('rc' === $match[1]) { + return 'RC'; + } + } + + return 'stable'; + } + + /** + * @param string $stability + * + * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' + */ + public static function normalizeStability($stability) + { + $stability = strtolower((string) $stability); + + if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) { + throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev'); + } + + return $stability === 'rc' ? 'RC' : $stability; + } + + /** + * Normalizes a version string to be able to perform comparisons on it. + * + * @param string $version + * @param ?string $fullVersion optional complete version string to give more context + * + * @throws \UnexpectedValueException + * + * @return string + */ + public function normalize($version, $fullVersion = null) + { + $version = trim((string) $version); + $origVersion = $version; + if (null === $fullVersion) { + $fullVersion = $version; + } + + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { + $version = $match[1]; + } + + // strip off stability flag + if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) { + $version = substr($version, 0, strlen($version) - strlen($match[0])); + } + + // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints + if (\in_array($version, array('master', 'trunk', 'default'), true)) { + $version = 'dev-' . $version; + } + + // if requirement is branch-like, use full name + if (stripos($version, 'dev-') === 0) { + return 'dev-' . substr($version, 4); + } + + // strip off build metadata + if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { + $version = $match[1]; + } + + // match classical versioning + if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = $matches[1] + . (!empty($matches[2]) ? $matches[2] : '.0') + . (!empty($matches[3]) ? $matches[3] : '.0') + . (!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; + // match date(time) based versioning + } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = (string) preg_replace('{\D}', '.', $matches[1]); + $index = 2; + } + + // add version modifiers if a version was matched + if (isset($index)) { + if (!empty($matches[$index])) { + if ('stable' === $matches[$index]) { + return $version; + } + $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : ''); + } + + if (!empty($matches[$index + 2])) { + $version .= '-dev'; + } + + return $version; + } + + // match dev branches + if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { + try { + $normalized = $this->normalizeBranch($match[1]); + // a branch ending with -dev is only valid if it is numeric + // if it gets prefixed with dev- it means the branch name should + // have had a dev- prefix already when passed to normalize + if (strpos($normalized, 'dev-') === false) { + return $normalized; + } + } catch (\Exception $e) { + } + } + + $extraMessage = ''; + if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; + } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) { + $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; + } + + throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); + } + + /** + * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. + * + * @param string $branch Branch name (e.g. 2.1.x-dev) + * + * @return string|false Numeric prefix if present (e.g. 2.1.) or false + */ + public function parseNumericAliasPrefix($branch) + { + if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) { + return $matches['version'] . '.'; + } + + return false; + } + + /** + * Normalizes a branch name to be able to perform comparisons on it. + * + * @param string $name + * + * @return string + */ + public function normalizeBranch($name) + { + $name = trim((string) $name); + + if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { + $version = ''; + for ($i = 1; $i < 5; ++$i) { + $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; + } + + return str_replace('x', '9999999', $version) . '-dev'; + } + + return 'dev-' . $name; + } + + /** + * Normalizes a default branch name (i.e. master on git) to 9999999-dev. + * + * @param string $name + * + * @return string + * + * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore + */ + public function normalizeDefaultBranch($name) + { + if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { + return '9999999-dev'; + } + + return (string) $name; + } + + /** + * Parses a constraint string into MultiConstraint and/or Constraint objects. + * + * @param string $constraints + * + * @return ConstraintInterface + */ + public function parseConstraints($constraints) + { + $prettyConstraint = (string) $constraints; + + $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints)); + if (false === $orConstraints) { + throw new \RuntimeException('Failed to preg_split string: '.$constraints); + } + $orGroups = array(); + + foreach ($orConstraints as $orConstraint) { + $andConstraints = preg_split('{(?< ,]) *(? 1) { + $constraintObjects = array(); + foreach ($andConstraints as $andConstraint) { + foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { + $constraintObjects[] = $parsedAndConstraint; + } + } + } else { + $constraintObjects = $this->parseConstraint($andConstraints[0]); + } + + if (1 === \count($constraintObjects)) { + $constraint = $constraintObjects[0]; + } else { + $constraint = new MultiConstraint($constraintObjects); + } + + $orGroups[] = $constraint; + } + + $parsedConstraint = MultiConstraint::create($orGroups, false); + + $parsedConstraint->setPrettyString($prettyConstraint); + + return $parsedConstraint; + } + + /** + * @param string $constraint + * + * @throws \UnexpectedValueException + * + * @return array + * + * @phpstan-return non-empty-array + */ + private function parseConstraint($constraint) + { + // strip off aliasing + if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) { + $constraint = $match[1]; + } + + // strip @stability flags, and keep it for later use + if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { + $constraint = '' !== $match[1] ? $match[1] : '*'; + if ($match[2] !== 'stable') { + $stabilityModifier = $match[2]; + } + } + + // get rid of #refs as those are used by composer only + if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) { + $constraint = $match[1]; + } + + if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) { + if (!empty($match[1]) || !empty($match[2])) { + return array(new Constraint('>=', '0.0.0.0-dev')); + } + + return array(new MatchAllConstraint()); + } + + $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?'; + + // Tilde Range + // + // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous + // version, to ensure that unstable instances of the current version are allowed. However, if a stability + // suffix is added to the constraint, then a >= match on the current version is used instead. + if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { + if (strpos($constraint, '~>') === 0) { + throw new \UnexpectedValueException( + 'Could not parse version constraint ' . $constraint . ': ' . + 'Invalid operator "~>", you probably meant to use the "~" operator' + ); + } + + // Work out which position in the version we are operating at + if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { + $position = 4; + } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above + if (!empty($matches[8])) { + $position++; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highPosition = max(1, $position - 1); + $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // Caret Range + // + // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. + // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for + // versions 0.X >=0.1.0, and no updates for versions 0.0.X + if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { + // Work out which position in the version we are operating at + if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { + $position = 1; + } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { + $position = 2; + } else { + $position = 3; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new Constraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound, + ); + } + + // X Range + // + // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. + // A partial version range is treated as an X-Range, so the special character is in fact optional. + if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { + if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } + + $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + + if ($lowVersion === '0.0.0.0-dev') { + return array(new Constraint('<', $highVersion)); + } + + return array( + new Constraint('>=', $lowVersion), + new Constraint('<', $highVersion), + ); + } + + // Hyphen Range + // + // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, + // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in + // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but + // nothing that would be greater than the provided tuple parts. + if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { + // Calculate the stability suffix + $lowStabilitySuffix = ''; + if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { + $lowStabilitySuffix = '-dev'; + } + + $lowVersion = $this->normalize($matches['from']); + $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); + + $empty = function ($x) { + return ($x === 0 || $x === '0') ? false : empty($x); + }; + + if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { + $highVersion = $this->normalize($matches['to']); + $upperBound = new Constraint('<=', $highVersion); + } else { + $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); + + // validate to version + $this->normalize($matches['to']); + + $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; + $upperBound = new Constraint('<', $highVersion); + } + + return array( + $lowerBound, + $upperBound, + ); + } + + // Basic Comparators + if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { + try { + try { + $version = $this->normalize($matches[2]); + } catch (\UnexpectedValueException $e) { + // recover from an invalid constraint like foobar-dev which should be dev-foobar + // except if the constraint uses a known operator, in which case it must be a parse error + if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { + $version = $this->normalize('dev-'.substr($matches[2], 0, -4)); + } else { + throw $e; + } + } + + $op = $matches[1] ?: '='; + + if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { + $version .= '-' . $stabilityModifier; + } elseif ('<' === $op || '>=' === $op) { + if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { + if (strpos($matches[2], 'dev-') !== 0) { + $version .= '-dev'; + } + } + } + + return array(new Constraint($matches[1] ?: '=', $version)); + } catch (\Exception $e) { + } + } + + $message = 'Could not parse version constraint ' . $constraint; + if (isset($e)) { + $message .= ': ' . $e->getMessage(); + } + + throw new \UnexpectedValueException($message); + } + + /** + * Increment, decrement, or simply pad a version number. + * + * Support function for {@link parseConstraint()} + * + * @param array $matches Array with version parts in array indexes 1,2,3,4 + * @param int $position 1,2,3,4 - which segment of the version to increment/decrement + * @param int $increment + * @param string $pad The string to pad version parts after $position + * + * @return string|null The new version + * + * @phpstan-param string[] $matches + */ + private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0') + { + for ($i = 4; $i > 0; --$i) { + if ($i > $position) { + $matches[$i] = $pad; + } elseif ($i === $position && $increment) { + $matches[$i] += $increment; + // If $matches[$i] was 0, carry the decrement + if ($matches[$i] < 0) { + $matches[$i] = $pad; + --$position; + + // Return null on a carry overflow + if ($i === 1) { + return null; + } + } + } + } + + return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; + } + + /** + * Expand shorthand stability string to long version. + * + * @param string $stability + * + * @return string + */ + private function expandStability($stability) + { + $stability = strtolower($stability); + + switch ($stability) { + case 'a': + return 'alpha'; + case 'b': + return 'beta'; + case 'p': + case 'pl': + return 'patch'; + case 'rc': + return 'RC'; + default: + return $stability; + } + } +} diff --git a/vendor/composer/spdx-licenses/LICENSE b/vendor/composer/spdx-licenses/LICENSE new file mode 100644 index 0000000..4669758 --- /dev/null +++ b/vendor/composer/spdx-licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2015 Composer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/composer/spdx-licenses/README.md b/vendor/composer/spdx-licenses/README.md new file mode 100644 index 0000000..c0995f9 --- /dev/null +++ b/vendor/composer/spdx-licenses/README.md @@ -0,0 +1,69 @@ +composer/spdx-licenses +====================== + +SPDX (Software Package Data Exchange) licenses list and validation library. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +[![Continuous Integration](https://github.com/composer/spdx-licenses/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/spdx-licenses/actions) + +Installation +------------ + +Install the latest version with: + +```bash +$ composer require composer/spdx-licenses +``` + +Basic Usage +----------- + +```php +getLicenseByIdentifier('MIT'); + +// get a license exception by identifier +$licenses->getExceptionByIdentifier('Autoconf-exception-3.0'); + +// get a license identifier by name +$licenses->getIdentifierByName('MIT License'); + +// check if a license is OSI approved by identifier +$licenses->isOsiApprovedByIdentifier('MIT'); + +// check if a license identifier is deprecated +$licenses->isDeprecatedByIdentifier('MIT'); + +// check if input is a valid SPDX license expression +$licenses->validate($input); +``` + +> Read the [specifications](https://spdx.org/specifications) +> to find out more about valid license expressions. + +Requirements +------------ + +* PHP 5.3.2 is required but using the latest version of PHP is highly recommended. + +License +------- + +composer/spdx-licenses is licensed under the MIT License, see the LICENSE file for details. + +Source +------ + +License information is curated by [SPDX](https://spdx.org/). The data is pulled from the +[License List Data](https://github.com/spdx/license-list-data) repository. + +* [Licenses](https://spdx.org/licenses/index.html) +* [License Exceptions](https://spdx.org/licenses/exceptions-index.html) diff --git a/vendor/composer/spdx-licenses/composer.json b/vendor/composer/spdx-licenses/composer.json new file mode 100644 index 0000000..e1701c0 --- /dev/null +++ b/vendor/composer/spdx-licenses/composer.json @@ -0,0 +1,59 @@ +{ + "name": "composer/spdx-licenses", + "description": "SPDX licenses list and validation library.", + "type": "library", + "license": "MIT", + "keywords": [ + "spdx", + "license", + "validator" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^7", + "phpstan/phpstan": "^1.11" + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Spdx\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "scripts": { + "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit", + "phpstan": "vendor/bin/phpstan analyse", + "sync-licenses": "bin/update-spdx-licenses" + } +} diff --git a/vendor/composer/spdx-licenses/res/spdx-exceptions.json b/vendor/composer/spdx-licenses/res/spdx-exceptions.json new file mode 100644 index 0000000..3e13535 --- /dev/null +++ b/vendor/composer/spdx-licenses/res/spdx-exceptions.json @@ -0,0 +1,239 @@ +{ + "389-exception": [ + "389 Directory Server Exception" + ], + "Asterisk-exception": [ + "Asterisk exception" + ], + "Asterisk-linking-protocols-exception": [ + "Asterisk linking protocols exception" + ], + "Autoconf-exception-2.0": [ + "Autoconf exception 2.0" + ], + "Autoconf-exception-3.0": [ + "Autoconf exception 3.0" + ], + "Autoconf-exception-generic": [ + "Autoconf generic exception" + ], + "Autoconf-exception-generic-3.0": [ + "Autoconf generic exception for GPL-3.0" + ], + "Autoconf-exception-macro": [ + "Autoconf macro exception" + ], + "Bison-exception-1.24": [ + "Bison exception 1.24" + ], + "Bison-exception-2.2": [ + "Bison exception 2.2" + ], + "Bootloader-exception": [ + "Bootloader Distribution Exception" + ], + "CGAL-linking-exception": [ + "CGAL Linking Exception" + ], + "Classpath-exception-2.0": [ + "Classpath exception 2.0" + ], + "CLISP-exception-2.0": [ + "CLISP exception 2.0" + ], + "cryptsetup-OpenSSL-exception": [ + "cryptsetup OpenSSL exception" + ], + "Digia-Qt-LGPL-exception-1.1": [ + "Digia Qt LGPL Exception version 1.1" + ], + "DigiRule-FOSS-exception": [ + "DigiRule FOSS License Exception" + ], + "eCos-exception-2.0": [ + "eCos exception 2.0" + ], + "erlang-otp-linking-exception": [ + "Erlang/OTP Linking Exception" + ], + "Fawkes-Runtime-exception": [ + "Fawkes Runtime Exception" + ], + "FLTK-exception": [ + "FLTK exception" + ], + "fmt-exception": [ + "fmt exception" + ], + "Font-exception-2.0": [ + "Font exception 2.0" + ], + "freertos-exception-2.0": [ + "FreeRTOS Exception 2.0" + ], + "GCC-exception-2.0": [ + "GCC Runtime Library exception 2.0" + ], + "GCC-exception-2.0-note": [ + "GCC Runtime Library exception 2.0 - note variant" + ], + "GCC-exception-3.1": [ + "GCC Runtime Library exception 3.1" + ], + "Gmsh-exception": [ + "Gmsh exception" + ], + "GNAT-exception": [ + "GNAT exception" + ], + "GNOME-examples-exception": [ + "GNOME examples exception" + ], + "GNU-compiler-exception": [ + "GNU Compiler Exception" + ], + "gnu-javamail-exception": [ + "GNU JavaMail exception" + ], + "GPL-3.0-389-ds-base-exception": [ + "GPL-3.0 389 DS Base Exception" + ], + "GPL-3.0-interface-exception": [ + "GPL-3.0 Interface Exception" + ], + "GPL-3.0-linking-exception": [ + "GPL-3.0 Linking Exception" + ], + "GPL-3.0-linking-source-exception": [ + "GPL-3.0 Linking Exception (with Corresponding Source)" + ], + "GPL-CC-1.0": [ + "GPL Cooperation Commitment 1.0" + ], + "GStreamer-exception-2005": [ + "GStreamer Exception (2005)" + ], + "GStreamer-exception-2008": [ + "GStreamer Exception (2008)" + ], + "harbour-exception": [ + "harbour exception" + ], + "i2p-gpl-java-exception": [ + "i2p GPL+Java Exception" + ], + "Independent-modules-exception": [ + "Independent Module Linking exception" + ], + "KiCad-libraries-exception": [ + "KiCad Libraries Exception" + ], + "LGPL-3.0-linking-exception": [ + "LGPL-3.0 Linking Exception" + ], + "libpri-OpenH323-exception": [ + "libpri OpenH323 exception" + ], + "Libtool-exception": [ + "Libtool Exception" + ], + "Linux-syscall-note": [ + "Linux Syscall Note" + ], + "LLGPL": [ + "LLGPL Preamble" + ], + "LLVM-exception": [ + "LLVM Exception" + ], + "LZMA-exception": [ + "LZMA exception" + ], + "mif-exception": [ + "Macros and Inline Functions Exception" + ], + "mxml-exception": [ + "mxml Exception" + ], + "Nokia-Qt-exception-1.1": [ + "Nokia Qt LGPL exception 1.1" + ], + "OCaml-LGPL-linking-exception": [ + "OCaml LGPL Linking Exception" + ], + "OCCT-exception-1.0": [ + "Open CASCADE Exception 1.0" + ], + "OpenJDK-assembly-exception-1.0": [ + "OpenJDK Assembly exception 1.0" + ], + "openvpn-openssl-exception": [ + "OpenVPN OpenSSL Exception" + ], + "PCRE2-exception": [ + "PCRE2 exception" + ], + "polyparse-exception": [ + "Polyparse Exception" + ], + "PS-or-PDF-font-exception-20170817": [ + "PS/PDF font exception (2017-08-17)" + ], + "QPL-1.0-INRIA-2004-exception": [ + "INRIA QPL 1.0 2004 variant exception" + ], + "Qt-GPL-exception-1.0": [ + "Qt GPL exception 1.0" + ], + "Qt-LGPL-exception-1.1": [ + "Qt LGPL exception 1.1" + ], + "Qwt-exception-1.0": [ + "Qwt exception 1.0" + ], + "romic-exception": [ + "Romic Exception" + ], + "RRDtool-FLOSS-exception-2.0": [ + "RRDtool FLOSS exception 2.0" + ], + "SANE-exception": [ + "SANE Exception" + ], + "SHL-2.0": [ + "Solderpad Hardware License v2.0" + ], + "SHL-2.1": [ + "Solderpad Hardware License v2.1" + ], + "stunnel-exception": [ + "stunnel Exception" + ], + "SWI-exception": [ + "SWI exception" + ], + "Swift-exception": [ + "Swift Exception" + ], + "Texinfo-exception": [ + "Texinfo exception" + ], + "u-boot-exception-2.0": [ + "U-Boot exception 2.0" + ], + "UBDL-exception": [ + "Unmodified Binary Distribution exception" + ], + "Universal-FOSS-exception-1.0": [ + "Universal FOSS Exception, Version 1.0" + ], + "vsftpd-openssl-exception": [ + "vsftpd OpenSSL exception" + ], + "WxWindows-exception-3.1": [ + "WxWindows Library Exception 3.1" + ], + "x11vnc-openssl-exception": [ + "x11vnc OpenSSL Exception" + ] +} \ No newline at end of file diff --git a/vendor/composer/spdx-licenses/res/spdx-licenses.json b/vendor/composer/spdx-licenses/res/spdx-licenses.json new file mode 100644 index 0000000..11b53c9 --- /dev/null +++ b/vendor/composer/spdx-licenses/res/spdx-licenses.json @@ -0,0 +1,3467 @@ +{ + "0BSD": [ + "BSD Zero Clause License", + true, + false + ], + "3D-Slicer-1.0": [ + "3D Slicer License v1.0", + false, + false + ], + "AAL": [ + "Attribution Assurance License", + true, + false + ], + "Abstyles": [ + "Abstyles License", + false, + false + ], + "AdaCore-doc": [ + "AdaCore Doc License", + false, + false + ], + "Adobe-2006": [ + "Adobe Systems Incorporated Source Code License Agreement", + false, + false + ], + "Adobe-Display-PostScript": [ + "Adobe Display PostScript License", + false, + false + ], + "Adobe-Glyph": [ + "Adobe Glyph List License", + false, + false + ], + "Adobe-Utopia": [ + "Adobe Utopia Font License", + false, + false + ], + "ADSL": [ + "Amazon Digital Services License", + false, + false + ], + "AFL-1.1": [ + "Academic Free License v1.1", + true, + false + ], + "AFL-1.2": [ + "Academic Free License v1.2", + true, + false + ], + "AFL-2.0": [ + "Academic Free License v2.0", + true, + false + ], + "AFL-2.1": [ + "Academic Free License v2.1", + true, + false + ], + "AFL-3.0": [ + "Academic Free License v3.0", + true, + false + ], + "Afmparse": [ + "Afmparse License", + false, + false + ], + "AGPL-1.0": [ + "Affero General Public License v1.0", + false, + true + ], + "AGPL-1.0-only": [ + "Affero General Public License v1.0 only", + false, + false + ], + "AGPL-1.0-or-later": [ + "Affero General Public License v1.0 or later", + false, + false + ], + "AGPL-3.0": [ + "GNU Affero General Public License v3.0", + true, + true + ], + "AGPL-3.0-only": [ + "GNU Affero General Public License v3.0 only", + true, + false + ], + "AGPL-3.0-or-later": [ + "GNU Affero General Public License v3.0 or later", + true, + false + ], + "Aladdin": [ + "Aladdin Free Public License", + false, + false + ], + "AMD-newlib": [ + "AMD newlib License", + false, + false + ], + "AMDPLPA": [ + "AMD's plpa_map.c License", + false, + false + ], + "AML": [ + "Apple MIT License", + false, + false + ], + "AML-glslang": [ + "AML glslang variant License", + false, + false + ], + "AMPAS": [ + "Academy of Motion Picture Arts and Sciences BSD", + false, + false + ], + "ANTLR-PD": [ + "ANTLR Software Rights Notice", + false, + false + ], + "ANTLR-PD-fallback": [ + "ANTLR Software Rights Notice with license fallback", + false, + false + ], + "any-OSI": [ + "Any OSI License", + false, + false + ], + "any-OSI-perl-modules": [ + "Any OSI License - Perl Modules", + false, + false + ], + "Apache-1.0": [ + "Apache License 1.0", + false, + false + ], + "Apache-1.1": [ + "Apache License 1.1", + true, + false + ], + "Apache-2.0": [ + "Apache License 2.0", + true, + false + ], + "APAFML": [ + "Adobe Postscript AFM License", + false, + false + ], + "APL-1.0": [ + "Adaptive Public License 1.0", + true, + false + ], + "App-s2p": [ + "App::s2p License", + false, + false + ], + "APSL-1.0": [ + "Apple Public Source License 1.0", + true, + false + ], + "APSL-1.1": [ + "Apple Public Source License 1.1", + true, + false + ], + "APSL-1.2": [ + "Apple Public Source License 1.2", + true, + false + ], + "APSL-2.0": [ + "Apple Public Source License 2.0", + true, + false + ], + "Arphic-1999": [ + "Arphic Public License", + false, + false + ], + "Artistic-1.0": [ + "Artistic License 1.0", + true, + false + ], + "Artistic-1.0-cl8": [ + "Artistic License 1.0 w/clause 8", + true, + false + ], + "Artistic-1.0-Perl": [ + "Artistic License 1.0 (Perl)", + true, + false + ], + "Artistic-2.0": [ + "Artistic License 2.0", + true, + false + ], + "Artistic-dist": [ + "Artistic License 1.0 (dist)", + false, + false + ], + "ASWF-Digital-Assets-1.0": [ + "ASWF Digital Assets License version 1.0", + false, + false + ], + "ASWF-Digital-Assets-1.1": [ + "ASWF Digital Assets License 1.1", + false, + false + ], + "Baekmuk": [ + "Baekmuk License", + false, + false + ], + "Bahyph": [ + "Bahyph License", + false, + false + ], + "Barr": [ + "Barr License", + false, + false + ], + "bcrypt-Solar-Designer": [ + "bcrypt Solar Designer License", + false, + false + ], + "Beerware": [ + "Beerware License", + false, + false + ], + "Bitstream-Charter": [ + "Bitstream Charter Font License", + false, + false + ], + "Bitstream-Vera": [ + "Bitstream Vera Font License", + false, + false + ], + "BitTorrent-1.0": [ + "BitTorrent Open Source License v1.0", + false, + false + ], + "BitTorrent-1.1": [ + "BitTorrent Open Source License v1.1", + false, + false + ], + "blessing": [ + "SQLite Blessing", + false, + false + ], + "BlueOak-1.0.0": [ + "Blue Oak Model License 1.0.0", + true, + false + ], + "Boehm-GC": [ + "Boehm-Demers-Weiser GC License", + false, + false + ], + "Boehm-GC-without-fee": [ + "Boehm-Demers-Weiser GC License (without fee)", + false, + false + ], + "Borceux": [ + "Borceux license", + false, + false + ], + "Brian-Gladman-2-Clause": [ + "Brian Gladman 2-Clause License", + false, + false + ], + "Brian-Gladman-3-Clause": [ + "Brian Gladman 3-Clause License", + false, + false + ], + "BSD-1-Clause": [ + "BSD 1-Clause License", + true, + false + ], + "BSD-2-Clause": [ + "BSD 2-Clause \"Simplified\" License", + true, + false + ], + "BSD-2-Clause-Darwin": [ + "BSD 2-Clause - Ian Darwin variant", + false, + false + ], + "BSD-2-Clause-first-lines": [ + "BSD 2-Clause - first lines requirement", + false, + false + ], + "BSD-2-Clause-FreeBSD": [ + "BSD 2-Clause FreeBSD License", + false, + true + ], + "BSD-2-Clause-NetBSD": [ + "BSD 2-Clause NetBSD License", + false, + true + ], + "BSD-2-Clause-Patent": [ + "BSD-2-Clause Plus Patent License", + true, + false + ], + "BSD-2-Clause-pkgconf-disclaimer": [ + "BSD 2-Clause pkgconf disclaimer variant", + false, + false + ], + "BSD-2-Clause-Views": [ + "BSD 2-Clause with views sentence", + false, + false + ], + "BSD-3-Clause": [ + "BSD 3-Clause \"New\" or \"Revised\" License", + true, + false + ], + "BSD-3-Clause-acpica": [ + "BSD 3-Clause acpica variant", + false, + false + ], + "BSD-3-Clause-Attribution": [ + "BSD with attribution", + false, + false + ], + "BSD-3-Clause-Clear": [ + "BSD 3-Clause Clear License", + false, + false + ], + "BSD-3-Clause-flex": [ + "BSD 3-Clause Flex variant", + false, + false + ], + "BSD-3-Clause-HP": [ + "Hewlett-Packard BSD variant license", + false, + false + ], + "BSD-3-Clause-LBNL": [ + "Lawrence Berkeley National Labs BSD variant license", + true, + false + ], + "BSD-3-Clause-Modification": [ + "BSD 3-Clause Modification", + false, + false + ], + "BSD-3-Clause-No-Military-License": [ + "BSD 3-Clause No Military License", + false, + false + ], + "BSD-3-Clause-No-Nuclear-License": [ + "BSD 3-Clause No Nuclear License", + false, + false + ], + "BSD-3-Clause-No-Nuclear-License-2014": [ + "BSD 3-Clause No Nuclear License 2014", + false, + false + ], + "BSD-3-Clause-No-Nuclear-Warranty": [ + "BSD 3-Clause No Nuclear Warranty", + false, + false + ], + "BSD-3-Clause-Open-MPI": [ + "BSD 3-Clause Open MPI variant", + false, + false + ], + "BSD-3-Clause-Sun": [ + "BSD 3-Clause Sun Microsystems", + false, + false + ], + "BSD-4-Clause": [ + "BSD 4-Clause \"Original\" or \"Old\" License", + false, + false + ], + "BSD-4-Clause-Shortened": [ + "BSD 4 Clause Shortened", + false, + false + ], + "BSD-4-Clause-UC": [ + "BSD-4-Clause (University of California-Specific)", + false, + false + ], + "BSD-4.3RENO": [ + "BSD 4.3 RENO License", + false, + false + ], + "BSD-4.3TAHOE": [ + "BSD 4.3 TAHOE License", + false, + false + ], + "BSD-Advertising-Acknowledgement": [ + "BSD Advertising Acknowledgement License", + false, + false + ], + "BSD-Attribution-HPND-disclaimer": [ + "BSD with Attribution and HPND disclaimer", + false, + false + ], + "BSD-Inferno-Nettverk": [ + "BSD-Inferno-Nettverk", + false, + false + ], + "BSD-Protection": [ + "BSD Protection License", + false, + false + ], + "BSD-Source-beginning-file": [ + "BSD Source Code Attribution - beginning of file variant", + false, + false + ], + "BSD-Source-Code": [ + "BSD Source Code Attribution", + false, + false + ], + "BSD-Systemics": [ + "Systemics BSD variant license", + false, + false + ], + "BSD-Systemics-W3Works": [ + "Systemics W3Works BSD variant license", + false, + false + ], + "BSL-1.0": [ + "Boost Software License 1.0", + true, + false + ], + "BUSL-1.1": [ + "Business Source License 1.1", + false, + false + ], + "bzip2-1.0.5": [ + "bzip2 and libbzip2 License v1.0.5", + false, + true + ], + "bzip2-1.0.6": [ + "bzip2 and libbzip2 License v1.0.6", + false, + false + ], + "C-UDA-1.0": [ + "Computational Use of Data Agreement v1.0", + false, + false + ], + "CAL-1.0": [ + "Cryptographic Autonomy License 1.0", + true, + false + ], + "CAL-1.0-Combined-Work-Exception": [ + "Cryptographic Autonomy License 1.0 (Combined Work Exception)", + true, + false + ], + "Caldera": [ + "Caldera License", + false, + false + ], + "Caldera-no-preamble": [ + "Caldera License (without preamble)", + false, + false + ], + "Catharon": [ + "Catharon License", + false, + false + ], + "CATOSL-1.1": [ + "Computer Associates Trusted Open Source License 1.1", + true, + false + ], + "CC-BY-1.0": [ + "Creative Commons Attribution 1.0 Generic", + false, + false + ], + "CC-BY-2.0": [ + "Creative Commons Attribution 2.0 Generic", + false, + false + ], + "CC-BY-2.5": [ + "Creative Commons Attribution 2.5 Generic", + false, + false + ], + "CC-BY-2.5-AU": [ + "Creative Commons Attribution 2.5 Australia", + false, + false + ], + "CC-BY-3.0": [ + "Creative Commons Attribution 3.0 Unported", + false, + false + ], + "CC-BY-3.0-AT": [ + "Creative Commons Attribution 3.0 Austria", + false, + false + ], + "CC-BY-3.0-AU": [ + "Creative Commons Attribution 3.0 Australia", + false, + false + ], + "CC-BY-3.0-DE": [ + "Creative Commons Attribution 3.0 Germany", + false, + false + ], + "CC-BY-3.0-IGO": [ + "Creative Commons Attribution 3.0 IGO", + false, + false + ], + "CC-BY-3.0-NL": [ + "Creative Commons Attribution 3.0 Netherlands", + false, + false + ], + "CC-BY-3.0-US": [ + "Creative Commons Attribution 3.0 United States", + false, + false + ], + "CC-BY-4.0": [ + "Creative Commons Attribution 4.0 International", + false, + false + ], + "CC-BY-NC-1.0": [ + "Creative Commons Attribution Non Commercial 1.0 Generic", + false, + false + ], + "CC-BY-NC-2.0": [ + "Creative Commons Attribution Non Commercial 2.0 Generic", + false, + false + ], + "CC-BY-NC-2.5": [ + "Creative Commons Attribution Non Commercial 2.5 Generic", + false, + false + ], + "CC-BY-NC-3.0": [ + "Creative Commons Attribution Non Commercial 3.0 Unported", + false, + false + ], + "CC-BY-NC-3.0-DE": [ + "Creative Commons Attribution Non Commercial 3.0 Germany", + false, + false + ], + "CC-BY-NC-4.0": [ + "Creative Commons Attribution Non Commercial 4.0 International", + false, + false + ], + "CC-BY-NC-ND-1.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", + false, + false + ], + "CC-BY-NC-ND-2.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", + false, + false + ], + "CC-BY-NC-ND-2.5": [ + "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", + false, + false + ], + "CC-BY-NC-ND-3.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", + false, + false + ], + "CC-BY-NC-ND-3.0-DE": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", + false, + false + ], + "CC-BY-NC-ND-3.0-IGO": [ + "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", + false, + false + ], + "CC-BY-NC-ND-4.0": [ + "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", + false, + false + ], + "CC-BY-NC-SA-1.0": [ + "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", + false, + false + ], + "CC-BY-NC-SA-2.0": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", + false, + false + ], + "CC-BY-NC-SA-2.0-DE": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", + false, + false + ], + "CC-BY-NC-SA-2.0-FR": [ + "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", + false, + false + ], + "CC-BY-NC-SA-2.0-UK": [ + "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", + false, + false + ], + "CC-BY-NC-SA-2.5": [ + "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", + false, + false + ], + "CC-BY-NC-SA-3.0": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", + false, + false + ], + "CC-BY-NC-SA-3.0-DE": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", + false, + false + ], + "CC-BY-NC-SA-3.0-IGO": [ + "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", + false, + false + ], + "CC-BY-NC-SA-4.0": [ + "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + false, + false + ], + "CC-BY-ND-1.0": [ + "Creative Commons Attribution No Derivatives 1.0 Generic", + false, + false + ], + "CC-BY-ND-2.0": [ + "Creative Commons Attribution No Derivatives 2.0 Generic", + false, + false + ], + "CC-BY-ND-2.5": [ + "Creative Commons Attribution No Derivatives 2.5 Generic", + false, + false + ], + "CC-BY-ND-3.0": [ + "Creative Commons Attribution No Derivatives 3.0 Unported", + false, + false + ], + "CC-BY-ND-3.0-DE": [ + "Creative Commons Attribution No Derivatives 3.0 Germany", + false, + false + ], + "CC-BY-ND-4.0": [ + "Creative Commons Attribution No Derivatives 4.0 International", + false, + false + ], + "CC-BY-SA-1.0": [ + "Creative Commons Attribution Share Alike 1.0 Generic", + false, + false + ], + "CC-BY-SA-2.0": [ + "Creative Commons Attribution Share Alike 2.0 Generic", + false, + false + ], + "CC-BY-SA-2.0-UK": [ + "Creative Commons Attribution Share Alike 2.0 England and Wales", + false, + false + ], + "CC-BY-SA-2.1-JP": [ + "Creative Commons Attribution Share Alike 2.1 Japan", + false, + false + ], + "CC-BY-SA-2.5": [ + "Creative Commons Attribution Share Alike 2.5 Generic", + false, + false + ], + "CC-BY-SA-3.0": [ + "Creative Commons Attribution Share Alike 3.0 Unported", + false, + false + ], + "CC-BY-SA-3.0-AT": [ + "Creative Commons Attribution Share Alike 3.0 Austria", + false, + false + ], + "CC-BY-SA-3.0-DE": [ + "Creative Commons Attribution Share Alike 3.0 Germany", + false, + false + ], + "CC-BY-SA-3.0-IGO": [ + "Creative Commons Attribution-ShareAlike 3.0 IGO", + false, + false + ], + "CC-BY-SA-4.0": [ + "Creative Commons Attribution Share Alike 4.0 International", + false, + false + ], + "CC-PDDC": [ + "Creative Commons Public Domain Dedication and Certification", + false, + false + ], + "CC-PDM-1.0": [ + "Creative Commons Public Domain Mark 1.0 Universal", + false, + false + ], + "CC-SA-1.0": [ + "Creative Commons Share Alike 1.0 Generic", + false, + false + ], + "CC0-1.0": [ + "Creative Commons Zero v1.0 Universal", + false, + false + ], + "CDDL-1.0": [ + "Common Development and Distribution License 1.0", + true, + false + ], + "CDDL-1.1": [ + "Common Development and Distribution License 1.1", + false, + false + ], + "CDL-1.0": [ + "Common Documentation License 1.0", + false, + false + ], + "CDLA-Permissive-1.0": [ + "Community Data License Agreement Permissive 1.0", + false, + false + ], + "CDLA-Permissive-2.0": [ + "Community Data License Agreement Permissive 2.0", + false, + false + ], + "CDLA-Sharing-1.0": [ + "Community Data License Agreement Sharing 1.0", + false, + false + ], + "CECILL-1.0": [ + "CeCILL Free Software License Agreement v1.0", + false, + false + ], + "CECILL-1.1": [ + "CeCILL Free Software License Agreement v1.1", + false, + false + ], + "CECILL-2.0": [ + "CeCILL Free Software License Agreement v2.0", + false, + false + ], + "CECILL-2.1": [ + "CeCILL Free Software License Agreement v2.1", + true, + false + ], + "CECILL-B": [ + "CeCILL-B Free Software License Agreement", + false, + false + ], + "CECILL-C": [ + "CeCILL-C Free Software License Agreement", + false, + false + ], + "CERN-OHL-1.1": [ + "CERN Open Hardware Licence v1.1", + false, + false + ], + "CERN-OHL-1.2": [ + "CERN Open Hardware Licence v1.2", + false, + false + ], + "CERN-OHL-P-2.0": [ + "CERN Open Hardware Licence Version 2 - Permissive", + true, + false + ], + "CERN-OHL-S-2.0": [ + "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", + true, + false + ], + "CERN-OHL-W-2.0": [ + "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", + true, + false + ], + "CFITSIO": [ + "CFITSIO License", + false, + false + ], + "check-cvs": [ + "check-cvs License", + false, + false + ], + "checkmk": [ + "Checkmk License", + false, + false + ], + "ClArtistic": [ + "Clarified Artistic License", + false, + false + ], + "Clips": [ + "Clips License", + false, + false + ], + "CMU-Mach": [ + "CMU Mach License", + false, + false + ], + "CMU-Mach-nodoc": [ + "CMU Mach - no notices-in-documentation variant", + false, + false + ], + "CNRI-Jython": [ + "CNRI Jython License", + false, + false + ], + "CNRI-Python": [ + "CNRI Python License", + true, + false + ], + "CNRI-Python-GPL-Compatible": [ + "CNRI Python Open Source GPL Compatible License Agreement", + false, + false + ], + "COIL-1.0": [ + "Copyfree Open Innovation License", + false, + false + ], + "Community-Spec-1.0": [ + "Community Specification License 1.0", + false, + false + ], + "Condor-1.1": [ + "Condor Public License v1.1", + false, + false + ], + "copyleft-next-0.3.0": [ + "copyleft-next 0.3.0", + false, + false + ], + "copyleft-next-0.3.1": [ + "copyleft-next 0.3.1", + false, + false + ], + "Cornell-Lossless-JPEG": [ + "Cornell Lossless JPEG License", + false, + false + ], + "CPAL-1.0": [ + "Common Public Attribution License 1.0", + true, + false + ], + "CPL-1.0": [ + "Common Public License 1.0", + true, + false + ], + "CPOL-1.02": [ + "Code Project Open License 1.02", + false, + false + ], + "Cronyx": [ + "Cronyx License", + false, + false + ], + "Crossword": [ + "Crossword License", + false, + false + ], + "CryptoSwift": [ + "CryptoSwift License", + false, + false + ], + "CrystalStacker": [ + "CrystalStacker License", + false, + false + ], + "CUA-OPL-1.0": [ + "CUA Office Public License v1.0", + true, + false + ], + "Cube": [ + "Cube License", + false, + false + ], + "curl": [ + "curl License", + false, + false + ], + "cve-tou": [ + "Common Vulnerability Enumeration ToU License", + false, + false + ], + "D-FSL-1.0": [ + "Deutsche Freie Software Lizenz", + false, + false + ], + "DEC-3-Clause": [ + "DEC 3-Clause License", + false, + false + ], + "diffmark": [ + "diffmark license", + false, + false + ], + "DL-DE-BY-2.0": [ + "Data licence Germany \u2013 attribution \u2013 version 2.0", + false, + false + ], + "DL-DE-ZERO-2.0": [ + "Data licence Germany \u2013 zero \u2013 version 2.0", + false, + false + ], + "DOC": [ + "DOC License", + false, + false + ], + "DocBook-DTD": [ + "DocBook DTD License", + false, + false + ], + "DocBook-Schema": [ + "DocBook Schema License", + false, + false + ], + "DocBook-Stylesheet": [ + "DocBook Stylesheet License", + false, + false + ], + "DocBook-XML": [ + "DocBook XML License", + false, + false + ], + "Dotseqn": [ + "Dotseqn License", + false, + false + ], + "DRL-1.0": [ + "Detection Rule License 1.0", + false, + false + ], + "DRL-1.1": [ + "Detection Rule License 1.1", + false, + false + ], + "DSDP": [ + "DSDP License", + false, + false + ], + "dtoa": [ + "David M. Gay dtoa License", + false, + false + ], + "dvipdfm": [ + "dvipdfm License", + false, + false + ], + "ECL-1.0": [ + "Educational Community License v1.0", + true, + false + ], + "ECL-2.0": [ + "Educational Community License v2.0", + true, + false + ], + "eCos-2.0": [ + "eCos license version 2.0", + false, + true + ], + "EFL-1.0": [ + "Eiffel Forum License v1.0", + true, + false + ], + "EFL-2.0": [ + "Eiffel Forum License v2.0", + true, + false + ], + "eGenix": [ + "eGenix.com Public License 1.1.0", + false, + false + ], + "Elastic-2.0": [ + "Elastic License 2.0", + false, + false + ], + "Entessa": [ + "Entessa Public License v1.0", + true, + false + ], + "EPICS": [ + "EPICS Open License", + false, + false + ], + "EPL-1.0": [ + "Eclipse Public License 1.0", + true, + false + ], + "EPL-2.0": [ + "Eclipse Public License 2.0", + true, + false + ], + "ErlPL-1.1": [ + "Erlang Public License v1.1", + false, + false + ], + "etalab-2.0": [ + "Etalab Open License 2.0", + false, + false + ], + "EUDatagrid": [ + "EU DataGrid Software License", + true, + false + ], + "EUPL-1.0": [ + "European Union Public License 1.0", + false, + false + ], + "EUPL-1.1": [ + "European Union Public License 1.1", + true, + false + ], + "EUPL-1.2": [ + "European Union Public License 1.2", + true, + false + ], + "Eurosym": [ + "Eurosym License", + false, + false + ], + "Fair": [ + "Fair License", + true, + false + ], + "FBM": [ + "Fuzzy Bitmap License", + false, + false + ], + "FDK-AAC": [ + "Fraunhofer FDK AAC Codec Library", + false, + false + ], + "Ferguson-Twofish": [ + "Ferguson Twofish License", + false, + false + ], + "Frameworx-1.0": [ + "Frameworx Open License 1.0", + true, + false + ], + "FreeBSD-DOC": [ + "FreeBSD Documentation License", + false, + false + ], + "FreeImage": [ + "FreeImage Public License v1.0", + false, + false + ], + "FSFAP": [ + "FSF All Permissive License", + false, + false + ], + "FSFAP-no-warranty-disclaimer": [ + "FSF All Permissive License (without Warranty)", + false, + false + ], + "FSFUL": [ + "FSF Unlimited License", + false, + false + ], + "FSFULLR": [ + "FSF Unlimited License (with License Retention)", + false, + false + ], + "FSFULLRWD": [ + "FSF Unlimited License (With License Retention and Warranty Disclaimer)", + false, + false + ], + "FSL-1.1-ALv2": [ + "Functional Source License, Version 1.1, ALv2 Future License", + false, + false + ], + "FSL-1.1-MIT": [ + "Functional Source License, Version 1.1, MIT Future License", + false, + false + ], + "FTL": [ + "Freetype Project License", + false, + false + ], + "Furuseth": [ + "Furuseth License", + false, + false + ], + "fwlw": [ + "fwlw License", + false, + false + ], + "Game-Programming-Gems": [ + "Game Programming Gems License", + false, + false + ], + "GCR-docs": [ + "Gnome GCR Documentation License", + false, + false + ], + "GD": [ + "GD License", + false, + false + ], + "generic-xts": [ + "Generic XTS License", + false, + false + ], + "GFDL-1.1": [ + "GNU Free Documentation License v1.1", + false, + true + ], + "GFDL-1.1-invariants-only": [ + "GNU Free Documentation License v1.1 only - invariants", + false, + false + ], + "GFDL-1.1-invariants-or-later": [ + "GNU Free Documentation License v1.1 or later - invariants", + false, + false + ], + "GFDL-1.1-no-invariants-only": [ + "GNU Free Documentation License v1.1 only - no invariants", + false, + false + ], + "GFDL-1.1-no-invariants-or-later": [ + "GNU Free Documentation License v1.1 or later - no invariants", + false, + false + ], + "GFDL-1.1-only": [ + "GNU Free Documentation License v1.1 only", + false, + false + ], + "GFDL-1.1-or-later": [ + "GNU Free Documentation License v1.1 or later", + false, + false + ], + "GFDL-1.2": [ + "GNU Free Documentation License v1.2", + false, + true + ], + "GFDL-1.2-invariants-only": [ + "GNU Free Documentation License v1.2 only - invariants", + false, + false + ], + "GFDL-1.2-invariants-or-later": [ + "GNU Free Documentation License v1.2 or later - invariants", + false, + false + ], + "GFDL-1.2-no-invariants-only": [ + "GNU Free Documentation License v1.2 only - no invariants", + false, + false + ], + "GFDL-1.2-no-invariants-or-later": [ + "GNU Free Documentation License v1.2 or later - no invariants", + false, + false + ], + "GFDL-1.2-only": [ + "GNU Free Documentation License v1.2 only", + false, + false + ], + "GFDL-1.2-or-later": [ + "GNU Free Documentation License v1.2 or later", + false, + false + ], + "GFDL-1.3": [ + "GNU Free Documentation License v1.3", + false, + true + ], + "GFDL-1.3-invariants-only": [ + "GNU Free Documentation License v1.3 only - invariants", + false, + false + ], + "GFDL-1.3-invariants-or-later": [ + "GNU Free Documentation License v1.3 or later - invariants", + false, + false + ], + "GFDL-1.3-no-invariants-only": [ + "GNU Free Documentation License v1.3 only - no invariants", + false, + false + ], + "GFDL-1.3-no-invariants-or-later": [ + "GNU Free Documentation License v1.3 or later - no invariants", + false, + false + ], + "GFDL-1.3-only": [ + "GNU Free Documentation License v1.3 only", + false, + false + ], + "GFDL-1.3-or-later": [ + "GNU Free Documentation License v1.3 or later", + false, + false + ], + "Giftware": [ + "Giftware License", + false, + false + ], + "GL2PS": [ + "GL2PS License", + false, + false + ], + "Glide": [ + "3dfx Glide License", + false, + false + ], + "Glulxe": [ + "Glulxe License", + false, + false + ], + "GLWTPL": [ + "Good Luck With That Public License", + false, + false + ], + "gnuplot": [ + "gnuplot License", + false, + false + ], + "GPL-1.0": [ + "GNU General Public License v1.0 only", + false, + true + ], + "GPL-1.0+": [ + "GNU General Public License v1.0 or later", + false, + true + ], + "GPL-1.0-only": [ + "GNU General Public License v1.0 only", + false, + false + ], + "GPL-1.0-or-later": [ + "GNU General Public License v1.0 or later", + false, + false + ], + "GPL-2.0": [ + "GNU General Public License v2.0 only", + true, + true + ], + "GPL-2.0+": [ + "GNU General Public License v2.0 or later", + true, + true + ], + "GPL-2.0-only": [ + "GNU General Public License v2.0 only", + true, + false + ], + "GPL-2.0-or-later": [ + "GNU General Public License v2.0 or later", + true, + false + ], + "GPL-2.0-with-autoconf-exception": [ + "GNU General Public License v2.0 w/Autoconf exception", + false, + true + ], + "GPL-2.0-with-bison-exception": [ + "GNU General Public License v2.0 w/Bison exception", + false, + true + ], + "GPL-2.0-with-classpath-exception": [ + "GNU General Public License v2.0 w/Classpath exception", + false, + true + ], + "GPL-2.0-with-font-exception": [ + "GNU General Public License v2.0 w/Font exception", + false, + true + ], + "GPL-2.0-with-GCC-exception": [ + "GNU General Public License v2.0 w/GCC Runtime Library exception", + false, + true + ], + "GPL-3.0": [ + "GNU General Public License v3.0 only", + true, + true + ], + "GPL-3.0+": [ + "GNU General Public License v3.0 or later", + true, + true + ], + "GPL-3.0-only": [ + "GNU General Public License v3.0 only", + true, + false + ], + "GPL-3.0-or-later": [ + "GNU General Public License v3.0 or later", + true, + false + ], + "GPL-3.0-with-autoconf-exception": [ + "GNU General Public License v3.0 w/Autoconf exception", + false, + true + ], + "GPL-3.0-with-GCC-exception": [ + "GNU General Public License v3.0 w/GCC Runtime Library exception", + true, + true + ], + "Graphics-Gems": [ + "Graphics Gems License", + false, + false + ], + "gSOAP-1.3b": [ + "gSOAP Public License v1.3b", + false, + false + ], + "gtkbook": [ + "gtkbook License", + false, + false + ], + "Gutmann": [ + "Gutmann License", + false, + false + ], + "HaskellReport": [ + "Haskell Language Report License", + false, + false + ], + "hdparm": [ + "hdparm License", + false, + false + ], + "HIDAPI": [ + "HIDAPI License", + false, + false + ], + "Hippocratic-2.1": [ + "Hippocratic License 2.1", + false, + false + ], + "HP-1986": [ + "Hewlett-Packard 1986 License", + false, + false + ], + "HP-1989": [ + "Hewlett-Packard 1989 License", + false, + false + ], + "HPND": [ + "Historical Permission Notice and Disclaimer", + true, + false + ], + "HPND-DEC": [ + "Historical Permission Notice and Disclaimer - DEC variant", + false, + false + ], + "HPND-doc": [ + "Historical Permission Notice and Disclaimer - documentation variant", + false, + false + ], + "HPND-doc-sell": [ + "Historical Permission Notice and Disclaimer - documentation sell variant", + false, + false + ], + "HPND-export-US": [ + "HPND with US Government export control warning", + false, + false + ], + "HPND-export-US-acknowledgement": [ + "HPND with US Government export control warning and acknowledgment", + false, + false + ], + "HPND-export-US-modify": [ + "HPND with US Government export control warning and modification rqmt", + false, + false + ], + "HPND-export2-US": [ + "HPND with US Government export control and 2 disclaimers", + false, + false + ], + "HPND-Fenneberg-Livingston": [ + "Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant", + false, + false + ], + "HPND-INRIA-IMAG": [ + "Historical Permission Notice and Disclaimer - INRIA-IMAG variant", + false, + false + ], + "HPND-Intel": [ + "Historical Permission Notice and Disclaimer - Intel variant", + false, + false + ], + "HPND-Kevlin-Henney": [ + "Historical Permission Notice and Disclaimer - Kevlin Henney variant", + false, + false + ], + "HPND-Markus-Kuhn": [ + "Historical Permission Notice and Disclaimer - Markus Kuhn variant", + false, + false + ], + "HPND-merchantability-variant": [ + "Historical Permission Notice and Disclaimer - merchantability variant", + false, + false + ], + "HPND-MIT-disclaimer": [ + "Historical Permission Notice and Disclaimer with MIT disclaimer", + false, + false + ], + "HPND-Netrek": [ + "Historical Permission Notice and Disclaimer - Netrek variant", + false, + false + ], + "HPND-Pbmplus": [ + "Historical Permission Notice and Disclaimer - Pbmplus variant", + false, + false + ], + "HPND-sell-MIT-disclaimer-xserver": [ + "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer", + false, + false + ], + "HPND-sell-regexpr": [ + "Historical Permission Notice and Disclaimer - sell regexpr variant", + false, + false + ], + "HPND-sell-variant": [ + "Historical Permission Notice and Disclaimer - sell variant", + false, + false + ], + "HPND-sell-variant-MIT-disclaimer": [ + "HPND sell variant with MIT disclaimer", + false, + false + ], + "HPND-sell-variant-MIT-disclaimer-rev": [ + "HPND sell variant with MIT disclaimer - reverse", + false, + false + ], + "HPND-UC": [ + "Historical Permission Notice and Disclaimer - University of California variant", + false, + false + ], + "HPND-UC-export-US": [ + "Historical Permission Notice and Disclaimer - University of California, US export warning", + false, + false + ], + "HTMLTIDY": [ + "HTML Tidy License", + false, + false + ], + "IBM-pibs": [ + "IBM PowerPC Initialization and Boot Software", + false, + false + ], + "ICU": [ + "ICU License", + true, + false + ], + "IEC-Code-Components-EULA": [ + "IEC Code Components End-user licence agreement", + false, + false + ], + "IJG": [ + "Independent JPEG Group License", + false, + false + ], + "IJG-short": [ + "Independent JPEG Group License - short", + false, + false + ], + "ImageMagick": [ + "ImageMagick License", + false, + false + ], + "iMatix": [ + "iMatix Standard Function Library Agreement", + false, + false + ], + "Imlib2": [ + "Imlib2 License", + false, + false + ], + "Info-ZIP": [ + "Info-ZIP License", + false, + false + ], + "Inner-Net-2.0": [ + "Inner Net License v2.0", + false, + false + ], + "InnoSetup": [ + "Inno Setup License", + false, + false + ], + "Intel": [ + "Intel Open Source License", + true, + false + ], + "Intel-ACPI": [ + "Intel ACPI Software License Agreement", + false, + false + ], + "Interbase-1.0": [ + "Interbase Public License v1.0", + false, + false + ], + "IPA": [ + "IPA Font License", + true, + false + ], + "IPL-1.0": [ + "IBM Public License v1.0", + true, + false + ], + "ISC": [ + "ISC License", + true, + false + ], + "ISC-Veillard": [ + "ISC Veillard variant", + false, + false + ], + "Jam": [ + "Jam License", + true, + false + ], + "JasPer-2.0": [ + "JasPer License", + false, + false + ], + "jove": [ + "Jove License", + false, + false + ], + "JPL-image": [ + "JPL Image Use Policy", + false, + false + ], + "JPNIC": [ + "Japan Network Information Center License", + false, + false + ], + "JSON": [ + "JSON License", + false, + false + ], + "Kastrup": [ + "Kastrup License", + false, + false + ], + "Kazlib": [ + "Kazlib License", + false, + false + ], + "Knuth-CTAN": [ + "Knuth CTAN License", + false, + false + ], + "LAL-1.2": [ + "Licence Art Libre 1.2", + false, + false + ], + "LAL-1.3": [ + "Licence Art Libre 1.3", + false, + false + ], + "Latex2e": [ + "Latex2e License", + false, + false + ], + "Latex2e-translated-notice": [ + "Latex2e with translated notice permission", + false, + false + ], + "Leptonica": [ + "Leptonica License", + false, + false + ], + "LGPL-2.0": [ + "GNU Library General Public License v2 only", + true, + true + ], + "LGPL-2.0+": [ + "GNU Library General Public License v2 or later", + true, + true + ], + "LGPL-2.0-only": [ + "GNU Library General Public License v2 only", + true, + false + ], + "LGPL-2.0-or-later": [ + "GNU Library General Public License v2 or later", + true, + false + ], + "LGPL-2.1": [ + "GNU Lesser General Public License v2.1 only", + true, + true + ], + "LGPL-2.1+": [ + "GNU Lesser General Public License v2.1 or later", + true, + true + ], + "LGPL-2.1-only": [ + "GNU Lesser General Public License v2.1 only", + true, + false + ], + "LGPL-2.1-or-later": [ + "GNU Lesser General Public License v2.1 or later", + true, + false + ], + "LGPL-3.0": [ + "GNU Lesser General Public License v3.0 only", + true, + true + ], + "LGPL-3.0+": [ + "GNU Lesser General Public License v3.0 or later", + true, + true + ], + "LGPL-3.0-only": [ + "GNU Lesser General Public License v3.0 only", + true, + false + ], + "LGPL-3.0-or-later": [ + "GNU Lesser General Public License v3.0 or later", + true, + false + ], + "LGPLLR": [ + "Lesser General Public License For Linguistic Resources", + false, + false + ], + "Libpng": [ + "libpng License", + false, + false + ], + "libpng-2.0": [ + "PNG Reference Library version 2", + false, + false + ], + "libselinux-1.0": [ + "libselinux public domain notice", + false, + false + ], + "libtiff": [ + "libtiff License", + false, + false + ], + "libutil-David-Nugent": [ + "libutil David Nugent License", + false, + false + ], + "LiLiQ-P-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1", + true, + false + ], + "LiLiQ-R-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1", + true, + false + ], + "LiLiQ-Rplus-1.1": [ + "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1", + true, + false + ], + "Linux-man-pages-1-para": [ + "Linux man-pages - 1 paragraph", + false, + false + ], + "Linux-man-pages-copyleft": [ + "Linux man-pages Copyleft", + false, + false + ], + "Linux-man-pages-copyleft-2-para": [ + "Linux man-pages Copyleft - 2 paragraphs", + false, + false + ], + "Linux-man-pages-copyleft-var": [ + "Linux man-pages Copyleft Variant", + false, + false + ], + "Linux-OpenIB": [ + "Linux Kernel Variant of OpenIB.org license", + false, + false + ], + "LOOP": [ + "Common Lisp LOOP License", + false, + false + ], + "LPD-document": [ + "LPD Documentation License", + false, + false + ], + "LPL-1.0": [ + "Lucent Public License Version 1.0", + true, + false + ], + "LPL-1.02": [ + "Lucent Public License v1.02", + true, + false + ], + "LPPL-1.0": [ + "LaTeX Project Public License v1.0", + false, + false + ], + "LPPL-1.1": [ + "LaTeX Project Public License v1.1", + false, + false + ], + "LPPL-1.2": [ + "LaTeX Project Public License v1.2", + false, + false + ], + "LPPL-1.3a": [ + "LaTeX Project Public License v1.3a", + false, + false + ], + "LPPL-1.3c": [ + "LaTeX Project Public License v1.3c", + true, + false + ], + "lsof": [ + "lsof License", + false, + false + ], + "Lucida-Bitmap-Fonts": [ + "Lucida Bitmap Fonts License", + false, + false + ], + "LZMA-SDK-9.11-to-9.20": [ + "LZMA SDK License (versions 9.11 to 9.20)", + false, + false + ], + "LZMA-SDK-9.22": [ + "LZMA SDK License (versions 9.22 and beyond)", + false, + false + ], + "Mackerras-3-Clause": [ + "Mackerras 3-Clause License", + false, + false + ], + "Mackerras-3-Clause-acknowledgment": [ + "Mackerras 3-Clause - acknowledgment variant", + false, + false + ], + "magaz": [ + "magaz License", + false, + false + ], + "mailprio": [ + "mailprio License", + false, + false + ], + "MakeIndex": [ + "MakeIndex License", + false, + false + ], + "man2html": [ + "man2html License", + false, + false + ], + "Martin-Birgmeier": [ + "Martin Birgmeier License", + false, + false + ], + "McPhee-slideshow": [ + "McPhee Slideshow License", + false, + false + ], + "metamail": [ + "metamail License", + false, + false + ], + "Minpack": [ + "Minpack License", + false, + false + ], + "MIPS": [ + "MIPS License", + false, + false + ], + "MirOS": [ + "The MirOS Licence", + true, + false + ], + "MIT": [ + "MIT License", + true, + false + ], + "MIT-0": [ + "MIT No Attribution", + true, + false + ], + "MIT-advertising": [ + "Enlightenment License (e16)", + false, + false + ], + "MIT-Click": [ + "MIT Click License", + false, + false + ], + "MIT-CMU": [ + "CMU License", + false, + false + ], + "MIT-enna": [ + "enna License", + false, + false + ], + "MIT-feh": [ + "feh License", + false, + false + ], + "MIT-Festival": [ + "MIT Festival Variant", + false, + false + ], + "MIT-Khronos-old": [ + "MIT Khronos - old variant", + false, + false + ], + "MIT-Modern-Variant": [ + "MIT License Modern Variant", + true, + false + ], + "MIT-open-group": [ + "MIT Open Group variant", + false, + false + ], + "MIT-testregex": [ + "MIT testregex Variant", + false, + false + ], + "MIT-Wu": [ + "MIT Tom Wu Variant", + false, + false + ], + "MITNFA": [ + "MIT +no-false-attribs license", + false, + false + ], + "MMIXware": [ + "MMIXware License", + false, + false + ], + "Motosoto": [ + "Motosoto License", + true, + false + ], + "MPEG-SSG": [ + "MPEG Software Simulation", + false, + false + ], + "mpi-permissive": [ + "mpi Permissive License", + false, + false + ], + "mpich2": [ + "mpich2 License", + false, + false + ], + "MPL-1.0": [ + "Mozilla Public License 1.0", + true, + false + ], + "MPL-1.1": [ + "Mozilla Public License 1.1", + true, + false + ], + "MPL-2.0": [ + "Mozilla Public License 2.0", + true, + false + ], + "MPL-2.0-no-copyleft-exception": [ + "Mozilla Public License 2.0 (no copyleft exception)", + true, + false + ], + "mplus": [ + "mplus Font License", + false, + false + ], + "MS-LPL": [ + "Microsoft Limited Public License", + false, + false + ], + "MS-PL": [ + "Microsoft Public License", + true, + false + ], + "MS-RL": [ + "Microsoft Reciprocal License", + true, + false + ], + "MTLL": [ + "Matrix Template Library License", + false, + false + ], + "MulanPSL-1.0": [ + "Mulan Permissive Software License, Version 1", + false, + false + ], + "MulanPSL-2.0": [ + "Mulan Permissive Software License, Version 2", + true, + false + ], + "Multics": [ + "Multics License", + true, + false + ], + "Mup": [ + "Mup License", + false, + false + ], + "NAIST-2003": [ + "Nara Institute of Science and Technology License (2003)", + false, + false + ], + "NASA-1.3": [ + "NASA Open Source Agreement 1.3", + true, + false + ], + "Naumen": [ + "Naumen Public License", + true, + false + ], + "NBPL-1.0": [ + "Net Boolean Public License v1", + false, + false + ], + "NCBI-PD": [ + "NCBI Public Domain Notice", + false, + false + ], + "NCGL-UK-2.0": [ + "Non-Commercial Government Licence", + false, + false + ], + "NCL": [ + "NCL Source Code License", + false, + false + ], + "NCSA": [ + "University of Illinois/NCSA Open Source License", + true, + false + ], + "Net-SNMP": [ + "Net-SNMP License", + false, + true + ], + "NetCDF": [ + "NetCDF license", + false, + false + ], + "Newsletr": [ + "Newsletr License", + false, + false + ], + "NGPL": [ + "Nethack General Public License", + true, + false + ], + "NICTA-1.0": [ + "NICTA Public Software License, Version 1.0", + false, + false + ], + "NIST-PD": [ + "NIST Public Domain Notice", + false, + false + ], + "NIST-PD-fallback": [ + "NIST Public Domain Notice with license fallback", + false, + false + ], + "NIST-Software": [ + "NIST Software License", + false, + false + ], + "NLOD-1.0": [ + "Norwegian Licence for Open Government Data (NLOD) 1.0", + false, + false + ], + "NLOD-2.0": [ + "Norwegian Licence for Open Government Data (NLOD) 2.0", + false, + false + ], + "NLPL": [ + "No Limit Public License", + false, + false + ], + "Nokia": [ + "Nokia Open Source License", + true, + false + ], + "NOSL": [ + "Netizen Open Source License", + false, + false + ], + "Noweb": [ + "Noweb License", + false, + false + ], + "NPL-1.0": [ + "Netscape Public License v1.0", + false, + false + ], + "NPL-1.1": [ + "Netscape Public License v1.1", + false, + false + ], + "NPOSL-3.0": [ + "Non-Profit Open Software License 3.0", + true, + false + ], + "NRL": [ + "NRL License", + false, + false + ], + "NTIA-PD": [ + "NTIA Public Domain Notice", + false, + false + ], + "NTP": [ + "NTP License", + true, + false + ], + "NTP-0": [ + "NTP No Attribution", + false, + false + ], + "Nunit": [ + "Nunit License", + false, + true + ], + "O-UDA-1.0": [ + "Open Use of Data Agreement v1.0", + false, + false + ], + "OAR": [ + "OAR License", + false, + false + ], + "OCCT-PL": [ + "Open CASCADE Technology Public License", + false, + false + ], + "OCLC-2.0": [ + "OCLC Research Public License 2.0", + true, + false + ], + "ODbL-1.0": [ + "Open Data Commons Open Database License v1.0", + false, + false + ], + "ODC-By-1.0": [ + "Open Data Commons Attribution License v1.0", + false, + false + ], + "OFFIS": [ + "OFFIS License", + false, + false + ], + "OFL-1.0": [ + "SIL Open Font License 1.0", + false, + false + ], + "OFL-1.0-no-RFN": [ + "SIL Open Font License 1.0 with no Reserved Font Name", + false, + false + ], + "OFL-1.0-RFN": [ + "SIL Open Font License 1.0 with Reserved Font Name", + false, + false + ], + "OFL-1.1": [ + "SIL Open Font License 1.1", + true, + false + ], + "OFL-1.1-no-RFN": [ + "SIL Open Font License 1.1 with no Reserved Font Name", + true, + false + ], + "OFL-1.1-RFN": [ + "SIL Open Font License 1.1 with Reserved Font Name", + true, + false + ], + "OGC-1.0": [ + "OGC Software License, Version 1.0", + false, + false + ], + "OGDL-Taiwan-1.0": [ + "Taiwan Open Government Data License, version 1.0", + false, + false + ], + "OGL-Canada-2.0": [ + "Open Government Licence - Canada", + false, + false + ], + "OGL-UK-1.0": [ + "Open Government Licence v1.0", + false, + false + ], + "OGL-UK-2.0": [ + "Open Government Licence v2.0", + false, + false + ], + "OGL-UK-3.0": [ + "Open Government Licence v3.0", + false, + false + ], + "OGTSL": [ + "Open Group Test Suite License", + true, + false + ], + "OLDAP-1.1": [ + "Open LDAP Public License v1.1", + false, + false + ], + "OLDAP-1.2": [ + "Open LDAP Public License v1.2", + false, + false + ], + "OLDAP-1.3": [ + "Open LDAP Public License v1.3", + false, + false + ], + "OLDAP-1.4": [ + "Open LDAP Public License v1.4", + false, + false + ], + "OLDAP-2.0": [ + "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", + false, + false + ], + "OLDAP-2.0.1": [ + "Open LDAP Public License v2.0.1", + false, + false + ], + "OLDAP-2.1": [ + "Open LDAP Public License v2.1", + false, + false + ], + "OLDAP-2.2": [ + "Open LDAP Public License v2.2", + false, + false + ], + "OLDAP-2.2.1": [ + "Open LDAP Public License v2.2.1", + false, + false + ], + "OLDAP-2.2.2": [ + "Open LDAP Public License 2.2.2", + false, + false + ], + "OLDAP-2.3": [ + "Open LDAP Public License v2.3", + false, + false + ], + "OLDAP-2.4": [ + "Open LDAP Public License v2.4", + false, + false + ], + "OLDAP-2.5": [ + "Open LDAP Public License v2.5", + false, + false + ], + "OLDAP-2.6": [ + "Open LDAP Public License v2.6", + false, + false + ], + "OLDAP-2.7": [ + "Open LDAP Public License v2.7", + false, + false + ], + "OLDAP-2.8": [ + "Open LDAP Public License v2.8", + true, + false + ], + "OLFL-1.3": [ + "Open Logistics Foundation License Version 1.3", + true, + false + ], + "OML": [ + "Open Market License", + false, + false + ], + "OpenPBS-2.3": [ + "OpenPBS v2.3 Software License", + false, + false + ], + "OpenSSL": [ + "OpenSSL License", + false, + false + ], + "OpenSSL-standalone": [ + "OpenSSL License - standalone", + false, + false + ], + "OpenVision": [ + "OpenVision License", + false, + false + ], + "OPL-1.0": [ + "Open Public License v1.0", + false, + false + ], + "OPL-UK-3.0": [ + "United Kingdom Open Parliament Licence v3.0", + false, + false + ], + "OPUBL-1.0": [ + "Open Publication License v1.0", + false, + false + ], + "OSET-PL-2.1": [ + "OSET Public License version 2.1", + true, + false + ], + "OSL-1.0": [ + "Open Software License 1.0", + true, + false + ], + "OSL-1.1": [ + "Open Software License 1.1", + false, + false + ], + "OSL-2.0": [ + "Open Software License 2.0", + true, + false + ], + "OSL-2.1": [ + "Open Software License 2.1", + true, + false + ], + "OSL-3.0": [ + "Open Software License 3.0", + true, + false + ], + "PADL": [ + "PADL License", + false, + false + ], + "Parity-6.0.0": [ + "The Parity Public License 6.0.0", + false, + false + ], + "Parity-7.0.0": [ + "The Parity Public License 7.0.0", + false, + false + ], + "PDDL-1.0": [ + "Open Data Commons Public Domain Dedication & License 1.0", + false, + false + ], + "PHP-3.0": [ + "PHP License v3.0", + true, + false + ], + "PHP-3.01": [ + "PHP License v3.01", + true, + false + ], + "Pixar": [ + "Pixar License", + false, + false + ], + "pkgconf": [ + "pkgconf License", + false, + false + ], + "Plexus": [ + "Plexus Classworlds License", + false, + false + ], + "pnmstitch": [ + "pnmstitch License", + false, + false + ], + "PolyForm-Noncommercial-1.0.0": [ + "PolyForm Noncommercial License 1.0.0", + false, + false + ], + "PolyForm-Small-Business-1.0.0": [ + "PolyForm Small Business License 1.0.0", + false, + false + ], + "PostgreSQL": [ + "PostgreSQL License", + true, + false + ], + "PPL": [ + "Peer Production License", + false, + false + ], + "PSF-2.0": [ + "Python Software Foundation License 2.0", + false, + false + ], + "psfrag": [ + "psfrag License", + false, + false + ], + "psutils": [ + "psutils License", + false, + false + ], + "Python-2.0": [ + "Python License 2.0", + true, + false + ], + "Python-2.0.1": [ + "Python License 2.0.1", + false, + false + ], + "python-ldap": [ + "Python ldap License", + false, + false + ], + "Qhull": [ + "Qhull License", + false, + false + ], + "QPL-1.0": [ + "Q Public License 1.0", + true, + false + ], + "QPL-1.0-INRIA-2004": [ + "Q Public License 1.0 - INRIA 2004 variant", + false, + false + ], + "radvd": [ + "radvd License", + false, + false + ], + "Rdisc": [ + "Rdisc License", + false, + false + ], + "RHeCos-1.1": [ + "Red Hat eCos Public License v1.1", + false, + false + ], + "RPL-1.1": [ + "Reciprocal Public License 1.1", + true, + false + ], + "RPL-1.5": [ + "Reciprocal Public License 1.5", + true, + false + ], + "RPSL-1.0": [ + "RealNetworks Public Source License v1.0", + true, + false + ], + "RSA-MD": [ + "RSA Message-Digest License", + false, + false + ], + "RSCPL": [ + "Ricoh Source Code Public License", + true, + false + ], + "Ruby": [ + "Ruby License", + false, + false + ], + "Ruby-pty": [ + "Ruby pty extension license", + false, + false + ], + "SAX-PD": [ + "Sax Public Domain Notice", + false, + false + ], + "SAX-PD-2.0": [ + "Sax Public Domain Notice 2.0", + false, + false + ], + "Saxpath": [ + "Saxpath License", + false, + false + ], + "SCEA": [ + "SCEA Shared Source License", + false, + false + ], + "SchemeReport": [ + "Scheme Language Report License", + false, + false + ], + "Sendmail": [ + "Sendmail License", + false, + false + ], + "Sendmail-8.23": [ + "Sendmail License 8.23", + false, + false + ], + "Sendmail-Open-Source-1.1": [ + "Sendmail Open Source License v1.1", + false, + false + ], + "SGI-B-1.0": [ + "SGI Free Software License B v1.0", + false, + false + ], + "SGI-B-1.1": [ + "SGI Free Software License B v1.1", + false, + false + ], + "SGI-B-2.0": [ + "SGI Free Software License B v2.0", + false, + false + ], + "SGI-OpenGL": [ + "SGI OpenGL License", + false, + false + ], + "SGP4": [ + "SGP4 Permission Notice", + false, + false + ], + "SHL-0.5": [ + "Solderpad Hardware License v0.5", + false, + false + ], + "SHL-0.51": [ + "Solderpad Hardware License, Version 0.51", + false, + false + ], + "SimPL-2.0": [ + "Simple Public License 2.0", + true, + false + ], + "SISSL": [ + "Sun Industry Standards Source License v1.1", + true, + false + ], + "SISSL-1.2": [ + "Sun Industry Standards Source License v1.2", + false, + false + ], + "SL": [ + "SL License", + false, + false + ], + "Sleepycat": [ + "Sleepycat License", + true, + false + ], + "SMAIL-GPL": [ + "SMAIL General Public License", + false, + false + ], + "SMLNJ": [ + "Standard ML of New Jersey License", + false, + false + ], + "SMPPL": [ + "Secure Messaging Protocol Public License", + false, + false + ], + "SNIA": [ + "SNIA Public License 1.1", + false, + false + ], + "snprintf": [ + "snprintf License", + false, + false + ], + "SOFA": [ + "SOFA Software License", + false, + false + ], + "softSurfer": [ + "softSurfer License", + false, + false + ], + "Soundex": [ + "Soundex License", + false, + false + ], + "Spencer-86": [ + "Spencer License 86", + false, + false + ], + "Spencer-94": [ + "Spencer License 94", + false, + false + ], + "Spencer-99": [ + "Spencer License 99", + false, + false + ], + "SPL-1.0": [ + "Sun Public License v1.0", + true, + false + ], + "ssh-keyscan": [ + "ssh-keyscan License", + false, + false + ], + "SSH-OpenSSH": [ + "SSH OpenSSH license", + false, + false + ], + "SSH-short": [ + "SSH short notice", + false, + false + ], + "SSLeay-standalone": [ + "SSLeay License - standalone", + false, + false + ], + "SSPL-1.0": [ + "Server Side Public License, v 1", + false, + false + ], + "StandardML-NJ": [ + "Standard ML of New Jersey License", + false, + true + ], + "SugarCRM-1.1.3": [ + "SugarCRM Public License v1.1.3", + false, + false + ], + "Sun-PPP": [ + "Sun PPP License", + false, + false + ], + "Sun-PPP-2000": [ + "Sun PPP License (2000)", + false, + false + ], + "SunPro": [ + "SunPro License", + false, + false + ], + "SWL": [ + "Scheme Widget Library (SWL) Software License Agreement", + false, + false + ], + "swrule": [ + "swrule License", + false, + false + ], + "Symlinks": [ + "Symlinks License", + false, + false + ], + "TAPR-OHL-1.0": [ + "TAPR Open Hardware License v1.0", + false, + false + ], + "TCL": [ + "TCL/TK License", + false, + false + ], + "TCP-wrappers": [ + "TCP Wrappers License", + false, + false + ], + "TermReadKey": [ + "TermReadKey License", + false, + false + ], + "TGPPL-1.0": [ + "Transitive Grace Period Public Licence 1.0", + false, + false + ], + "ThirdEye": [ + "ThirdEye License", + false, + false + ], + "threeparttable": [ + "threeparttable License", + false, + false + ], + "TMate": [ + "TMate Open Source License", + false, + false + ], + "TORQUE-1.1": [ + "TORQUE v2.5+ Software License v1.1", + false, + false + ], + "TOSL": [ + "Trusster Open Source License", + false, + false + ], + "TPDL": [ + "Time::ParseDate License", + false, + false + ], + "TPL-1.0": [ + "THOR Public License 1.0", + false, + false + ], + "TrustedQSL": [ + "TrustedQSL License", + false, + false + ], + "TTWL": [ + "Text-Tabs+Wrap License", + false, + false + ], + "TTYP0": [ + "TTYP0 License", + false, + false + ], + "TU-Berlin-1.0": [ + "Technische Universitaet Berlin License 1.0", + false, + false + ], + "TU-Berlin-2.0": [ + "Technische Universitaet Berlin License 2.0", + false, + false + ], + "Ubuntu-font-1.0": [ + "Ubuntu Font Licence v1.0", + false, + false + ], + "UCAR": [ + "UCAR License", + false, + false + ], + "UCL-1.0": [ + "Upstream Compatibility License v1.0", + true, + false + ], + "ulem": [ + "ulem License", + false, + false + ], + "UMich-Merit": [ + "Michigan/Merit Networks License", + false, + false + ], + "Unicode-3.0": [ + "Unicode License v3", + true, + false + ], + "Unicode-DFS-2015": [ + "Unicode License Agreement - Data Files and Software (2015)", + false, + false + ], + "Unicode-DFS-2016": [ + "Unicode License Agreement - Data Files and Software (2016)", + true, + false + ], + "Unicode-TOU": [ + "Unicode Terms of Use", + false, + false + ], + "UnixCrypt": [ + "UnixCrypt License", + false, + false + ], + "Unlicense": [ + "The Unlicense", + true, + false + ], + "Unlicense-libtelnet": [ + "Unlicense - libtelnet variant", + false, + false + ], + "Unlicense-libwhirlpool": [ + "Unlicense - libwhirlpool variant", + false, + false + ], + "UPL-1.0": [ + "Universal Permissive License v1.0", + true, + false + ], + "URT-RLE": [ + "Utah Raster Toolkit Run Length Encoded License", + false, + false + ], + "Vim": [ + "Vim License", + false, + false + ], + "VOSTROM": [ + "VOSTROM Public License for Open Source", + false, + false + ], + "VSL-1.0": [ + "Vovida Software License v1.0", + true, + false + ], + "W3C": [ + "W3C Software Notice and License (2002-12-31)", + true, + false + ], + "W3C-19980720": [ + "W3C Software Notice and License (1998-07-20)", + false, + false + ], + "W3C-20150513": [ + "W3C Software Notice and Document License (2015-05-13)", + true, + false + ], + "w3m": [ + "w3m License", + false, + false + ], + "Watcom-1.0": [ + "Sybase Open Watcom Public License 1.0", + true, + false + ], + "Widget-Workshop": [ + "Widget Workshop License", + false, + false + ], + "Wsuipa": [ + "Wsuipa License", + false, + false + ], + "WTFPL": [ + "Do What The F*ck You Want To Public License", + false, + false + ], + "wwl": [ + "WWL License", + false, + false + ], + "wxWindows": [ + "wxWindows Library License", + true, + true + ], + "X11": [ + "X11 License", + false, + false + ], + "X11-distribute-modifications-variant": [ + "X11 License Distribution Modification Variant", + false, + false + ], + "X11-swapped": [ + "X11 swapped final paragraphs", + false, + false + ], + "Xdebug-1.03": [ + "Xdebug License v 1.03", + false, + false + ], + "Xerox": [ + "Xerox License", + false, + false + ], + "Xfig": [ + "Xfig License", + false, + false + ], + "XFree86-1.1": [ + "XFree86 License 1.1", + false, + false + ], + "xinetd": [ + "xinetd License", + false, + false + ], + "xkeyboard-config-Zinoviev": [ + "xkeyboard-config Zinoviev License", + false, + false + ], + "xlock": [ + "xlock License", + false, + false + ], + "Xnet": [ + "X.Net License", + true, + false + ], + "xpp": [ + "XPP License", + false, + false + ], + "XSkat": [ + "XSkat License", + false, + false + ], + "xzoom": [ + "xzoom License", + false, + false + ], + "YPL-1.0": [ + "Yahoo! Public License v1.0", + false, + false + ], + "YPL-1.1": [ + "Yahoo! Public License v1.1", + false, + false + ], + "Zed": [ + "Zed License", + false, + false + ], + "Zeeff": [ + "Zeeff License", + false, + false + ], + "Zend-2.0": [ + "Zend License v2.0", + false, + false + ], + "Zimbra-1.3": [ + "Zimbra Public License v1.3", + false, + false + ], + "Zimbra-1.4": [ + "Zimbra Public License v1.4", + false, + false + ], + "Zlib": [ + "zlib License", + true, + false + ], + "zlib-acknowledgement": [ + "zlib/libpng License with Acknowledgement", + false, + false + ], + "ZPL-1.1": [ + "Zope Public License 1.1", + false, + false + ], + "ZPL-2.0": [ + "Zope Public License 2.0", + true, + false + ], + "ZPL-2.1": [ + "Zope Public License 2.1", + true, + false + ] +} \ No newline at end of file diff --git a/vendor/composer/spdx-licenses/src/SpdxLicenses.php b/vendor/composer/spdx-licenses/src/SpdxLicenses.php new file mode 100644 index 0000000..7b94b24 --- /dev/null +++ b/vendor/composer/spdx-licenses/src/SpdxLicenses.php @@ -0,0 +1,357 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\Spdx; + +class SpdxLicenses +{ + /** @var string */ + const LICENSES_FILE = 'spdx-licenses.json'; + + /** @var string */ + const EXCEPTIONS_FILE = 'spdx-exceptions.json'; + + /** + * Contains all the licenses. + * + * The array is indexed by license identifiers, which contain + * a numerically indexed array with license details. + * + * [ lowercased license identifier => + * [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] + * , ... + * ] + * + * @var array + */ + private $licenses; + + /** + * @var string + */ + private $licensesExpression; + + /** + * Contains all the license exceptions. + * + * The array is indexed by license exception identifiers, which contain + * a numerically indexed array with license exception details. + * + * [ lowercased exception identifier => + * [ 0 => exception identifier (string), 1 => full name (string) ] + * , ... + * ] + * + * @var array + */ + private $exceptions; + + /** + * @var string + */ + private $exceptionsExpression; + + public function __construct() + { + $this->loadLicenses(); + $this->loadExceptions(); + } + + /** + * Returns license metadata by license identifier. + * + * This function adds a link to the full license text to the license metadata. + * The array returned is in the form of: + * + * [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ] + * + * @param string $identifier + * + * @return array{0: string, 1: bool, 2: string, 3: bool}|null + */ + public function getLicenseByIdentifier($identifier) + { + $key = strtolower($identifier); + + if (!isset($this->licenses[$key])) { + return null; + } + + list($identifier, $name, $isOsiApproved, $isDeprecatedLicenseId) = $this->licenses[$key]; + + return array( + $name, + $isOsiApproved, + 'https://spdx.org/licenses/' . $identifier . '.html#licenseText', + $isDeprecatedLicenseId, + ); + } + + /** + * Returns all licenses information, keyed by the lowercased license identifier. + * + * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ] + */ + public function getLicenses() + { + return $this->licenses; + } + + /** + * Returns license exception metadata by license exception identifier. + * + * This function adds a link to the full license exception text to the license exception metadata. + * The array returned is in the form of: + * + * [ 0 => full name (string), 1 => link to license text (string) ] + * + * @param string $identifier + * + * @return array{0: string, 1: string}|null + */ + public function getExceptionByIdentifier($identifier) + { + $key = strtolower($identifier); + + if (!isset($this->exceptions[$key])) { + return null; + } + + list($identifier, $name) = $this->exceptions[$key]; + + return array( + $name, + 'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText', + ); + } + + /** + * Returns the short identifier of a license (or license exception) by full name. + * + * @param string $name + * + * @return string|null + */ + public function getIdentifierByName($name) + { + foreach ($this->licenses as $licenseData) { + if ($licenseData[1] === $name) { + return $licenseData[0]; + } + } + + foreach ($this->exceptions as $licenseData) { + if ($licenseData[1] === $name) { + return $licenseData[0]; + } + } + + return null; + } + + /** + * Returns the OSI Approved status for a license by identifier. + * + * @param string $identifier + * + * @return bool + */ + public function isOsiApprovedByIdentifier($identifier) + { + return $this->licenses[strtolower($identifier)][2]; + } + + /** + * Returns the deprecation status for a license by identifier. + * + * @param string $identifier + * + * @return bool + */ + public function isDeprecatedByIdentifier($identifier) + { + return $this->licenses[strtolower($identifier)][3]; + } + + /** + * @param string[]|string $license + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function validate($license) + { + if (is_array($license)) { + $count = count($license); + if ($count !== count(array_filter($license, 'is_string'))) { + throw new \InvalidArgumentException('Array of strings expected.'); + } + $license = $count > 1 ? '(' . implode(' OR ', $license) . ')' : (string) reset($license); + } + + if (!is_string($license)) { + throw new \InvalidArgumentException(sprintf( + 'Array or String expected, %s given.', + gettype($license) + )); + } + + return $this->isValidLicenseString($license); + } + + /** + * @return string + */ + public static function getResourcesDir() + { + return dirname(__DIR__) . '/res'; + } + + /** + * @return void + */ + private function loadLicenses() + { + if (null !== $this->licenses) { + return; + } + + $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE); + if (false === $json) { + throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE); + } + $this->licenses = array(); + + foreach (json_decode($json, true) as $identifier => $license) { + $this->licenses[strtolower($identifier)] = array($identifier, $license[0], $license[1], $license[2]); + } + } + + /** + * @return void + */ + private function loadExceptions() + { + if (null !== $this->exceptions) { + return; + } + + $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); + if (false === $json) { + throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); + } + $this->exceptions = array(); + + foreach (json_decode($json, true) as $identifier => $exception) { + $this->exceptions[strtolower($identifier)] = array($identifier, $exception[0]); + } + } + + /** + * @return string + */ + private function getLicensesExpression() + { + if (null === $this->licensesExpression) { + $licenses = array_map('preg_quote', array_keys($this->licenses)); + rsort($licenses); + $licenses = implode('|', $licenses); + $this->licensesExpression = $licenses; + } + + return $this->licensesExpression; + } + + /** + * @return string + */ + private function getExceptionsExpression() + { + if (null === $this->exceptionsExpression) { + $exceptions = array_map('preg_quote', array_keys($this->exceptions)); + rsort($exceptions); + $exceptions = implode('|', $exceptions); + $this->exceptionsExpression = $exceptions; + } + + return $this->exceptionsExpression; + } + + /** + * @param string $license + * + * @throws \RuntimeException + * + * @return bool + */ + private function isValidLicenseString($license) + { + if (isset($this->licenses[strtolower($license)])) { + return true; + } + + $licenses = $this->getLicensesExpression(); + $exceptions = $this->getExceptionsExpression(); + + $regex = <<[\pL\pN.-]{1,}) + + # license-id: taken from list + (?{$licenses}) + + # license-exception-id: taken from list + (?{$exceptions}) + + # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring) + (?(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring)) + + # simple-expresssion: license-id / license-id+ / license-ref + (?(?&licenseid)\+? | (?&licenseid) | (?&licenseref)) + + # compound-expression: 1*( + # simple-expression / + # simple-expression WITH license-exception-id / + # compound-expression AND compound-expression / + # compound-expression OR compound-expression + # ) / ( compound-expression ) ) + (? + (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))? + | \( \s* (?&compound_expression) \s* \) + ) + (? + (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))? + ) + + # license-expression: 1*1(simple-expression / compound-expression) + (?(?&compound_expression) | (?&simple_expression)) +) # end of define + +^(NONE | NOASSERTION | (?&license_expression))$ +}xi +REGEX; + + $match = preg_match($regex, $license); + + if (0 === $match) { + return false; + } + + if (false === $match) { + throw new \RuntimeException('Regex failed to compile/run.'); + } + + return true; + } +} diff --git a/vendor/composer/xdebug-handler/CHANGELOG.md b/vendor/composer/xdebug-handler/CHANGELOG.md new file mode 100644 index 0000000..62ebe22 --- /dev/null +++ b/vendor/composer/xdebug-handler/CHANGELOG.md @@ -0,0 +1,143 @@ +## [Unreleased] + +## [3.0.5] - 2024-05-06 + * Fixed: fail restart if PHP_BINARY is not available + +## [3.0.4] - 2024-03-26 + * Added: Functional tests. + * Fixed: Incompatibility with PHPUnit 10. + +## [3.0.3] - 2022-02-25 + * Added: support for composer/pcre versions 2 and 3. + +## [3.0.2] - 2022-02-24 + * Fixed: regression in 3.0.1 affecting Xdebug 2 + +## [3.0.1] - 2022-01-04 + * Fixed: error when calling `isXdebugActive` before class instantiation. + +## [3.0.0] - 2021-12-23 + * Removed: support for legacy PHP versions (< PHP 7.2.5). + * Added: type declarations to arguments and return values. + * Added: strict typing to all classes. + +## [2.0.3] - 2021-12-08 + * Added: support, type annotations and refactoring for stricter PHPStan analysis. + +## [2.0.2] - 2021-07-31 + * Added: support for `xdebug_info('mode')` in Xdebug 3.1. + * Added: support for Psr\Log versions 2 and 3. + * Fixed: remove ini directives from non-cli HOST/PATH sections. + +## [2.0.1] - 2021-05-05 + * Fixed: don't restart if the cwd is a UNC path and cmd.exe will be invoked. + +## [2.0.0] - 2021-04-09 + * Break: this is a major release, see [UPGRADE.md](UPGRADE.md) for more information. + * Break: removed optional `$colorOption` constructor param and passthru fallback. + * Break: renamed `requiresRestart` param from `$isLoaded` to `$default`. + * Break: changed `restart` param `$command` from a string to an array. + * Added: support for Xdebug3 to only restart if Xdebug is not running with `xdebug.mode=off`. + * Added: `isXdebugActive()` method to determine if Xdebug is still running in the restart. + * Added: feature to bypass the shell in PHP-7.4+ by giving `proc_open` an array of arguments. + * Added: Process utility class to the API. + +## [1.4.6] - 2021-03-25 + * Fixed: fail restart if `proc_open` has been disabled in `disable_functions`. + * Fixed: enable Windows CTRL event handling in the restarted process. + +## [1.4.5] - 2020-11-13 + * Fixed: use `proc_open` when available for correct FD forwarding to the restarted process. + +## [1.4.4] - 2020-10-24 + * Fixed: exception if 'pcntl_signal' is disabled. + +## [1.4.3] - 2020-08-19 + * Fixed: restore SIGINT to default handler in restarted process if no other handler exists. + +## [1.4.2] - 2020-06-04 + * Fixed: ignore SIGINTs to let the restarted process handle them. + +## [1.4.1] - 2020-03-01 + * Fixed: restart fails if an ini file is empty. + +## [1.4.0] - 2019-11-06 + * Added: support for `NO_COLOR` environment variable: https://no-color.org + * Added: color support for Hyper terminal: https://github.com/zeit/hyper + * Fixed: correct capitalization of Xdebug (apparently). + * Fixed: improved handling for uopz extension. + +## [1.3.3] - 2019-05-27 + * Fixed: add environment changes to `$_ENV` if it is being used. + +## [1.3.2] - 2019-01-28 + * Fixed: exit call being blocked by uopz extension, resulting in application code running twice. + +## [1.3.1] - 2018-11-29 + * Fixed: fail restart if `passthru` has been disabled in `disable_functions`. + * Fixed: fail restart if an ini file cannot be opened, otherwise settings will be missing. + +## [1.3.0] - 2018-08-31 + * Added: `setPersistent` method to use environment variables for the restart. + * Fixed: improved debugging by writing output to stderr. + * Fixed: no restart when `php_ini_scanned_files` is not functional and is needed. + +## [1.2.1] - 2018-08-23 + * Fixed: fatal error with apc, when using `apc.mmap_file_mask`. + +## [1.2.0] - 2018-08-16 + * Added: debug information using `XDEBUG_HANDLER_DEBUG`. + * Added: fluent interface for setters. + * Added: `PhpConfig` helper class for calling PHP sub-processes. + * Added: `PHPRC` original value to restart stettings, for use in a restarted process. + * Changed: internal procedure to disable ini-scanning, using `-n` command-line option. + * Fixed: replaced `escapeshellarg` usage to avoid locale problems. + * Fixed: improved color-option handling to respect double-dash delimiter. + * Fixed: color-option handling regression from main script changes. + * Fixed: improved handling when checking main script. + * Fixed: handling for standard input, that never actually did anything. + * Fixed: fatal error when ctype extension is not available. + +## [1.1.0] - 2018-04-11 + * Added: `getRestartSettings` method for calling PHP processes in a restarted process. + * Added: API definition and @internal class annotations. + * Added: protected `requiresRestart` method for extending classes. + * Added: `setMainScript` method for applications that change the working directory. + * Changed: private `tmpIni` variable to protected for extending classes. + * Fixed: environment variables not available in $_SERVER when restored in the restart. + * Fixed: relative path problems caused by Phar::interceptFileFuncs. + * Fixed: incorrect handling when script file cannot be found. + +## [1.0.0] - 2018-03-08 + * Added: PSR3 logging for optional status output. + * Added: existing ini settings are merged to catch command-line overrides. + * Added: code, tests and other artefacts to decouple from Composer. + * Break: the following class was renamed: + - `Composer\XdebugHandler` -> `Composer\XdebugHandler\XdebugHandler` + +[Unreleased]: https://github.com/composer/xdebug-handler/compare/3.0.5...HEAD +[3.0.5]: https://github.com/composer/xdebug-handler/compare/3.0.4...3.0.5 +[3.0.4]: https://github.com/composer/xdebug-handler/compare/3.0.3...3.0.4 +[3.0.3]: https://github.com/composer/xdebug-handler/compare/3.0.2...3.0.3 +[3.0.2]: https://github.com/composer/xdebug-handler/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/composer/xdebug-handler/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/composer/xdebug-handler/compare/2.0.3...3.0.0 +[2.0.3]: https://github.com/composer/xdebug-handler/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/composer/xdebug-handler/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/composer/xdebug-handler/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/composer/xdebug-handler/compare/1.4.6...2.0.0 +[1.4.6]: https://github.com/composer/xdebug-handler/compare/1.4.5...1.4.6 +[1.4.5]: https://github.com/composer/xdebug-handler/compare/1.4.4...1.4.5 +[1.4.4]: https://github.com/composer/xdebug-handler/compare/1.4.3...1.4.4 +[1.4.3]: https://github.com/composer/xdebug-handler/compare/1.4.2...1.4.3 +[1.4.2]: https://github.com/composer/xdebug-handler/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/composer/xdebug-handler/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/composer/xdebug-handler/compare/1.3.3...1.4.0 +[1.3.3]: https://github.com/composer/xdebug-handler/compare/1.3.2...1.3.3 +[1.3.2]: https://github.com/composer/xdebug-handler/compare/1.3.1...1.3.2 +[1.3.1]: https://github.com/composer/xdebug-handler/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/composer/xdebug-handler/compare/1.2.1...1.3.0 +[1.2.1]: https://github.com/composer/xdebug-handler/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/composer/xdebug-handler/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/composer/xdebug-handler/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/composer/xdebug-handler/compare/d66f0d15cb57...1.0.0 diff --git a/vendor/robertdevore/wpcom-check/LICENSE b/vendor/composer/xdebug-handler/LICENSE similarity index 96% rename from vendor/robertdevore/wpcom-check/LICENSE rename to vendor/composer/xdebug-handler/LICENSE index 4b4627a..963618a 100644 --- a/vendor/robertdevore/wpcom-check/LICENSE +++ b/vendor/composer/xdebug-handler/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Robert DeVore +Copyright (c) 2017 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/composer/xdebug-handler/README.md b/vendor/composer/xdebug-handler/README.md new file mode 100644 index 0000000..f7f581a --- /dev/null +++ b/vendor/composer/xdebug-handler/README.md @@ -0,0 +1,305 @@ +# composer/xdebug-handler + +[![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler)](https://packagist.org/packages/composer/xdebug-handler) +[![Continuous Integration](https://github.com/composer/xdebug-handler/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/xdebug-handler/actions?query=branch:main) +![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) +![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler?colorB=8892BF) + +Restart a CLI process without loading the Xdebug extension, unless `xdebug.mode=off`. + +Originally written as part of [composer/composer](https://github.com/composer/composer), +now extracted and made available as a stand-alone library. + +### Version 3 + +Removed support for legacy PHP versions and added type declarations. + +Long term support for version 2 (PHP 5.3.2 - 7.2.4) follows [Composer 2.2 LTS](https://blog.packagist.com/composer-2-2/) policy. + +## Installation + +Install the latest version with: + +```bash +$ composer require composer/xdebug-handler +``` + +## Requirements + +* PHP 7.2.5 minimum, although using the latest PHP version is highly recommended. + +## Basic Usage +```php +use Composer\XdebugHandler\XdebugHandler; + +$xdebug = new XdebugHandler('myapp'); +$xdebug->check(); +unset($xdebug); +``` + +The constructor takes a single parameter, `$envPrefix`, which is upper-cased and prepended to default base values to create two distinct environment variables. The above example enables the use of: + +- `MYAPP_ALLOW_XDEBUG=1` to override automatic restart and allow Xdebug +- `MYAPP_ORIGINAL_INIS` to obtain ini file locations in a restarted process + +## Advanced Usage + +* [How it works](#how-it-works) +* [Limitations](#limitations) +* [Helper methods](#helper-methods) +* [Setter methods](#setter-methods) +* [Process configuration](#process-configuration) +* [Troubleshooting](#troubleshooting) +* [Extending the library](#extending-the-library) +* [Examples](#examples) + +### How it works + +A temporary ini file is created from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out. Current ini settings are merged, so that most ini settings made on the command-line or by the application are included (see [Limitations](#limitations)) + +* `MYAPP_ALLOW_XDEBUG` is set with internal data to flag and use in the restart. +* The command-line and environment are [configured](#process-configuration) for the restart. +* The application is restarted in a new process. + * The restart settings are stored in the environment. + * `MYAPP_ALLOW_XDEBUG` is unset. + * The application runs and exits. +* The main process exits with the exit code from the restarted process. + +See [Examples](#examples) for further information. + +#### Signal handling +Asynchronous signal handling is automatically enabled if the pcntl extension is loaded. `SIGINT` is set to `SIG_IGN` in the parent +process and restored to `SIG_DFL` in the restarted process (if no other handler has been set). + +From PHP 7.4 on Windows, `CTRL+C` and `CTRL+BREAK` handling is automatically enabled in the restarted process and ignored in the parent process. + +### Limitations +There are a few things to be aware of when running inside a restarted process. + +* Extensions set on the command-line will not be loaded. +* Ini file locations will be reported as per the restart - see [getAllIniFiles()](#getallinifiles-array). +* Php sub-processes may be loaded with Xdebug enabled - see [Process configuration](#process-configuration). + +### Helper methods +These static methods provide information from the current process, regardless of whether it has been restarted or not. + +#### _getAllIniFiles(): array_ +Returns an array of the original ini file locations. Use this instead of calling `php_ini_loaded_file` and `php_ini_scanned_files`, which will report the wrong values in a restarted process. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$files = XdebugHandler::getAllIniFiles(); + +# $files[0] always exists, it could be an empty string +$loadedIni = array_shift($files); +$scannedInis = $files; +``` + +These locations are also available in the `MYAPP_ORIGINAL_INIS` environment variable. This is a path-separated string comprising the location returned from `php_ini_loaded_file`, which could be empty, followed by locations parsed from calling `php_ini_scanned_files`. + +#### _getRestartSettings(): ?array_ +Returns an array of settings that can be used with PHP [sub-processes](#sub-processes), or null if the process was not restarted. + +```php +use Composer\XdebugHandler\XdebugHandler; + +$settings = XdebugHandler::getRestartSettings(); +/** + * $settings: array (if the current process was restarted, + * or called with the settings from a previous restart), or null + * + * 'tmpIni' => the temporary ini file used in the restart (string) + * 'scannedInis' => if there were any scanned inis (bool) + * 'scanDir' => the original PHP_INI_SCAN_DIR value (false|string) + * 'phprc' => the original PHPRC value (false|string) + * 'inis' => the original inis from getAllIniFiles (array) + * 'skipped' => the skipped version from getSkippedVersion (string) + */ +``` + +#### _getSkippedVersion(): string_ +Returns the Xdebug version string that was skipped by the restart, or an empty string if there was no restart (or Xdebug is still loaded, perhaps by an extending class restarting for a reason other than removing Xdebug). + +```php +use Composer\XdebugHandler\XdebugHandler; + +$version = XdebugHandler::getSkippedVersion(); +# $version: '3.1.1' (for example), or an empty string +``` + +#### _isXdebugActive(): bool_ +Returns true if Xdebug is loaded and is running in an active mode (if it supports modes). Returns false if Xdebug is not loaded, or it is running with `xdebug.mode=off`. + +### Setter methods +These methods implement a fluent interface and must be called before the main `check()` method. + +#### _setLogger(LoggerInterface $logger): self_ +Enables the output of status messages to an external PSR3 logger. All messages are reported with either `DEBUG` or `WARNING` log levels. For example (showing the level and message): + +``` +// No restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=off +DEBUG No restart (APP_ALLOW_XDEBUG=0) Allowed by xdebug.mode + +// Restart overridden +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.1) xdebug.mode=coverage,debug,develop +DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) + +// Failed restart +DEBUG Checking MYAPP_ALLOW_XDEBUG +DEBUG The Xdebug extension is loaded (3.1.0) +WARNING No restart (Unable to create temp ini file at: ...) +``` + +Status messages can also be output with `XDEBUG_HANDLER_DEBUG`. See [Troubleshooting](#troubleshooting). + +#### _setMainScript(string $script): self_ +Sets the location of the main script to run in the restart. This is only needed in more esoteric use-cases, or if the `argv[0]` location is inaccessible. The script name `--` is supported for standard input. + +#### _setPersistent(): self_ +Configures the restart using [persistent settings](#persistent-settings), so that Xdebug is not loaded in any sub-process. + +Use this method if your application invokes one or more PHP sub-process and the Xdebug extension is not needed. This avoids the overhead of implementing specific [sub-process](#sub-processes) strategies. + +Alternatively, this method can be used to set up a default _Xdebug-free_ environment which can be changed if a sub-process requires Xdebug, then restored afterwards: + +```php +function SubProcessWithXdebug() +{ + $phpConfig = new Composer\XdebugHandler\PhpConfig(); + + # Set the environment to the original configuration + $phpConfig->useOriginal(); + + # run the process with Xdebug loaded + ... + + # Restore Xdebug-free environment + $phpConfig->usePersistent(); +} +``` + +### Process configuration +The library offers two strategies to invoke a new PHP process without loading Xdebug, using either _standard_ or _persistent_ settings. Note that this is only important if the application calls a PHP sub-process. + +#### Standard settings +Uses command-line options to remove Xdebug from the new process only. + +* The -n option is added to the command-line. This tells PHP not to scan for additional inis. +* The temporary ini is added to the command-line with the -c option. + +>_If the new process calls a PHP sub-process, Xdebug will be loaded in that sub-process (unless it implements xdebug-handler, in which case there will be another restart)._ + +This is the default strategy used in the restart. + +#### Persistent settings +Uses environment variables to remove Xdebug from the new process and persist these settings to any sub-process. + +* `PHP_INI_SCAN_DIR` is set to an empty string. This tells PHP not to scan for additional inis. +* `PHPRC` is set to the temporary ini. + +>_If the new process calls a PHP sub-process, Xdebug will not be loaded in that sub-process._ + +This strategy can be used in the restart by calling [setPersistent()](#setpersistent-self). + +#### Sub-processes +The `PhpConfig` helper class makes it easy to invoke a PHP sub-process (with or without Xdebug loaded), regardless of whether there has been a restart. + +Each of its methods returns an array of PHP options (to add to the command-line) and sets up the environment for the required strategy. The [getRestartSettings()](#getrestartsettings-array) method is used internally. + +* `useOriginal()` - Xdebug will be loaded in the new process. +* `useStandard()` - Xdebug will **not** be loaded in the new process - see [standard settings](#standard-settings). +* `userPersistent()` - Xdebug will **not** be loaded in the new process - see [persistent settings](#persistent-settings) + +If there was no restart, an empty options array is returned and the environment is not changed. + +```php +use Composer\XdebugHandler\PhpConfig; + +$config = new PhpConfig; + +$options = $config->useOriginal(); +# $options: empty array +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->useStandard(); +# $options: [-n, -c, tmpIni] +# environment: PHPRC and PHP_INI_SCAN_DIR set to original values + +$options = $config->usePersistent(); +# $options: empty array +# environment: PHPRC=tmpIni, PHP_INI_SCAN_DIR='' +``` + +### Troubleshooting +The following environment settings can be used to troubleshoot unexpected behavior: + +* `XDEBUG_HANDLER_DEBUG=1` Outputs status messages to `STDERR`, if it is defined, irrespective of any PSR3 logger. Each message is prefixed `xdebug-handler[pid]`, where pid is the process identifier. + +* `XDEBUG_HANDLER_DEBUG=2` As above, but additionally saves the temporary ini file and reports its location in a status message. + +### Extending the library +The API is defined by classes and their accessible elements that are not annotated as @internal. The main class has two protected methods that can be overridden to provide additional functionality: + +#### _requiresRestart(bool $default): bool_ +By default the process will restart if Xdebug is loaded and not running with `xdebug.mode=off`. Extending this method allows an application to decide, by returning a boolean (or equivalent) value. +It is only called if `MYAPP_ALLOW_XDEBUG` is empty, so it will not be called in the restarted process (where this variable contains internal data), or if the restart has been overridden. + +Note that the [setMainScript()](#setmainscriptstring-script-self) and [setPersistent()](#setpersistent-self) setters can be used here, if required. + +#### _restart(array $command): void_ +An application can extend this to modify the temporary ini file, its location given in the `tmpIni` property. New settings can be safely appended to the end of the data, which is `PHP_EOL` terminated. + +The `$command` parameter is an array of unescaped command-line arguments that will be used for the new process. + +Remember to finish with `parent::restart($command)`. + +#### Example +This example demonstrates two ways to extend basic functionality: + +* To avoid the overhead of spinning up a new process, the restart is skipped if a simple help command is requested. + +* The application needs write-access to phar files, so it will force a restart if `phar.readonly` is set (regardless of whether Xdebug is loaded) and change this value in the temporary ini file. + +```php +use Composer\XdebugHandler\XdebugHandler; +use MyApp\Command; + +class MyRestarter extends XdebugHandler +{ + private $required; + + protected function requiresRestart(bool $default): bool + { + if (Command::isHelp()) { + # No need to disable Xdebug for this + return false; + } + + $this->required = (bool) ini_get('phar.readonly'); + return $this->required || $default; + } + + protected function restart(array $command): void + { + if ($this->required) { + # Add required ini setting to tmpIni + $content = file_get_contents($this->tmpIni); + $content .= 'phar.readonly=0'.PHP_EOL; + file_put_contents($this->tmpIni, $content); + } + + parent::restart($command); + } +} +``` + +### Examples +The `tests\App` directory contains command-line scripts that demonstrate the internal workings in a variety of scenarios. +See [Functional Test Scripts](./tests/App/README.md). + +## License +composer/xdebug-handler is licensed under the MIT License, see the LICENSE file for details. diff --git a/vendor/composer/xdebug-handler/composer.json b/vendor/composer/xdebug-handler/composer.json new file mode 100644 index 0000000..d205dc1 --- /dev/null +++ b/vendor/composer/xdebug-handler/composer.json @@ -0,0 +1,44 @@ +{ + "name": "composer/xdebug-handler", + "description": "Restarts a process without Xdebug.", + "type": "library", + "license": "MIT", + "keywords": [ + "xdebug", + "performance" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3", + "composer/pcre": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\XdebugHandler\\Tests\\": "tests" + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + } +} diff --git a/vendor/composer/xdebug-handler/src/PhpConfig.php b/vendor/composer/xdebug-handler/src/PhpConfig.php new file mode 100644 index 0000000..7edac88 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/PhpConfig.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Composer\XdebugHandler; + +/** + * @author John Stevenson + * + * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string} + */ +class PhpConfig +{ + /** + * Use the original PHP configuration + * + * @return string[] Empty array of PHP cli options + */ + public function useOriginal(): array + { + $this->getDataAndReset(); + return []; + } + + /** + * Use standard restart settings + * + * @return string[] PHP cli options + */ + public function useStandard(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + return ['-n', '-c', $data['tmpIni']]; + } + + return []; + } + + /** + * Use environment variables to persist settings + * + * @return string[] Empty array of PHP cli options + */ + public function usePersistent(): array + { + $data = $this->getDataAndReset(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['tmpIni']); + $this->updateEnv('PHP_INI_SCAN_DIR', ''); + } + + return []; + } + + /** + * Returns restart data if available and resets the environment + * + * @phpstan-return restartData|null + */ + private function getDataAndReset(): ?array + { + $data = XdebugHandler::getRestartSettings(); + if ($data !== null) { + $this->updateEnv('PHPRC', $data['phprc']); + $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']); + } + + return $data; + } + + /** + * Updates a restart settings value in the environment + * + * @param string $name + * @param string|false $value + */ + private function updateEnv(string $name, $value): void + { + Process::setEnv($name, false !== $value ? $value : null); + } +} diff --git a/vendor/composer/xdebug-handler/src/Process.php b/vendor/composer/xdebug-handler/src/Process.php new file mode 100644 index 0000000..4e9f076 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Process.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; + +/** + * Process utility functions + * + * @author John Stevenson + */ +class Process +{ + /** + * Escapes a string to be used as a shell argument. + * + * From https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string $arg The argument to be escaped + * @param bool $meta Additionally escape cmd.exe meta characters + * @param bool $module The argument is the module to invoke + */ + public static function escape(string $arg, bool $meta = true, bool $module = false): string + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return "'".str_replace("'", "'\\''", $arg)."'"; + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + + $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); + $dquotes = (bool) $dquotes; + + if ($meta) { + $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg); + + if (!$meta) { + $quote = $quote || strpbrk($arg, '^&|<>()') !== false; + } elseif ($module && !$dquotes && $quote) { + $meta = false; + } + } + + if ($quote) { + $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"'; + } + + if ($meta) { + $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg); + } + + return $arg; + } + + /** + * Escapes an array of arguments that make up a shell command + * + * @param string[] $args Argument list, with the module name first + */ + public static function escapeShellCommand(array $args): string + { + $command = ''; + $module = array_shift($args); + + if ($module !== null) { + $command = self::escape($module, true, true); + + foreach ($args as $arg) { + $command .= ' '.self::escape($arg); + } + } + + return $command; + } + + /** + * Makes putenv environment changes available in $_SERVER and $_ENV + * + * @param string $name + * @param ?string $value A null value unsets the variable + */ + public static function setEnv(string $name, ?string $value = null): bool + { + $unset = null === $value; + + if (!putenv($unset ? $name : $name.'='.$value)) { + return false; + } + + if ($unset) { + unset($_SERVER[$name]); + } else { + $_SERVER[$name] = $value; + } + + // Update $_ENV if it is being used + if (false !== stripos((string) ini_get('variables_order'), 'E')) { + if ($unset) { + unset($_ENV[$name]); + } else { + $_ENV[$name] = $value; + } + } + + return true; + } +} diff --git a/vendor/composer/xdebug-handler/src/Status.php b/vendor/composer/xdebug-handler/src/Status.php new file mode 100644 index 0000000..96c5944 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/Status.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * @author John Stevenson + * @internal + */ +class Status +{ + const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; + const CHECK = 'Check'; + const ERROR = 'Error'; + const INFO = 'Info'; + const NORESTART = 'NoRestart'; + const RESTART = 'Restart'; + const RESTARTING = 'Restarting'; + const RESTARTED = 'Restarted'; + + /** @var bool */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string|null */ + private $loaded; + + /** @var LoggerInterface|null */ + private $logger; + + /** @var bool */ + private $modeOff; + + /** @var float */ + private $time; + + /** + * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name + * @param bool $debug Whether debug output is required + */ + public function __construct(string $envAllowXdebug, bool $debug) + { + $start = getenv(self::ENV_RESTART); + Process::setEnv(self::ENV_RESTART); + $this->time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0; + + $this->envAllowXdebug = $envAllowXdebug; + $this->debug = $debug && defined('STDERR'); + $this->modeOff = false; + } + + /** + * Activates status message output to a PSR3 logger + * + * @return void + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Calls a handler method to report a message + * + * @throws \InvalidArgumentException If $op is not known + */ + public function report(string $op, ?string $data): void + { + if ($this->logger !== null || $this->debug) { + $param = (string) $data; + + switch($op) { + case self::CHECK: + $this->reportCheck($param); + break; + case self::ERROR: + $this->reportError($param); + break; + case self::INFO: + $this->reportInfo($param); + break; + case self::NORESTART: + $this->reportNoRestart(); + break; + case self::RESTART: + $this->reportRestart(); + break; + case self::RESTARTED: + $this->reportRestarted(); + break; + case self::RESTARTING: + $this->reportRestarting($param); + break; + default: + throw new \InvalidArgumentException('Unknown op handler: '.$op); + } + } + } + + /** + * Outputs a status message + */ + private function output(string $text, ?string $level = null): void + { + if ($this->logger !== null) { + $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); + } + + if ($this->debug) { + fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); + } + } + + /** + * Checking status message + */ + private function reportCheck(string $loaded): void + { + list($version, $mode) = explode('|', $loaded); + + if ($version !== '') { + $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : ''); + } + $this->modeOff = $mode === 'off'; + $this->output('Checking '.$this->envAllowXdebug); + } + + /** + * Error status message + */ + private function reportError(string $error): void + { + $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + } + + /** + * Info status message + */ + private function reportInfo(string $info): void + { + $this->output($info); + } + + /** + * No restart status message + */ + private function reportNoRestart(): void + { + $this->output($this->getLoadedMessage()); + + if ($this->loaded !== null) { + $text = sprintf('No restart (%s)', $this->getEnvAllow()); + if (!((bool) getenv($this->envAllowXdebug))) { + $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application'); + } + $this->output($text); + } + } + + /** + * Restart status message + */ + private function reportRestart(): void + { + $this->output($this->getLoadedMessage()); + Process::setEnv(self::ENV_RESTART, (string) microtime(true)); + } + + /** + * Restarted status message + */ + private function reportRestarted(): void + { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); + $level = $this->loaded !== null ? LogLevel::WARNING : null; + $this->output($text, $level); + } + + /** + * Restarting status message + */ + private function reportRestarting(string $command): void + { + $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); + $this->output($text); + $text = 'Running: '.$command; + $this->output($text); + } + + /** + * Returns the _ALLOW_XDEBUG environment variable as name=value + */ + private function getEnvAllow(): string + { + return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); + } + + /** + * Returns the Xdebug status and version + */ + private function getLoadedMessage(): string + { + $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded'; + return 'The Xdebug extension is '.$loaded; + } +} diff --git a/vendor/composer/xdebug-handler/src/XdebugHandler.php b/vendor/composer/xdebug-handler/src/XdebugHandler.php new file mode 100644 index 0000000..a665939 --- /dev/null +++ b/vendor/composer/xdebug-handler/src/XdebugHandler.php @@ -0,0 +1,722 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Composer\XdebugHandler; + +use Composer\Pcre\Preg; +use Psr\Log\LoggerInterface; + +/** + * @author John Stevenson + * + * @phpstan-import-type restartData from PhpConfig + */ +class XdebugHandler +{ + const SUFFIX_ALLOW = '_ALLOW_XDEBUG'; + const SUFFIX_INIS = '_ORIGINAL_INIS'; + const RESTART_ID = 'internal'; + const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS'; + const DEBUG = 'XDEBUG_HANDLER_DEBUG'; + + /** @var string|null */ + protected $tmpIni; + + /** @var bool */ + private static $inRestart; + + /** @var string */ + private static $name; + + /** @var string|null */ + private static $skipped; + + /** @var bool */ + private static $xdebugActive; + + /** @var string|null */ + private static $xdebugMode; + + /** @var string|null */ + private static $xdebugVersion; + + /** @var bool */ + private $cli; + + /** @var string|null */ + private $debug; + + /** @var string */ + private $envAllowXdebug; + + /** @var string */ + private $envOriginalInis; + + /** @var bool */ + private $persistent; + + /** @var string|null */ + private $script; + + /** @var Status */ + private $statusWriter; + + /** + * Constructor + * + * The $envPrefix is used to create distinct environment variables. It is + * uppercased and prepended to the default base values. For example 'myapp' + * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS. + * + * @param string $envPrefix Value used in environment variables + * @throws \RuntimeException If the parameter is invalid + */ + public function __construct(string $envPrefix) + { + if ($envPrefix === '') { + throw new \RuntimeException('Invalid constructor parameter'); + } + + self::$name = strtoupper($envPrefix); + $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW; + $this->envOriginalInis = self::$name.self::SUFFIX_INIS; + + self::setXdebugDetails(); + self::$inRestart = false; + + if ($this->cli = PHP_SAPI === 'cli') { + $this->debug = (string) getenv(self::DEBUG); + } + + $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); + } + + /** + * Activates status message output to a PSR3 logger + */ + public function setLogger(LoggerInterface $logger): self + { + $this->statusWriter->setLogger($logger); + return $this; + } + + /** + * Sets the main script location if it cannot be called from argv + */ + public function setMainScript(string $script): self + { + $this->script = $script; + return $this; + } + + /** + * Persist the settings to keep Xdebug out of sub-processes + */ + public function setPersistent(): self + { + $this->persistent = true; + return $this; + } + + /** + * Checks if Xdebug is loaded and the process needs to be restarted + * + * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG + * environment variable to 1. This variable is used internally so that + * the restarted process is created only once. + */ + public function check(): void + { + $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode); + $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); + + if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) { + // Restart required + $this->notify(Status::RESTART); + $command = $this->prepareRestart(); + + if ($command !== null) { + $this->restart($command); + } + return; + } + + if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { + // Restarted, so unset environment variable and use saved values + $this->notify(Status::RESTARTED); + + Process::setEnv($this->envAllowXdebug); + self::$inRestart = true; + + if (self::$xdebugVersion === null) { + // Skipped version is only set if Xdebug is not loaded + self::$skipped = $envArgs[1]; + } + + $this->tryEnableSignals(); + + // Put restart settings in the environment + $this->setEnvRestartSettings($envArgs); + return; + } + + $this->notify(Status::NORESTART); + $settings = self::getRestartSettings(); + + if ($settings !== null) { + // Called with existing settings, so sync our settings + $this->syncSettings($settings); + } + } + + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be an empty string. + * + * @return non-empty-list + */ + public static function getAllIniFiles(): array + { + if (self::$name !== null) { + $env = getenv(self::$name.self::SUFFIX_INIS); + + if (false !== $env) { + return explode(PATH_SEPARATOR, $env); + } + } + + $paths = [(string) php_ini_loaded_file()]; + $scanned = php_ini_scanned_files(); + + if ($scanned !== false) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } + + /** + * Returns an array of restart settings or null + * + * Settings will be available if the current process was restarted, or + * called with the settings from an existing restart. + * + * @phpstan-return restartData|null + */ + public static function getRestartSettings(): ?array + { + $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); + + if (count($envArgs) !== 6 + || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { + return null; + } + + return [ + 'tmpIni' => $envArgs[0], + 'scannedInis' => (bool) $envArgs[1], + 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], + 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], + 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), + 'skipped' => $envArgs[5], + ]; + } + + /** + * Returns the Xdebug version that triggered a successful restart + */ + public static function getSkippedVersion(): string + { + return (string) self::$skipped; + } + + /** + * Returns whether Xdebug is loaded and active + * + * true: if Xdebug is loaded and is running in an active mode. + * false: if Xdebug is not loaded, or it is running with xdebug.mode=off. + */ + public static function isXdebugActive(): bool + { + self::setXdebugDetails(); + return self::$xdebugActive; + } + + /** + * Allows an extending class to decide if there should be a restart + * + * The default is to restart if Xdebug is loaded and its mode is not "off". + */ + protected function requiresRestart(bool $default): bool + { + return $default; + } + + /** + * Allows an extending class to access the tmpIni + * + * @param non-empty-list $command + */ + protected function restart(array $command): void + { + $this->doRestart($command); + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param non-empty-list $command + * @phpstan-return never + */ + private function doRestart(array $command): void + { + if (PHP_VERSION_ID >= 70400) { + $cmd = $command; + $displayCmd = sprintf('[%s]', implode(', ', $cmd)); + } else { + $cmd = Process::escapeShellCommand($command); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // Outer quotes required on cmd string below PHP 8 + $cmd = '"'.$cmd.'"'; + } + $displayCmd = $cmd; + } + + $this->tryEnableSignals(); + $this->notify(Status::RESTARTING, $displayCmd); + + $process = proc_open($cmd, [], $pipes); + if (is_resource($process)) { + $exitCode = proc_close($process); + } + + if (!isset($exitCode)) { + // Unlikely that php or the default shell cannot be invoked + $this->notify(Status::ERROR, 'Unable to restart process'); + $exitCode = -1; + } else { + $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); + } + + if ($this->debug === '2') { + $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); + } else { + @unlink((string) $this->tmpIni); + } + + exit($exitCode); + } + + /** + * Returns the command line array if everything was written for the restart + * + * If any of the following fails (however unlikely) we must return false to + * stop potential recursion: + * - tmp ini file creation + * - environment variable creation + * + * @return non-empty-list|null + */ + private function prepareRestart(): ?array + { + if (!$this->cli) { + $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI); + return null; + } + + if (($argv = $this->checkServerArgv()) === null) { + $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected'); + return null; + } + + if (!$this->checkConfiguration($info)) { + $this->notify(Status::ERROR, $info); + return null; + } + + $mainScript = (string) $this->script; + if (!$this->checkMainScript($mainScript, $argv)) { + $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript); + return null; + } + + $tmpDir = sys_get_temp_dir(); + $iniError = 'Unable to create temp ini file at: '.$tmpDir; + + if (($tmpfile = @tempnam($tmpDir, '')) === false) { + $this->notify(Status::ERROR, $iniError); + return null; + } + + $error = null; + $iniFiles = self::getAllIniFiles(); + $scannedInis = count($iniFiles) > 1; + + if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) { + $this->notify(Status::ERROR, $error ?? $iniError); + @unlink($tmpfile); + return null; + } + + if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) { + $this->notify(Status::ERROR, 'Unable to set environment variables'); + @unlink($tmpfile); + return null; + } + + $this->tmpIni = $tmpfile; + + return $this->getCommand($argv, $tmpfile, $mainScript); + } + + /** + * Returns true if the tmp ini file was written + * + * @param non-empty-list $iniFiles All ini files used in the current process + */ + private function writeTmpIni(string $tmpFile, array $iniFiles, ?string &$error): bool + { + // $iniFiles has at least one item and it may be empty + if ($iniFiles[0] === '') { + array_shift($iniFiles); + } + + $content = ''; + $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi'; + $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + + foreach ($iniFiles as $file) { + // Check for inaccessible ini files + if (($data = @file_get_contents($file)) === false) { + $error = 'Unable to read ini: '.$file; + return false; + } + // Check and remove directives after HOST and PATH sections + if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches)) { + $data = substr($data, 0, $matches[0][1]); + } + $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL; + } + + // Merge loaded settings into our ini content, if it is valid + $config = parse_ini_string($content); + $loaded = ini_get_all(null, false); + + if (false === $config || false === $loaded) { + $error = 'Unable to parse ini data'; + return false; + } + + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= 'opcache.enable_cli=0'.PHP_EOL; + + return (bool) @file_put_contents($tmpFile, $content); + } + + /** + * Returns the command line arguments for the restart + * + * @param non-empty-list $argv + * @return non-empty-list + */ + private function getCommand(array $argv, string $tmpIni, string $mainScript): array + { + $php = [PHP_BINARY]; + $args = array_slice($argv, 1); + + if (!$this->persistent) { + // Use command-line options + array_push($php, '-n', '-c', $tmpIni); + } + + return array_merge($php, [$mainScript], $args); + } + + /** + * Returns true if the restart environment variables were set + * + * No need to update $_SERVER since this is set in the restarted process. + * + * @param non-empty-list $iniFiles All ini files used in the current process + */ + private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpIni): bool + { + $scanDir = getenv('PHP_INI_SCAN_DIR'); + $phprc = getenv('PHPRC'); + + // Make original inis available to restarted process + if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { + return false; + } + + if ($this->persistent) { + // Use the environment to persist the settings + if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$tmpIni)) { + return false; + } + } + + // Flag restarted process and save values for it to use + $envArgs = [ + self::RESTART_ID, + self::$xdebugVersion, + (int) $scannedInis, + false === $scanDir ? '*' : $scanDir, + false === $phprc ? '*' : $phprc, + ]; + + return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); + } + + /** + * Logs status messages + */ + private function notify(string $op, ?string $data = null): void + { + $this->statusWriter->report($op, $data); + } + + /** + * Returns default, changed and command-line ini settings + * + * @param mixed[] $loadedConfig All current ini settings + * @param mixed[] $iniConfig Settings from user ini files + * + */ + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + // Value will either be null, string or array (HHVM only) + if (!is_string($value) + || strpos($name, 'xdebug') === 0 + || $name === 'apc.mmap_file_mask') { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Double-quote escape each value + $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; + } + } + + return $content; + } + + /** + * Returns true if the script name can be used + * + * @param non-empty-list $argv + */ + private function checkMainScript(string &$mainScript, array $argv): bool + { + if ($mainScript !== '') { + // Allow an application to set -- for standard input + return file_exists($mainScript) || '--' === $mainScript; + } + + if (file_exists($mainScript = $argv[0])) { + return true; + } + + // Use a backtrace to resolve Phar and chdir issues. + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $main = end($trace); + + if ($main !== false && isset($main['file'])) { + return file_exists($mainScript = $main['file']); + } + + return false; + } + + /** + * Adds restart settings to the environment + * + * @param non-empty-list $envArgs + */ + private function setEnvRestartSettings(array $envArgs): void + { + $settings = [ + php_ini_loaded_file(), + $envArgs[2], + $envArgs[3], + $envArgs[4], + getenv($this->envOriginalInis), + self::$skipped, + ]; + + Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); + } + + /** + * Syncs settings and the environment if called with existing settings + * + * @phpstan-param restartData $settings + */ + private function syncSettings(array $settings): void + { + if (false === getenv($this->envOriginalInis)) { + // Called by another app, so make original inis available + Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); + } + + self::$skipped = $settings['skipped']; + $this->notify(Status::INFO, 'Process called with existing restart settings'); + } + + /** + * Returns true if there are no known configuration issues + */ + private function checkConfiguration(?string &$info): bool + { + if (!function_exists('proc_open')) { + $info = 'proc_open function is disabled'; + return false; + } + + if (!file_exists(PHP_BINARY)) { + $info = 'PHP_BINARY is not available'; + return false; + } + + if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) { + // uopz works at opcode level and disables exit calls + if (function_exists('uopz_allow_exit')) { + @uopz_allow_exit(true); + } else { + $info = 'uopz extension is not compatible'; + return false; + } + } + + // Check UNC paths when using cmd.exe + if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) { + $workingDir = getcwd(); + + if ($workingDir === false) { + $info = 'unable to determine working directory'; + return false; + } + + if (0 === strpos($workingDir, '\\\\')) { + $info = 'cmd.exe does not support UNC paths: '.$workingDir; + return false; + } + } + + return true; + } + + /** + * Enables async signals and control interrupts in the restarted process + * + * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+. + */ + private function tryEnableSignals(): void + { + if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { + pcntl_async_signals(true); + $message = 'Async signals enabled'; + + if (!self::$inRestart) { + // Restarting, so ignore SIGINT in parent + pcntl_signal(SIGINT, SIG_IGN); + } elseif (is_int(pcntl_signal_get_handler(SIGINT))) { + // Restarted, no handler set so force default action + pcntl_signal(SIGINT, SIG_DFL); + } + } + + if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) { + // Restarting, so set a handler to ignore CTRL events in the parent. + // This ensures that CTRL+C events will be available in the child + // process without having to enable them there, which is unreliable. + sapi_windows_set_ctrl_handler(function ($evt) {}); + } + } + + /** + * Returns $_SERVER['argv'] if it is as expected + * + * @return non-empty-list|null + */ + private function checkServerArgv(): ?array + { + $result = []; + + if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { + foreach ($_SERVER['argv'] as $value) { + if (!is_string($value)) { + return null; + } + + $result[] = $value; + } + } + + return count($result) > 0 ? $result : null; + } + + /** + * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode + */ + private static function setXdebugDetails(): void + { + if (self::$xdebugActive !== null) { + return; + } + + self::$xdebugActive = false; + if (!extension_loaded('xdebug')) { + return; + } + + $version = phpversion('xdebug'); + self::$xdebugVersion = $version !== false ? $version : 'unknown'; + + if (version_compare(self::$xdebugVersion, '3.1', '>=')) { + $modes = xdebug_info('mode'); + self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes); + self::$xdebugActive = self::$xdebugMode !== 'off'; + return; + } + + // See if xdebug.mode is supported in this version + $iniMode = ini_get('xdebug.mode'); + if ($iniMode === false) { + self::$xdebugActive = true; + return; + } + + // Environment value wins but cannot be empty + $envMode = (string) getenv('XDEBUG_MODE'); + if ($envMode !== '') { + self::$xdebugMode = $envMode; + } else { + self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off'; + } + + // An empty comma-separated list is treated as mode 'off' + if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) { + self::$xdebugMode = 'off'; + } + + self::$xdebugActive = self::$xdebugMode !== 'off'; + } +} diff --git a/vendor/dealerdirect/phpcodesniffer-composer-installer/LICENSE.md b/vendor/dealerdirect/phpcodesniffer-composer-installer/LICENSE.md new file mode 100644 index 0000000..9bc8806 --- /dev/null +++ b/vendor/dealerdirect/phpcodesniffer-composer-installer/LICENSE.md @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2016-2022 Dealerdirect B.V. and contributors +Copyright (c) 2022 PHPCSStandards and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/dealerdirect/phpcodesniffer-composer-installer/README.md b/vendor/dealerdirect/phpcodesniffer-composer-installer/README.md new file mode 100644 index 0000000..e8c263a --- /dev/null +++ b/vendor/dealerdirect/phpcodesniffer-composer-installer/README.md @@ -0,0 +1,285 @@ +# PHP_CodeSniffer Standards Composer Installer Plugin + +![Project Stage][project-stage-shield] +![Last Commit][last-updated-shield] +![Awesome][awesome-shield] +[![License][license-shield]](LICENSE.md) + +[![Tests][ghactionstest-shield]][ghactions] +[![Latest Version on Packagist][packagist-version-shield]][packagist-version] +[![Packagist][packagist-shield]][packagist] + +[![Contributor Covenant][code-of-conduct-shield]][code-of-conduct] + +This composer installer plugin allows for easy installation of [PHP_CodeSniffer][codesniffer] coding standards (rulesets). + +No more symbolic linking of directories, checking out repositories on specific locations or changing +the `phpcs` configuration. + +## Usage + +Installation can be done with [Composer][composer], by requiring this package as a development dependency: + +```bash +composer require --dev dealerdirect/phpcodesniffer-composer-installer +``` + +When using Composer 2.2 or higher, Composer will [ask for your permission](https://blog.packagist.com/composer-2-2/#more-secure-plugin-execution) to allow this plugin to execute code. For this plugin to be functional, permission needs to be granted. + +When permission has been granted, the following snippet will automatically be added to your `composer.json` file by Composer: +```json +{ + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} +``` + +When using Composer < 2.2, you can add the permission flag ahead of the upgrade to Composer 2.2, by running: +```bash +composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true +``` + +That's it. + +### Compatibility + +This plugin is compatible with: + +- PHP **5.4+**, **7.x**, and **8.x** (Support for PHP v8 is available since [`v0.7.0`][v0.7]) +- [Composer][composer] **1.x** and **2.x** (Support for Composer v2 is available since [`v0.7.0`][v0.7]) +- [PHP_CodeSniffer][codesniffer] **2.x** and **3.x** (Support for PHP_CodeSniffer v3 is available since [`v0.4.0`][v0.4]) + + +> **ℹ️ Please Note:** [Composer treats _minor_ releases below 1.0.0 as _major_ releases][composer-manual-caret]. So version `0.7.x` (or higher) of this plugin must be _explicitly_ set as version constraint when using Composer 2.x or PHP 8.0. In other words: using `^0.6` will **not** work with Composer 2.x or PHP 8.0. + +### How it works + +Basically, this plugin executes the following steps: + +- This plugin searches for [`phpcodesniffer-standard` packages][] in all of your currently installed Composer packages. +- Matching packages and the project itself are scanned for PHP_CodeSniffer rulesets. +- The plugin will call PHP_CodeSniffer and configure the `installed_paths` option. + +### Example project + +The following is an example Composer project and has included +multiple `phpcodesniffer-standard` packages. + +```json +{ + "name": "example/project", + "description": "Just an example project", + "type": "project", + "require": {}, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "*", + "object-calisthenics/phpcs-calisthenics-rules": "*", + "phpcompatibility/php-compatibility": "*", + "wp-coding-standards/wpcs": "*" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} +``` + +After running `composer install` PHP_CodeSniffer just works: + +```bash +$ ./vendor/bin/phpcs -i +The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend, ObjectCalisthenics, +PHPCompatibility, WordPress, WordPress-Core, WordPress-Docs and WordPress-Extra +``` + +### Calling the plugin directly + +In some circumstances, it is desirable to call this plugin's functionality +directly. For instance, during development or in [CI][definition-ci] environments. + +As the plugin requires Composer to work, direct calls need to be wired through a +project's `composer.json`. + +This is done by adding a call to the `Plugin::run` function in the `script` +section of the `composer.json`: + +```json +{ + "scripts": { + "install-codestandards": [ + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run" + ] + } +} +``` + +The command can then be called using `composer run-script install-codestandards` or +referenced from other script configurations, as follows: + +```json +{ + "scripts": { + "install-codestandards": [ + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run" + ], + "post-install-cmd": [ + "@install-codestandards" + ] + } +} +``` + +For more details about Composer scripts, please refer to [the section on scripts +in the Composer manual][composer-manual-scripts]. + +### Changing the Coding Standards search depth + +By default, this plugin searches up for Coding Standards up to three directories +deep. In most cases, this should be sufficient. However, this plugin allows +you to customize the search depth setting if needed. + +```json +{ + "extra": { + "phpcodesniffer-search-depth": 5 + } +} +``` + +### Caveats + +When this plugin is installed globally, composer will load the _global_ plugin rather +than the one from the local repository. Despite [this behavior being documented +in the composer manual][using-composer-plugins], it could potentially confuse +as another version of the plugin could be run and not the one specified by the project. + +## Developing Coding Standards + +Coding standard can be developed normally, as documented by [PHP_CodeSniffer][codesniffer], in the [Coding Standard Tutorial][tutorial]. + +Create a composer package of your coding standard by adding a `composer.json` file. + +```json +{ + "name" : "acme/phpcodesniffer-our-standards", + "description" : "Package contains all coding standards of the Acme company", + "require" : { + "php" : ">=5.4.0", + "squizlabs/php_codesniffer" : "^3.6" + }, + "type" : "phpcodesniffer-standard" +} +``` + +Requirements: +* The repository may contain one or more standards. +* Each standard can have a separate directory no deeper than 3 levels from the repository root. +* The package `type` must be `phpcodesniffer-standard`. Without this, the plugin will not trigger. + +### Requiring the plugin from within your coding standard + +If your coding standard itself depends on additional external PHPCS standards, this plugin can +make life easier on your end-users by taking care of the installation of all standards - yours +and your dependencies - for them. + +This can help reduce the number of support questions about setting the `installed_paths`, as well +as simplify your standard's installation instructions. + +For this to work, make sure your external standard adds this plugin to the `composer.json` config +via `require`, **not** `require-dev`. + +> :warning: Your end-user may already `require-dev` this plugin and/or other external standards used +> by your end-users may require this plugin as well. +> +> To prevent your end-users getting into "_dependency hell_", make sure to make the version requirement +> for this plugin flexible. +> +> As, for now, this plugin is still regarded as "unstable" (version < 1.0), remember that Composer +> treats unstable minors as majors and will not be able to resolve one config requiring this plugin +> at version `^0.5`, while another requires it at version `^0.6`. +> Either allow multiple minors or use `*` as the version requirement. +> +> Some examples of flexible requirements which can be used: +> ```bash +> composer require dealerdirect/phpcodesniffer-composer-installer:"*" +> composer require dealerdirect/phpcodesniffer-composer-installer:"0.*" +> composer require dealerdirect/phpcodesniffer-composer-installer:"^0.4.1 || ^0.5 || ^0.6 || ^0.7" +> ``` + +## Changelog + +This repository does not contain a `CHANGELOG.md` file, however, we do publish a changelog on each release +using the [GitHub releases][changelog] functionality. + +## Contributing + +This is an active open-source project. We are always open to people who want to +use the code or contribute to it. + +We've set up a separate document for our [contribution guidelines][contributing-guidelines]. + +Thank you for being involved! :heart_eyes: + +## Authors & contributors + +The original idea and setup of this repository is by [Franck Nijhof][frenck], employee @ Dealerdirect. + +For a full list of all author and/or contributors, check [the contributors page][contributors]. + +## License + +The MIT License (MIT) + +Copyright (c) 2016-2022 Dealerdirect B.V. and contributors +Copyright (c) 2022 PHPCSStandards and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +[awesome-shield]: https://img.shields.io/badge/awesome%3F-yes-brightgreen.svg +[changelog]: https://github.com/PHPCSStandards/composer-installer/releases +[code-of-conduct-shield]: https://img.shields.io/badge/Contributor%20Covenant-v2.0-ff69b4.svg +[code-of-conduct]: CODE_OF_CONDUCT.md +[codesniffer]: https://github.com/squizlabs/PHP_CodeSniffer +[composer-manual-scripts]: https://getcomposer.org/doc/articles/scripts.md +[composer-manual-caret]: https://getcomposer.org/doc/articles/versions.md#caret-version-range- +[composer]: https://getcomposer.org/ +[contributing-guidelines]: CONTRIBUTING.md +[contributors]: https://github.com/PHPCSStandards/composer-installer/graphs/contributors +[definition-ci]: https://en.wikipedia.org/wiki/Continuous_integration +[frenck]: https://github.com/frenck +[last-updated-shield]: https://img.shields.io/github/last-commit/PHPCSStandards/composer-installer.svg +[license-shield]: https://img.shields.io/github/license/PHPCSStandards/composer-installer.svg +[packagist-shield]: https://img.shields.io/packagist/dt/dealerdirect/phpcodesniffer-composer-installer.svg +[packagist-version-shield]: https://img.shields.io/packagist/v/dealerdirect/phpcodesniffer-composer-installer.svg +[packagist-version]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer +[packagist]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer +[`phpcodesniffer-standard` packages]: https://packagist.org/explore/?type=phpcodesniffer-standard +[project-stage-shield]: https://img.shields.io/badge/Project%20Stage-Development-yellowgreen.svg +[scrutinizer-shield]: https://img.shields.io/scrutinizer/g/dealerdirect/phpcodesniffer-composer-installer.svg +[scrutinizer]: https://scrutinizer-ci.com/g/dealerdirect/phpcodesniffer-composer-installer/ +[ghactionstest-shield]: https://github.com/PHPCSStandards/composer-installer/actions/workflows/integrationtest.yml/badge.svg +[ghactions]: https://github.com/PHPCSStandards/composer-installer/actions/workflows/integrationtest.yml +[tutorial]: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Coding-Standard-Tutorial +[using-composer-plugins]: https://getcomposer.org/doc/articles/plugins.md#using-plugins +[v0.4]: https://github.com/PHPCSStandards/composer-installer/releases/tag/v0.4.0 +[v0.7]: https://github.com/PHPCSStandards/composer-installer/releases/tag/v0.7.0 diff --git a/vendor/dealerdirect/phpcodesniffer-composer-installer/composer.json b/vendor/dealerdirect/phpcodesniffer-composer-installer/composer.json new file mode 100644 index 0000000..bf3355a --- /dev/null +++ b/vendor/dealerdirect/phpcodesniffer-composer-installer/composer.json @@ -0,0 +1,71 @@ +{ + "name": "dealerdirect/phpcodesniffer-composer-installer", + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "type": "composer-plugin", + "keywords": [ + "composer", "installer", "plugin", + "phpcs", "phpcbf", "codesniffer", "phpcodesniffer", "php_codesniffer", + "standard", "standards", "style guide", "stylecheck", + "qa", "quality", "code quality", "tests" + ], + "homepage": "http://www.dealerdirect.com", + "license": "MIT", + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name" : "Contributors", + "homepage" : "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "require": { + "php": ">=5.4", + "composer-plugin-api": "^1.0 || ^2.0", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "ext-json": "*", + "ext-zip": "*", + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "yoast/phpunit-polyfills": "^1.0" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Tests\\": "tests/" + } + }, + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "scripts": { + "install-codestandards": [ + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run" + ], + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" + ], + "test": [ + "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" + ], + "coverage": [ + "@php ./vendor/phpunit/phpunit/phpunit" + ] + } +} diff --git a/vendor/dealerdirect/phpcodesniffer-composer-installer/src/Plugin.php b/vendor/dealerdirect/phpcodesniffer-composer-installer/src/Plugin.php new file mode 100644 index 0000000..a2863d6 --- /dev/null +++ b/vendor/dealerdirect/phpcodesniffer-composer-installer/src/Plugin.php @@ -0,0 +1,637 @@ + + */ +class Plugin implements PluginInterface, EventSubscriberInterface +{ + const KEY_MAX_DEPTH = 'phpcodesniffer-search-depth'; + + const MESSAGE_ERROR_WRONG_MAX_DEPTH = + 'The value of "%s" (in the composer.json "extra".section) must be an integer larger than %d, %s given.'; + + const MESSAGE_NOT_INSTALLED = 'PHPCodeSniffer is not installed'; + const MESSAGE_NOTHING_TO_INSTALL = 'No PHPCS standards to install or update'; + const MESSAGE_PLUGIN_UNINSTALLED = 'PHPCodeSniffer Composer Installer is uninstalled'; + const MESSAGE_RUNNING_INSTALLER = 'Running PHPCodeSniffer Composer Installer'; + + const PACKAGE_NAME = 'squizlabs/php_codesniffer'; + const PACKAGE_TYPE = 'phpcodesniffer-standard'; + + const PHPCS_CONFIG_REGEX = '`%s:[^\r\n]+`'; + const PHPCS_CONFIG_KEY = 'installed_paths'; + + const PLUGIN_NAME = 'dealerdirect/phpcodesniffer-composer-installer'; + + /** + * @var Composer + */ + private $composer; + + /** + * @var string + */ + private $cwd; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var array + */ + private $installedPaths; + + /** + * @var IOInterface + */ + private $io; + + /** + * @var ProcessExecutor + */ + private $processExecutor; + + /** + * Triggers the plugin's main functionality. + * + * Makes it possible to run the plugin as a custom command. + * + * @param Event $event + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + */ + public static function run(Event $event) + { + $io = $event->getIO(); + $composer = $event->getComposer(); + + $instance = new static(); + + $instance->io = $io; + $instance->composer = $composer; + $instance->init(); + $instance->onDependenciesChangedEvent(); + } + + /** + * {@inheritDoc} + * + * @throws \RuntimeException + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + */ + public function activate(Composer $composer, IOInterface $io) + { + $this->composer = $composer; + $this->io = $io; + + $this->init(); + } + + /** + * {@inheritDoc} + */ + public function deactivate(Composer $composer, IOInterface $io) + { + } + + /** + * {@inheritDoc} + */ + public function uninstall(Composer $composer, IOInterface $io) + { + } + + /** + * Prepares the plugin so it's main functionality can be run. + * + * @throws \RuntimeException + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + */ + private function init() + { + $this->cwd = getcwd(); + $this->installedPaths = array(); + + $this->processExecutor = new ProcessExecutor($this->io); + $this->filesystem = new Filesystem($this->processExecutor); + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array( + ScriptEvents::POST_INSTALL_CMD => array( + array('onDependenciesChangedEvent', 0), + ), + ScriptEvents::POST_UPDATE_CMD => array( + array('onDependenciesChangedEvent', 0), + ), + ); + } + + /** + * Entry point for post install and post update events. + * + * @throws \InvalidArgumentException + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + */ + public function onDependenciesChangedEvent() + { + $io = $this->io; + $isVerbose = $io->isVerbose(); + $exitCode = 0; + + if ($isVerbose) { + $io->write(sprintf('%s', self::MESSAGE_RUNNING_INSTALLER)); + } + + if ($this->isPHPCodeSnifferInstalled() === true) { + $this->loadInstalledPaths(); + $installPathCleaned = $this->cleanInstalledPaths(); + $installPathUpdated = $this->updateInstalledPaths(); + + if ($installPathCleaned === true || $installPathUpdated === true) { + $exitCode = $this->saveInstalledPaths(); + } elseif ($isVerbose) { + $io->write(sprintf('%s', self::MESSAGE_NOTHING_TO_INSTALL)); + } + } else { + $pluginPackage = $this + ->composer + ->getRepositoryManager() + ->getLocalRepository() + ->findPackages(self::PLUGIN_NAME) + ; + + $isPluginUninstalled = count($pluginPackage) === 0; + + if ($isPluginUninstalled) { + if ($isVerbose) { + $io->write(sprintf('%s', self::MESSAGE_PLUGIN_UNINSTALLED)); + } + } else { + $exitCode = 1; + if ($isVerbose) { + $io->write(sprintf('%s', self::MESSAGE_NOT_INSTALLED)); + } + } + } + + return $exitCode; + } + + /** + * Load all paths from PHP_CodeSniffer into an array. + * + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + */ + private function loadInstalledPaths() + { + if ($this->isPHPCodeSnifferInstalled() === true) { + $this->processExecutor->execute( + $this->getPhpcsCommand() . ' --config-show', + $output, + $this->getPHPCodeSnifferInstallPath() + ); + + $regex = sprintf(self::PHPCS_CONFIG_REGEX, self::PHPCS_CONFIG_KEY); + if (preg_match($regex, $output, $match) === 1) { + $phpcsInstalledPaths = str_replace(self::PHPCS_CONFIG_KEY . ': ', '', $match[0]); + $phpcsInstalledPaths = trim($phpcsInstalledPaths); + + if ($phpcsInstalledPaths !== '') { + $this->installedPaths = explode(',', $phpcsInstalledPaths); + } + } + } + } + + /** + * Save all coding standard paths back into PHP_CodeSniffer + * + * @throws LogicException + * @throws ProcessFailedException + * @throws RuntimeException + * + * @return int Exit code. 0 for success, 1 or higher for failure. + */ + private function saveInstalledPaths() + { + // Check if we found installed paths to set. + if (count($this->installedPaths) !== 0) { + sort($this->installedPaths); + $paths = implode(',', $this->installedPaths); + $arguments = array('--config-set', self::PHPCS_CONFIG_KEY, $paths); + $configMessage = sprintf( + 'PHP CodeSniffer Config %s set to %s', + self::PHPCS_CONFIG_KEY, + $paths + ); + } else { + // Delete the installed paths if none were found. + $arguments = array('--config-delete', self::PHPCS_CONFIG_KEY); + $configMessage = sprintf( + 'PHP CodeSniffer Config %s delete', + self::PHPCS_CONFIG_KEY + ); + } + + // Prepare message in case of failure + $failMessage = sprintf( + 'Failed to set PHP CodeSniffer %s Config', + self::PHPCS_CONFIG_KEY + ); + + // Okay, lets rock! + $command = vsprintf( + '%s %s', + array( + 'phpcs command' => $this->getPhpcsCommand(), + 'arguments' => implode(' ', $arguments), + ) + ); + + $exitCode = $this->processExecutor->execute($command, $configResult, $this->getPHPCodeSnifferInstallPath()); + if ($exitCode === 0) { + $exitCode = $this->verifySaveSuccess(); + } + + if ($exitCode === 0) { + $this->io->write($configMessage); + } else { + $this->io->write($failMessage); + } + + if ($this->io->isVerbose() && !empty($configResult)) { + $this->io->write(sprintf('%s', $configResult)); + } + + return $exitCode; + } + + /** + * Verify that the paths which were expected to be saved, have been. + * + * @return int Exit code. 0 for success, 1 for failure. + */ + private function verifySaveSuccess() + { + $exitCode = 1; + $expectedPaths = $this->installedPaths; + + // Request the currently set installed paths after the save. + $this->loadInstalledPaths(); + + $registeredPaths = array_intersect($this->installedPaths, $expectedPaths); + $registeredCount = count($registeredPaths); + $expectedCount = count($expectedPaths); + + if ($expectedCount === $registeredCount) { + $exitCode = 0; + } + + if ($exitCode === 1 && $this->io->isVerbose()) { + $verificationMessage = sprintf( + "Paths to external standards found by the plugin: %s\n" + . 'Actual paths registered with PHPCS: %s', + implode(', ', $expectedPaths), + implode(', ', $this->installedPaths) + ); + $this->io->write($verificationMessage); + } + + return $exitCode; + } + + /** + * Get the command to call PHPCS. + */ + protected function getPhpcsCommand() + { + // Determine the path to the main PHPCS file. + $phpcsPath = $this->getPHPCodeSnifferInstallPath(); + if (file_exists($phpcsPath . '/bin/phpcs') === true) { + // PHPCS 3.x. + $phpcsExecutable = './bin/phpcs'; + } else { + // PHPCS 2.x. + $phpcsExecutable = './scripts/phpcs'; + } + + return vsprintf( + '%s %s', + array( + 'php executable' => $this->getPhpExecCommand(), + 'phpcs executable' => $phpcsExecutable, + ) + ); + } + + /** + * Get the path to the current PHP version being used. + * + * Duplicate of the same in the EventDispatcher class in Composer itself. + */ + protected function getPhpExecCommand() + { + $finder = new PhpExecutableFinder(); + + $phpPath = $finder->find(false); + + if ($phpPath === false) { + throw new \RuntimeException('Failed to locate PHP binary to execute ' . $phpPath); + } + + $phpArgs = $finder->findArguments(); + $phpArgs = $phpArgs + ? ' ' . implode(' ', $phpArgs) + : '' + ; + + $command = ProcessExecutor::escape($phpPath) . + $phpArgs . + ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')) . + ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')) . + ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')) + ; + + return $command; + } + + /** + * Iterate trough all known paths and check if they are still valid. + * + * If path does not exists, is not an directory or isn't readable, the path + * is removed from the list. + * + * @return bool True if changes where made, false otherwise + */ + private function cleanInstalledPaths() + { + $changes = false; + foreach ($this->installedPaths as $key => $path) { + // This might be a relative path as well + $alternativePath = realpath($this->getPHPCodeSnifferInstallPath() . \DIRECTORY_SEPARATOR . $path); + + if ( + (is_dir($path) === false || is_readable($path) === false) && + ( + $alternativePath === false || + is_dir($alternativePath) === false || + is_readable($alternativePath) === false + ) + ) { + unset($this->installedPaths[$key]); + $changes = true; + } + } + return $changes; + } + + /** + * Check all installed packages (including the root package) against + * the installed paths from PHP_CodeSniffer and add the missing ones. + * + * @return bool True if changes where made, false otherwise + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + private function updateInstalledPaths() + { + $changes = false; + $searchPaths = array(); + + // Add root package only if it has the expected package type. + if ( + $this->composer->getPackage() instanceof RootPackageInterface + && $this->composer->getPackage()->getType() === self::PACKAGE_TYPE + ) { + $searchPaths[] = $this->cwd; + } + + $codingStandardPackages = $this->getPHPCodingStandardPackages(); + foreach ($codingStandardPackages as $package) { + $installPath = $this->composer->getInstallationManager()->getInstallPath($package); + if ($this->filesystem->isAbsolutePath($installPath) === false) { + $installPath = $this->filesystem->normalizePath( + $this->cwd . \DIRECTORY_SEPARATOR . $installPath + ); + } + $searchPaths[] = $installPath; + } + + // Nothing to do. + if ($searchPaths === array()) { + return false; + } + + $finder = new Finder(); + $finder->files() + ->depth('<= ' . $this->getMaxDepth()) + ->depth('>= ' . $this->getMinDepth()) + ->ignoreUnreadableDirs() + ->ignoreVCS(true) + ->in($searchPaths) + ->name('ruleset.xml'); + + // Process each found possible ruleset. + foreach ($finder as $ruleset) { + $standardsPath = $ruleset->getPath(); + + // Pick the directory above the directory containing the standard, unless this is the project root. + if ($standardsPath !== $this->cwd) { + $standardsPath = dirname($standardsPath); + } + + // Use relative paths for local project repositories. + if ($this->isRunningGlobally() === false) { + $standardsPath = $this->filesystem->findShortestPath( + $this->getPHPCodeSnifferInstallPath(), + $standardsPath, + true + ); + } + + // De-duplicate and add when directory is not configured. + if (in_array($standardsPath, $this->installedPaths, true) === false) { + $this->installedPaths[] = $standardsPath; + $changes = true; + } + } + + return $changes; + } + + /** + * Iterates through Composers' local repository looking for valid Coding + * Standard packages. + * + * @return array Composer packages containing coding standard(s) + */ + private function getPHPCodingStandardPackages() + { + $codingStandardPackages = array_filter( + $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(), + function (PackageInterface $package) { + if ($package instanceof AliasPackage) { + return false; + } + return $package->getType() === Plugin::PACKAGE_TYPE; + } + ); + + return $codingStandardPackages; + } + + /** + * Searches for the installed PHP_CodeSniffer Composer package + * + * @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against + * + * @return PackageInterface|null + */ + private function getPHPCodeSnifferPackage($versionConstraint = null) + { + $packages = $this + ->composer + ->getRepositoryManager() + ->getLocalRepository() + ->findPackages(self::PACKAGE_NAME, $versionConstraint); + + return array_shift($packages); + } + + /** + * Returns the path to the PHP_CodeSniffer package installation location + * + * @return string + */ + private function getPHPCodeSnifferInstallPath() + { + return $this->composer->getInstallationManager()->getInstallPath($this->getPHPCodeSnifferPackage()); + } + + /** + * Simple check if PHP_CodeSniffer is installed. + * + * @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against + * + * @return bool Whether PHP_CodeSniffer is installed + */ + private function isPHPCodeSnifferInstalled($versionConstraint = null) + { + return ($this->getPHPCodeSnifferPackage($versionConstraint) !== null); + } + + /** + * Test if composer is running "global" + * This check kinda dirty, but it is the "Composer Way" + * + * @return bool Whether Composer is running "globally" + * + * @throws \RuntimeException + */ + private function isRunningGlobally() + { + return ($this->composer->getConfig()->get('home') === $this->cwd); + } + + /** + * Determines the maximum search depth when searching for Coding Standards. + * + * @return int + * + * @throws \InvalidArgumentException + */ + private function getMaxDepth() + { + $maxDepth = 3; + + $extra = $this->composer->getPackage()->getExtra(); + + if (array_key_exists(self::KEY_MAX_DEPTH, $extra)) { + $maxDepth = $extra[self::KEY_MAX_DEPTH]; + $minDepth = $this->getMinDepth(); + + if ( + (string) (int) $maxDepth !== (string) $maxDepth /* Must be an integer or cleanly castable to one */ + || $maxDepth <= $minDepth /* Larger than the minimum */ + || is_float($maxDepth) === true /* Within the boundaries of integer */ + ) { + $message = vsprintf( + self::MESSAGE_ERROR_WRONG_MAX_DEPTH, + array( + 'key' => self::KEY_MAX_DEPTH, + 'min' => $minDepth, + 'given' => var_export($maxDepth, true), + ) + ); + + throw new \InvalidArgumentException($message); + } + } + + return (int) $maxDepth; + } + + /** + * Returns the minimal search depth for Coding Standard packages. + * + * Usually this is 0, unless PHP_CodeSniffer >= 3 is used. + * + * @return int + */ + private function getMinDepth() + { + if ($this->isPHPCodeSnifferInstalled('>= 3.0.0') !== true) { + return 1; + } + return 0; + } +} diff --git a/vendor/eftec/bladeone/.php_cs b/vendor/eftec/bladeone/.php_cs new file mode 100644 index 0000000..ea6193d --- /dev/null +++ b/vendor/eftec/bladeone/.php_cs @@ -0,0 +1,12 @@ +in(__DIR__ . '/lib'); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + ]) + ->setFinder($finder); \ No newline at end of file diff --git a/vendor/eftec/bladeone/BladeOneCache.md b/vendor/eftec/bladeone/BladeOneCache.md new file mode 100644 index 0000000..463f995 --- /dev/null +++ b/vendor/eftec/bladeone/BladeOneCache.md @@ -0,0 +1,54 @@ +# BladeOneCache extension library (optional) + +Requires: BladeOne + +This library adds cache to the visual layer and business/logic layer. +For using this library, the code requires to include and use the trait BladeOneCache + +```php +class MyBlade extends bladeone\BladeOne { + use bladeone\BladeOneCache; +} +$blade=new MyBlade($views,$compiledFolder); +``` +- Where MyBlade is a new class that extends the BladeOne class and use the cache features. + + + +## New Tags (template file) + +### cache + +```html +@ cache(1,86400). + +@ endcache() + +@ cache(2,86400). + +@ endcache() +``` + +- @cache([id],[duration]) start a new cache block +- The cacheid (optional) indicates the id of the cache. It shoulds be unique. If not id then its added automatically +- The duration (optional) in seconds indicates the duration of the cache. +- @endcache +- End of the cache block. It shouldn't be stacked. + +## New Business Logic / Controller function + +### function cacheExpired +```php +if ($blade->cacheExpired('hellocache',1,5)) { // 'hellocache' = template, 1 = id cache, 5 = duration (seconds) + // cache expired, so we should do some stuff (such as read from the database) +} +``` + +- function cacheExpired(cachefile,cacheid,duration) returns true if the cache expires (and it shoulds be calculated and rebuild), otherwise false +- cachefile indicates the template to use. +- cacheid indicates the id of the cache. +- duration indicates the duration of the cache (in seconds) + +> Note : if BLADEONE_MODE = 1 (**forced**) then the cache system is never used. + +> Note : The cache system works per **template** and **cacheid**. I.e. its possible to cache a part of a template for a limited time, while caching the rest for a long while. diff --git a/vendor/eftec/bladeone/BladeOneHtml.md b/vendor/eftec/bladeone/BladeOneHtml.md new file mode 100644 index 0000000..bd9353f --- /dev/null +++ b/vendor/eftec/bladeone/BladeOneHtml.md @@ -0,0 +1,129 @@ +# BladeOneHtml extension library (optional) + +Requires: BladeOne + +For using this tag, the code requires to use the class BladeOneHtml + +## New Tags + +### Select/endselect + +```html +@select('id1') + @item('0','--Select a country--',$countrySelected,'') + @items($countries,'id','name',$countrySelected,'') +@endselect() +``` + +- `@select`/`@endselect` creates the **select** tag. The first value is the id and name of the tag. +- `@item` allows to add one element **option** tag. +- The first value is the id and the second is the visible text. +- The third tag indicates the selected element. It could be a single value or an array of elements. +- `@items` allows to add one list of elements **option** tag. +- The first value is the list of values, +- the second and third is the id and name. +- And the fourth one is the selected value (optional) + +![select](http://i.imgur.com/yaMavQB.jpg?1) + +### Input + +```html +@input('iduser',$currentUser,'text'[,$extra]) +``` + +`@input` creates a **input** tag. The first value is the id/name, the second is the default value, the third is the type (by default is text for textbox)*[]: + +### Form/endform +```form +@form(['action'],['post'][,$extra]) + ... form goes here +@endform +``` +`@form` creates **form** html tag. The first value (optional) is the action, the second value (optional) is the method ('post','get') + +### listboxes +```html +@listboxes('idlistbox',$countries,'id','name',$multipleSelect) +``` + +- `@listboxes(id,$listvalues,$fieldid,$fieldvalue,·selected,[$extra])` Creates a list box +- The `id` indicates the id of the object. It shoulds be unique. +- `$listvalues` indicates the vlaues to show in the object. +- `$fieldid` indicates the field used as key. +- `$fieldvalue` indicates the field used to show. +- `$selected` indicates the selected values. +- `$extra` (optional) indicates the extra value (see note below). + +### selectgroup + +### radio/endradio + +### checkbox/endcheckbox + +Single checkbox: +```html +@checkbox('idsimple','777','SelectMe','777') +``` + +Multiple checkboxes: +```html +@checkbox('id3') + @item('0','--Select a country--')
+ @items($countries,'id','name',$countrySelected,'%s
') +@endcheckbox() +``` + +- `@checkbox(id,$value,$label,$valueSelected,[$extra])` +- `id` indicates the id of the object. It shoulds be unique +- `$value` indicates the value of the checkbox when its selected. +- `$label` shows the label of the object. +- `$valueselected` indicates the selected value. If $value is equals to $valueselected then the checkbox is checked +- `$extra` (optional) indicates the extra value (see section note below). + +> Note: It could be generates as a single value or as a list of checkboxes (see examples) + + +### item/trio + +### items/trios + +### textarea + +### hidden + +### label +```html +@label('id','Select the value:') +``` +- `@label(id,$label,[$extra])` +- `id` indicates the id of the input object related with the label. +- `$label` shows the label of the object. +- `$extra` (optional) indicates the extra value (see section note below). + + +### commandbutton + +### link (new in 1.6) +```html +@link('http://www.google.com','Go to google') +``` + + + +### NOTE: Extra Parameter + +Additionally, you can add an (optional) last parameter with additional value (see the example of @select) + +```html + +
+ + @select('id1') + @item('0','--Select a country--',"",class='form-control'") + @items($countries,'id','name',"",$countrySelected) + @endselect() + +
+``` + diff --git a/vendor/eftec/bladeone/BladeOneLang.md b/vendor/eftec/bladeone/BladeOneLang.md new file mode 100644 index 0000000..dec9a04 --- /dev/null +++ b/vendor/eftec/bladeone/BladeOneLang.md @@ -0,0 +1,76 @@ +# BladeOneLang extension library (optional) + +Requires: BladeOne + +This library adds cache to the visual layer and business/logic layer. +For using this library, the code requires to include and use the trait BladeOneCache + +Setting: +```php +class MyBlade extends bladeone\BladeOne { + use bladeone\BladeOneLang; +} +$blade=new MyBlade($views,$compiledFolder); + +$blade->missingLog='c:\temp\missingkey.txt'; // (optional) if a traduction is missing the it will be saved here. + +$lang='jp'; // try es,jp or fr +include './lang/'.$lang.'.php'; +``` + +Where /lang/es.php is simmilar to: + +```php +'Sombrero' + ,'Cat'=>'Gato' + ,'Cats'=>'Gatos' + ,'%s is a nice cat'=>'%s es un buen gato' +); +``` +Template file +```php +Hat in spanish is @_e('Hat')
+There is one @_n('Cat','Cats',1)
+@_ef('%s is a nice cat','Cheshire')
+``` +Returns: +Hat in spanish is Sombrero +There is one Gato +Cheshire es un buen gato. + + + + + +- Where MyBlade is a new class that extends the bladeone class and use the Lang features. + + + +## Template methods + +### @_e('Word or phrase') + +it tries to translate the word if its in the array defined by `BladeOneLang::$dictionary`. +If there is not a entry with the word 'Hat' (case sensitive) then it returns 'Hat'. Also, if the log file is define, the it also saves an entry with the missing word. + +For the previous example. `@_e('Hat')` returns Sombrero. + +### @_ef('some phrase %s another words %s %i','word1','word2',20) + +Its the same than `@_e`, however it parses the text (using `sprintf`). +If the operation fails then, it returns the original expression without translation. + +For the previous example.` @_ef('%s is a nice cat','Cheshire')` returns Cheshire es un buen gato. + +### @_n('Singular','Plural',number) + +If number is plural (more than 1) then it translates (if any) the second word, otherwise it translates the first word. +If not number is used then it always translates the singular expression. + +For the previous example.` @_n('Cat','Cats',100)` returns Cheshire es un buen gato. + diff --git a/vendor/eftec/bladeone/BladeOneLogic.md b/vendor/eftec/bladeone/BladeOneLogic.md new file mode 100644 index 0000000..ac19e40 --- /dev/null +++ b/vendor/eftec/bladeone/BladeOneLogic.md @@ -0,0 +1,10 @@ +#BladeOneLogic extension library (optional) + +Requires: BladeOne + +For using this tag, the code requires to use the class BladeOneLogic + +## Defintion of Blade Template +For using this tag, the code requires to use the class BladeOneLogic that extends the class BladeOne. +The code extends the class BladeOneHtml by creating a daisy chain. + diff --git a/vendor/eftec/bladeone/LICENSE b/vendor/eftec/bladeone/LICENSE new file mode 100644 index 0000000..291b1fe --- /dev/null +++ b/vendor/eftec/bladeone/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jorge Patricio Castro Castillo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice, other copyright notices and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/eftec/bladeone/README.md b/vendor/eftec/bladeone/README.md new file mode 100644 index 0000000..9688311 --- /dev/null +++ b/vendor/eftec/bladeone/README.md @@ -0,0 +1,485 @@ +![Logo](https://raw.githubusercontent.com/EFTEC/BladeOne/gh-pages/images/bladelogo.png) + +# BladeOne Blade Template Engine +BladeOne is a standalone version of Blade Template Engine that uses a single PHP file and can be ported and used in different projects. It allows you to use blade template outside Laravel. + +Бладеоне-это отдельная версия ядра Blade-шаблонов, которая использует один PHP-файл и может быть портирована и использована в различных проектах. Он позволяет использовать шаблон Blade за пределами laravel. + + +[![Packagist](https://img.shields.io/packagist/v/eftec/bladeone.svg)](https://packagist.org/packages/eftec/bladeone) +[![Total Downloads](https://poser.pugx.org/eftec/bladeone/downloads)](https://packagist.org/packages/eftec/bladeone) +[![Maintenance](https://img.shields.io/maintenance/yes/2021.svg)]() +[![composer](https://img.shields.io/badge/composer-%3E1.6-blue.svg)]() +[![php](https://img.shields.io/badge/php->5.6-green.svg)]() +[![php](https://img.shields.io/badge/php-7.x-green.svg)]() +[![php](https://img.shields.io/badge/php-8.x-green.svg)]() +[![CocoaPods](https://img.shields.io/badge/docs-70%25-yellow.svg)]() + + +NOTE: So far it's apparently the only one project that it's updated with the latest version of **Blade 7 (March 2020)**. It misses some commands [missing](#missing) but nothing more. + + +Примечание: до сих пор это, видимо, только один проект, который обновляется с последней версией ** Blade 7 (2020 Марта) **. Он пропускает некоторые команды [отсутствует](#missing), но ничего больше. + +## Comparison with Twig + +> (spoiler) Twig is slower. 😊 + +| | First Time Time | First Time Memory | Overload First Time | Second Time | Second Time Memory | +|----------|-----------------|-------------------|---------------------|-------------|--------------------| +| BladeOne | 1962ms | 2024kb | 263 | 1917ms | 2024kb | +| Twig | 3734ms | 2564kb | 123 | 3604ms | 2327kb | + +What it was tested?. It was tested two features (that are the most used): It was tested with an array with +1000 elements and tested many times. + +[Comparison with Twig](https://github.com/EFTEC/BladeOne/wiki/Comparison-with-Twig) + + + +## NOTE about questions, reports, doubts or suggesting: + +✔ If you want to open an inquiry, do you have a doubt, or you find a bug, then you could open an [ISSUE](https://github.com/EFTEC/BladeOne/issues). +Please, don't email me (or send me PM) directly for question or reports. +Also, if you want to reopen a report, then you are open to do that. +I will try to answer all and every one of the question (in my limited time). + +## Some example +| [ExampleTicketPHP](https://github.com/jorgecc/ExampleTicketPHP) | [Example cupcakes](https://github.com/EFTEC/example.cupcakes) | [Example Search](https://github.com/EFTEC/example-search) | [Example Editable Grid](https://github.com/EFTEC/example-php-editablegrid) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| example php bladeone | example php bladeone cupcakes | example php bladeone search | example php bladeone search | + +[https://www.southprojects.com](https://www.southprojects.com) + + +## Manual + +* [BladeOne Manual](https://github.com/EFTEC/BladeOne/wiki/BladeOne-Manual) +* [Template tags (views)](https://github.com/EFTEC/BladeOne/wiki/Template-tags) + * [Template variables](https://github.com/EFTEC/BladeOne/wiki/Template-variables) + * [Template inheritance](https://github.com/EFTEC/BladeOne/wiki/Template-inheritance) + * [Template component](https://github.com/EFTEC/BladeOne/wiki/Template-Component) + * [Template stack](https://github.com/EFTEC/BladeOne/wiki/Template-stack) + * [Template asset, relative, base, current and canonical links](https://github.com/EFTEC/BladeOne/wiki/Template-Asset,-Relative,-Base-and-Canonical-Links) + * [Template calling methods](https://github.com/EFTEC/BladeOne/wiki/Template-calling-methods) + * [Template logic](https://github.com/EFTEC/BladeOne/wiki/Template-logic) + * [Template loop](https://github.com/EFTEC/BladeOne/wiki/Template-loop) + * [Template Pipes (Filter)](https://github.com/EFTEC/BladeOne/wiki/Template-Pipes-(Filter)) +* [Methods of the class](https://github.com/EFTEC/BladeOne/wiki/Methods-of-the-class) +* [Injecting logic before the view (composer)](https://github.com/EFTEC/BladeOne/wiki/Injecting-logic-before-the-view-(composer)) +* [Extending the class](https://github.com/EFTEC/BladeOne/wiki/Extending-the-class) +* [Using BladeOne with YAF Yet Another Framework](https://github.com/EFTEC/BladeOne/wiki/Using--BladeOne-with-YAF) +* [Differences between Blade and BladeOne](https://github.com/EFTEC/BladeOne/wiki/Differences-between-Blade-and-BladeOne) +* [Comparision with Twig (May-2020)](https://github.com/EFTEC/BladeOne/wiki/Comparison-with-Twig) +* [Changelog](https://github.com/EFTEC/BladeOne/wiki/Changelog) +* [Changes between 2.x and 3.0 and TODO](https://github.com/EFTEC/BladeOne/wiki/Changes-between-2.x-and-3.0-and-TODO) +* [Code Protection (Sourceguardian and similars)](https://github.com/EFTEC/BladeOne/wiki/Code-Protection-(Sourceguardian-and-similars)) + +## Why does it support PHP 5.x? + +As for today (January 2021), PHP 5.x is still strong even when it is discontinued, but my main problem is the performance. + +* PHP 7.x 60.2% +* PHP 5.x 39.5% +* PHP 8.x 00.1% + +* PHP 7.0 brings some new features and definitions. One is the use of type-hinting. While it could be useful, but it affects the performance +so there is not reason to use it for this library (we use PHPDOC and it doesn't affect the performance). +* PHP 7.0 adds some new features such as is_countable(). However, it is slower than the method used here. +* We could also use Null Coalescing Operator, but it is not slower or faster than a ternary operator. +* PHP 8.0 also adds str_contains(), but it doesn't bring a sustancial performance but syntax sugar. + + +## Laravel blade tutorial + +You can find some tutorials and example on the folder [Examples](examples). + +You could also check the wiki [Wiki](https://github.com/EFTEC/BladeOne/wiki) + +## About this version +By standard, The original Blade library is part of Laravel (Illuminate components) and to use this template library, you require install Laravel and Illuminate-view components. +The syntax of Blade is pretty nice and bright. It's based in C# Razor (another template library for C#). It's starting to be considered a de-facto standard template system for many PHP (Smarty has been riding off the sunset since years ago) so, if we can use it without Laravel then its a big plus for many projects. +In fact, in theory, it is even possible to use with Laravel. +Exists different versions of Blade Template that runs without Laravel, but most requires 50 or more files, and those templates add a new level of complexity, so they are not removing Laravel but hiding: + +- More files to manage. +- Changes to the current project (if you want to integrate the template into an existent one) +- Incompatibilities amongst other projects. +- Slowness (if your server is not using op-cache) +- Most of the code in the original Blade is used for future use, including the chance to use a different template engine. +- Some Laravel legacy code. + +This project uses a single file called BladeOne.php and a single class (called BladeOne). +If you want to use it then include it, creates the folders and that's it!. Nothing more (not even namespaces)*[]: It is also possible to use Blade even with Laravel or any other framework. After all, BladeOne is native, so it's possible to integrate into almost any project. + +## Why to use it instead of native PHP? + +### Separation of concerns +Let’s say that we have the next code + +```php +//some PHP code +// some HTML code +// more PHP code +// more HTML code. +``` +It leads to a mess of a code. For example, let’s say that we oversee changing the visual layout of the page. In this case, we should change all the code and we could even break part of the programming. +Instead, using a template system works in the next way: +```php +// some php code +ShowTemplate(); +``` +We are separating the visual layer from the code layer. As a plus, we could assign a non-php-programmer in charge to edit the template, and he/she doesn’t need to touch or know our php code. +## Security +Let’s say that we have the next exercise (it’s a dummy example) +```php +$name=@$_GET['name']; +echo "my name is ".$name; +``` +It could be separates as two files: +```php // index.php +$name=@$_GET['name']; +include "template.php"; +``` +```php +// template.php +echo "my name is ".$name; +``` +Even for this simple example, there is a risk of hacking. How? A user could sends malicious code by using the GET variable, such as html or even javascript. The second file should be written as follow: +```php + // template.php +echo "my name is ".html_entities($name); +``` +html_entities should be used in every single part of the visual layer (html) where the user could injects malicious code, and it’s a real tedious work. BladeOne does it automatically. +```php +// template.blade.php +My name is {{$name}} +``` +## Easy to use + +BladeOne is focused on an easy syntax that it's fast to learn and to write, while it could keep the power of PHP. + +Let's consider the next template: + +```php // template.php + +``` +With BladeOne, we could do the same with +```php // template.blade.php + +``` +And if we use thehtml extension we could even reduce to + +```php // template.blade.php +@select('id1') + @items($countries,'value','text','','') +@endselect() +``` + + + + +### Performance + +This library works in two stages. + +The first is when the template calls the first time. In this case, the template compiles and store in a folder. +The second time the template calls then, it uses the compiled file. The compiled file consist mainly in native PHP, so **the performance is equals than native code.** since the compiled version IS PHP. + +### Scalable + +You could add and use your own function by adding a new method (or extending) to the BladeOne class. NOTE: The function should start with the name "compile" +```php +protected function compileMyFunction($expression) +{ + return $this->phpTag . "echo 'YAY MY FUNCTION IS WORKING'; ?>"; +} +``` + +Where the function could be used in a template as follow +```php +@myFunction('param','param2'...) +``` +Alternatively, BladeOne allows to run arbitrary code from any class or method if its defined. +```php +{{SomeClass::SomeMethod('param','param2'...)}} +``` +## Install (pick one of the next one) + +1) Download the file manually then unzip (using WinRAR,7zip or any other program) https://github.com/EFTEC/BladeOne/archive/master.zip +2) git clone https://github.com/EFTEC/BladeOne +3) Composer. See [usage](#usage) +4) wget https://github.com/EFTEC/BladeOne/archive/master.zip + unzip master.zip + +## Usage + +If you use **composer**, then you could add the library using the next command (command line) + +> composer require eftec/bladeone + +If you don't use it, then you could download the library and include it manually. + +### Implicit definition + +```php +use eftec\bladeone\BladeOne; + +$views = __DIR__ . '/views'; +$cache = __DIR__ . '/cache'; +$blade = new BladeOne($views,$cache,BladeOne::MODE_DEBUG); // MODE_DEBUG allows to pinpoint troubles. +echo $blade->run("hello",array("variable1"=>"value1")); // it calls /views/hello.blade.php +``` + +Where `$views` is the folder where the views (templates not compiled) will be stored. +`$cache` is the folder where the compiled files will be stored. + +In this example, the BladeOne opens the template **hello**. So in the views folder it should exist a file called **hello.blade.php** + +views/hello.blade.php: +```html +

Title

+{{$variable1}} +``` + +### Explicit + +In this mode, it uses the folders ```__DIR__/views``` and ```__DIR__/compiles```, also it uses the mode as MODE_AUTO. + +```php +use eftec\bladeone\BladeOne; + +$blade = new BladeOne(); // MODE_DEBUG allows to pinpoint troubles. +echo $blade->run("hello",array("variable1"=>"value1")); // it calls /views/hello.blade.php +``` + +### Fluent + +```php +use eftec\bladeone\BladeOne; + +$blade = new BladeOne(); // MODE_DEBUG allows to pinpoint troubles. +echo $blade->setView('hello') // it sets the view to render + ->share(array("variable1"=>"value1")) // it sets the variables to sends to the view + ->run(); // it calls /views/hello.blade.php +``` + +## Filter (Pipes) + +It is possible to modify the result by adding filters to the result. + +Let's say we have the next value $name='Jack Sparrow' + +```php +$blade=new BladeOne(); +$blade->pipeEnable=true; // pipes are disable by default so it must be enable. +echo $blade->run('template',['name'=>'Jack Sparrow']); +``` + +Our view could look like: + +```php + {{$name}} or {!! $name !!} // Jack Sparrow +``` + +What if we want to show the name in uppercase?. + +We could do in our code $name=strtoupper('Jack Sparrow'). With Pipes, we could do the same as follow: + +```php + {{$name | strtoupper}} // JACK SPARROW +``` + +We could also add arguments and chain methods. + +```php + {{$name | strtoupper | substr:0,5}} // JACK +``` + +You can find more information on https://github.com/EFTEC/BladeOne/wiki/Template-Pipes-(Filter) + + + +## Security (optional) + +```php +require "vendor/autoload.php"; + +Use eftec\bladeone; + +$views = __DIR__ . '/views'; +$cache = __DIR__ . '/cache'; +$blade=new bladeone\BladeOne($views,$cache,BladeOne::MODE_AUTO); + +$blade->setAuth('johndoe','admin'); // where johndoe is an user and admin is the role. The role is optional + +echo $blade->run("hello",array("variable1"=>"value1")); +``` + +If you log in using blade then you could use the tags @auth/@endauth/@guest/@endguest + + +```html +@auth + // The user is authenticated... +@endauth + +@guest + // The user is not authenticated... +@endguest +``` + +or + +```html +@auth('admin') + // The user is authenticated... +@endauth + +@guest('admin') + // The user is not authenticated... +@endguest +``` + + + +## Extensions Libraries (optional) + +[BladeOneCache Documentation](BladeOneCache.md) + +[https://github.com/eftec/BladeOneHtml](https://github.com/eftec/BladeOneHtml) + + +## Calling a static methods inside the template. + +Since **3.34**, BladeOne allows to call a static method inside a class. + +Let's say we have a class with namespace \namespace1\namespace2 + +```php +namespace namespace1\namespace2 { + class SomeClass { + public static function Method($arg='') { + return "hi world"; + } + } +} +``` + +### Method 1 PHP Style + +We could add a "use" in the template. Example: + +Add the next line to the template +```html +@use(\namespace1\namespace2) +``` + +and the next lines to the template (different methods) + +```html +{{SomeClass::Method()}} +{!! SomeClass::Method() !!} +@SomeClass::Method() +``` + +> All those methods are executed at runtime + + +### Method 2 Alias +Or we could define alias for each classes. + +php code: +```php + $blade = new BladeOne(); + // with the method addAliasClasses + $blade->addAliasClasses('SomeClass', '\namespace1\namespace2\SomeClass'); + // with the setter setAliasClasses + $blade->setAliasClasses(['SomeClass'=>'\namespace1\namespace2\SomeClass']); + // or directly in the field + $blade->aliasClasses=['SomeClass'=>'\namespace1\namespace2\SomeClass']; +``` + +Template: +```html +{{SomeClass::Method()}} +{!! SomeClass::Method() !!} +@SomeClass::Method() +``` + +> We won't need alias or use for global classes. + + + +## Named argument (since 3.38) + +BladeOne allows named arguments. This feature must be implemented per function. + +Let's say the next problem: + +It is the old library BladeOneHtml: + +``` +@select('id1') + @item('0','--Select a country--',"",class='form-control'") + @items($countries,'id','name',"",$countrySelected) +@endselect +``` + +And it is the next library: + +```html +@select(id="aaa" value=$selection values=$countries alias=$country) + @item(value='aaa' text='-- select a country--') + @items( id="chkx" value=$country->id text=$country->name) +@endselect +``` + +The old method **select** only allows a limited number of arguments. And the order of the arguments is important. + +The new method **select** allows to add different types of arguments + +## BladeOneHtml + +It is a new extension to BladeOne. It allows to create html components easily and with near-to-native performance. + +It uses a new feature of BladeOne: named arguments + +Example to create a select: + +```html +@select(id="aaa" value=$selection values=$countries alias=$country) + @item(value='aaa' text='-- select a country--') + @items( id="chkx" value=$country->id text=$country->name) +@endselect +``` + +[https://github.com/eftec/BladeOneHtml](https://github.com/eftec/BladeOneHtml) + +You could download it or add it via Composer + +> composer require eftec/bladeonehtml + + +## Collaboration + +You are welcome to use it, share it, ask for changes and whatever you want to. Just keeps the copyright notice in the file. + +## Future +* Blade locator/container + + + +## License +MIT License. +BladeOne (c) 2016-2021 Jorge Patricio Castro Castillo +Blade (c) 2012 Laravel Team (This code is based and inspired in the work of the team of Laravel, however BladeOne is mostly a original work) + diff --git a/vendor/eftec/bladeone/composer.json b/vendor/eftec/bladeone/composer.json new file mode 100644 index 0000000..d120f00 --- /dev/null +++ b/vendor/eftec/bladeone/composer.json @@ -0,0 +1,62 @@ +{ + "name": "eftec/bladeone", + "description": "The standalone version Blade Template Engine from Laravel in a single php file", + "type": "library", + "keywords": [ + "blade", + "template", + "view", + "php", + "templating" + ], + "homepage": "https://github.com/EFTEC/BladeOne", + "license": "MIT", + "authors": [ + { + "name": "Jorge Patricio Castro Castillo", + "email": "jcastro@eftec.cl" + } + ], + "config": { + "platform": { + "php": "5.6.1" + } + }, + "require": { + "php": ">=5.6", + "ext-json": "*" + }, + "suggest": { + "ext-mbstring": "This extension is used if it's active", + "eftec/bladeonehtml": "Extension to create forms" + }, + "archive": { + "exclude": [ + "/examples" + ] + }, + "autoload": { + "psr-4": { + "eftec\\bladeone\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "eftec\\tests\\": "tests/" + } + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^3.5.4", + "friendsofphp/php-cs-fixer": "^2.16.1" + }, + "scripts": { + "sniff": [ + "phpcs --extensions=php ." + ], + "fix": [ + "php-cs-fixer fix", + "phpcbf --extensions=php ." + ] + } +} diff --git a/vendor/eftec/bladeone/lib/BladeOne.php b/vendor/eftec/bladeone/lib/BladeOne.php new file mode 100644 index 0000000..a2d5d4a --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOne.php @@ -0,0 +1,3963 @@ + + * @copyright Copyright (c) 2016-2021 Jorge Patricio Castro Castillo MIT License. + * Don't delete this comment, its part of the license. + * Part of this code is based in the work of Laravel PHP Components. + * @version 3.52 + * @link https://github.com/EFTEC/BladeOne + */ +class BladeOne +{ + // + + /** @var int BladeOne reads if the compiled file has changed. If has changed,then the file is replaced. */ + const MODE_AUTO = 0; + /** @var int Then compiled file is always replaced. It's slow and it's useful for development. */ + const MODE_SLOW = 1; + /** @var int The compiled file is never replaced. It's fast and it's useful for production. */ + const MODE_FAST = 2; + /** @var int DEBUG MODE, the file is always compiled and the filename is identifiable. */ + const MODE_DEBUG = 5; + /** @var array Hold dictionary of translations */ + public static $dictionary = []; + /** @var string PHP tag. You could use < ?php or < ? (if shorttag is active in php.ini) */ + public $phpTag = ' + * If false (default value), then the variables defined in the include as arguments are defined globally.
+ * Example: (includeScope=false)
+ *
+     * @include("template",['a1'=>'abc']) // a1 is equals to abc
+     * @include("template",[]) // a1 is equals to abc
+     * 
+ * Example: (includeScope=true)
+ *
+     * @include("template",['a1'=>'abc']) // a1 is equals to abc
+     * @include("template",[]) // a1 is not defined
+     * 
+ */ + public $includeScope = false; + /** + * @var callable[] It allows to parse the compiled output using a function. + * This function doesn't require to return a value
+ * Example: this converts all compiled result in uppercase (note, content is a ref) + *
+     * $this->compileCallbacks[]= static function (&$content, $templatename=null) {
+     *      $content=strtoupper($content);
+     * };
+     * 
+ */ + public $compileCallbacks = []; + /** @var array All of the registered extensions. */ + protected $extensions = []; + /** @var array All of the finished, captured sections. */ + protected $sections = []; + /** @var string The template currently being compiled. For example "folder.template" */ + protected $fileName; + protected $currentView; + protected $notFoundPath; + /** @var string File extension for the template files. */ + protected $fileExtension = '.blade.php'; + /** @var array The stack of in-progress sections. */ + protected $sectionStack = []; + /** @var array The stack of in-progress loops. */ + protected $loopsStack = []; + /** @var array Dictionary of variables */ + protected $variables = []; + /** @var null Dictionary of global variables */ + protected $variablesGlobal = []; + /** @var array All of the available compiler functions. */ + protected $compilers = [ + 'Extensions', + 'Statements', + 'Comments', + 'Echos', + ]; + /** @var string|null it allows to sets the stack */ + protected $viewStack; + /** @var array used by $this->composer() */ + protected $composerStack = []; + /** @var array The stack of in-progress push sections. */ + protected $pushStack = []; + /** @var array All of the finished, captured push sections. */ + protected $pushes = []; + /** @var int The number of active rendering operations. */ + protected $renderCount = 0; + /** @var string[] Get the template path for the compiled views. */ + protected $templatePath; + /** @var string Get the compiled path for the compiled views. If null then it uses the default path */ + protected $compiledPath; + /** @var string the extension of the compiled file. */ + protected $compileExtension = '.bladec'; + /** @var array Custom "directive" dictionary. Those directives run at compile time. */ + protected $customDirectives = []; + /** @var bool[] Custom directive dictionary. Those directives run at runtime. */ + protected $customDirectivesRT = []; + /** @var callable Function used for resolving injected classes. */ + protected $injectResolver; + /** @var array Used for conditional if. */ + protected $conditions = []; + /** @var int Unique counter. It's used for extends */ + protected $uidCounter = 0; + /** @var string The main url of the system. Don't use raw $_SERVER values unless the value is sanitized */ + protected $baseUrl = '.'; + /** @var string|null The base domain of the system */ + protected $baseDomain; + /** @var string|null It stores the current canonical url. */ + protected $canonicalUrl; + /** @var string|null It stores the current url including arguments */ + protected $currentUrl; + /** @var string it is a relative path calculated between baseUrl and the current url. Example ../../ */ + protected $relativePath = ''; + /** @var string[] Dictionary of assets */ + protected $assetDict; + /** @var bool if true then it removes tabs and unneeded spaces */ + protected $optimize = true; + /** @var bool if false, then the template is not compiled (but executed on memory). */ + protected $isCompiled = true; + /** @var bool */ + protected $isRunFast = false; // stored for historical purpose. + /** @var array Array of opening and closing tags for raw echos. */ + protected $rawTags = ['{!!', '!!}']; + /** @var array Array of opening and closing tags for regular echos. */ + protected $contentTags = ['{{', '}}']; + /** @var array Array of opening and closing tags for escaped echos. */ + protected $escapedTags = ['{{{', '}}}']; + /** @var string The "regular" / legacy echo string format. */ + protected $echoFormat = '\htmlentities(%s, ENT_QUOTES, \'UTF-8\', false)'; + protected $echoFormatOld = 'static::e(%s)'; + /** @var array Lines that will be added at the footer of the template */ + protected $footer = []; + /** @var string Placeholder to temporary mark the position of verbatim blocks. */ + protected $verbatimPlaceholder = '$__verbatim__$'; + /** @var array Array to temporary store the verbatim blocks found in the template. */ + protected $verbatimBlocks = []; + /** @var int Counter to keep track of nested forelse statements. */ + protected $forelseCounter = 0; + /** @var array The components being rendered. */ + protected $componentStack = []; + /** @var array The original data passed to the component. */ + protected $componentData = []; + /** @var array The slot contents for the component. */ + protected $slots = []; + /** @var array The names of the slots being rendered. */ + protected $slotStack = []; + /** @var string tag unique */ + protected $PARENTKEY = '@parentXYZABC'; + /** + * Indicates the compile mode. + * if the constant BLADEONE_MODE is defined, then it is used instead of this field. + * + * @var int=[BladeOne::MODE_AUTO,BladeOne::MODE_DEBUG,BladeOne::MODE_SLOW,BladeOne::MODE_FAST][$i] + */ + protected $mode; + /** @var int Indicates the number of open switches */ + private $switchCount = 0; + /** @var bool Indicates if the switch is recently open */ + private $firstCaseInSwitch = true; + + //
+ + // + + /** + * Bob the constructor. + * The folder at $compiledPath is created in case it doesn't exist. + * + * @param string|array $templatePath If null then it uses (caller_folder)/views + * @param string $compiledPath If null then it uses (caller_folder)/compiles + * @param int $mode =[BladeOne::MODE_AUTO,BladeOne::MODE_DEBUG,BladeOne::MODE_FAST,BladeOne::MODE_SLOW][$i] + */ + public function __construct($templatePath = null, $compiledPath = null, $mode = 0) + { + if ($templatePath === null) { + $templatePath = \getcwd() . '/views'; + } + if ($compiledPath === null) { + $compiledPath = \getcwd() . '/compiles'; + } + $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath]; + $this->compiledPath = $compiledPath; + $this->setMode($mode); + $this->authCallBack = function ($action = null, /** @noinspection PhpUnusedParameterInspection */ $subject = null) { + return \in_array($action, $this->currentPermission, true); + }; + + $this->authAnyCallBack = function ($array = []) { + foreach ($array as $permission) { + if (\in_array($permission, $this->currentPermission, true)) { + return true; + } + } + return false; + }; + + $this->errorCallBack = static function (/** @noinspection PhpUnusedParameterInspection */ $key = null) { + return false; + }; + + if (!\is_dir($this->compiledPath)) { + $ok = @\mkdir($this->compiledPath, 0777, true); + if ($ok === false) { + $this->showError( + 'Constructing', + "Unable to create the compile folder [$this->compiledPath]. Check the permissions of it's parent folder.", + true + ); + } + } + // If the traits has "Constructors", then we call them. + // Requisites. + // 1- the method must be public or protected + // 2- it must doesn't have arguments + // 3- It must have the name of the trait. i.e. trait=MyTrait, method=MyTrait() + $traits = get_declared_traits(); + if ($traits !== null) { + foreach ($traits as $trait) { + $r = explode('\\', $trait); + $name = end($r); + if (is_callable([$this, $name]) && method_exists($this, $name)) { + $this->{$name}(); + } + } + } + } + // + // + + /** + * Show an error in the web. + * + * @param string $id Title of the error + * @param string $text Message of the error + * @param bool $critic if true then the compilation is ended, otherwise it continues + * @param bool $alwaysThrow if true then it always throw a runtime exception. + * @return string + * @throws \RuntimeException + */ + public function showError($id, $text, $critic = false, $alwaysThrow = false) + { + \ob_get_clean(); + if (($this->throwOnError || $alwaysThrow) && $critic === true) { + throw new \RuntimeException("BladeOne Error [$id] $text"); + } else { + echo "
"; + echo "BladeOne Error [$id]:
"; + echo "$text
\n"; + if ($critic) { + die(1); + } + if ($this->throwOnError) { + error_log("BladeOne Error [$id] $text"); + } + } + return ''; + } + + /** + * Escape HTML entities in a string. + * + * @param string $value + * @return string + */ + public static function e($value) + { + return (\is_array($value) || \is_object($value)) + ? \htmlentities(\print_r($value, true), ENT_QUOTES, 'UTF-8', false) + : \htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } + + protected static function convertArgCallBack($k, $v) + { + return $k . "='$v' "; + } + + /** + * @param mixed|\DateTime $variable + * @param string|null $format + * @return string + */ + public function format($variable, $format = null) + { + if ($variable instanceof \DateTime) { + $format = $format === null ? 'Y/m/d' : $format; + return $variable->format($format); + } + $format = $format === null ? '%s' : $format; + return sprintf($format, $variable); + } + + /** + * It converts a text into a php code with echo
+ * Example:
+ *
+     * $this->wrapPHP('$hello'); // "< ?php echo $this->e($hello); ? >"
+     * $this->wrapPHP('$hello',''); // < ?php echo $this->e($hello); ? >
+     * $this->wrapPHP('$hello','',false); // < ?php echo $hello; ? >
+     * $this->wrapPHP('"hello"'); // "< ?php echo $this->e("hello"); ? >"
+     * $this->wrapPHP('hello()'); // "< ?php echo $this->e(hello()); ? >"
+     * 
+ * + * @param string $input The input value + * @param string $quote The quote used (to quote the result) + * @param bool $parse If the result will be parsed or not. If false then it's returned without $this->e + * @return string + */ + public function wrapPHP($input, $quote = '"', $parse = true) + { + if (strpos($input, '(') !== false && !$this->isQuoted($input)) { + if ($parse) { + return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote; + } + + return $quote . $this->phpTagEcho . $input . ';?>' . $quote; + } + if (strpos($input, '$') === false) { + if ($parse) { + return self::enq($input); + } + + return $input; + } + if ($parse) { + return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote; + } + return $quote . $this->phpTagEcho . $input . ';?>' . $quote; + } + + /** + * Returns true if the text is surrounded by quotes (double or single quote) + * + * @param string|null $text + * @return bool + */ + public function isQuoted($text) + { + if (!$text || strlen($text) < 2) { + return false; + } + if ($text[0] === '"' && substr($text, -1) === '"') { + return true; + } + return ($text[0] === "'" && substr($text, -1) === "'"); + } + + /** + * Escape HTML entities in a string. + * + * @param string $value + * @return string + */ + public static function enq($value) + { + if (\is_array($value) || \is_object($value)) { + return \htmlentities(\print_r($value, true), ENT_NOQUOTES, 'UTF-8', false); + } + return \htmlentities($value, ENT_NOQUOTES, 'UTF-8', false); + } + + /** + * @param string $view example "folder.template" + * @param string|null $alias example "mynewop". If null then it uses the name of the template. + */ + public function addInclude($view, $alias = null) + { + if (!isset($alias)) { + $alias = \explode('.', $view); + $alias = \end($alias); + } + $this->directive($alias, function ($expression) use ($view) { + $expression = $this->stripParentheses($expression) ?: '[]'; + return "$this->phpTag echo \$this->runChild('$view', $expression); ?>"; + }); + } + + /** + * Register a handler for custom directives. + * + * @param string $name + * @param callable $handler + * @return void + */ + public function directive($name, callable $handler) + { + $this->customDirectives[$name] = $handler; + $this->customDirectivesRT[$name] = false; + } + + /** + * Strip the parentheses from the given expression. + * + * @param string $expression + * @return string + */ + public function stripParentheses($expression) + { + if (static::startsWith($expression, '(')) { + $expression = \substr($expression, 1, -1); + } + return $expression; + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '') { + if (\function_exists('mb_strpos')) { + if (\mb_strpos($haystack, $needle) === 0) { + return true; + } + } elseif (\strpos($haystack, $needle) === 0) { + return true; + } + } + } + + return false; + } + + /** + * If false then the file is not compiled and it is executed directly from the memory.
+ * By default the value is true
+ * It also sets the mode to MODE_SLOW + * + * @param bool $bool + * @return BladeOne + * @see \eftec\bladeone\BladeOne::setMode + */ + public function setIsCompiled($bool = false) + { + $this->isCompiled = $bool; + if (!$bool) { + $this->setMode(self::MODE_SLOW); + } + return $this; + } + + /** + * It sets the template and compile path (without trailing slash). + *

Example:setPath("somefolder","otherfolder"); + * + * @param null|string|string[] $templatePath If null then it uses the current path /views folder + * @param null|string $compiledPath If null then it uses the current path /views folder + */ + public function setPath($templatePath, $compiledPath) + { + if ($templatePath === null) { + $templatePath = \getcwd() . '/views'; + } + if ($compiledPath === null) { + $compiledPath = \getcwd() . '/compiles'; + } + $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath]; + $this->compiledPath = $compiledPath; + } + + /** + * @return array + */ + public function getAliasClasses() + { + return $this->aliasClasses; + } + + /** + * @param array $aliasClasses + */ + public function setAliasClasses($aliasClasses) + { + $this->aliasClasses = $aliasClasses; + } + + /** + * @param string $aliasName + * @param string $classWithNS + */ + public function addAliasClasses($aliasName, $classWithNS) + { + $this->aliasClasses[$aliasName] = $classWithNS; + } + // + // + + /** + * Authentication. Sets with a user,role and permission + * + * @param string $user + * @param null $role + * @param array $permission + */ + public function setAuth($user = '', $role = null, $permission = []) + { + $this->currentUser = $user; + $this->currentRole = $role; + $this->currentPermission = $permission; + } + + /** + * run the blade engine. It returns the result of the code. + * + * @param string HTML to parse + * @param array $data + * @return string + * @throws Exception + */ + public function runString($string, $data = []) + { + $php = $this->compileString($string); + + $obLevel = \ob_get_level(); + \ob_start(); + \extract($data, EXTR_SKIP); + + $previousError = \error_get_last(); + + try { + @eval('?' . '>' . $php); + } catch (Exception $e) { + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + throw $e; + } catch (ParseError $e) { // PHP 7 + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + $this->showError('runString', $e->getMessage(). ' '.$e->getCode(), true); + return ''; + } + + $lastError = \error_get_last(); // PHP 5.6 + if ($previousError != $lastError && $lastError['type'] == E_PARSE) { + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + $this->showError('runString', $lastError['message']. ' '.$lastError['type'], true); + return ''; + } + + return \ob_get_clean(); + } + + /** + * Compile the given Blade template contents. + * + * @param string $value + * @return string + */ + public function compileString($value) + { + $result = ''; + if (\strpos($value, '@verbatim') !== false) { + $value = $this->storeVerbatimBlocks($value); + } + $this->footer = []; + // Here we will loop through all of the tokens returned by the Zend lexer and + // parse each one into the corresponding valid PHP. We will then have this + // template as the correctly rendered PHP that can be rendered natively. + foreach (\token_get_all($value) as $token) { + $result .= \is_array($token) ? $this->parseToken($token) : $token; + } + if (!empty($this->verbatimBlocks)) { + $result = $this->restoreVerbatimBlocks($result); + } + // If there are any footer lines that need to get added to a template we will + // add them here at the end of the template. This gets used mainly for the + // template inheritance via the extends keyword that should be appended. + if (\count($this->footer) > 0) { + $result = \ltrim($result, PHP_EOL) + . PHP_EOL . \implode(PHP_EOL, \array_reverse($this->footer)); + } + return $result; + } + + /** + * Store the verbatim blocks and replace them with a temporary placeholder. + * + * @param string $value + * @return string + */ + protected function storeVerbatimBlocks($value) + { + return \preg_replace_callback('/(?verbatimBlocks[] = $matches[1]; + return $this->verbatimPlaceholder; + }, $value); + } + + /** + * Parse the tokens from the template. + * + * @param array $token + * + * @return string + * + * @see \eftec\bladeone\BladeOne::compileStatements + * @see \eftec\bladeone\BladeOne::compileExtends + * @see \eftec\bladeone\BladeOne::compileComments + * @see \eftec\bladeone\BladeOne::compileEchos + */ + protected function parseToken($token) + { + list($id, $content) = $token; + if ($id == T_INLINE_HTML) { + foreach ($this->compilers as $type) { + $content = $this->{"compile$type"}($content); + } + } + return $content; + } + + /** + * Replace the raw placeholders with the original code stored in the raw blocks. + * + * @param string $result + * @return string + */ + protected function restoreVerbatimBlocks($result) + { + $result = \preg_replace_callback('/' . \preg_quote($this->verbatimPlaceholder) . '/', function () { + return \array_shift($this->verbatimBlocks); + }, $result); + $this->verbatimBlocks = []; + return $result; + } + + /** + * it calculates the relative path of a web.
+ * This function uses the current url and the baseurl + * + * @param string $relativeWeb . Example img/images.jpg + * @return string Example ../../img/images.jpg + */ + public function relative($relativeWeb) + { + if (isset($this->assetDict[$relativeWeb])) { + return $this->assetDict[$relativeWeb]; + } + // relativepath is calculated when + return $this->relativePath . $relativeWeb; + } + + /** + * It add an alias to the link of the resources.
+ * addAssetDict('name','url/res.jpg')
+ * addAssetDict(['name'=>'url/res.jpg','name2'=>'url/res2.jpg'); + * + * @param string|array $name example 'css/style.css', you could also add an array + * @param string $url example https://www.web.com/style.css' + */ + public function addAssetDict($name, $url = '') + { + if (\is_array($name)) { + if ($this->assetDict === null) { + $this->assetDict = $name; + } else { + $this->assetDict = \array_merge($this->assetDict, $name); + } + } else { + $this->assetDict[$name] = $url; + } + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePush($expression) + { + return $this->phpTag . "\$this->startPush$expression; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePushOnce($expression) + { + $key = '$__pushonce__' . \trim(\substr($expression, 2, -2)); + return $this->phpTag . "if(!isset($key)): $key=1; \$this->startPush$expression; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePrepend($expression) + { + return $this->phpTag . "\$this->startPush$expression; ?>"; + } + + /** + * Start injecting content into a push section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startPush($section, $content = '') + { + if ($content === '') { + if (\ob_start()) { + $this->pushStack[] = $section; + } + } else { + $this->extendPush($section, $content); + } + } + + /* + * endswitch tag + */ + + /** + * Append content to a given push section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendPush($section, $content) + { + if (!isset($this->pushes[$section])) { + $this->pushes[$section] = []; // start an empty section + } + if (!isset($this->pushes[$section][$this->renderCount])) { + $this->pushes[$section][$this->renderCount] = $content; + } else { + $this->pushes[$section][$this->renderCount] .= $content; + } + } + + /** + * Start injecting content into a push section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startPrepend($section, $content = '') + { + if ($content === '') { + if (\ob_start()) { + \array_unshift($this->pushStack[], $section); + } + } else { + $this->extendPush($section, $content); + } + } + + /** + * Stop injecting content into a push section. + * + * @return string + */ + public function stopPush() + { + if (empty($this->pushStack)) { + $this->showError('stopPush', 'Cannot end a section without first starting one', true); + } + $last = \array_pop($this->pushStack); + $this->extendPush($last, \ob_get_clean()); + return $last; + } + + /** + * Stop injecting content into a push section. + * + * @return string + */ + public function stopPrepend() + { + if (empty($this->pushStack)) { + $this->showError('stopPrepend', 'Cannot end a section without first starting one', true); + } + $last = \array_shift($this->pushStack); + $this->extendStartPush($last, \ob_get_clean()); + return $last; + } + + /** + * Append content to a given push section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendStartPush($section, $content) + { + if (!isset($this->pushes[$section])) { + $this->pushes[$section] = []; // start an empty section + } + if (!isset($this->pushes[$section][$this->renderCount])) { + $this->pushes[$section][$this->renderCount] = $content; + } else { + $this->pushes[$section][$this->renderCount] = $content . $this->pushes[$section][$this->renderCount]; + } + } + + /** + * Get the string contents of a push section. + * + * @param string $section + * @param string $default + * @return string + */ + public function yieldPushContent($section, $default = '') + { + if (!isset($this->pushes[$section])) { + return $default; + } + return \implode(\array_reverse($this->pushes[$section])); + } + + /** + * Get the string contents of a push section. + * + * @param int|string $each if int, then it split the foreach every $each numbers.
+ * if string, "c3" it means that it will split in 3 columns
+ * @param string $splitText + * @param string $splitEnd + * @return string + */ + public function splitForeach($each = 1, $splitText = ',', $splitEnd = '') + { + $loopStack = static::last($this->loopsStack); // array(7) { ["index"]=> int(0) ["remaining"]=> int(6) ["count"]=> int(5) ["first"]=> bool(true) ["last"]=> bool(false) ["depth"]=> int(1) ["parent"]=> NULL } + if (($loopStack['index']) == $loopStack['count'] - 1) { + return $splitEnd; + } + $eachN = 0; + if (is_numeric($each)) { + $eachN = $each; + } elseif (strlen($each) > 1) { + if ($each[0] === 'c') { + $eachN = $loopStack['count'] / substr($each, 1); + } + } else { + $eachN = PHP_INT_MAX; + } + + if (($loopStack['index'] + 1) % $eachN === 0) { + return $splitText; + } + return ''; + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (\is_null($callback)) { + return empty($array) ? static::value($default) : \end($array); + } + return static::first(\array_reverse($array), $callback, $default); + } + + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + public static function value($value) + { + return $value instanceof Closure ? $value() : $value; + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (\is_null($callback)) { + return empty($array) ? static::value($default) : \reset($array); + } + foreach ($array as $key => $value) { + if ($callback($key, $value)) { + return $value; + } + } + return static::value($default); + } + + /** + * @param string $name + * @param $args [] + * @return string + * @throws BadMethodCallException + */ + public function __call($name, $args) + { + if ($name === 'if') { + return $this->registerIfStatement(isset($args[0]) ? $args[0] : null, isset($args[1]) ? $args[1] : null); + } + $this->showError('call', "function $name is not defined
", true, true); + return ''; + } + + /** + * Register an "if" statement directive. + * + * @param string $name + * @param callable $callback + * @return string + */ + public function registerIfStatement($name, callable $callback) + { + $this->conditions[$name] = $callback; + + $this->directive($name, function ($expression) use ($name) { + $tmp = $this->stripParentheses($expression); + return $expression !== '' + ? $this->phpTag . " if (\$this->check('$name', $tmp)): ?>" + : $this->phpTag . " if (\$this->check('$name')): ?>"; + }); + + $this->directive('else' . $name, function ($expression) use ($name) { + $tmp = $this->stripParentheses($expression); + return $expression !== '' + ? $this->phpTag . " elseif (\$this->check('$name', $tmp)): ?>" + : $this->phpTag . " elseif (\$this->check('$name')): ?>"; + }); + + $this->directive('end' . $name, function () { + return $this->phpTag . ' endif; ?>'; + }); + return ''; + } + + /** + * Check the result of a condition. + * + * @param string $name + * @param array $parameters + * @return bool + */ + public function check($name, ...$parameters) + { + return \call_user_func($this->conditions[$name], ...$parameters); + } + + /** + * @param bool $bool + * @param string $view name of the view + * @param array $value arrays of values + * @return string + * @throws Exception + */ + public function includeWhen($bool = false, $view = '', $value = []) + { + if ($bool) { + return $this->runChild($view, $value); + } + return ''; + } + + /** + * Macro of function run + * + * @param $view + * @param array $variables + * @return string + * @throws Exception + */ + public function runChild($view, $variables = []) + { + if (\is_array($variables)) { + if ($this->includeScope) { + $backup = $this->variables; + } else { + $backup = null; + } + $newVariables = \array_merge($this->variables, $variables); + } else { + if ($variables === null) { + $newVariables = $this->variables; + var_dump($newVariables); + die(1); + } + + $this->showError('run/include', "RunChild: Include/run variables should be defined as array ['idx'=>'value']", true); + return ''; + } + $r = $this->runInternal($view, $newVariables, false, false, $this->isRunFast); + if ($backup !== null) { + $this->variables = $backup; + } + return $r; + } + + /** + * run the blade engine. It returns the result of the code. + * + * @param string $view + * @param array $variables + * @param bool $forced if true then it recompiles no matter if the compiled file exists or not. + * @param bool $isParent + * @param bool $runFast if true then the code is not compiled neither checked and it runs directly the compiled + * version. + * @return string + * @throws Exception + * @noinspection PhpUnusedParameterInspection + */ + private function runInternal($view, $variables = [], $forced = false, $isParent = true, $runFast = false) + { + $this->currentView = $view; + if (@\count($this->composerStack)) { + $this->evalComposer($view); + } + if (@\count($this->variablesGlobal) > 0) { + $this->variables = \array_merge($variables, $this->variablesGlobal); + $this->variablesGlobal = []; // used so we delete it. + } else { + $this->variables = $variables; + } + if (!$runFast) { + // a) if the compile is forced then we compile the original file, then save the file. + // b) if the compile is not forced then we read the datetime of both file and we compared. + // c) in both cases, if the compiled doesn't exist then we compile. + if ($view) { + $this->fileName = $view; + } + $result = $this->compile($view, $forced); + if (!$this->isCompiled) { + return $this->evaluateText($result, $this->variables); + } + } elseif ($view) { + $this->fileName = $view; + } + $this->isRunFast = $runFast; + return $this->evaluatePath($this->getCompiledFile(), $this->variables); + } + + protected function evalComposer($view) + { + foreach ($this->composerStack as $viewKey => $fn) { + if ($this->wildCardComparison($view, $viewKey)) { + if (is_callable($fn)) { + $fn($this); + } elseif ($this->methodExistsStatic($fn, 'composer')) { + // if the method exists statically then $fn is the class and 'composer' is the name of the method + $fn::composer($this); + } elseif (is_object($fn) || class_exists($fn)) { + // if $fn is an object or it is a class and the class exists. + $instance = (is_object($fn)) ? $fn : new $fn(); + if (method_exists($instance, 'composer')) { + // and the method exists inside the instance. + $instance->composer($this); + } else { + if ($this->mode === self::MODE_DEBUG) { + $this->showError('evalComposer', "BladeOne: composer() added an incorrect method [$fn]", true, true); + return; + } + $this->showError('evalComposer', 'BladeOne: composer() added an incorrect method', true, true); + return; + } + } else { + $this->showError('evalComposer', 'BladeOne: composer() added an incorrect method', true, true); + } + } + } + } + + /** + * It compares with wildcards (*) and returns true if both strings are equals
+ * The wildcards only works at the beginning and/or at the end of the string.
+ * Example:
+ *

+     * Text::wildCardComparison('abcdef','abc*'); // true
+     * Text::wildCardComparison('abcdef','*def'); // true
+     * Text::wildCardComparison('abcdef','*abc*'); // true
+     * Text::wildCardComparison('abcdef','*cde*'); // true
+     * Text::wildCardComparison('abcdef','*cde'); // false
+     *
+     * 
+ * + * @param string $text + * @param string|null $textWithWildcard + * + * @return bool + */ + protected function wildCardComparison($text, $textWithWildcard) + { + if (($textWithWildcard === null && $textWithWildcard === '') + || strpos($textWithWildcard, '*') === false) { + // if the text with wildcard is null or empty or it contains two ** or it contains no * then.. + return $text == $textWithWildcard; + } + if ($textWithWildcard === '*' || $textWithWildcard === '**') { + return true; + } + $c0 = $textWithWildcard[0]; + $c1 = substr($textWithWildcard, -1); + $textWithWildcardClean = str_replace('*', '', $textWithWildcard); + $p0 = strpos($text, $textWithWildcardClean); + if ($p0 === false) { + // no matches. + return false; + } + if ($c0 === '*' && $c1 === '*') { + // $textWithWildcard='*asasasas*' + return true; + } + if ($c1 === '*') { + // $textWithWildcard='asasasas*' + return $p0 === 0; + } + // $textWithWildcard='*asasasas' + $len = strlen($textWithWildcardClean); + return (substr($text, -$len) === $textWithWildcardClean); + } + + protected function methodExistsStatic($class, $method) + { + try { + $mc = new \ReflectionMethod($class, $method); + return $mc->isStatic(); + } catch (\ReflectionException $e) { + return false; + } + } + + /** + * Compile the view at the given path. + * + * @param string $templateName The name of the template. Example folder.template + * @param bool $forced If the compilation will be forced (always compile) or not. + * @return boolean|string True if the operation was correct, or false (if not exception) + * if it fails. It returns a string (the content compiled) if isCompiled=false + * @throws Exception + */ + public function compile($templateName = null, $forced = false) + { + $compiled = $this->getCompiledFile($templateName); + $template = $this->getTemplateFile($templateName); + if (!$this->isCompiled) { + $contents = $this->compileString($this->getFile($template)); + $this->compileCallBacks($contents, $templateName); + return $contents; + } + if ($forced || $this->isExpired($templateName)) { + // compile the original file + $contents = $this->compileString($this->getFile($template)); + $this->compileCallBacks($contents, $templateName); + $dir = \dirname($compiled); + if (!\is_dir($dir)) { + $ok = @\mkdir($dir, 0777, true); + if ($ok === false) { + $this->showError( + 'Compiling', + "Unable to create the compile folder [$dir]. Check the permissions of it's parent folder.", + true + ); + return false; + } + } + if ($this->optimize) { + // removes space and tabs and replaces by a single space + $contents = \preg_replace('/^ {2,}/m', ' ', $contents); + $contents = \preg_replace('/^\t{2,}/m', ' ', $contents); + } + $ok = @\file_put_contents($compiled, $contents); + if ($ok === false) { + $this->showError( + 'Compiling', + "Unable to save the file [$compiled]. Check the compile folder is defined and has the right permission" + ); + return false; + } + } + return true; + } + + /** + * Get the full path of the compiled file. + * + * @param string $templateName + * @return string + */ + public function getCompiledFile($templateName = '') + { + $templateName = (empty($templateName)) ? $this->fileName : $templateName; + if ($this->getMode() == self::MODE_DEBUG) { + return $this->compiledPath . '/' . $templateName . $this->compileExtension; + } + + return $this->compiledPath . '/' . \sha1($templateName) . $this->compileExtension; + } + + /** + * Get the mode of the engine.See BladeOne::MODE_* constants + * + * @return int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i] + */ + public function getMode() + { + if (\defined('BLADEONE_MODE')) { + $this->mode = BLADEONE_MODE; + } + return $this->mode; + } + + /** + * Set the compile mode + * + * @param $mode int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i] + * @return void + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + /** + * Get the full path of the template file. + *

Example: getTemplateFile('.abc.def')

+ * + * @param string $templateName template name. If not template is set then it uses the base template. + * @return string + */ + public function getTemplateFile($templateName = '') + { + $templateName = (empty($templateName)) ? $this->fileName : $templateName; + if (\strpos($templateName, '/') !== false) { + return $this->locateTemplate($templateName); // it's a literal + } + $arr = \explode('.', $templateName); + $c = \count($arr); + if ($c == 1) { + // its in the root of the template folder. + return $this->locateTemplate($templateName . $this->fileExtension); + } + + $file = $arr[$c - 1]; + \array_splice($arr, $c - 1, $c - 1); // delete the last element + $path = \implode('/', $arr); + return $this->locateTemplate($path . '/' . $file . $this->fileExtension); + } + + /** + * Find template file with the given name in all template paths in the order the paths were written + * + * @param string $name Filename of the template (without path) + * @return string template file + */ + private function locateTemplate($name) + { + $this->notFoundPath = ''; + foreach ($this->templatePath as $dir) { + $path = $dir . '/' . $name; + if (\is_file($path)) { + return $path; + } + + $this->notFoundPath .= $path . ","; + } + return ''; + } + + /** + * Get the contents of a file. + * + * @param string $fullFileName It gets the content of a filename or returns ''. + * + * @return string + */ + public function getFile($fullFileName) + { + if (\is_file($fullFileName)) { + return \file_get_contents($fullFileName); + } + $this->showError('getFile', "File does not exist at paths (separated by comma) [$this->notFoundPath] or permission denied", true); + return ''; + } + + protected function compileCallBacks(&$contents, $templateName) + { + if (!empty($this->compileCallbacks)) { + foreach ($this->compileCallbacks as $callback) { + if (is_callable($callback)) { + $callback($contents, $templateName); + } + } + } + } + + /** + * Determine if the view has expired. + * + * @param string|null $fileName + * @return bool + */ + public function isExpired($fileName) + { + $compiled = $this->getCompiledFile($fileName); + $template = $this->getTemplateFile($fileName); + if (!\is_file($template)) { + if ($this->mode == self::MODE_DEBUG) { + $this->showError('Read file', 'Template not found :' . $this->fileName . " on file: $template", true); + } else { + $this->showError('Read file', 'Template not found :' . $this->fileName, true); + } + } + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if (!$this->compiledPath || !\is_file($compiled)) { + return true; + } + return \filemtime($compiled) < \filemtime($template); + } + + /** + * Evaluates a text (string) using the current variables + * + * @param string $content + * @param array $variables + * @return string + * @throws Exception + */ + protected function evaluateText($content, $variables) + { + \ob_start(); + \extract($variables); + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try { + eval(' ?>' . $content . $this->phpTag); + } catch (Exception $e) { + $this->handleViewException($e); + } + return \ltrim(\ob_get_clean()); + } + + /** + * Handle a view exception. + * + * @param Exception $e + * @return void + * @throws $e + */ + protected function handleViewException($e) + { + \ob_get_clean(); + throw $e; + } + + /** + * Evaluates a compiled file using the current variables + * + * @param string $compiledFile full path of the compile file. + * @param array $variables + * @return string + * @throws Exception + */ + protected function evaluatePath($compiledFile, $variables) + { + \ob_start(); + // note, the variables are extracted locally inside this method, + // they are not global variables :-3 + \extract($variables); + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try { + /** @noinspection PhpIncludeInspection */ + include $compiledFile; + } catch (Exception $e) { + $this->handleViewException($e); + } + return \ltrim(\ob_get_clean()); + } + + /** + * @param array $views array of views + * @param array $value + * @return string + * @throws Exception + */ + public function includeFirst($views = [], $value = []) + { + foreach ($views as $view) { + if ($this->templateExist($view)) { + return $this->runChild($view, $value); + } + } + return ''; + } + + /** + * Returns true if the template exists. Otherwise it returns false + * + * @param $templateName + * @return bool + */ + private function templateExist($templateName) + { + $file = $this->getTemplateFile($templateName); + return \is_file($file); + } + + /** + * Convert an array such as ["class1"=>"myclass","style="mystyle"] to class1='myclass' style='mystyle' string + * + * @param array|string $array array to convert + * @return string + */ + public function convertArg($array) + { + if (!\is_array($array)) { + return $array; // nothing to convert. + } + return \implode(' ', \array_map('static::convertArgCallBack', \array_keys($array), $array)); + } + + /** + * Returns the current token. if there is not a token then it generates a new one. + * It could require an open session. + * + * @param bool $fullToken It returns a token with the current ip. + * @param string $tokenId [optional] Name of the token. + * + * @return string + */ + public function getCsrfToken($fullToken = false, $tokenId = '_token') + { + if ($this->csrf_token == '') { + $this->regenerateToken($tokenId); + } + if ($fullToken) { + return $this->csrf_token . '|' . $this->ipClient(); + } + return $this->csrf_token; + } + + /** + * Regenerates the csrf token and stores in the session. + * It requires an open session. + * + * @param string $tokenId [optional] Name of the token. + */ + public function regenerateToken($tokenId = '_token') + { + try { + $this->csrf_token = \bin2hex(\random_bytes(10)); + } catch (Exception $e) { + $this->csrf_token = '123456789012345678901234567890'; // unable to generates a random token. + } + @$_SESSION[$tokenId] = $this->csrf_token . '|' . $this->ipClient(); + } + + public function ipClient() + { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) + && \preg_match('/^([d]{1,3}).([d]{1,3}).([d]{1,3}).([d]{1,3})$/', $_SERVER['HTTP_X_FORWARDED_FOR'])) { + return $_SERVER['HTTP_X_FORWARDED_FOR']; + } + return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; + } + + /** + * Validates if the csrf token is valid or not.
+ * It requires an open session. + * + * @param bool $alwaysRegenerate [optional] Default is false.
+ * If true then it will generate a new token regardless + * of the method.
+ * If false, then it will generate only if the method is POST.
+ * Note: You must not use true if you want to use csrf with AJAX. + * + * @param string $tokenId [optional] Name of the token. + * + * @return bool It returns true if the token is valid or it is generated. Otherwise, false. + */ + public function csrfIsValid($alwaysRegenerate = false, $tokenId = '_token') + { + if (@$_SERVER['REQUEST_METHOD'] === 'POST' && $alwaysRegenerate === false) { + $this->csrf_token = isset($_POST[$tokenId]) ? $_POST[$tokenId] : null; // ping pong the token. + return $this->csrf_token . '|' . $this->ipClient() === (isset($_SESSION[$tokenId]) ? $_SESSION[$tokenId] : null); + } + + if ($this->csrf_token == '' || $alwaysRegenerate) { + // if not token then we generate a new one + $this->regenerateToken($tokenId); + } + return true; + } + + /** + * Stop injecting content into a section and return its contents. + * + * @return string + */ + public function yieldSection() + { + $sc = $this->stopSection(); + return isset($this->sections[$sc]) ? $this->sections[$sc] : null; + } + + /** + * Stop injecting content into a section. + * + * @param bool $overwrite + * @return string + */ + public function stopSection($overwrite = false) + { + if (empty($this->sectionStack)) { + $this->showError('stopSection', 'Cannot end a section without first starting one.', true, true); + } + $last = \array_pop($this->sectionStack); + if ($overwrite) { + $this->sections[$last] = \ob_get_clean(); + } else { + $this->extendSection($last, \ob_get_clean()); + } + return $last; + } + + /** + * Append content to a given section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendSection($section, $content) + { + if (isset($this->sections[$section])) { + $content = \str_replace($this->PARENTKEY, $content, $this->sections[$section]); + } + $this->sections[$section] = $content; + } + + public function dump($object, $jsconsole = false) + { + if (!$jsconsole) { + echo '
';
+            \var_dump($object);
+            echo '
'; + } else { + /** @noinspection BadExpressionStatementJS */ + /** @noinspection JSVoidFunctionReturnValueUsed */ + echo ''; + } + } + + /** + * Start injecting content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startSection($section, $content = '') + { + if ($content === '') { + \ob_start() && $this->sectionStack[] = $section; + } else { + $this->extendSection($section, $content); + } + } + + /** + * Stop injecting content into a section and append it. + * + * @return string + * @throws InvalidArgumentException + */ + public function appendSection() + { + if (empty($this->sectionStack)) { + $this->showError('appendSection', 'Cannot end a section without first starting one.', true, true); + } + $last = \array_pop($this->sectionStack); + if (isset($this->sections[$last])) { + $this->sections[$last] .= \ob_get_clean(); + } else { + $this->sections[$last] = \ob_get_clean(); + } + return $last; + } + + /** + * Adds a global variable. If $varname is an array then it merges all the values. + * Example: + *
+     * $this->share('variable',10.5);
+     * $this->share('variable2','hello');
+     * // or we could add the two variables as:
+     * $this->share(['variable'=>10.5,'variable2'=>'hello']);
+     * 
+ * + * @param string|array $varname It is the name of the variable or it is an associative array + * @param mixed $value + * @return $this + * @see \eftec\bladeone\BladeOne::share + */ + public function with($varname, $value = null) + { + return $this->share($varname, $value); + } + + /** + * Adds a global variable. If $varname is an array then it merges all the values. + * Example: + *
+     * $this->share('variable',10.5);
+     * $this->share('variable2','hello');
+     * // or we could add the two variables as:
+     * $this->share(['variable'=>10.5,'variable2'=>'hello']);
+     * 
+ * + * @param string|array $varname It is the name of the variable or it is an associative array + * @param mixed $value + * @return $this + */ + public function share($varname, $value = null) + { + if (is_array($varname)) { + $this->variablesGlobal = \array_merge($this->variablesGlobal, $varname); + } else { + $this->variablesGlobal[$varname] = $value; + } + return $this; + } + + /** + * Get the string contents of a section. + * + * @param string $section + * @param string $default + * @return string + */ + public function yieldContent($section, $default = '') + { + if (isset($this->sections[$section])) { + return \str_replace($this->PARENTKEY, $default, $this->sections[$section]); + } + + return $default; + } + + /** + * Register a custom Blade compiler. + * + * @param callable $compiler + * @return void + */ + public function extend(callable $compiler) + { + $this->extensions[] = $compiler; + } + + /** + * Register a handler for custom directives for run at runtime + * + * @param string $name + * @param callable $handler + * @return void + */ + public function directiveRT($name, callable $handler) + { + $this->customDirectives[$name] = $handler; + $this->customDirectivesRT[$name] = true; + } + + /** + * Sets the escaped content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @return void + */ + public function setEscapedContentTags($openTag, $closeTag) + { + $this->setContentTags($openTag, $closeTag, true); + } + + /** + * Gets the content tags used for the compiler. + * + * @return array + */ + public function getContentTags() + { + return $this->getTags(); + } + + /** + * Sets the content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @param bool $escaped + * @return void + */ + public function setContentTags($openTag, $closeTag, $escaped = false) + { + $property = ($escaped === true) ? 'escapedTags' : 'contentTags'; + $this->{$property} = [\preg_quote($openTag), \preg_quote($closeTag)]; + } + + /** + * Gets the tags used for the compiler. + * + * @param bool $escaped + * @return array + */ + protected function getTags($escaped = false) + { + $tags = $escaped ? $this->escapedTags : $this->contentTags; + return \array_map('stripcslashes', $tags); + } + + /** + * Gets the escaped content tags used for the compiler. + * + * @return array + */ + public function getEscapedContentTags() + { + return $this->getTags(true); + } + + /** + * Sets the function used for resolving classes with inject. + * + * @param callable $function + */ + public function setInjectResolver(callable $function) + { + $this->injectResolver = $function; + } + + /** + * Get the file extension for template files. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Set the file extension for the template files. + * It must includes the leading dot e.g. .blade.php + * + * @param string $fileExtension Example: .prefix.ext + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Get the file extension for template files. + * + * @return string + */ + public function getCompiledExtension() + { + return $this->compileExtension; + } + + /** + * Set the file extension for the compiled files. + * Including the leading dot for the extension is required, e.g. .bladec + * + * @param $fileExtension + */ + public function setCompiledExtension($fileExtension) + { + $this->compileExtension = $fileExtension; + } + + /** + * Add new loop to the stack. + * + * @param array|Countable $data + * @return void + */ + public function addLoop($data) + { + $length = \is_array($data) || $data instanceof Countable ? \count($data) : null; + $parent = static::last($this->loopsStack); + $this->loopsStack[] = [ + 'index' => -1, + 'iteration' => 0, + 'remaining' => isset($length) ? $length + 1 : null, + 'count' => $length, + 'first' => true, + 'even' => true, + 'odd' => false, + 'last' => isset($length) ? $length == 1 : null, + 'depth' => \count($this->loopsStack) + 1, + 'parent' => $parent ? (object)$parent : null, + ]; + } + + /** + * Increment the top loop's indices. + * + * @return object + */ + public function incrementLoopIndices() + { + $c = \count($this->loopsStack) - 1; + $loop = &$this->loopsStack[$c]; + + $loop['index']++; + $loop['iteration']++; + $loop['first'] = $loop['index'] == 0; + $loop['even'] = $loop['index'] % 2 == 0; + $loop['odd'] = !$loop['even']; + if (isset($loop['count'])) { + $loop['remaining']--; + $loop['last'] = $loop['index'] == $loop['count'] - 1; + } + return (object)$loop; + } + + /** + * Pop a loop from the top of the loop stack. + * + * @return void + */ + public function popLoop() + { + \array_pop($this->loopsStack); + } + + /** + * Get an instance of the first loop in the stack. + * + * @return object + */ + public function getFirstLoop() + { + return ($last = static::last($this->loopsStack)) ? (object)$last : null; + } + + /** + * Get the rendered contents of a partial from a loop. + * + * @param string $view + * @param array $data + * @param string $iterator + * @param string $empty + * @return string + * @throws Exception + */ + public function renderEach($view, $data, $iterator, $empty = 'raw|') + { + $result = ''; + + if (\count($data) > 0) { + // If is actually data in the array, we will loop through the data and append + // an instance of the partial view to the final result HTML passing in the + // iterated value of this data array, allowing the views to access them. + foreach ($data as $key => $value) { + $data = ['key' => $key, $iterator => $value]; + $result .= $this->runChild($view, $data); + } + } elseif (static::startsWith($empty, 'raw|')) { + $result = \substr($empty, 4); + } else { + $result = $this->run($empty, []); + } + return $result; + } + + /** + * Run the blade engine. It returns the result of the code. + * + * @param string|null $view The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.blade") + * @param array $variables An associative arrays with the values to display. + * @return string + * @throws Exception + */ + public function run($view = null, $variables = []) + { + $mode = $this->getMode(); + + if ($view === null) { + $view = $this->viewStack; + } + $this->viewStack = null; + if ($view === null) { + $this->showError('run', 'BladeOne: view not set', true); + return ''; + } + + $forced = $mode & 1; // mode=1 forced:it recompiles no matter if the compiled file exists or not. + $runFast = $mode & 2; // mode=2 runfast: the code is not compiled neither checked and it runs directly the compiled + $this->sections = []; + if ($mode == 3) { + $this->showError('run', "we can't force and run fast at the same time", true); + } + return $this->runInternal($view, $variables, $forced, true, $runFast); + } + + /** + * It sets the current view
+ * This value is cleared when it is used (method run).
+ * Example:
+ *
+     * $this->setView('folder.view')->share(['var1'=>20])->run(); // or $this->run('folder.view',['var1'=>20]);
+     * 
+ * + * @param string $view + * @return BladeOne + */ + public function setView($view) + { + $this->viewStack = $view; + return $this; + } + + /** + * It injects a function, an instance, or a method class when a view is called.
+ * It could be stacked. If it sets null then it clears all definitions. + * Example:
+ *
+     * $this->composer('folder.view',function($bladeOne) { $bladeOne->share('newvalue','hi there'); });
+     * $this->composer('folder.view','namespace1\namespace2\SomeClass'); // SomeClass must exists and it must has the
+     *                                                                   // method 'composer'
+     * $this->composer('folder.*',$instance); // $instance must has the method called 'composer'
+     * $this->composer(); // clear all composer.
+     * 
+ * + * @param string|array|null $view It could contains wildcards (*). Example: 'aa.bb.cc','*.bb.cc','aa.bb.*','*.bb.*' + * + * @param callable|string|null $functionOrClass + * @return BladeOne + */ + public function composer($view = null, $functionOrClass = null) + { + if ($view === null && $functionOrClass === null) { + $this->composerStack = []; + return $this; + } + if (is_array($view)) { + foreach ($view as $v) { + $this->composerStack[$v] = $functionOrClass; + } + } else { + $this->composerStack[$view] = $functionOrClass; + } + + return $this; + } + + /** + * Start a component rendering process. + * + * @param string $name + * @param array $data + * @return void + */ + public function startComponent($name, array $data = []) + { + if (\ob_start()) { + $this->componentStack[] = $name; + + $this->componentData[$this->currentComponent()] = $data; + + $this->slots[$this->currentComponent()] = []; + } + } + + /** + * Get the index for the current component. + * + * @return int + */ + protected function currentComponent() + { + return \count($this->componentStack) - 1; + } + + /** + * Render the current component. + * + * @return string + * @throws Exception + */ + public function renderComponent() + { + //echo "
render
"; + $name = \array_pop($this->componentStack); + //return $this->runChild($name, $this->componentData()); + $cd = $this->componentData(); + $clean = array_keys($cd); + $r = $this->runChild($name, $cd); + // we clean variables defined inside the component (so they are garbaged when the component is used) + foreach ($clean as $key) { + unset($this->variables[$key]); + } + return $r; + } + + /** + * Get the data for the given component. + * + * @return array + */ + protected function componentData() + { + $cs = count($this->componentStack); + //echo "
"; + //echo "
data:
"; + //var_dump($this->componentData); + //echo "
datac:
"; + //var_dump(count($this->componentStack)); + return array_merge( + $this->componentData[$cs], + ['slot' => trim(ob_get_clean())], + $this->slots[$cs] + ); + } + + /** + * Start the slot rendering process. + * + * @param string $name + * @param string|null $content + * @return void + */ + public function slot($name, $content = null) + { + if (\count(\func_get_args()) === 2) { + $this->slots[$this->currentComponent()][$name] = $content; + } elseif (\ob_start()) { + $this->slots[$this->currentComponent()][$name] = ''; + + $this->slotStack[$this->currentComponent()][] = $name; + } + } + + /** + * Save the slot content for rendering. + * + * @return void + */ + public function endSlot() + { + static::last($this->componentStack); + + $currentSlot = \array_pop( + $this->slotStack[$this->currentComponent()] + ); + + $this->slots[$this->currentComponent()] + [$currentSlot] = \trim(\ob_get_clean()); + } + + /** + * @return string + */ + public function getPhpTag() + { + return $this->phpTag; + } + + /** + * @param string $phpTag + */ + public function setPhpTag($phpTag) + { + $this->phpTag = $phpTag; + } + + /** + * @return string + */ + public function getCurrentUser() + { + return $this->currentUser; + } + + /** + * @param string $currentUser + */ + public function setCurrentUser($currentUser) + { + $this->currentUser = $currentUser; + } + + /** + * @return string + */ + public function getCurrentRole() + { + return $this->currentRole; + } + + /** + * @param string $currentRole + */ + public function setCurrentRole($currentRole) + { + $this->currentRole = $currentRole; + } + + /** + * @return string[] + */ + public function getCurrentPermission() + { + return $this->currentPermission; + } + + /** + * @param string[] $currentPermission + */ + public function setCurrentPermission($currentPermission) + { + $this->currentPermission = $currentPermission; + } + + /** + * Returns the current base url without trailing slash. + * + * @return string + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * It sets the base url and it also calculates the relative path.
+ * The base url defines the "root" of the project, not always the level of the domain but it could be + * any folder.
+ * This value is used to calculate the relativity of the resources but it is also used to set the domain.
+ * Note: The trailing slash is removed automatically if it's present.
+ * Note: We should not use arguments or name of the script.
+ * Examples:
+ *
+     * $this->setBaseUrl('http://domain.dom/myblog');
+     * $this->setBaseUrl('http://domain.dom/corporate/erp');
+     * $this->setBaseUrl('http://domain.dom/blog.php?args=20'); // avoid this one.
+     * $this->setBaseUrl('http://another.dom');
+     * 
+ * + * @param string $baseUrl Example http://www.web.com/folder https://www.web.com/folder/anotherfolder + * @return BladeOne + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = \rtrim($baseUrl, '/'); // base with the url trimmed + $this->baseDomain = @parse_url($this->baseUrl)['host']; + $currentUrl = $this->getCurrentUrlCalculated(); + if ($currentUrl === '') { + $this->relativePath = ''; + return $this; + } + if (\strpos($currentUrl, $this->baseUrl) === 0) { + $part = \str_replace($this->baseUrl, '', $currentUrl); + $numf = \substr_count($part, '/') - 1; + $numf = ($numf > 10) ? 10 : $numf; // avoid overflow + $this->relativePath = ($numf < 0) ? '' : \str_repeat('../', $numf); + } else { + $this->relativePath = ''; + } + return $this; + } + + /** + * It gets the full current url calculated with the information sends by the user.
+ * Note: If we set baseurl, then it always uses the baseurl as domain (it's safe).
+ * Note: This information could be forged/faked by the end-user.
+ * Note: It returns empty '' if it is called in a command line interface / non-web.
+ * Note: It doesn't returns the user and password.
+ * @param bool $noArgs if true then it excludes the arguments. + * @return string + */ + public function getCurrentUrlCalculated($noArgs = false) + { + if (!isset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])) { + return ''; + } + $host = $this->baseDomain !== null ? $this->baseDomain : $_SERVER['HTTP_HOST']; // <-- it could be forged! + $link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http"); + $port = $_SERVER['SERVER_PORT']; + $port2 = (($link === 'http' && $port === '80') || ($link === 'https' && $port === '443')) ? '' : ':' . $port; + $link .= "://$host{$port2}$_SERVER[REQUEST_URI]"; + if ($noArgs) { + $link = @explode('?', $link)[0]; + } + return $link; + } + + /** + * It returns the relative path to the base url or empty if not set
+ * Example:
+ *
+     * // current url='http://domain.dom/page/subpage/web.php?aaa=2
+     * $this->setBaseUrl('http://domain.dom/');
+     * $this->getRelativePath(); // '../../'
+     * $this->setBaseUrl('http://domain.dom/');
+     * $this->getRelativePath(); // '../../'
+     * 
+ * Note:The relative path is calculated when we set the base url. + * + * @return string + * @see \eftec\bladeone\BladeOne::setBaseUrl + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * It gets the full current canonical url.
+ * Example: https://www.mysite.com/aaa/bb/php.php?aa=bb + *
    + *
  • It returns the $this->canonicalUrl value if is not null
  • + *
  • Otherwise, it returns the $this->currentUrl if not null
  • + *
  • Otherwise, the url is calculated with the information sends by the user
  • + *
+ * + * @return string|null + */ + public function getCanonicalUrl() + { + return $this->canonicalUrl !== null ? $this->canonicalUrl : $this->getCurrentUrl(); + } + + /** + * It sets the full canonical url.
+ * Example: https://www.mysite.com/aaa/bb/php.php?aa=bb + * + * @param string|null $canonUrl + * @return BladeOne + */ + public function setCanonicalUrl($canonUrl = null) + { + $this->canonicalUrl = $canonUrl; + return $this; + } + + /** + * It gets the full current url
+ * Example: https://www.mysite.com/aaa/bb/php.php?aa=bb + *
    + *
  • It returns the $this->currentUrl if not null
  • + *
  • Otherwise, the url is calculated with the information sends by the user
  • + *
+ * + * @param bool $noArgs if true then it ignore the arguments. + * @return string|null + */ + public function getCurrentUrl($noArgs = false) + { + $link = $this->currentUrl !== null ? $this->currentUrl : $this->getCurrentUrlCalculated(); + if ($noArgs) { + $link = @explode('?', $link)[0]; + } + return $link; + } + + /** + * It sets the full current url.
+ * Example: https://www.mysite.com/aaa/bb/php.php?aa=bb + * Note: If the current url is not set, then the system could calculate the current url. + * + * @param string|null $currentUrl + * @return BladeOne + */ + public function setCurrentUrl($currentUrl = null) + { + $this->currentUrl = $currentUrl; + return $this; + } + + /** + * If true then it optimizes the result (it removes tab and extra spaces). + * + * @param bool $bool + * @return BladeOne + */ + public function setOptimize($bool = false) + { + $this->optimize = $bool; + return $this; + } + + /** + * It sets the callback function for authentication. It is used by @can and @cannot + * + * @param callable $fn + */ + public function setCanFunction(callable $fn) + { + $this->authCallBack = $fn; + } + + /** + * It sets the callback function for authentication. It is used by @canany + * + * @param callable $fn + */ + public function setAnyFunction(callable $fn) + { + $this->authAnyCallBack = $fn; + } + + /** + * It sets the callback function for errors. It is used by @error + * + * @param callable $fn + */ + public function setErrorFunction(callable $fn) + { + $this->errorCallBack = $fn; + } + + //
+ // + + /** + * Get the entire loop stack. + * + * @return array + */ + public function getLoopStack() + { + return $this->loopsStack; + } + + /** + * It adds a string inside a quoted string
+ * example:
+ *
+     * $this->addInsideQuote("'hello'"," world"); // 'hello world'
+     * $this->addInsideQuote("hello"," world"); // hello world
+     * 
+ * + * @param $quoted + * @param $newFragment + * @return string + */ + public function addInsideQuote($quoted, $newFragment) + { + if ($this->isQuoted($quoted)) { + return substr($quoted, 0, -1) . $newFragment . substr($quoted, -1); + } + return $quoted . $newFragment; + } + + /** + * Return true if the string is a php variable (it starts with $) + * + * @param string|null $text + * @return bool + */ + public function isVariablePHP($text) + { + if (!$text || strlen($text) < 2) { + return false; + } + return $text[0] === '$'; + } + + /** + * Its the same than @_e, however it parses the text (using sprintf). + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _ef($phrase) + { + $argv = \func_get_args(); + $r = $this->_e($phrase); + $argv[0] = $r; // replace the first argument with the translation. + $result = @sprintf(...$argv); + return ($result == false) ? $r : $result; + } + + /** + * Tries to translate the word if its in the array defined by BladeOneLang::$dictionary + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _e($phrase) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return $phrase; + } + + return static::$dictionary[$phrase]; + } + + /** + * Log a missing translation into the file $this->missingLog.
+ * If the file is not defined, then it doesn't write the log. + * + * @param string $txt Message to write on. + */ + private function missingTranslation($txt) + { + if (!$this->missingLog) { + return; // if there is not a file assigned then it skips saving. + } + $fz = @\filesize($this->missingLog); + if (\is_object($txt) || \is_array($txt)) { + $txt = \print_r($txt, true); + } + // Rewrite file if more than 100000 bytes + $mode = ($fz > 100000) ? 'w' : 'a'; + $fp = \fopen($this->missingLog, $mode); + \fwrite($fp, $txt . "\n"); + \fclose($fp); + } + + /** + * if num is more than one then it returns the phrase in plural, otherwise the phrase in singular. + * Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People' + * + * @param string $phrase + * @param string $phrases + * @param int $num + * + * @return string + */ + public function _n($phrase, $phrases, $num = 0) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return ($num <= 1) ? $phrase : $phrases; + } + + return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases); + } + + /** + * @param $expression + * @return string + * @see \eftec\bladeone\BladeOne::getCanonicalUrl + */ + public function compileCanonical($expression = null) + { + return ''; + } + + /** + * @param $expression + * @return string + * @see \eftec\bladeone\BladeOne::getBaseUrl() + */ + public function compileBase($expression = null) + { + return ''; + } + + protected function compileUse($expression) + { + return $this->phpTag . 'use ' . $this->stripParentheses($expression) . '; ?>'; + } + + protected function compileSwitch($expression) + { + $this->switchCount++; + $this->firstCaseInSwitch = true; + return $this->phpTag . "switch $expression {"; + } + //
+ // + + protected function compileDump($expression) + { + return $this->phpTagEcho . " \$this->dump$expression;?>"; + } + + protected function compileRelative($expression) + { + return $this->phpTagEcho . " \$this->relative$expression;?>"; + } + + protected function compileMethod($expression) + { + $v = $this->stripParentheses($expression); + + return ""; + } + + protected function compilecsrf($expression = null) + { + $expression = ($expression === null) ? "'_token'" : $expression; + return ""; + } + + protected function compileDd($expression) + { + return $this->phpTagEcho . " '
'; var_dump$expression; echo '
';?>"; + } + + /** + * Execute the case tag. + * + * @param $expression + * @return string + */ + protected function compileCase($expression) + { + if ($this->firstCaseInSwitch) { + $this->firstCaseInSwitch = false; + return 'case ' . $expression . ': ?>'; + } + return $this->phpTag . "case $expression: ?>"; + } + + /** + * Compile the while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileWhile($expression) + { + return $this->phpTag . "while$expression: ?>"; + } + + /** + * default tag used for switch/case + * + * @return string + */ + protected function compileDefault() + { + if ($this->firstCaseInSwitch) { + return $this->showError('@default', '@switch without any @case', true); + } + return $this->phpTag . 'default: ?>'; + } + + protected function compileEndSwitch() + { + --$this->switchCount; + if ($this->switchCount < 0) { + return $this->showError('@endswitch', 'Missing @switch', true); + } + return $this->phpTag . '} // end switch ?>'; + } + + /** + * Compile while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInject($expression) + { + $ex = $this->stripParentheses($expression); + $p0 = \strpos($ex, ','); + if ($p0 == false) { + $var = $this->stripQuotes($ex); + $namespace = ''; + } else { + $var = $this->stripQuotes(\substr($ex, 0, $p0)); + $namespace = $this->stripQuotes(\substr($ex, $p0 + 1)); + } + return $this->phpTag . "\$$var = \$this->injectClass('$namespace', '$var'); ?>"; + } + + /** + * Remove first and end quote from a quoted string of text + * + * @param mixed $text + * @return null|string|string[] + */ + public function stripQuotes($text) + { + if (!$text || strlen($text) < 2) { + return $text; + } + $text = trim($text); + $p0 = $text[0]; + $p1 = \substr($text, -1); + if ($p0 === $p1 && ($p0 === '"' || $p0 === "'")) { + return \substr($text, 1, -1); + } + return $text; + } + + /** + * Execute the user defined extensions. + * + * @param string $value + * @return string + */ + protected function compileExtensions($value) + { + foreach ($this->extensions as $compiler) { + $value = $compiler($value, $this); + } + return $value; + } + + /** + * Compile Blade comments into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileComments($value) + { + $pattern = \sprintf('/%s--(.*?)--%s/s', $this->contentTags[0], $this->contentTags[1]); + return \preg_replace($pattern, $this->phpTag . '/*$1*/ ?>', $value); + } + + /** + * Compile Blade echos into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEchos($value) + { + foreach ($this->getEchoMethods() as $method => $length) { + $value = $this->$method($value); + } + return $value; + } + + /** + * Get the echo methods in the proper order for compilation. + * + * @return array + */ + protected function getEchoMethods() + { + $methods = [ + 'compileRawEchos' => \strlen(\stripcslashes($this->rawTags[0])), + 'compileEscapedEchos' => \strlen(\stripcslashes($this->escapedTags[0])), + 'compileRegularEchos' => \strlen(\stripcslashes($this->contentTags[0])), + ]; + \uksort($methods, static function ($method1, $method2) use ($methods) { + // Ensure the longest tags are processed first + if ($methods[$method1] > $methods[$method2]) { + return -1; + } + if ($methods[$method1] < $methods[$method2]) { + return 1; + } + // Otherwise give preference to raw tags (assuming they've overridden) + if ($method1 === 'compileRawEchos') { + return -1; + } + if ($method2 === 'compileRawEchos') { + return 1; + } + if ($method1 === 'compileEscapedEchos') { + return -1; + } + if ($method2 === 'compileEscapedEchos') { + return 1; + } + throw new BadMethodCallException("Method [$method1] not defined"); + }); + return $methods; + } + + /** + * Compile Blade statements that start with "@". + * + * @param string $value + * + * @return array|string|string[]|null + */ + protected function compileStatements($value) + { + /** + * @param array $match + * [0]=full expression with @ and parenthesis + * [1]=expression without @ and argument + * [2]=???? + * [3]=argument with parenthesis and without the first @ + * [4]=argument without parenthesis. + * + * @return mixed|string + */ + $callback = function ($match) { + if (static::contains($match[1], '@')) { + // @@escaped tag + $match[0] = isset($match[3]) ? $match[1] . $match[3] : $match[1]; + } else { + if (strpos($match[1], '::') !== false) { + // Someclass::method + return $this->compileStatementClass($match); + } + if (isset($this->customDirectivesRT[$match[1]])) { + if ($this->customDirectivesRT[$match[1]] == true) { + $match[0] = $this->compileStatementCustom($match); + } else { + $match[0] = \call_user_func( + $this->customDirectives[$match[1]], + $this->stripParentheses(static::get($match, 3)) + ); + } + } elseif (\method_exists($this, $method = 'compile' . \ucfirst($match[1]))) { + // it calls the function compile + $match[0] = $this->$method(static::get($match, 3)); + } else { + return $match[0]; + } + } + return isset($match[3]) ? $match[0] : $match[0] . $match[2]; + }; + /* return \preg_replace_callback('/\B@(@?\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); */ + return preg_replace_callback('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '') { + if (\function_exists('mb_strpos')) { + if (\mb_strpos($haystack, $needle) !== false) { + return true; + } + } elseif (\strpos($haystack, $needle) !== false) { + return true; + } + } + } + + return false; + } + + private function compileStatementClass($match) + { + if (isset($match[3])) { + return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . $match[3] . '; ?>'; + } + + return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . '(); ?>'; + } + + /** + * Util method to fix namespace of a class
+ * Example: "SomeClass::method()" -> "\namespace\SomeClass::method()"
+ * + * @param string $text + * + * @return string + * @see \eftec\bladeone\BladeOne::$aliasClasses + */ + private function fixNamespaceClass($text) + { + if (strpos($text, '::') === false) { + return $text; + } + $classPart = explode('::', $text, 2); + if (isset($this->aliasClasses[$classPart[0]])) { + $classPart[0] = $this->aliasClasses[$classPart[0]]; + } + return $classPart[0] . '::' . $classPart[1]; + } + + /** + * For compile custom directive at runtime. + * + * @param $match + * @return string + */ + protected function compileStatementCustom($match) + { + $v = $this->stripParentheses(static::get($match, 3)); + $v = ($v == '') ? '' : ',' . $v; + return $this->phpTag . 'call_user_func($this->customDirectives[\'' . $match[1] . '\']' . $v . '); ?>'; + } + + /** + * Get an item from an array using "dot" notation. + * + * @param ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + $accesible = \is_array($array) || $array instanceof ArrayAccess; + if (!$accesible) { + return static::value($default); + } + if (\is_null($key)) { + return $array; + } + if (static::exists($array, $key)) { + return $array[$key]; + } + foreach (\explode('.', $key) as $segment) { + if (static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return static::value($default); + } + } + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + return \array_key_exists($key, $array); + } + + /** + * This method removes the parenthesis of the expression and parse the arguments. + * @param string $expression + * @return array + */ + protected function getArgs($expression) + { + return $this->parseArgs($this->stripParentheses($expression), ' '); + } + + /** + * It separates a string using a separator and excluding quotes and double quotes. + * + * @param string $text + * @param string $separator + * @return array + */ + public function parseArgs($text, $separator = ',') + { + if ($text === null || $text === '') { + return []; //nothing to convert. + } + $chars = str_split($text); + $parts = []; + $nextpart = ''; + $strL = count($chars); + /** @noinspection ForeachInvariantsInspection */ + for ($i = 0; $i < $strL; $i++) { + $char = $chars[$i]; + if ($char === '"' || $char === "'") { + $inext = strpos($text, $char, $i + 1); + $inext = $inext === false ? $strL : $inext; + $nextpart .= substr($text, $i, $inext - $i + 1); + $i = $inext; + } else { + $nextpart .= $char; + } + if ($char === $separator) { + $parts[] = substr($nextpart, 0, -1); + $nextpart = ''; + } + } + if ($nextpart !== '') { + $parts[] = $nextpart; + } + $result = []; + foreach ($parts as $part) { + $r = explode('=', $part, 2); + $result[trim($r[0])] = count($r) === 2 ? trim($r[1]) : null; + } + return $result; + } + + /** + * Compile the "raw" echo statements. + * + * @param string $value + * @return string + */ + protected function compileRawEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + return $matches[1] ? \substr( + $matches[0], + 1 + ) : $this->phpTagEcho . $this->compileEchoDefaults($matches[2]) . '; ?>' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the default values for the echo statement. + * + * @param string $value + * @return string + */ + protected function compileEchoDefaults($value) + { + $result = \preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/s', 'isset($1) ? $1 : $2', $value); + if (!$this->pipeEnable) { + return $this->fixNamespaceClass($result); + } + return $this->pipeDream($this->fixNamespaceClass($result)); + } + + /** + * It converts a string separated by pipes | into an filtered expression.
+ * If the method exists (as directive), then it is used
+ * If the method exists (in this class) then it is used
+ * Otherwise, it uses a global function.
+ * If you want to escape the "|", then you could use "/|"
+ * Note: It only works if $this->pipeEnable=true and by default it is false
+ * Example:
+ *
+     * $this->pipeDream('$name | strtolower | substr:0,4'); // strtolower(substr($name ,0,4)
+     * $this->pipeDream('$name| getMode') // $this->getMode($name)
+     * 
+ * + * @param string $result + * @return string + * @\eftec\bladeone\BladeOne::$pipeEnable + */ + protected function pipeDream($result) + { + $array = preg_split('~\\\\.(*SKIP)(*FAIL)|\|~s', $result); + $c = count($array) - 1; // base zero. + if ($c === 0) { + return $result; + } + + $prev = ''; + for ($i = $c; $i >= 1; $i--) { + $r = @explode(':', $array[$i], 2); + $fnName = trim($r[0]); + $fnNameF = $fnName[0]; // first character + if ($fnNameF === '"' || $fnNameF === '\'' || $fnNameF === '$' || is_numeric($fnNameF)) { + $fnName = '!isset(' . $array[0] . ') ? ' . $fnName . ' : '; + } elseif (isset($this->customDirectives[$fnName])) { + $fnName = '$this->customDirectives[\'' . $fnName . '\']'; + } elseif (method_exists($this, $fnName)) { + $fnName = '$this->' . $fnName; + } + if ($i === 1) { + $prev = $fnName . '(' . $array[0]; + if (count($r) === 2) { + $prev .= ',' . $r[1]; + } + $prev .= ')'; + } else { + $prev = $fnName . '(' . $prev; + if (count($r) === 2) { + if ($i === 2) { + $prev .= ','; + } + $prev .= $r[1] . ')'; + } + } + } + return $prev; + } + + /** + * Compile the "regular" echo statements. {{ }} + * + * @param string $value + * @return string + */ + protected function compileRegularEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + $wrapped = \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])); + return $matches[1] ? \substr($matches[0], 1) : $this->phpTagEcho . $wrapped . '; ?>' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the escaped echo statements. {!! !!} + * + * @param string $value + * @return string + */ + protected function compileEscapedEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + + return $matches[1] ? $matches[0] : $this->phpTag + . \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])) . '; ?>' + . $whitespace; + //return $matches[1] ? $matches[0] : $this->phpTag + // . 'echo static::e(' . $this->compileEchoDefaults($matches[2]) . '); ? >' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the each statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEach($expression) + { + return $this->phpTagEcho . "\$this->renderEach$expression; ?>"; + } + + protected function compileSet($expression) + { + //$segments = \explode('=', \preg_replace("/[()\\\']/", '', $expression)); + $segments = \explode('=', $this->stripParentheses($expression)); + $value = (\count($segments) >= 2) ? '=@' . $segments[1] : '++'; + return $this->phpTag . \trim($segments[0]) . $value . ';?>'; + } + + /** + * Compile the yield statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileYield($expression) + { + return $this->phpTagEcho . "\$this->yieldContent$expression; ?>"; + } + + /** + * Compile the show statements into valid PHP. + * + * @return string + */ + protected function compileShow() + { + return $this->phpTagEcho . '$this->yieldSection(); ?>'; + } + + /** + * Compile the section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSection($expression) + { + return $this->phpTag . "\$this->startSection$expression; ?>"; + } + + /** + * Compile the append statements into valid PHP. + * + * @return string + */ + protected function compileAppend() + { + return $this->phpTag . '$this->appendSection(); ?>'; + } + + /** + * Compile the auth statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileAuth($expression = '') + { + $role = $this->stripParentheses($expression); + if ($role == '') { + return $this->phpTag . 'if(isset($this->currentUser)): ?>'; + } + + return $this->phpTag . "if(isset(\$this->currentUser) && \$this->currentRole==$role): ?>"; + } + + /** + * Compile the elseauth statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseAuth($expression = '') + { + $role = $this->stripParentheses($expression); + if ($role == '') { + return $this->phpTag . 'else: ?>'; + } + + return $this->phpTag . "elseif(isset(\$this->currentUser) && \$this->currentRole==$role): ?>"; + } + + /** + * Compile the end-auth statements into valid PHP. + * + * @return string + */ + protected function compileEndAuth() + { + return $this->phpTag . 'endif; ?>'; + } + + protected function compileCan($expression) + { + $v = $this->stripParentheses($expression); + return $this->phpTag . 'if (call_user_func($this->authCallBack,' . $v . ')): ?>'; + } + + /** + * Compile the else statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseCan($expression = '') + { + $v = $this->stripParentheses($expression); + if ($v) { + return $this->phpTag . 'elseif (call_user_func($this->authCallBack,' . $v . ')): ?>'; + } + + return $this->phpTag . 'else: ?>'; + } + //
+ // + + protected function compileCannot($expression) + { + $v = $this->stripParentheses($expression); + return $this->phpTag . 'if (!call_user_func($this->authCallBack,' . $v . ')): ?>'; + } + + /** + * Compile the elsecannot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseCannot($expression = '') + { + $v = $this->stripParentheses($expression); + if ($v) { + return $this->phpTag . 'elseif (!call_user_func($this->authCallBack,' . $v . ')): ?>'; + } + + return $this->phpTag . 'else: ?>'; + } + + /** + * Compile the canany statements into valid PHP. + * canany(['edit','write']) + * + * @param $expression + * @return string + */ + protected function compileCanAny($expression) + { + $role = $this->stripParentheses($expression); + return $this->phpTag . 'if (call_user_func($this->authAnyCallBack,' . $role . ')): ?>'; + } + + /** + * Compile the else statements into valid PHP. + * + * @param $expression + * @return string + */ + protected function compileElseCanAny($expression) + { + $role = $this->stripParentheses($expression); + if ($role == '') { + return $this->phpTag . 'else: ?>'; + } + return $this->phpTag . 'elseif (call_user_func($this->authAnyCallBack,' . $role . ')): ?>'; + } + + /** + * Compile the guest statements into valid PHP. + * + * @param null $expression + * @return string + */ + protected function compileGuest($expression = null) + { + if ($expression === null) { + return $this->phpTag . 'if(!isset($this->currentUser)): ?>'; + } + + $role = $this->stripParentheses($expression); + if ($role == '') { + return $this->phpTag . 'if(!isset($this->currentUser)): ?>'; + } + + return $this->phpTag . "if(!isset(\$this->currentUser) || \$this->currentRole!=$role): ?>"; + } + + /** + * Compile the else statements into valid PHP. + * + * @param $expression + * @return string + */ + protected function compileElseGuest($expression) + { + $role = $this->stripParentheses($expression); + if ($role == '') { + return $this->phpTag . 'else: ?>'; + } + + return $this->phpTag . "elseif(!isset(\$this->currentUser) || \$this->currentRole!=$role): ?>"; + } + + /** + * /** + * Compile the end-auth statements into valid PHP. + * + * @return string + */ + protected function compileEndGuest() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-section statements into valid PHP. + * + * @return string + */ + protected function compileEndsection() + { + return $this->phpTag . '$this->stopSection(); ?>'; + } + + /** + * Compile the stop statements into valid PHP. + * + * @return string + */ + protected function compileStop() + { + return $this->phpTag . '$this->stopSection(); ?>'; + } + + /** + * Compile the overwrite statements into valid PHP. + * + * @return string + */ + protected function compileOverwrite() + { + return $this->phpTag . '$this->stopSection(true); ?>'; + } + + /** + * Compile the unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnless($expression) + { + return $this->phpTag . "if ( ! $expression): ?>"; + } + + /** + * Compile the User statements into valid PHP. + * + * @return string + */ + protected function compileUser() + { + return $this->phpTagEcho . "'" . $this->currentUser . "'; ?>"; + } + + /** + * Compile the endunless statements into valid PHP. + * + * @return string + */ + protected function compileEndunless() + { + return $this->phpTag . 'endif; ?>'; + } + // + // + + /** + * @error('key') + * + * @param $expression + * @return string + */ + protected function compileError($expression) + { + $key = $this->stripParentheses($expression); + return $this->phpTag . '$message = call_user_func($this->errorCallBack,' . $key . '); if ($message): ?>'; + } + + /** + * Compile the end-error statements into valid PHP. + * + * @return string + */ + protected function compileEndError() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the else statements into valid PHP. + * + * @return string + */ + protected function compileElse() + { + return $this->phpTag . 'else: ?>'; + } + + /** + * Compile the for statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileFor($expression) + { + return $this->phpTag . "for$expression: ?>"; + } + // + // + + /** + * Compile the foreach statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForeach($expression) + { + //\preg_match('/\( *(.*) * as *([^\)]*)/', $expression, $matches); + \preg_match('/\( *(.*) * as *([^)]*)/', $expression, $matches); + $iteratee = \trim($matches[1]); + $iteration = \trim($matches[2]); + $initLoop = "\$__currentLoopData = $iteratee; \$this->addLoop(\$__currentLoopData);\$this->getFirstLoop();\n"; + $iterateLoop = '$loop = $this->incrementLoopIndices(); '; + return $this->phpTag . "$initLoop foreach(\$__currentLoopData as $iteration): $iterateLoop ?>"; + } + + /** + * Compile a split of a foreach cycle. Used for example when we want to separate limites each "n" elements. + * + * @param string $expression + * @return string + */ + protected function compileSplitForeach($expression) + { + return $this->phpTagEcho . '$this::splitForeach' . $expression . '; ?>'; + } + + /** + * Compile the break statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileBreak($expression) + { + return $expression ? $this->phpTag . "if$expression break; ?>" : $this->phpTag . 'break; ?>'; + } + + /** + * Compile the continue statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileContinue($expression) + { + return $expression ? $this->phpTag . "if$expression continue; ?>" : $this->phpTag . 'continue; ?>'; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForelse($expression) + { + $empty = '$__empty_' . ++$this->forelseCounter; + return $this->phpTag . "$empty = true; foreach$expression: $empty = false; ?>"; + } + + /** + * Compile the if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIf($expression) + { + return $this->phpTag . "if$expression: ?>"; + } + // + // + + /** + * Compile the else-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseif($expression) + { + return $this->phpTag . "elseif$expression: ?>"; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression empty if it's inside a for loop. + * @return string + */ + protected function compileEmpty($expression = '') + { + if ($expression == '') { + $empty = '$__empty_' . $this->forelseCounter--; + return $this->phpTag . "endforeach; if ($empty): ?>"; + } + return $this->phpTag . "if (empty$expression): ?>"; + } + + /** + * Compile the has section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileHasSection($expression) + { + return $this->phpTag . "if (! empty(trim(\$this->yieldContent$expression))): ?>"; + } + + /** + * Compile the end-while statements into valid PHP. + * + * @return string + */ + protected function compileEndwhile() + { + return $this->phpTag . 'endwhile; ?>'; + } + + /** + * Compile the end-for statements into valid PHP. + * + * @return string + */ + protected function compileEndfor() + { + return $this->phpTag . 'endfor; ?>'; + } + + /** + * Compile the end-for-each statements into valid PHP. + * + * @return string + */ + protected function compileEndforeach() + { + return $this->phpTag . 'endforeach; $this->popLoop(); $loop = $this->getFirstLoop(); ?>'; + } + + /** + * Compile the end-can statements into valid PHP. + * + * @return string + */ + protected function compileEndcan() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-can statements into valid PHP. + * + * @return string + */ + protected function compileEndcanany() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-cannot statements into valid PHP. + * + * @return string + */ + protected function compileEndcannot() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-if statements into valid PHP. + * + * @return string + */ + protected function compileEndif() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-for-else statements into valid PHP. + * + * @return string + */ + protected function compileEndforelse() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the raw PHP statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compilePhp($expression) + { + return $expression ? $this->phpTag . "$expression; ?>" : $this->phpTag . ''; + } + + // + + /** + * Compile end-php statement into valid PHP. + * + * @return string + */ + protected function compileEndphp() + { + return ' ?>'; + } + + /** + * Compile the unset statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnset($expression) + { + return $this->phpTag . "unset$expression; ?>"; + } + + /** + * Compile the extends statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileExtends($expression) + { + $expression = $this->stripParentheses($expression); + // $_shouldextend avoids to runchild if it's not evaluated. + // For example @if(something) @extends('aaa.bb') @endif() + // If something is false then it's not rendered at the end (footer) of the script. + $this->uidCounter++; + $data = $this->phpTag . 'if (isset($_shouldextend[' . $this->uidCounter . '])) { echo $this->runChild(' . $expression . '); } ?>'; + $this->footer[] = $data; + return $this->phpTag . '$_shouldextend[' . $this->uidCounter . ']=1; ?>'; + } + + + /** + * Execute the @parent command. This operation works in tandem with extendSection + * + * @return string + * @see extendSection + */ + protected function compileParent() + { + return $this->PARENTKEY; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInclude($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->runChild(' . $expression . '); ?>'; + } + + /** + * It loads an compiled template and paste inside the code.
+ * It uses more disk space but it decreases the number of includes
+ * + * @param $expression + * @return string + * @throws Exception + */ + protected function compileIncludeFast($expression) + { + $expression = $this->stripParentheses($expression); + $ex = $this->stripParentheses($expression); + $exp = \explode(',', $ex); + $file = $this->stripQuotes(isset($exp[0]) ? $exp[0] : null); + $fileC = $this->getCompiledFile($file); + if (!@\is_file($fileC)) { + // if the file doesn't exist then it's created + $this->compile($file, true); + } + return $this->getFile($fileC); + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeIf($expression) + { + return $this->phpTag . 'if ($this->templateExist' . $expression . ') echo $this->runChild' . $expression . '; ?>'; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeWhen($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->includeWhen(' . $expression . '); ?>'; + } + + /** + * Compile the includefirst statement + * + * @param string $expression + * @return string + */ + protected function compileIncludeFirst($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->includeFirst(' . $expression . '); ?>'; + } + + /** + * Compile the {@}compilestamp statement. + * + * @param string $expression + * + * @return false|string + */ + protected function compileCompileStamp($expression) + { + $expression = $this->stripQuotes($this->stripParentheses($expression)); + $expression = ($expression === '') ? 'Y-m-d H:i:s' : $expression; + return date($expression); + } + + /** + * compile the {@}viewname statement
+ * {@}viewname('compiled') returns the full compiled path + * {@}viewname('template') returns the full template path + * {@}viewname('') returns the view name. + * + * @param mixed $expression + * + * @return string + */ + protected function compileViewName($expression) + { + $expression = $this->stripQuotes($this->stripParentheses($expression)); + switch ($expression) { + case 'compiled': + return $this->getCompiledFile($this->fileName); + case 'template': + return $this->getTemplateFile($this->fileName); + default: + return $this->fileName; + } + } + + /** + * Compile the stack statements into the content. + * + * @param string $expression + * @return string + */ + protected function compileStack($expression) + { + return $this->phpTagEcho . "\$this->yieldPushContent$expression; ?>"; + } + + /** + * Compile the endpush statements into valid PHP. + * + * @return string + */ + protected function compileEndpush() + { + return $this->phpTag . '$this->stopPush(); ?>'; + } + + /** + * Compile the endpushonce statements into valid PHP. + * + * @return string + */ + protected function compileEndpushOnce() + { + return $this->phpTag . '$this->stopPush(); endif; ?>'; + } + + /** + * Compile the endpush statements into valid PHP. + * + * @return string + */ + protected function compileEndPrepend() + { + return $this->phpTag . '$this->stopPrepend(); ?>'; + } + + /** + * Compile the component statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileComponent($expression) + { + return $this->phpTag . " \$this->startComponent$expression; ?>"; + } + + /** + * Compile the end-component statements into valid PHP. + * + * @return string + */ + protected function compileEndComponent() + { + return $this->phpTagEcho . '$this->renderComponent(); ?>'; + } + + /** + * Compile the slot statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSlot($expression) + { + return $this->phpTag . " \$this->slot$expression; ?>"; + } + + /** + * Compile the end-slot statements into valid PHP. + * + * @return string + */ + protected function compileEndSlot() + { + return $this->phpTag . ' $this->endSlot(); ?>'; + } + + protected function compileAsset($expression) + { + return $this->phpTagEcho . " (isset(\$this->assetDict[$expression]))?\$this->assetDict[$expression]:\$this->baseUrl.'/'.$expression; ?>"; + } + + protected function compileJSon($expression) + { + $parts = \explode(',', $this->stripParentheses($expression)); + $options = isset($parts[1]) ? \trim($parts[1]) : JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; + $depth = isset($parts[2]) ? \trim($parts[2]) : 512; + return $this->phpTagEcho . " json_encode($parts[0], $options, $depth); ?>"; + } + //
+ + // + + protected function compileIsset($expression) + { + return $this->phpTag . "if(isset$expression): ?>"; + } + + protected function compileEndIsset() + { + return $this->phpTag . 'endif; ?>'; + } + + protected function compileEndEmpty() + { + return $this->phpTag . 'endif; ?>'; + } + + // + + /** + * Resolve a given class using the injectResolver callable. + * + * @param string $className + * @param string|null $variableName + * @return mixed + */ + protected function injectClass($className, $variableName = null) + { + if (isset($this->injectResolver)) { + return call_user_func($this->injectResolver, $className, $variableName); + } + + $fullClassName = $className . "\\" . $variableName; + return new $fullClassName(); + } + + /** + * Used for @_e directive. + * + * @param $expression + * + * @return string + */ + protected function compile_e($expression) + { + return $this->phpTagEcho . "\$this->_e$expression; ?>"; + } + + /** + * Used for @_ef directive. + * + * @param $expression + * + * @return string + */ + protected function compile_ef($expression) + { + return $this->phpTagEcho . "\$this->_ef$expression; ?>"; + } + + // + + /** + * Used for @_n directive. + * + * @param $expression + * + * @return string + */ + protected function compile_n($expression) + { + return $this->phpTagEcho . "\$this->_n$expression; ?>"; + } + + // +} diff --git a/vendor/eftec/bladeone/lib/BladeOneCache.php b/vendor/eftec/bladeone/lib/BladeOneCache.php new file mode 100644 index 0000000..1faa98a --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneCache.php @@ -0,0 +1,322 @@ + + * @ cache([cacheid],[duration=86400]). The id is optional. The duration of the cache is in seconds + * // content here + * @ endcache() + * + * It also adds a new function (optional) to the business or logic layer + * + * if ($blade->cacheExpired('hellocache',1,5)) { //'helloonecache' =template, =1 id cache, 5=duration (seconds) + * // cache expired, so we should do some stuff (such as read from the database) + * } + * + * + * @package BladeOneCache + * @version 3.42 2020-04-25 + * @link https://github.com/EFTEC/BladeOne + * @author Jorge Patricio Castro Castillo + */ +trait BladeOneCache +{ + protected $curCacheId = 0; + protected $curCacheDuration = 0; + protected $curCachePosition = 0; + protected $cacheRunning = false; + protected $cachePageRunning = false; + protected $cacheLog; + /** + * @var array avoids to compare the file different times. It also avoids race conditions. + */ + private $cacheExpired = []; + /** + * @var string=['get','post','getpost','request',null][$i] + */ + private $cacheStrategy; + /** @var array|null */ + private $cacheStrategyIndex; + + /** + * @return null|string $cacheStrategy=['get','post','getpost','request',null][$i] + */ + public function getCacheStrategy() + { + return $this->cacheStrategy; + } + + /** + * It sets the cache log. If not cache log then it does not generates a log file
+ * The cache log stores each time a template is creates or expired.
+ * + * @param string $file + */ + public function setCacheLog($file) + { + $this->cacheLog=$file; + } + + public function writeCacheLog($txt, $nivel) + { + if (!$this->cacheLog) { + return; // if there is not a file assigned then it skips saving. + } + $fz = @filesize($this->cacheLog); + if (is_object($txt) || is_array($txt)) { + $txt = print_r($txt, true); + } + // Rewrite file if more than 100000 bytes + $mode=($fz > 100000) ? 'w':'a'; + $fp = fopen($this->cacheLog, $mode); + if ($fp === false) { + return; + } + switch ($nivel) { + case 1: + $txtNivel='expired'; + break; + case 2: + $txtNivel='new'; + break; + default: + $txtNivel='other'; + } + $txtarg=json_encode($this->cacheUniqueGUID(false)); + fwrite($fp, date('c') . "\t$txt\t$txtNivel\t$txtarg\n"); + fclose($fp); + } + + /** + * It sets the strategy of the cache page. + * + * @param null|string $cacheStrategy =['get','post','getpost','request',null][$i] + * @param array|null $index if null then it reads all indexes. If not, it reads a indexes. + */ + public function setCacheStrategy($cacheStrategy, $index = null) + { + $this->cacheStrategy = $cacheStrategy; + $this->cacheStrategyIndex = $index; + } + + /** + * It obtains an unique GUID based in:
+ * get= parameters from the url
+ * post= parameters sends via post
+ * getpost = a mix between get and post
+ * request = get, post and cookies (including sessions)
+ * MD5 could generate colisions (2^64 = 18,446,744,073,709,551,616) but the end hash is the sum of the hash of + * the page + this GUID. + * + * @param bool $serialize if true then it serializes using md5 + * @return string + */ + private function cacheUniqueGUID($serialize = true) + { + switch ($this->cacheStrategy) { + case 'get': + $r = $_GET; + break; + case 'post': + $r = $_POST; + break; + case 'getpost': + $arr = array_merge($_GET, $_POST); + $r = $arr; + break; + case 'request': + $r = $_REQUEST; + break; + default: + $r = null; + } + if ($this->cacheStrategyIndex === null || !is_array($r)) { + $r= serialize($r); + } else { + $copy=[]; + foreach ($r as $key => $item) { + if (in_array($key, $this->cacheStrategyIndex, true)) { + $copy[$key]=$item; + } + } + $r=serialize($copy); + } + return $serialize===true ? md5($r): $r; + } + + public function compileCache($expression) + { + // get id of template + // if the file exists then + // compare date. + // if the date is too old then re-save. + // else + // save for the first time. + + return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>"; + } + + public function compileEndCache($expression) + { + return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>"; + } + + /** + * It get the filename of the compiled file (cached). If cache is not enabled, then it + * returns the regular file. + * + * @param string $view + * @return string The full filename + */ + private function getCompiledFileCache($view) + { + $id = $this->cacheUniqueGUID(); + if ($id !== null) { + return str_replace($this->compileExtension, '_cache' . $id + . $this->compileExtension, $this->getCompiledFile($view)); + } + return $this->getCompiledFile($view); + } + + /** + * run the blade engine. It returns the result of the code. + * + * @param string $view The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.blade") + * @param array $variables An associative arrays with the values to display. + * @param int $ttl time to live (in second). + * @return string + */ + public function runCache($view, $variables = [], $ttl = 86400) + { + $this->cachePageRunning = true; + $cacheStatus=$this->cachePageExpired($view, $ttl); + if ($cacheStatus!==0) { + $this->writeCacheLog($view, $cacheStatus); + $this->cacheStart('_page_', $ttl); + $content = $this->run($view, $variables); // if no cache, then it runs normally. + $this->fileName = $view; // sometimes the filename is replaced (@include), so we restore it + $this->cacheEnd($content); // and it stores as a cache paged. + } else { + $content = $this->getFile($this->getCompiledFileCache($view)); + } + $this->cachePageRunning = false; + return $content; + } + + /** + * Returns true if the block cache expired (or doesn't exist), otherwise false. + * + * @param string $templateName name of the template to use (such hello for template hello.blade.php) + * @param string $id (id of cache, optional, if not id then it adds automatically a number) + * @param int $cacheDuration (duration of the cache in seconds) + * @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache file (if any) + */ + public function cacheExpired($templateName, $id, $cacheDuration) + { + if ($this->getMode() & 1) { + return 2; // forced mode, hence it always expires. (fast mode is ignored). + } + $compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id; + return $this->cacheExpiredInt($compiledFile, $cacheDuration); + } + + /** + * It returns true if the whole page expired. + * + * @param string $templateName + * @param int $cacheDuration is seconds. + * @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any) + */ + public function cachePageExpired($templateName, $cacheDuration) + { + if ($this->getMode() & 1) { + return 2; // forced mode, hence it always expires. (fast mode is ignored). + } + $compiledFile = $this->getCompiledFileCache($templateName); + return $this->CacheExpiredInt($compiledFile, $cacheDuration); + } + + /** + * This method is used by cacheExpired() and cachePageExpired() + * + * @param string $compiledFile + * @param int $cacheDuration is seconds. + * @return int|mixed 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any) + */ + private function cacheExpiredInt($compiledFile, $cacheDuration) + { + if (isset($this->cacheExpired[$compiledFile])) { + // if the information is already in the array then returns it. + return $this->cacheExpired[$compiledFile]; + } + $date = @filemtime($compiledFile); + if ($date) { + if ($date + $cacheDuration < time()) { + $this->cacheExpired[$compiledFile] = 1; + return 2; // time-out. + } + } else { + $this->cacheExpired[$compiledFile] = 2; + return 1; // no file + } + $this->cacheExpired[$compiledFile] = 0; + return 0; // cache active. + } + + public function cacheStart($id = '', $cacheDuration = 86400) + { + $this->curCacheId = ($id == '') ? ($this->curCacheId + 1) : $id; + $this->curCacheDuration = $cacheDuration; + $this->curCachePosition = strlen(ob_get_contents()); + if ($this->cachePageRunning) { + $compiledFile = $this->getCompiledFileCache($this->fileName); + } else { + $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; + } + + if ($this->cacheExpired('', $id, $cacheDuration) !==0) { + $this->cacheRunning = false; + } else { + $this->cacheRunning = true; + $content = $this->getFile($compiledFile); + echo $content; + } + } + + public function cacheEnd($txt = null) + { + if (!$this->cacheRunning) { + $txt = ($txt !== null) ? $txt : substr(ob_get_contents(), $this->curCachePosition); + if ($this->cachePageRunning) { + $compiledFile = $this->getCompiledFileCache($this->fileName); + } else { + $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; + } + file_put_contents($compiledFile, $txt); + } + $this->cacheRunning = false; + } +} diff --git a/vendor/eftec/bladeone/lib/BladeOneCacheRedis.php b/vendor/eftec/bladeone/lib/BladeOneCacheRedis.php new file mode 100644 index 0000000..5dd126f --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneCacheRedis.php @@ -0,0 +1,153 @@ + + * @ cache([cacheid],[duration=86400]). The id is optional. The duration of the cache is in seconds + * // content here + * @ endcache() + * + * It also adds a new function (optional) to the business or logic layer + * + * if ($blade->cacheExpired('hellocache',1,5)) { //'helloonecache' =template, =1 id cache, 5=duration (seconds) + * // cache expired, so we should do some stuff (such as read from the database) + * } + * + * + * @package BladeOneCacheRedis + * @version 0.1 2017-12-15 NOT YET IMPLEMENTED, ITS A WIP!!!!!!!! + * @link https://github.com/EFTEC/BladeOne + * @author Jorge Patricio Castro Castillo + */ +const CACHEREDIS_SCOPEURL = 1; + +trait BladeOneCacheRedis +{ + protected $curCacheId = 0; + protected $curCacheDuration = ""; + protected $curCachePosition = 0; + protected $cacheRunning = false; + /** @var \Redis $redis */ + protected $redis; + protected $redisIP = '127.0.0.1'; + protected $redisPort = 6379; + protected $redisTimeOut = 2.5; + protected $redisConnected = false; + protected $redisNamespace = 'bladeonecache:'; + protected $redisBase = 0; + private $cacheExpired = []; // avoids to compare the file different times. + + // + public function compileCache($expression) + { + // get id of template + // if the file exists then + // compare date. + // if the date is too old then re-save. + // else + // save for the first time. + + return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>"; + } + + public function compileEndCache($expression) + { + return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>"; + } + // + + public function connect($redisIP = null, $redisPort = null, $redisTimeOut = null) + { + if ($this->redisConnected) { + return true; + } + if (!\class_exists('Redis')) { + return false; // it requires redis. + } + if ($redisIP !== null) { + $this->redisIP = $redisIP; + $this->redisPort = $redisPort; + $this->redisTimeOut = $redisTimeOut; + } + $this->redis = new Redis(); + // 2.5 sec timeout. + $this->redisConnected = $this->redis->connect($this->redisIP, $this->redisPort, $this->redisTimeOut); + + return $this->redisConnected; + } + + /** + * Returns true if the cache expired (or doesn't exist), otherwise false. + * + * @param string $templateName name of the template to use (such hello for template hello.blade.php) + * @param string $id (id of cache, optional, if not id then it adds automatically a number) + * @param int $scope scope of the cache. + * @param int $cacheDuration (duration of the cache in seconds) + * @return bool (return if the cache expired) + */ + public function cacheExpired($templateName, $id, $scope, $cacheDuration) + { + if ($this->getMode() & 1) { + return true; // forced mode, hence it always expires. (fast mode is ignored). + } + $compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id; + if (isset($this->cacheExpired[$compiledFile])) { + // if the information is already in the array then returns it. + return $this->cacheExpired[$compiledFile]; + } + $date = @\filemtime($compiledFile); + if ($date) { + if ($date + $cacheDuration < \time()) { + $this->cacheExpired[$compiledFile] = true; + return true; // time-out. + } + } else { + $this->cacheExpired[$compiledFile] = true; + return true; // no file + } + $this->cacheExpired[$compiledFile] = false; + return false; // cache active. + } + + public function cacheStart($id = "", $cacheDuration = 86400) + { + $this->curCacheId = ($id == "") ? ($this->curCacheId + 1) : $id; + $this->curCacheDuration = $cacheDuration; + $this->curCachePosition = \strlen(\ob_get_contents()); + $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; + if ($this->cacheExpired('', $id, $cacheDuration)) { + $this->cacheRunning = false; + } else { + $this->cacheRunning = true; + $content = $this->getFile($compiledFile); + echo $content; + } + // getFile($fileName) + } + + public function cacheEnd() + { + if (!$this->cacheRunning) { + $txt = \substr(\ob_get_contents(), $this->curCachePosition); + $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; + \file_put_contents($compiledFile, $txt); + } + $this->cacheRunning = false; + } + + private function keyByScope($scope) + { + $key = ''; + if ($scope && CACHEREDIS_SCOPEURL) { + $key .= $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; + } + } +} diff --git a/vendor/eftec/bladeone/lib/BladeOneCustom.php b/vendor/eftec/bladeone/lib/BladeOneCustom.php new file mode 100644 index 0000000..3830e65 --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneCustom.php @@ -0,0 +1,55 @@ + + /** + * Usage @panel('title',true,true).....@endpanel() + * + * @param $expression + * @return string + */ + protected function compilePanel($expression) + { + $this->customItem[] = 'Panel'; + return $this->phpTag . "echo \$this->panel{$expression}; ?>"; + } + + protected function compileEndPanel() + { + $r = @array_pop($this->customItem); + if ($r === null) { + $this->showError('@endpanel', 'Missing @compilepanel or so many @compilepanel', true); + } + return ' '; // we don't need to create a function for this. + } + + //
+ + // + protected function panel($title = '', $toggle = true, $dismiss = true) + { + return "
+
+
+ " . (($toggle) ? "" : '') . ' + ' . (($dismiss) ? "" : '') . " +
+ +

$title

+
+
"; + } + // +} diff --git a/vendor/eftec/bladeone/lib/BladeOneHtml.php b/vendor/eftec/bladeone/lib/BladeOneHtml.php new file mode 100644 index 0000000..61a24ca --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneHtml.php @@ -0,0 +1,551 @@ + + * select: + * @ select('idCountry','value',[,$extra]) + * @ item('0','--select a country'[,$extra]) + * @ items($countries,'id','name',$currentCountry[,$extra]) + * @ endselect() + * input: + * @ input('iduser',$currentUser,'text'[,$extra]) + * button: + * @ commandbutton('idbutton','value','text'[,$extra]) + * + * + * Note. The names of the tags are based in Java Server Faces (JSF) + * + * @package BladeOneHtml + * @version 1.9.2 2020-05-28 (1) + * @link https://github.com/EFTEC/BladeOne + * @author Jorge Patricio Castro Castillo + * @deprecated use https://github.com/eftec/BladeOneHtml + */ +trait BladeOneHtml +{ + protected $htmlItem = []; // indicates the type of the current tag. such as select/selectgroup/etc. + protected $htmlCurrentId = []; //indicates the id of the current tag. + + // + protected function compileSelect($expression) + { + $this->htmlItem[] = 'select'; + return $this->phpTag . "echo \$this->select{$expression}; ?>"; + } + + protected function compileListBoxes($expression) + { + return $this->phpTag . "echo \$this->listboxes{$expression}; ?>"; + } + + protected function compileLink($expression) + { + return $this->phpTag . "echo \$this->link{$expression}; ?>"; + } + + protected function compileSelectGroup($expression) + { + $this->htmlItem[] = 'selectgroup'; + $this->compilePush(''); + return $this->phpTag . "echo \$this->select{$expression}; ?>"; + } + + protected function compileRadio($expression) + { + $this->htmlItem[] = 'radio'; + return $this->phpTag . "echo \$this->radio{$expression}; ?>"; + } + + protected function compileCheckbox($expression) + { + $this->htmlItem[] = 'checkbox'; + return $this->phpTag . "echo \$this->checkbox{$expression}; ?>"; + } + + protected function compileEndSelect() + { + $r = @\array_pop($this->htmlItem); + if (\is_null($r)) { + $this->showError("@endselect", "Missing @select or so many @endselect", true); + } + return $this->phpTag . "echo ''; ?>"; + } + + protected function compileEndRadio() + { + $r = @\array_pop($this->htmlItem); + if (\is_null($r)) { + return $this->showError("@EndRadio", "Missing @Radio or so many @EndRadio", true); + } + return ''; + } + + protected function compileEndCheckbox() + { + $r = @\array_pop($this->htmlItem); + if (\is_null($r)) { + return $this->showError("@EndCheckbox", "Missing @Checkbox or so many @EndCheckbox", true); + } + return ''; + } + + protected function compileItem($expression) + { + // we add a new attribute with the type of the current open tag + $r = \end($this->htmlItem); + $x = \trim($expression); + $x = "('{$r}'," . \substr($x, 1); + return $this->phpTag . "echo \$this->item{$x}; ?>"; + } + + protected function compileItems($expression) + { + // we add a new attribute with the type of the current open tag + $r = \end($this->htmlItem); + $x = \trim($expression); + $x = "('{$r}'," . \substr($x, 1); + return $this->phpTag . "echo \$this->items{$x}; ?>"; + } + + protected function compileTrio($expression) + { + // we add a new attribute with the type of the current open tag + $r = \end($this->htmlItem); + $x = \trim($expression); + $x = "('{$r}'," . \substr($x, 1); + return $this->phpTag . "echo \$this->trio{$x}; ?>"; + } + + protected function compileTrios($expression) + { + // we add a new attribute with the type of the current open tag + $r = \end($this->htmlItem); + $x = \trim($expression); + $x = "('{$r}'," . \substr($x, 1); + return $this->phpTag . "echo \$this->trios{$x}; ?>"; + } + + protected function compileInput($expression) + { + return $this->phpTag . "echo \$this->input{$expression}; ?>"; + } + + protected function compileFile($expression) + { + return $this->phpTag . "echo \$this->file{$expression}; ?>"; + } + + protected function compileImage($expression) + { + return $this->phpTag . "echo \$this->image{$expression}; ?>"; + } + + protected function compileTextArea($expression) + { + return $this->phpTag . "echo \$this->textArea{$expression}; ?>"; + } + + protected function compileHidden($expression) + { + return $this->phpTag . "echo \$this->hidden{$expression}; ?>"; + } + + protected function compileLabel($expression) + { + return $this->phpTag . "// {$expression} \n echo \$this->label{$expression}; ?>"; + } + + protected function compileCommandButton($expression) + { + return $this->phpTag . "echo \$this->commandButton{$expression}; ?>"; + } + + protected function compileForm($expression) + { + return $this->phpTag . "echo \$this->form{$expression}; ?>"; + } + + protected function compileEndForm() + { + return $this->phpTag . "echo ''; ?>"; + } + // + + // + public function select($name, $value, $extra = '') + { + if (\strpos($extra, 'readonly') === false) { + return " + \n"; + if (\count($allvalues) == 0) { + $allvalues = []; + } + $html2 = ""; + foreach ($allvalues as $v) { + if (\is_object($v)) { + $v = (array)$v; + } + if (!$this->listboxesFindArray($v[$fieldId], $selectedId, $fieldId)) { + $html .= "\n"; + } else { + $html2 .= "\n"; + } + } + $html .= " \n"; + $html .= " \n"; + $html .= " \n"; + $html .= "
\n"; + $html .= "
\n"; + $html .= "
\n"; + $html .= "
\n"; + $html .= " \n"; + $html .= " \n"; + $html .= " \n"; + $html .= " \n"; + $html .= " \n"; + $html .= "\n"; + return $html; + } + + public function selectGroup($name, $extra = '') + { + return $this->selectGroup($name, $extra); + } + + public function radio($id, $value = '', $text = '', $valueSelected = '', $extra = '') + { + $num = \func_num_args(); + if ($num > 2) { + if ($value == $valueSelected) { + if (\is_array($extra)) { + $extra['checked'] = 'checked'; + } else { + $extra .= ' checked="checked"'; + } + } + return $this->input($id, $value, 'radio', $extra) . ' ' . $text; + } + + $this->htmlCurrentId[] = $id; + return ''; + } + + /** + * @param $id + * @param string $value + * @param string $text + * @param string|null $valueSelected + * @param string|array $extra + * @return string + */ + public function checkbox($id, $value = '', $text = '', $valueSelected = '', $extra = '') + { + $num = \func_num_args(); + if ($num > 2) { + if ($value == $valueSelected) { + if (\is_array($extra)) { + $extra['checked'] = 'checked'; + } else { + $extra .= ' checked="checked"'; + } + } + return $this->input($id, $value, 'checkbox', $extra) . ' ' . $text; + } + + $this->htmlCurrentId[] = $id; + return ''; + } + + /** + * @param string $type type of the current open tag + * @param array|string $valueId if is an array then the first value is used as value, the second is used as + * extra + * @param $valueText + * @param array|string $selectedItem Item selected (optional) + * @param string $wrapper Wrapper of the element. For example,
  • %s
  • + * @param string $extra + * @return string + * @internal param string $fieldId Field of the id + * @internal param string $fieldText Field of the value visible + */ + public function item($type, $valueId, $valueText, $selectedItem = '', $wrapper = '', $extra = '') + { + $id = @\end($this->htmlCurrentId); + $wrapper = ($wrapper == '') ? '%s' : $wrapper; + if (\is_array($selectedItem)) { + $found = \in_array($valueId, $selectedItem); + } else { + $found = $valueId == $selectedItem; + } + + $valueHtml = (!\is_array($valueId)) ? "value='{$valueId}'" : "value='{$valueId[0]}' data='{$valueId[1]}'"; + switch ($type) { + case 'select': + $selected = ($found) ? 'selected' : ''; + return \sprintf($wrapper, "\n"); + break; + case 'radio': + $selected = ($found) ? 'checked' : ''; + return \sprintf($wrapper, "convertArg($extra) . "> {$valueText}\n"); + break; + case 'checkbox': + $selected = ($found) ? 'checked' : ''; + return \sprintf($wrapper, "convertArg($extra) . "> {$valueText}\n"); + break; + + default: + return '???? type undefined: [$type] on @item
    '; + } + } + + /** + * @param string $type type of the current open tag + * @param array $arrValues Array of objects/arrays to show. + * @param string $fieldId Field of the id (for arrValues) + * @param string $fieldText Field of the id of selectedItem + * @param array|string $selectedItem Item selected (optional) + * @param string $selectedFieldId field of the selected item. + * @param string $wrapper Wrapper of the element. For example,
  • %s
  • + * @param string $extra (optional) is used for add additional information for the html object (such + * as class) + * @return string + * @version 1.1 2017 + */ + public function items( + $type, + $arrValues, + $fieldId, + $fieldText, + $selectedItem = '', + $selectedFieldId = '', + $wrapper = '', + $extra = '' + ) { + if (\count($arrValues) == 0) { + return ""; + } + + if (\is_object(@$arrValues[0])) { + $arrValues = (array)$arrValues; + } + if (\is_array($selectedItem)) { + if (\is_object(@$selectedItem[0])) { + $primitiveArray = []; + foreach ($selectedItem as $v) { + $primitiveArray[] = $v->{$selectedFieldId}; + } + $selectedItem = $primitiveArray; + } + } + $result = ''; + if (\is_object($selectedItem)) { + $selectedItem = (array)$selectedItem; + } + foreach ($arrValues as $v) { + if (\is_object($v)) { + $v = (array)$v; + } + $result .= $this->item($type, $v[$fieldId], $v[$fieldText], $selectedItem, $wrapper, $extra); + } + return $result; + } + + /** + * @param string $type type of the current open tag + * @param string $valueId value of the trio + * @param string $valueText visible value of the trio. + * @param string $value3 extra third value for select value or visual + * @param array|string $selectedItem Item selected (optional) + * @param string $wrapper Wrapper of the element. For example,
  • %s
  • + * @param string $extra + * @return string + * @internal param string $fieldId Field of the id + * @internal param string $fieldText Field of the value visible + */ + public function trio($type, $valueId, $valueText, $value3 = '', $selectedItem = '', $wrapper = '', $extra = '') + { + $id = @\end($this->htmlCurrentId); + $wrapper = ($wrapper == '') ? '%s' : $wrapper; + if (\is_array($selectedItem)) { + $found = \in_array($valueId, $selectedItem); + } else { + $found = $valueId == $selectedItem; + } + switch ($type) { + case 'selectgroup': + $selected = ($found) ? 'selected' : ''; + return \sprintf($wrapper, "\n"); + break; + default: + return '???? type undefined: [$type] on @item
    '; + } + } + + /** + * @param string $type type of the current open tag + * @param array $arrValues Array of objects/arrays to show. + * @param string $fieldId Field of the id + * @param string $fieldText Field of the value visible + * @param string $fieldThird + * @param array|string $selectedItem Item selected (optional) + * @param string $wrapper Wrapper of the element. For example,
  • %s
  • + * @param string $extra (optional) is used for add additional information for the html object (such as + * class) + * @return string + * @version 1.0 + */ + public function trios( + $type, + $arrValues, + $fieldId, + $fieldText, + $fieldThird, + $selectedItem = '', + $wrapper = '', + $extra = '' + ) { + if (\count($arrValues) === 0) { + return ""; + } + if (\is_object($arrValues[0])) { + $arrValues = (array)$arrValues; + } + $result = ''; + $oldV3 = ""; + foreach ($arrValues as $v) { + if (\is_object($v)) { + $v = (array)$v; + } + $v3 = $v[$fieldThird]; + if ($type === 'selectgroup') { + if ($v3 != $oldV3) { + if ($oldV3 != "") { + $result .= ""; + } + $oldV3 = $v3; + $result .= ""; + } + } + if ($result) { + $result .= $this->trio($type, $v[$fieldId], $v[$fieldText], $v3, $selectedItem, $wrapper, $extra); + } + } + if ($type === 'selectgroup' && $oldV3 != "") { + $result .= ""; + } + return $result; + } + protected $paginationStructure=['selHtml'=>'
  • %2s
  • ' + ,'html'=>'
  • %2s
  • ' + ,'maxItem'=>5 + ,'url'=>'']; + public function pagination($id, $curPage, $maxPage, $baseUrl, $extra='') + { + $r="
      "; + + $r.="
    "; + return $r; + } + + public function input($id, $value = '', $type = 'text', $extra = '') + { + return "convertArg($extra) . " value='" . static::e($value) . "' />\n"; + } + + public function file($id, $fullfilepath = '', $file = '', $extra = '') + { + return "$file + + convertArg($extra) . " value='" . static::e($fullfilepath) . "' />\n"; + } + + public function textArea($id, $value = '', $extra = '') + { + $value = \str_replace('\n', "\n", $value); + return "\n"; + } + + public function hidden($id, $value = '', $extra = '') + { + return $this->input($id, $value, 'hidden', $extra); + } + + public function label($id, $value = '', $extra = '') + { + return ""; + } + + public function commandButton($id, $value = '', $text = 'Button', $type = 'submit', $extra = '') + { + return "\n"; + } + + public function form($action, $method = 'post', $extra = '') + { + return "
    convertArg($extra)}>"; + } + + // +} diff --git a/vendor/eftec/bladeone/lib/BladeOneHtmlBootstrap.php b/vendor/eftec/bladeone/lib/BladeOneHtmlBootstrap.php new file mode 100644 index 0000000..1a69b4f --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneHtmlBootstrap.php @@ -0,0 +1,260 @@ + + * select: + * @ select('idCountry','value',[,$extra]) + * @ item('0','--select a country'[,$extra]) + * @ items($countries,'id','name',$currentCountry[,$extra]) + * @ endselect() + * input: + * @ input('iduser',$currentUser,'text'[,$extra]) + * button: + * @ commandbutton('idbutton','value','text'[,$extra]) + * + * + * Note. The names of the tags are based in Java Server Faces (JSF) + * + * @package BladeOneHtmlBootstrap + * @version 1.9.1 2018-06-11 (1) + * @link https://github.com/EFTEC/BladeOne + * @author Jorge Patricio Castro Castillo + * @deprecated use https://github.com/eftec/BladeOneHtml + */ +trait BladeOneHtmlBootstrap +{ + use BladeOneHtml { + BladeOneHtml::select as selectParent; + BladeOneHtml::input as inputParent; + BladeOneHtml::commandButton as commandButtonParent; + BladeOneHtml::textArea as textAreaParent; + BladeOneHtml::item as itemParent; + BladeOneHtml::checkbox as checkboxParent; + BladeOneHtml::compileEndCheckbox as compileEndCheckboxParent; + BladeOneHtml::radio as radioParent; + BladeOneHtml::compileEndRadio as compileEndRadioParent; + } + + // + public function select($name, $value, $extra = '') + { + $extra = $this->addClass($extra, 'form-control'); + return $this->selectParent($name, $value, $extra); + } + + public function input($id, $value = '', $type = 'text', $extra = '') + { + $extra = $this->addClass($extra, 'form-control'); + return $this->inputParent($id, $value, $type, $extra); + } + + public function commandButton($id, $value = '', $text = 'Button', $type = 'submit', $extra = '') + { + $extra = $this->addClass($extra, 'btn'); + return $this->commandButtonParent($id, $value, $text, $type, $extra); + } + + public function textArea($id, $value = '', $extra = '') + { + $extra = $this->addClass($extra, 'form-control'); + return $this->textAreaParent($id, $value, $extra); + } + + + public function file($id, $fullfilepath = '', $file = '', $extra = '') + { + return " +
    + +
    "; + // return "$file + // + // convertArg($extra)." value='".static::e($fullfilepath)."' />\n"; + } + + /** + * @param string $id of the field + * @param string $fullfilepath full file path of the image + * @param string $file filename of the file + * @param string $extra extra field of the input file + * @return string html + */ + public function image($id, $fullfilepath = '', $file = '', $extra = '') + { + return " + +
    + + +
    "; + // return "$file + // + // convertArg($extra)." value='".static::e($fullfilepath)."' />\n"; + } + + /** + * @param string $type type of the current open tag + * @param array|string $valueId if is an array then the first value is used as value, the second is used as + * extra + * @param $valueText + * @param array|string $selectedItem Item selected (optional) + * @param string $wrapper Wrapper of the element. For example,
  • %s
  • + * @param string $extra + * @return string + * @internal param string $fieldId Field of the id + * @internal param string $fieldText Field of the value visible + */ + public function item($type, $valueId, $valueText, $selectedItem = '', $wrapper = '', $extra = '') + { + $id = @\end($this->htmlCurrentId); + $wrapper = ($wrapper == '') ? '%s' : $wrapper; + + if (\is_array($selectedItem)) { + $found = \in_array($valueId, $selectedItem); + } else { + if (\is_null($selectedItem)) { + // diferentiate null = '' != 0 + $found = $valueId === '' || $valueId === null; + } else { + $found = $selectedItem == $valueId; + } + } + $valueHtml = (!\is_array($valueId)) ? "value='{$valueId}'" : "value='{$valueId[0]}' data='{$valueId[1]}'"; + switch ($type) { + case 'select': + $selected = ($found) ? 'selected' : ''; + return \sprintf($wrapper, "\n"); + break; + case 'radio': + $selected = ($found) ? 'checked' : ''; + return \sprintf($wrapper, "\n"); + break; + case 'checkbox': + $selected = ($found) ? 'checked' : ''; + return \sprintf($wrapper, "\n"); + break; + + default: + return '???? type undefined: [$type] on @item
    '; + } + } + + public function checkbox($id, $value = '', $text = '', $valueSelected = '', $extra = '') + { + $num = \func_num_args(); + if ($num > 2) { + if ($value == $valueSelected) { + if (\is_array($extra)) { + $extra['checked'] = 'checked'; + } else { + $extra .= ' checked="checked"'; + } + } + //return '
    '; + return '
    '; + } else { + \array_push($this->htmlCurrentId, $id); + return '
    '; + //return '
    '; + } + } + + public function radio($id, $value = '', $text = '', $valueSelected = '', $extra = '') + { + $num = \func_num_args(); + if ($num > 2) { + if ($value == $valueSelected) { + if (\is_array($extra)) { + $extra['checked'] = 'checked'; + } else { + $extra .= ' checked="checked"'; + } + } + return '
    '; + } else { + \array_push($this->htmlCurrentId, $id); + return '
    '; + } + } + + public function compileEndCheckbox() + { + $r = $this->compileEndCheckboxParent(); + $r .= '
    '; + return $r; + } + + public function compileEndRadio() + { + $r = $this->compileEndRadioParent(); + $r .= '
    '; + return $r; + } + // + + // + + /** + * It adds a class to a html tag parameter + * + * @example addClass('type="text" class="btn","btn-standard") + * @param string|array $txt + * @param string $newclass The class(es) to add, example "class1" or "class1 class" + * @return string|array + */ + protected function addClass($txt, $newclass) + { + if (\is_array($txt)) { + $txt = \array_change_key_case($txt); + @$txt['class'] = ' ' . $newclass; + return $txt; + } + $p0 = \stripos(' ' . $txt, ' class'); + if ($p0 === false) { + // if the content of the tag doesn't contain a class then it adds one. + return $txt . ' class="' . $newclass . '"'; + } + // the class tag exists so we found the closes character ' or " and we add the class (or classes) inside it + // may be it could duplicates the tag. + $p1 = \strpos($txt, "'", $p0); + $p2 = \strpos($txt, '"', $p0); + $p1 = ($p1 === false) ? 99999 : $p1; + $p2 = ($p2 === false) ? 99999 : $p2; + + if ($p1 < $p2) { + return \substr_replace($txt, $newclass . ' ', $p1 + 1, 0); + } else { + echo $p2 . "#"; + return \substr_replace($txt, $newclass . ' ', $p2 + 1, 0); + } + } + + protected function separatesParam($txt) + { + $result = []; + \preg_match_all("~\"[^\"]++\"|'[^']++'|\([^)]++\)|[^,]++~", $txt, $result); + return $result; + } + // +} diff --git a/vendor/eftec/bladeone/lib/BladeOneLang.php b/vendor/eftec/bladeone/lib/BladeOneLang.php new file mode 100644 index 0000000..6c4899c --- /dev/null +++ b/vendor/eftec/bladeone/lib/BladeOneLang.php @@ -0,0 +1,154 @@ + + * select: + * @ _e('hello') + * @ _n('Product','Products',$n) + * @ _ef('hello %s',$user) + * + * + * @package eftec\bladeone + * @version 1.1 2019-08-09 + * @link https://github.com/EFTEC/BladeOne + * @author Jorge Patricio Castro Castillo + * @copyright 2017 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license. + * @deprecated Note: It is not needing anymore (BladeOne already includes the same functionalities). It is keep for compatibility purpose. + */ +trait BladeOneLang +{ + /** @var string The path to the missing translations log file. If empty then every missing key is not saved. */ + public $missingLog = ''; + + /** @var array Hold dictionary of translations */ + public static $dictionary = []; + + /** + * Tries to translate the word if its in the array defined by BladeOneLang::$dictionary + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _e($phrase) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return $phrase; + } else { + return static::$dictionary[$phrase]; + } + } + + /** + * Its the same than @_e, however it parses the text (using sprintf). + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _ef($phrase) + { + $argv = \func_get_args(); + $r = $this->_e($phrase); + $argv[0] = $r; // replace the first argument with the translation. + $result = @\call_user_func_array("sprintf", $argv); + $result = ($result === false) ? $r : $result; + return $result; + } + + /** + * if num is more than one then it returns the phrase in plural, otherwise the phrase in singular. + * Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People' + * + * @param string $phrase + * @param string $phrases + * @param int $num + * + * @return string + */ + public function _n($phrase, $phrases, $num = 0) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return ($num <= 1) ? $phrase : $phrases; + } else { + return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases); + } + } + + // + + /** + * Used for @_e directive. + * + * @param $expression + * + * @return string + */ + protected function compile_e($expression) + { + return $this->phpTag . "echo \$this->_e{$expression}; ?>"; + } + + /** + * Used for @_ef directive. + * + * @param $expression + * + * @return string + */ + protected function compile_ef($expression) + { + return $this->phpTag . "echo \$this->_ef{$expression}; ?>"; + } + + /** + * Used for @_n directive. + * + * @param $expression + * + * @return string + */ + protected function compile_n($expression) + { + return $this->phpTag . "echo \$this->_n{$expression}; ?>"; + } + + // + + /** + * Log a missing translation into the file $this->missingLog.
    + * If the file is not defined, then it doesn't write the log. + * + * @param string $txt Message to write on. + */ + private function missingTranslation($txt) + { + if (!$this->missingLog) { + return; // if there is not a file assigned then it skips saving. + } + + $fz = @\filesize($this->missingLog); + $mode = 'a'; + + if (\is_object($txt) || \is_array($txt)) { + $txt = \print_r($txt, true); + } + + // Rewrite file if more than 100000 bytes + if ($fz > 100000) { + $mode = 'w'; + } + + $fp = \fopen($this->missingLog, 'w'); + \fwrite($fp, $txt . "\n"); + \fclose($fp); + } +} diff --git a/vendor/eftec/bladeone/phpcs.xml b/vendor/eftec/bladeone/phpcs.xml new file mode 100644 index 0000000..eeb8b35 --- /dev/null +++ b/vendor/eftec/bladeone/phpcs.xml @@ -0,0 +1,24 @@ + + + + PHPCS standards for EFTEC/BladeOne + + */docs/* + */examples/* + */tests/* + */vendor/* + + + + + + + 0 + + + + + /lib/BladeOneLang\.php + + + \ No newline at end of file diff --git a/vendor/eftec/bladeone/readme.template.md b/vendor/eftec/bladeone/readme.template.md new file mode 100644 index 0000000..71546b7 --- /dev/null +++ b/vendor/eftec/bladeone/readme.template.md @@ -0,0 +1,40 @@ +#Templates +##Template Inheritance + +Example + +_master.blade.php_ is the layout/masterpage template : +```html +

    Title

    +@section('header') +@show +.... +@yield('footer') +``` +_page.blade.php_ is the template that is using the layout page: +```html +@extends('master') +@section('header') +.... +@endsection + +``` + + +#### In the master page (layout) +|Tag|Note|status| +|---|---|---| +|@section('sidebar')|Start a new section|0.2b ok| +|@show|Indicates where the content of section will be displayed|0.2 ok| +|@yield('title')|Show here the content of a section|0.2b ok| + +#### Using the master page (using the layout) +|Tag|Note|status| +|---|---|---| +|@extends('layouts.master')|Indicates the layout to use|0.2b ok| +|@section('title', 'Page Title')|Sends a single text to a section|0.2b ok| +|@section('sidebar')|Start a block of code to send to a section|0.2b ok| +|@endsection|End a block of code|0.2b ok| +|@parent|Show the original code of the section|REMOVED(*)| + +Note :(*) This feature is in the original documentation but its not implemented neither its required. May be its an obsolete feature. diff --git a/vendor/gettext/gettext/CHANGELOG.md b/vendor/gettext/gettext/CHANGELOG.md new file mode 100644 index 0000000..b4d0f81 --- /dev/null +++ b/vendor/gettext/gettext/CHANGELOG.md @@ -0,0 +1,231 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +Previous releases are documented in [github releases](https://github.com/oscarotero/Gettext/releases) + +## [4.8.12] - 2024-05-18 +### Fixed +- Parsing of PO files with empty comments instead of empty lines [#296] + +## [4.8.11] - 2023-08-14 +### Fixed +- PHP 5.4 support [#289] + +## [4.8.10] - 2023-08-10 +### Fixed +- Previous version was tagged with the incorrect branch. + +## [4.8.9] - 2023-08-10 +### Fixed +- PHP 8.1 deprecation warning [#289] + +## [4.8.8] - 2022-12-08 +### Fixed +- PHP functions prefixed with a slash are being ignored [#284], [#288] + +## [4.8.7] - 2022-08-02 +### Fixed +- Suppress deprecation error on PHP 8.1 [#280] + +## [4.8.6] - 2021-10-19 +### Fixed +- Parse PO files with multiline disabled entries [#274] + +## [4.8.5] - 2021-07-13 +### Fixed +- Prevent adding the same translator comment to multiple functions [#271] + +## [4.8.4] - 2021-03-10 +### Fixed +- PHP 8 compatibilty [#266] + +## [4.8.3] - 2020-11-18 +### Fixed +- Blade extractor for Laravel8/Jetstream [#261] + +## [4.8.2] - 2019-12-02 +### Fixed +- UTF-8 handling for VueJs extractor [#242] + +## [4.8.1] - 2019-11-15 +### Fixed +- Php error when scanning for a single domain but other string found [#238] + +## [4.8.0] - 2019-11-04 +### Changed +- Many `private` properties and methods were changed to `protected` in order to improve the extensibility [#231] + +### Fixed +- PHP 7.4 support [#230] + +## [4.7.0] - 2019-10-07 +### Added +- Support for UnitID in Xliff [#221] [#224] [#225] +- Support for scan multiple domains at the same time [#223] + +### Fixed +- New lines in windows [#218] [#226] + +## [4.6.3] - 2019-07-15 +### Added +- Some VueJs extraction improvements and additions [#205], [#213] + +### Fixed +- Multiline extractions in jsCode [#200] +- Support for js template literals [#214] +- Fixed tabs in PHP comments [#215] + +## [4.6.2] - 2019-01-12 +### Added +- New option `facade` in blade extractor to use a facade instead create a blade compiler [#197], [#198] + +### Fixed +- Added php-7.3 to travis +- Added VueJS extractor method docblocks for IDEs [#191] + +## [4.6.1] - 2018-08-27 +### Fixed +- VueJS DOM parsing [#188] +- Javascript parser was unable to extract some functions [#187] + +## [4.6.0] - 2018-06-26 +### Added +- New extractor for VueJs [#178] + +### Fixed +- Do not include empty translations containing the headers in the translator [#182] +- Test enhancement [#177] + +## [4.5.0] - 2018-04-23 +### Added +- Support for disabled translations + +### Fixed +- Added php-7.2 to travis +- Fixed po tests on bigendian [#159] +- Improved comment estraction [#166] +- Fixed incorrect docs to dn__ function [#170] +- Ignored phpcs.xml file on export [#168] +- Improved `@method` docs in `Translations` [#175] + +## [4.4.4] - 2018-02-21 +### Fixed +- Changed the comment extraction to be compatible with gettext behaviour: the comment must be placed in the line preceding the function [#161] + +### Security +- Validate eval input from plural forms [#156] + +## [4.4.3] - 2017-08-09 +### Fixed +- Handle `NULL` arguments on extract entries in php. For example `dn__(null, 'singular', 'plural')`. +- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dn__` and `dngettext` entries [#155]. +- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dnpgettext` correctly. + +## [4.4.2] - 2017-07-27 +### Fixed +- Clone the translations in `Translations::mergeWith` to prevent that the translation is referenced in both places. [#152] +- Fixed escaped quotes in the javascript extractor [#154] + +## [4.4.1] - 2017-05-20 +### Fixed +- Fixed a bug where the options was not passed correctly to the merging Translations object [#147] +- Unified the plural behaviours between PHP gettext and Translator when the plural translation is unknown [#148] +- Removed the deprecated function `create_function()` and use `eval()` instead + +## [4.4.0] - 2017-05-10 +### Added +- New option `noLocation` to po generator, to omit the references [#143] +- New options `delimiter`, `enclosure` and `escape_char` to Csv and CsvDictionary extractors and generators [#145] +- Added the missing `dn__()` function [#146] + +### Fixed +- Improved the code style including php_codesniffer in development + +## [4.3.0] - 2017-03-04 +### Added +- Added support for named placeholders (using `strtr`). For example: + ```php + __('Hello :name', [':name' => 'World']); + ``` +- Added support for Twig v2 +- New function `BaseTranslator::includeFunctions()` to include the functions file without register any translator + +### Fixed +- Fixed a bug related with the javascript source extraction with single quotes + +[#143]: https://github.com/oscarotero/Gettext/issues/143 +[#145]: https://github.com/oscarotero/Gettext/issues/145 +[#146]: https://github.com/oscarotero/Gettext/issues/146 +[#147]: https://github.com/oscarotero/Gettext/issues/147 +[#148]: https://github.com/oscarotero/Gettext/issues/148 +[#152]: https://github.com/oscarotero/Gettext/issues/152 +[#154]: https://github.com/oscarotero/Gettext/issues/154 +[#155]: https://github.com/oscarotero/Gettext/issues/155 +[#156]: https://github.com/oscarotero/Gettext/issues/156 +[#159]: https://github.com/oscarotero/Gettext/issues/159 +[#161]: https://github.com/oscarotero/Gettext/issues/161 +[#166]: https://github.com/oscarotero/Gettext/issues/166 +[#168]: https://github.com/oscarotero/Gettext/issues/168 +[#170]: https://github.com/oscarotero/Gettext/issues/170 +[#175]: https://github.com/oscarotero/Gettext/issues/175 +[#177]: https://github.com/oscarotero/Gettext/issues/177 +[#178]: https://github.com/oscarotero/Gettext/issues/178 +[#182]: https://github.com/oscarotero/Gettext/issues/182 +[#187]: https://github.com/oscarotero/Gettext/issues/187 +[#188]: https://github.com/oscarotero/Gettext/issues/188 +[#191]: https://github.com/oscarotero/Gettext/issues/191 +[#197]: https://github.com/oscarotero/Gettext/issues/197 +[#198]: https://github.com/oscarotero/Gettext/issues/198 +[#200]: https://github.com/oscarotero/Gettext/issues/200 +[#205]: https://github.com/oscarotero/Gettext/issues/205 +[#213]: https://github.com/oscarotero/Gettext/issues/213 +[#214]: https://github.com/oscarotero/Gettext/issues/214 +[#215]: https://github.com/oscarotero/Gettext/issues/215 +[#218]: https://github.com/oscarotero/Gettext/issues/218 +[#221]: https://github.com/oscarotero/Gettext/issues/221 +[#223]: https://github.com/oscarotero/Gettext/issues/223 +[#224]: https://github.com/oscarotero/Gettext/issues/224 +[#225]: https://github.com/oscarotero/Gettext/issues/225 +[#226]: https://github.com/oscarotero/Gettext/issues/226 +[#230]: https://github.com/oscarotero/Gettext/issues/230 +[#231]: https://github.com/oscarotero/Gettext/issues/231 +[#238]: https://github.com/oscarotero/Gettext/issues/238 +[#242]: https://github.com/oscarotero/Gettext/issues/242 +[#261]: https://github.com/oscarotero/Gettext/issues/261 +[#266]: https://github.com/oscarotero/Gettext/issues/266 +[#271]: https://github.com/oscarotero/Gettext/issues/271 +[#274]: https://github.com/oscarotero/Gettext/issues/274 +[#280]: https://github.com/oscarotero/Gettext/issues/280 +[#284]: https://github.com/oscarotero/Gettext/issues/284 +[#288]: https://github.com/oscarotero/Gettext/issues/288 +[#289]: https://github.com/oscarotero/Gettext/issues/289 +[#296]: https://github.com/oscarotero/Gettext/issues/296 + +[4.8.12]: https://github.com/oscarotero/Gettext/compare/v4.8.11...v4.8.12 +[4.8.11]: https://github.com/oscarotero/Gettext/compare/v4.8.10...v4.8.11 +[4.8.10]: https://github.com/oscarotero/Gettext/compare/v4.8.9...v4.8.10 +[4.8.9]: https://github.com/oscarotero/Gettext/compare/v4.8.8...v4.8.9 +[4.8.8]: https://github.com/oscarotero/Gettext/compare/v4.8.7...v4.8.8 +[4.8.7]: https://github.com/oscarotero/Gettext/compare/v4.8.6...v4.8.7 +[4.8.6]: https://github.com/oscarotero/Gettext/compare/v4.8.5...v4.8.6 +[4.8.5]: https://github.com/oscarotero/Gettext/compare/v4.8.4...v4.8.5 +[4.8.4]: https://github.com/oscarotero/Gettext/compare/v4.8.3...v4.8.4 +[4.8.3]: https://github.com/oscarotero/Gettext/compare/v4.8.2...v4.8.3 +[4.8.2]: https://github.com/oscarotero/Gettext/compare/v4.8.1...v4.8.2 +[4.8.1]: https://github.com/oscarotero/Gettext/compare/v4.8.0...v4.8.1 +[4.8.0]: https://github.com/oscarotero/Gettext/compare/v4.7.0...v4.8.0 +[4.7.0]: https://github.com/oscarotero/Gettext/compare/v4.6.3...v4.7.0 +[4.6.3]: https://github.com/oscarotero/Gettext/compare/v4.6.2...v4.6.3 +[4.6.2]: https://github.com/oscarotero/Gettext/compare/v4.6.1...v4.6.2 +[4.6.1]: https://github.com/oscarotero/Gettext/compare/v4.6.0...v4.6.1 +[4.6.0]: https://github.com/oscarotero/Gettext/compare/v4.5.0...v4.6.0 +[4.5.0]: https://github.com/oscarotero/Gettext/compare/v4.4.4...v4.5.0 +[4.4.4]: https://github.com/oscarotero/Gettext/compare/v4.4.3...v4.4.4 +[4.4.3]: https://github.com/oscarotero/Gettext/compare/v4.4.2...v4.4.3 +[4.4.2]: https://github.com/oscarotero/Gettext/compare/v4.4.1...v4.4.2 +[4.4.1]: https://github.com/oscarotero/Gettext/compare/v4.4.0...v4.4.1 +[4.4.0]: https://github.com/oscarotero/Gettext/compare/v4.3.0...v4.4.0 +[4.3.0]: https://github.com/oscarotero/Gettext/releases/tag/v4.3.0 diff --git a/vendor/gettext/gettext/CONTRIBUTING.md b/vendor/gettext/gettext/CONTRIBUTING.md new file mode 100644 index 0000000..eda824f --- /dev/null +++ b/vendor/gettext/gettext/CONTRIBUTING.md @@ -0,0 +1,17 @@ +Contributing to Gettext +======================= + +Looking to contribute something to this library? Here's how you can help. + +## Bugs + +A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you! + +Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, version of gettext, etc, and steps required to reproduce the issue. + +## Pull Requests + +Good pull requests – patches, improvements, new features – are a fantastic help. New extractors or generator are welcome. Before create a pull request, please follow these instructions: + +* The code must be PSR-2 compliant +* Write some tests diff --git a/vendor/gettext/gettext/LICENSE b/vendor/gettext/gettext/LICENSE new file mode 100644 index 0000000..2385321 --- /dev/null +++ b/vendor/gettext/gettext/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Oscar Otero Marzoa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gettext/gettext/README.md b/vendor/gettext/gettext/README.md new file mode 100644 index 0000000..4911afb --- /dev/null +++ b/vendor/gettext/gettext/README.md @@ -0,0 +1,425 @@ +Gettext +======= + +[![Build Status](https://travis-ci.org/oscarotero/Gettext.png?branch=master)](https://travis-ci.org/oscarotero/Gettext) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/oscarotero/Gettext/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/oscarotero/Gettext/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/gettext/gettext/v/stable.svg)](https://packagist.org/packages/gettext/gettext) +[![Total Downloads](https://poser.pugx.org/gettext/gettext/downloads.svg)](https://packagist.org/packages/gettext/gettext) +[![Monthly Downloads](https://poser.pugx.org/gettext/gettext/d/monthly.png)](https://packagist.org/packages/gettext/gettext) +[![License](https://poser.pugx.org/gettext/gettext/license.svg)](https://packagist.org/packages/gettext/gettext) + +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47/big.png)](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47) + +Created by Oscar Otero (MIT License) + +Gettext is a PHP (>=5.4) library to import/export/edit gettext from PO, MO, PHP, JS files, etc. + +## Installation + +With composer (recomended): + +``` +composer require gettext/gettext +``` + +If you don't use composer in your project, you have to download and place this package in a directory of your project. You need to install also [gettext/languages](https://github.com/mlocati/cldr-to-gettext-plural-rules). Then, include the autoloaders of both projects in any place of your php code: + +```php +include_once "libs/gettext/src/autoloader.php"; +include_once "libs/cldr-to-gettext-plural-rules/src/autoloader.php"; +``` + +## Classes and functions + +This package contains the following classes: + +* `Gettext\Translation` - A translation definition +* `Gettext\Translations` - A collection of translations +* `Gettext\Extractors\*` - Import translations from various sources (po, mo, php, js, etc) +* `Gettext\Generators\*` - Export translations to various formats (po, mo, php, json, etc) +* `Gettext\Translator` - To use the translations in your php templates instead the [gettext extension](http://php.net/gettext) +* `Gettext\GettextTranslator` - To use the [gettext extension](http://php.net/gettext) + +## Usage example + +```php +use Gettext\Translations; + +//import from a .po file: +$translations = Translations::fromPoFile('locales/gl.po'); + +//edit some translations: +$translation = $translations->find(null, 'apple'); + +if ($translation) { + $translation->setTranslation('Mazá'); +} + +//export to a php array: +$translations->toPhpArrayFile('locales/gl.php'); + +//and to a .mo file +$translations->toMoFile('Locale/gl/LC_MESSAGES/messages.mo'); +``` + +If you want use this translations in your php templates without using the gettext extension: + +```php +use Gettext\Translator; + +//Create the translator instance +$t = new Translator(); + +//Load your translations (exported as PhpArray): +$t->loadTranslations('locales/gl.php'); + +//Use it: +echo $t->gettext('apple'); // "Mazá" + +//If you want use global functions: +$t->register(); + +echo __('apple'); // "Mazá" +``` + +To use this translations with the gettext extension: + +```php +use Gettext\GettextTranslator; + +//Create the translator instance +$t = new GettextTranslator(); + +//Set the language and load the domain +$t->setLanguage('gl'); +$t->loadDomain('messages', 'Locale'); + +//Use it: +echo $t->gettext('apple'); // "Mazá" + +//Or use the gettext functions +echo gettext('apple'); // "Mazá" + +//If you want use the global functions +$t->register(); + +echo __('apple'); // "Mazá" + +//And use sprintf/strtr placeholders +echo __('Hello %s', 'world'); //Hello world +echo __('Hello {name}', ['{name}' => 'world']); //Hello world +``` + +The benefits of using the functions provided by this library (`__()` instead `_()` or `gettext()`) are: + +* You are using the same functions, no matter whether the translations are provided by gettext extension or any other method. +* You can use variables easier because `sprintf` functionality is included. For example: `__('Hello %s', 'world')` instead `sprintf(_('Hello %s'), 'world')`. +* You can also use named placeholders if the second argument is an array. For example: `__('Hello %name%', ['%name%' => 'world'])` instead of `strtr(_('Hello %name%'), ['%name%' => 'world'])`. + +## Translation + +The `Gettext\Translation` class stores all information about a translation: the original text, the translated text, source references, comments, etc. + +```php +// __construct($context, $original, $plural) +$translation = new Gettext\Translation('comments', 'One comment', '%s comments'); + +$translation->setTranslation('Un comentario'); +$translation->setPluralTranslation('%s comentarios'); + +$translation->addReference('templates/comments/comment.php', 34); +$translation->addComment('To display the amount of comments in a post'); + +echo $translation->getContext(); // comments +echo $translation->getOriginal(); // One comment +echo $translation->getTranslation(); // Un comentario + +// etc... +``` + +## Translations + +The `Gettext\Translations` class stores a collection of translations: + +```php +$translations = new Gettext\Translations(); + +//You can add new translations using the array syntax +$translations[] = new Gettext\Translation('comments', 'One comment', '%s comments'); + +//Or using the "insert" method +$insertedTranslation = $translations->insert('comments', 'One comment', '%s comments'); + +//Find a specific translation +$translation = $translations->find('comments', 'One comment'); + +//Edit headers, domain, etc +$translations->setHeader('Last-Translator', 'Oscar Otero'); +$translations->setDomain('my-blog'); +``` + +## Extractors + +The extrators allows to fetch gettext values from any source. For example, to scan a .po file: + +```php +$translations = new Gettext\Translations(); + +//From a file +Gettext\Extractors\Po::fromFile('locales/en.po', $translations); + +//From a string +$string = file_get_contents('locales2/en.po'); +Gettext\Extractors\Po::fromString($string, $translations); +``` + +The better way to use extractors is using the magic methods of `Gettext\Translations`: + +```php +//Create a Translations instance using a po file +$translations = Gettext\Translations::fromPoFile('locales/en.po'); + +//Add more messages from other files +$translations->addFromPoFile('locales2/en.po'); +``` + +The available extractors are the following: + +Name | Description | Example +---- | ----------- | -------- +**Blade** | Scans a Blade template (For laravel users). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/blade/input.php) +**Csv** | Gets the messages from csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv) +**CsvDictionary** | Gets the messages from csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv) +**Jed** | Gets the messages from a json compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json) +**JsCode** | Scans javascript code looking for all gettext functions (the same than PhpCode but for javascript). You can use [the javascript gettext-translator library](https://github.com/oscarotero/gettext-translator) | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/jscode/input.js) +**Json** | Gets the messages from json compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json) +**JsonDictionary** | Gets the messages from a json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json) +**Mo** | Gets the messages from MO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo) +**PhpArray** | Gets the messages from a php file that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php) +**PhpCode** | Scans php code looking for all gettext functions (see `translator_functions.php`). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/phpcode/input.php) +**Po** | Gets the messages from PO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po) +**Twig** | To scan a Twig template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/twig/input.php) +**Xliff** | Gets the messages from [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf) +**Yaml** | Gets the messages from yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml) +**YamlDictionary** | Gets the messages from a yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml) +**VueJs** | Gets the messages from a VueJs template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/vuejs/input.vue) + +## Generators + +The generators export a `Gettext\Translations` instance to any format (po, mo, array, etc). + +```php +//Save to a file +Gettext\Generators\Po::toFile($translations, 'locales/en.po'); + +//Return as a string +$content = Gettext\Generators\Po::toString($translations); +file_put_contents('locales/en.po', $content); +``` + +Like extractors, the better way to use generators is using the magic methods of `Gettext\Translations`: + +```php +//Extract messages from a php code file +$translations = Gettext\Translations::fromPhpCodeFile('templates/index.php'); + +//Export to a po file +$translations->toPoFile('locales/en.po'); + +//Export to a po string +$content = $translations->toPoString(); +file_put_contents('locales/en.po', $content); +``` + +The available generators are the following: + +Name | Description | Example +---- | ----------- | -------- +**Csv** | Exports to csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv) +**CsvDictionary** | Exports to csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv) +**Json** | Exports to json, compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json) +**JsonDictionary** | Exports to json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json) +**Mo** | Exports to Mo. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo) +**PhpArray** | Exports to php code that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php) +**Po** | Exports to Po. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po) +**Jed** | Exports to json format compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json) +**Xliff** | Exports to [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf) +**Yaml** | Exports to yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml) +**YamlDictionary** | Exports to yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml) + +## Translator + +The class `Gettext\Translator` implements the gettext functions in php. Useful if you don't have the native gettext extension for php or want to avoid problems with it. You can load the translations from a php array file or using a `Gettext\Translations` instance: + +```php +use Gettext\Translator; + +//Create a new instance of the translator +$t = new Translator(); + +//Load the translations using any of the following ways: + +// 1. from php files (generated by Gettext\Extractors\PhpArray) +$t->loadTranslations('locales/gl.php'); + +// 2. using the array directly +$array = include 'locales/gl.php'; +$t->loadTranslations($array); + +// 3. using a Gettext\Translations instance (slower) +$translations = Gettext\Translations::fromPoFile('locales/gl.po'); +$t->loadTranslations($translations); + +//Now you can use it in your templates +echo $t->gettext('apple'); +``` + +## GettextTranslator + +The class `Gettext\GettextTranslator` uses the gettext extension. It's useful because combines the performance of using real gettext functions but with the same API than `Translator` class, so you can switch to one or other translator deppending of the environment without change code of your app. + +```php +use Gettext\GettextTranslator; + +//Create a new instance +$t = new GettextTranslator(); + +//It detects the environment variables to set the locale, but you can change it: +$t->setLanguage('gl'); + +//Load the domains: +$t->loadDomain('messages', 'project/Locale'); +//this means you have the file "project/Locale/gl/LC_MESSAGES/messages.po" + +//Now you can use it in your templates +echo $t->gettext('apple'); +``` + +## Global functions + +To ease the use of translations in your php templates, you can use the provided functions: + +```php +//Register the translator to use the global functions +$t->register(); + +echo __('apple'); // it's the same than $t->gettext('apple'); +``` + +You can scan the php files containing these functions and extract the values with the PhpCode extractor: + +```html + + + + + + +``` + + +## Merge translations + +To work with different translations you may want merge them in an unique file. There are two ways to do this: + +The simplest way is adding new translations: + +```php +use Gettext\Translations; + +$translations = Translations::fromPoFile('my-file1.po'); +$translations->addFromPoFile('my-file2.po'); +``` + +A more advanced way is merge two `Translations` instances: + +```php +use Gettext\Translations; + +//Create a new Translations instances with our translations. + +$translations1 = Translations::fromPoFile('my-file1.po'); +$translations2 = Translations::fromPoFile('my-file2.po'); + +//Merge one inside other: +$translations1->mergeWith($translations2); + +//Now translations1 has all values +``` + +The second argument of `mergeWith` defines how the merge will be done. Use the `Gettext\Merge` constants to configure the merging: + +Constant | Description +--------- | ----------- +`Merge::ADD` | Adds the translations from `$translations2` that are missing +`Merge::REMOVE` | Removes the translations missing in `$translations2` +`Merge::HEADERS_ADD` | Adds the headers from `$translations2` that are missing +`Merge::HEADERS_REMOVE` | Removes the headers missing in `$translations2` +`Merge::HEADERS_OVERRIDE` | Overrides the headers with the values of `$translations2` +`Merge::LANGUAGE_OVERRIDE` | Set the language defined in `$translations2` +`Merge::DOMAIN_OVERRIDE` | Set the domain defined in `$translations2` +`Merge::TRANSLATION_OVERRIDE` | Override the translation and plural translations with the value of `$translation2` +`Merge::COMMENTS_OURS` | Use only the comments of `$translation1` +`Merge::COMMENTS_THEIRS` | Use only the comments of `$translation2` +`Merge::EXTRACTED_COMMENTS_OURS` | Use only the extracted comments of `$translation1` +`Merge::EXTRACTED_COMMENTS_THEIRS` | Use only the extracted comments of `$translation2` +`Merge::FLAGS_OURS` | Use only the flags of `$translation1` +`Merge::FLAGS_THEIRS` | Use only the flags of `$translation2` +`Merge::REFERENCES_OURS` | Use only the references of `$translation1` +`Merge::REFERENCES_THEIRS` | Use only the references of `$translation2` + +Example: + +```php +use Gettext\Translations; +use Gettext\Merge; + +//Scan the php code to find the latest gettext translations +$phpTranslations = Translations::fromPhpCodeFile('my-templates.php'); + +//Get the translations of the code that are stored in a po file +$poTranslations = Translations::fromPoFile('locale.po'); + +//Merge the translations from the po file using the references from `$phpTranslations`: +$translations->mergeWith($poTranslations, Merge::REFERENCES_OURS); + +//Now save a po file with the result +$translations->toPoFile('locale.po'); +``` + +Note, if the second argument is not defined, the default value is `Merge::DEFAULTS` that's equivalent to `Merge::ADD | Merge::HEADERS_ADD`. + +## Use from CLI + +There's a Robo task to use this library from the command line interface: https://github.com/oscarotero/GettextRobo + +## Use in the browser + +If you want to use your translations in the browser, there's a javascript translator: https://github.com/oscarotero/gettext-translator + +## Third party packages + +Twig integration: + +* [jaimeperez/twig-configurable-i18n](https://packagist.org/packages/jaimeperez/twig-configurable-i18n) +* [cemerson/translator-twig-extension](https://packagist.org/packages/cemerson/translator-twig-extension) + +Framework integration: + +* [Laravel 5](https://packagist.org/packages/eusonlito/laravel-gettext) +* [CakePHP 3](https://packagist.org/packages/k1low/po) +* [Symfony 2](https://packagist.org/packages/mablae/gettext-bundle) + +[add your package](https://github.com/oscarotero/Gettext/issues/new) + +## Contributors + +Thanks to all [contributors](https://github.com/oscarotero/Gettext/graphs/contributors) specially to [@mlocati](https://github.com/mlocati). + +## Donations + +If this library is useful for you, consider to donate to the author. + +[Buy me a beer :beer:](https://www.paypal.me/oscarotero) + +Thanks in advance! diff --git a/vendor/gettext/gettext/composer.json b/vendor/gettext/gettext/composer.json new file mode 100644 index 0000000..289e14f --- /dev/null +++ b/vendor/gettext/gettext/composer.json @@ -0,0 +1,59 @@ +{ + "name": "gettext/gettext", + "type": "library", + "description": "PHP gettext manager", + "keywords": ["js", "gettext", "i18n", "translation", "po", "mo"], + "homepage": "https://github.com/oscarotero/Gettext", + "license": "MIT", + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/oscarotero/Gettext/issues" + }, + "require": { + "php": ">=5.4.0", + "gettext/languages": "^2.3" + }, + "require-dev": { + "illuminate/view": "^5.0.x-dev", + "twig/twig": "^1.31|^2.0", + "twig/extensions": "*", + "symfony/yaml": "~2", + "phpunit/phpunit": "^4.8|^5.7|^6.5", + "squizlabs/php_codesniffer": "^3.0" + }, + "suggest": { + "illuminate/view": "Is necessary if you want to use the Blade extractor", + "twig/twig": "Is necessary if you want to use the Twig extractor", + "twig/extensions": "Is necessary if you want to use the Twig extractor", + "symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator" + }, + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Gettext\\Tests\\": "tests" + } + }, + "scripts": { + "test": [ + "phpunit", + "phpcs" + ] + }, + "config": { + "allow-plugins": { + "kylekatarnls/update-helper": false + } + } +} diff --git a/vendor/gettext/gettext/src/BaseTranslator.php b/vendor/gettext/gettext/src/BaseTranslator.php new file mode 100644 index 0000000..c52038f --- /dev/null +++ b/vendor/gettext/gettext/src/BaseTranslator.php @@ -0,0 +1,39 @@ +withoutComponentTags(); + } + + $string = $bladeCompiler->compileString($string); + } else { + $string = $options['facade']::compileString($string); + } + + PhpCode::fromString($string, $translations, $options); + } +} diff --git a/vendor/gettext/gettext/src/Extractors/Csv.php b/vendor/gettext/gettext/src/Extractors/Csv.php new file mode 100644 index 0000000..fba3a98 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/Csv.php @@ -0,0 +1,53 @@ + ",", + 'enclosure' => '"', + 'escape_char' => "\\" + ]; + + /** + * {@inheritdoc} + */ + public static function fromString($string, Translations $translations, array $options = []) + { + $options += static::$options; + $handle = fopen('php://memory', 'w'); + + fputs($handle, $string); + rewind($handle); + + while ($row = static::fgetcsv($handle, $options)) { + $context = array_shift($row); + $original = array_shift($row); + + if ($context === '' && $original === '') { + static::extractHeaders(array_shift($row), $translations); + continue; + } + + $translation = $translations->insert($context, $original); + + if (!empty($row)) { + $translation->setTranslation(array_shift($row)); + $translation->setPluralTranslations($row); + } + } + + fclose($handle); + } +} diff --git a/vendor/gettext/gettext/src/Extractors/CsvDictionary.php b/vendor/gettext/gettext/src/Extractors/CsvDictionary.php new file mode 100644 index 0000000..807357c --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/CsvDictionary.php @@ -0,0 +1,47 @@ + ",", + 'enclosure' => '"', + 'escape_char' => "\\" + ]; + + /** + * {@inheritdoc} + */ + public static function fromString($string, Translations $translations, array $options = []) + { + $options += static::$options; + $handle = fopen('php://memory', 'w'); + + fputs($handle, $string); + rewind($handle); + + while ($row = static::fgetcsv($handle, $options)) { + list($original, $translation) = $row + ['', '']; + + if ($original === '') { + static::extractHeaders($translation, $translations); + continue; + } + + $translations->insert(null, $original)->setTranslation($translation); + } + + fclose($handle); + } +} diff --git a/vendor/gettext/gettext/src/Extractors/Extractor.php b/vendor/gettext/gettext/src/Extractors/Extractor.php new file mode 100644 index 0000000..59974aa --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/Extractor.php @@ -0,0 +1,80 @@ +setDomain($headers['domain']); + } + + if (!empty($headers['lang'])) { + $translations->setLanguage($headers['lang']); + } + + if (!empty($headers['plural-forms'])) { + $translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']); + } + + $context_glue = '\u0004'; + + foreach ($messages as $key => $translation) { + $key = explode($context_glue, $key); + $context = isset($key[1]) ? array_shift($key) : ''; + + $translations->insert($context, array_shift($key)) + ->setTranslation(array_shift($translation)) + ->setPluralTranslations($translation); + } + } +} diff --git a/vendor/gettext/gettext/src/Extractors/JsCode.php b/vendor/gettext/gettext/src/Extractors/JsCode.php new file mode 100644 index 0000000..0a88d4f --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/JsCode.php @@ -0,0 +1,74 @@ + [], + + 'functions' => [ + 'gettext' => 'gettext', + '__' => 'gettext', + 'ngettext' => 'ngettext', + 'n__' => 'ngettext', + 'pgettext' => 'pgettext', + 'p__' => 'pgettext', + 'dgettext' => 'dgettext', + 'd__' => 'dgettext', + 'dngettext' => 'dngettext', + 'dn__' => 'dngettext', + 'dpgettext' => 'dpgettext', + 'dp__' => 'dpgettext', + 'npgettext' => 'npgettext', + 'np__' => 'npgettext', + 'dnpgettext' => 'dnpgettext', + 'dnp__' => 'dnpgettext', + 'noop' => 'noop', + 'noop__' => 'noop', + ], + ]; + + protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner'; + + /** + * @inheritdoc + * @throws Exception + */ + public static function fromString($string, Translations $translations, array $options = []) + { + static::fromStringMultiple($string, [$translations], $options); + } + + /** + * @inheritDoc + * @throws Exception + */ + public static function fromStringMultiple($string, array $translations, array $options = []) + { + $options += static::$options; + + /** @var FunctionsScanner $functions */ + $functions = new static::$functionsScannerClass($string); + $functions->saveGettextFunctions($translations, $options); + } + + /** + * @inheritDoc + * @throws Exception + */ + public static function fromFileMultiple($file, array $translations, array $options = []) + { + foreach (static::getFiles($file) as $file) { + $options['file'] = $file; + static::fromStringMultiple(static::readFile($file), $translations, $options); + } + } +} diff --git a/vendor/gettext/gettext/src/Extractors/Json.php b/vendor/gettext/gettext/src/Extractors/Json.php new file mode 100644 index 0000000..3aaea29 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/Json.php @@ -0,0 +1,26 @@ +seekto($originals); + $table_originals = static::readIntArray($stream, $byteOrder, $total * 2); + + $stream->seekto($tran); + $table_translations = static::readIntArray($stream, $byteOrder, $total * 2); + + for ($i = 0; $i < $total; ++$i) { + $next = $i * 2; + + $stream->seekto($table_originals[$next + 2]); + $original = $stream->read($table_originals[$next + 1]); + + $stream->seekto($table_translations[$next + 2]); + $translated = $stream->read($table_translations[$next + 1]); + + if ($original === '') { + // Headers + foreach (explode("\n", $translated) as $headerLine) { + if ($headerLine === '') { + continue; + } + + $headerChunks = preg_split('/:\s*/', $headerLine, 2); + $translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : ''); + } + + continue; + } + + $chunks = explode("\x04", $original, 2); + + if (isset($chunks[1])) { + $context = $chunks[0]; + $original = $chunks[1]; + } else { + $context = ''; + } + + $chunks = explode("\x00", $original, 2); + + if (isset($chunks[1])) { + $original = $chunks[0]; + $plural = $chunks[1]; + } else { + $plural = ''; + } + + $translation = $translations->insert($context, $original, $plural); + + if ($translated === '') { + continue; + } + + if ($plural === '') { + $translation->setTranslation($translated); + continue; + } + + $v = explode("\x00", $translated); + $translation->setTranslation(array_shift($v)); + $translation->setPluralTranslations($v); + } + } + + /** + * @param StringReader $stream + * @param string $byteOrder + */ + protected static function readInt(StringReader $stream, $byteOrder) + { + if (($read = $stream->read(4)) === false) { + return false; + } + + $read = unpack($byteOrder, $read); + + return array_shift($read); + } + + /** + * @param StringReader $stream + * @param string $byteOrder + * @param int $count + */ + protected static function readIntArray(StringReader $stream, $byteOrder, $count) + { + return unpack($byteOrder.$count, $stream->read(4 * $count)); + } +} diff --git a/vendor/gettext/gettext/src/Extractors/PhpArray.php b/vendor/gettext/gettext/src/Extractors/PhpArray.php new file mode 100644 index 0000000..3e4b262 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/PhpArray.php @@ -0,0 +1,33 @@ + false, + + 'constants' => [], + + 'functions' => [ + 'gettext' => 'gettext', + '__' => 'gettext', + 'ngettext' => 'ngettext', + 'n__' => 'ngettext', + 'pgettext' => 'pgettext', + 'p__' => 'pgettext', + 'dgettext' => 'dgettext', + 'd__' => 'dgettext', + 'dngettext' => 'dngettext', + 'dn__' => 'dngettext', + 'dpgettext' => 'dpgettext', + 'dp__' => 'dpgettext', + 'npgettext' => 'npgettext', + 'np__' => 'npgettext', + 'dnpgettext' => 'dnpgettext', + 'dnp__' => 'dnpgettext', + 'noop' => 'noop', + 'noop__' => 'noop', + ], + ]; + + protected static $functionsScannerClass = 'Gettext\Utils\PhpFunctionsScanner'; + + /** + * {@inheritdoc} + * @throws Exception + */ + public static function fromString($string, Translations $translations, array $options = []) + { + static::fromStringMultiple($string, [$translations], $options); + } + + /** + * @inheritDoc + * @throws Exception + */ + public static function fromStringMultiple($string, array $translations, array $options = []) + { + $options += static::$options; + + /** @var FunctionsScanner $functions */ + $functions = new static::$functionsScannerClass($string); + + if ($options['extractComments'] !== false) { + $functions->enableCommentsExtraction($options['extractComments']); + } + + $functions->saveGettextFunctions($translations, $options); + } + + /** + * @inheritDoc + */ + public static function fromFileMultiple($file, array $translations, array $options = []) + { + foreach (static::getFiles($file) as $file) { + $options['file'] = $file; + static::fromStringMultiple(static::readFile($file), $translations, $options); + } + } + + + /** + * Decodes a T_CONSTANT_ENCAPSED_STRING string. + * + * @param string $value + * + * @return string + */ + public static function convertString($value) + { + if (strpos($value, '\\') === false) { + return substr($value, 1, -1); + } + + if ($value[0] === "'") { + return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']); + } + + $value = substr($value, 1, -1); + + return preg_replace_callback( + '/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/', + function ($match) { + switch ($match[1][0]) { + case 'n': + return "\n"; + case 'r': + return "\r"; + case 't': + return "\t"; + case 'v': + return "\v"; + case 'e': + return "\e"; + case 'f': + return "\f"; + case '$': + return '$'; + case '"': + return '"'; + case '\\': + return '\\'; + case 'x': + return chr(hexdec(substr($match[1], 1))); + case 'u': + return static::unicodeChar(hexdec(substr($match[1], 1))); + default: + return chr(octdec($match[1])); + } + }, + $value + ); + } + + /** + * @param $dec + * @return string|null + * @see http://php.net/manual/en/function.chr.php#118804 + */ + protected static function unicodeChar($dec) + { + if ($dec < 0x80) { + return chr($dec); + } + + if ($dec < 0x0800) { + return chr(0xC0 + ($dec >> 6)) + . chr(0x80 + ($dec & 0x3f)); + } + + if ($dec < 0x010000) { + return chr(0xE0 + ($dec >> 12)) + . chr(0x80 + (($dec >> 6) & 0x3f)) + . chr(0x80 + ($dec & 0x3f)); + } + + if ($dec < 0x200000) { + return chr(0xF0 + ($dec >> 18)) + . chr(0x80 + (($dec >> 12) & 0x3f)) + . chr(0x80 + (($dec >> 6) & 0x3f)) + . chr(0x80 + ($dec & 0x3f)); + } + + return null; + } +} diff --git a/vendor/gettext/gettext/src/Extractors/Po.php b/vendor/gettext/gettext/src/Extractors/Po.php new file mode 100644 index 0000000..5c9cee6 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/Po.php @@ -0,0 +1,219 @@ +createNewTranslation('', ''); + + for ($n = count($lines); $i < $n; ++$i) { + $line = trim($lines[$i]); + $line = static::fixMultiLines($line, $lines, $i); + + if ($line === "#") { + $line = ""; + } + + if ($line === '') { + if ($translation->is('', '')) { + static::extractHeaders($translation->getTranslation(), $translations); + } elseif ($translation->hasOriginal()) { + $translations[] = $translation; + } + + $translation = $translations->createNewTranslation('', ''); + continue; + } + + $splitLine = preg_split('/\s+/', $line, 2); + $key = $splitLine[0]; + $data = isset($splitLine[1]) ? $splitLine[1] : ''; + + if ($key === '#~') { + $translation->setDisabled(true); + + $splitLine = preg_split('/\s+/', $data, 2); + $key = $splitLine[0]; + $data = isset($splitLine[1]) ? $splitLine[1] : ''; + } + + switch ($key) { + case '#': + $translation->addComment($data); + $append = null; + break; + + case '#.': + $translation->addExtractedComment($data); + $append = null; + break; + + case '#,': + foreach (array_map('trim', explode(',', trim($data))) as $value) { + $translation->addFlag($value); + } + $append = null; + break; + + case '#:': + foreach (preg_split('/\s+/', trim($data)) as $value) { + if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) { + $translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null); + } + } + $append = null; + break; + + case 'msgctxt': + $translation = $translation->getClone(static::convertString($data)); + $append = 'Context'; + break; + + case 'msgid': + $translation = $translation->getClone(null, static::convertString($data)); + $append = 'Original'; + break; + + case 'msgid_plural': + $translation->setPlural(static::convertString($data)); + $append = 'Plural'; + break; + + case 'msgstr': + case 'msgstr[0]': + $translation->setTranslation(static::convertString($data)); + $append = 'Translation'; + break; + + case 'msgstr[1]': + $translation->setPluralTranslations([static::convertString($data)]); + $append = 'PluralTranslation'; + break; + + default: + if (strpos($key, 'msgstr[') === 0) { + $p = $translation->getPluralTranslations(); + $p[] = static::convertString($data); + + $translation->setPluralTranslations($p); + $append = 'PluralTranslation'; + break; + } + + if (isset($append)) { + if ($append === 'Context') { + $translation = $translation->getClone($translation->getContext() + ."\n" + .static::convertString($data)); + break; + } + + if ($append === 'Original') { + $translation = $translation->getClone(null, $translation->getOriginal() + ."\n" + .static::convertString($data)); + break; + } + + if ($append === 'PluralTranslation') { + $p = $translation->getPluralTranslations(); + $p[] = array_pop($p)."\n".static::convertString($data); + $translation->setPluralTranslations($p); + break; + } + + $getMethod = 'get'.$append; + $setMethod = 'set'.$append; + $translation->$setMethod($translation->$getMethod()."\n".static::convertString($data)); + } + break; + } + } + + if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) { + $translations[] = $translation; + } + } + + /** + * Gets one string from multiline strings. + * + * @param string $line + * @param array $lines + * @param int &$i + * + * @return string + */ + protected static function fixMultiLines($line, array $lines, &$i) + { + for ($j = $i, $t = count($lines); $j < $t; ++$j) { + if (substr($line, -1, 1) == '"' && isset($lines[$j + 1])) { + $nextLine = trim($lines[$j + 1]); + if (substr($nextLine, 0, 1) == '"') { + $line = substr($line, 0, -1).substr($nextLine, 1); + continue; + } + if (substr($nextLine, 0, 4) == '#~ "') { + $line = substr($line, 0, -1).substr($nextLine, 4); + continue; + } + } + $i = $j; + break; + } + + return $line; + } + + /** + * Convert a string from its PO representation. + * + * @param string $value + * + * @return string + */ + public static function convertString($value) + { + if (!$value) { + return ''; + } + + if ($value[0] === '"') { + $value = substr($value, 1, -1); + } + + return strtr( + $value, + [ + '\\\\' => '\\', + '\\a' => "\x07", + '\\b' => "\x08", + '\\t' => "\t", + '\\n' => "\n", + '\\v' => "\x0b", + '\\f' => "\x0c", + '\\r' => "\r", + '\\"' => '"', + ] + ); + } +} diff --git a/vendor/gettext/gettext/src/Extractors/Twig.php b/vendor/gettext/gettext/src/Extractors/Twig.php new file mode 100644 index 0000000..2060d08 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/Twig.php @@ -0,0 +1,45 @@ + 'notes:', + 'twig' => null, + ]; + + /** + * {@inheritdoc} + */ + public static function fromString($string, Translations $translations, array $options = []) + { + $options += static::$options; + + $twig = $options['twig'] ?: static::createTwig(); + + PhpCode::fromString($twig->compileSource(new Twig_Source($string, '')), $translations, $options); + } + + /** + * Returns a Twig instance. + * + * @return Twig_Environment + */ + protected static function createTwig() + { + $twig = new Twig_Environment(new Twig_Loader_Array(['' => ''])); + $twig->addExtension(new Twig_Extensions_Extension_I18n()); + + return static::$options['twig'] = $twig; + } +} diff --git a/vendor/gettext/gettext/src/Extractors/VueJs.php b/vendor/gettext/gettext/src/Extractors/VueJs.php new file mode 100644 index 0000000..0d29f45 --- /dev/null +++ b/vendor/gettext/gettext/src/Extractors/VueJs.php @@ -0,0 +1,423 @@ + [], + + 'functions' => [ + 'gettext' => 'gettext', + '__' => 'gettext', + 'ngettext' => 'ngettext', + 'n__' => 'ngettext', + 'pgettext' => 'pgettext', + 'p__' => 'pgettext', + 'dgettext' => 'dgettext', + 'd__' => 'dgettext', + 'dngettext' => 'dngettext', + 'dn__' => 'dngettext', + 'dpgettext' => 'dpgettext', + 'dp__' => 'dpgettext', + 'npgettext' => 'npgettext', + 'np__' => 'npgettext', + 'dnpgettext' => 'dnpgettext', + 'dnp__' => 'dnpgettext', + 'noop' => 'noop', + 'noop__' => 'noop', + ], + ]; + + protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner'; + + /** + * @inheritDoc + * @throws Exception + */ + public static function fromFileMultiple($file, array $translations, array $options = []) + { + foreach (static::getFiles($file) as $file) { + $options['file'] = $file; + static::fromStringMultiple(static::readFile($file), $translations, $options); + } + } + + /** + * @inheritdoc + * @throws Exception + */ + public static function fromString($string, Translations $translations, array $options = []) + { + static::fromStringMultiple($string, [$translations], $options); + } + + /** + * @inheritDoc + * @throws Exception + */ + public static function fromStringMultiple($string, array $translations, array $options = []) + { + $options += static::$options; + $options += [ + // HTML attribute prefixes we parse as JS which could contain translations (are JS expressions) + 'attributePrefixes' => [ + ':', + 'v-bind:', + 'v-on:', + 'v-text', + ], + // HTML Tags to parse + 'tagNames' => [ + 'translate', + ], + // HTML tags to parse when attribute exists + 'tagAttributes' => [ + 'v-translate', + ], + // Comments + 'commentAttributes' => [ + 'translate-comment', + ], + 'contextAttributes' => [ + 'translate-context', + ], + // Attribute with plural content + 'pluralAttributes' => [ + 'translate-plural', + ], + ]; + + // Ok, this is the weirdest hack, but let me explain: + // On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag. + // So if there are new lines between